From 4316f47ef699e6ea62b5f5ecade9e4d71480653b Mon Sep 17 00:00:00 2001 From: Scribble Date: Fri, 19 Jun 2026 21:11:44 +0200 Subject: [PATCH 01/10] [KillTheRNG] Improve squid rng --- .../java/com/minecrafttas/tasmod/TASmod.java | 4 ++++ .../tasmod/ktrng/builtin/SquidRNG.java | 16 ++++++++++++++++ .../mixin/killtherng/MixinEntitySquid.java | 4 ++++ 3 files changed, 24 insertions(+) create mode 100644 src/main/java/com/minecrafttas/tasmod/ktrng/builtin/SquidRNG.java diff --git a/src/main/java/com/minecrafttas/tasmod/TASmod.java b/src/main/java/com/minecrafttas/tasmod/TASmod.java index 3f05ad30..a7ce4939 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; @@ -110,6 +111,8 @@ public class TASmod implements ModInitializer, EventServerStart, EventServerInit 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 +176,7 @@ public void onServerStart(MinecraftServer server) { EventListenerRegistry.register(globalRandomness); } mathRandomness = new MathRNG(0); + squidRNG.setSeed(globalRandomness.getCurrentSeed() + 1000L); } @Override 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; } } From 959c0fa482ef46e67eb724851a9845d5d681846e Mon Sep 17 00:00:00 2001 From: Scribble Date: Fri, 19 Jun 2026 21:11:44 +0200 Subject: [PATCH 02/10] [Savestates] First look at storing entity AI in savestates --- build.gradle | 2 +- .../java/com/minecrafttas/tasmod/TASmod.java | 3 + .../SavestateStorageExtensionBase.java | 9 +- .../SavestateStorageExtensionRegistry.java | 4 +- .../storage/builtin/ClientMotionStorage.java | 4 +- .../storage/builtin/EntityAiTaskStorage.java | 161 ++++++++++++++++++ .../BlockPosTypeAdapterFactory.java | 41 +++++ .../EntityClassTypeAdapterFactory.java | 49 ++++++ .../EntityTypeAdapterFactory.java | 93 ++++++++++ .../typeadapters/ItemTypeAdapter.java | 25 +++ .../typeadapters/WorldTypeAdapterFactory.java | 72 ++++++++ .../minecrafttas/tasmod/util/JsonUtils.java | 14 +- src/main/resources/tasmod.accesswidener | 8 + 13 files changed, 471 insertions(+), 14 deletions(-) create mode 100644 src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/EntityAiTaskStorage.java create mode 100644 src/main/java/com/minecrafttas/tasmod/savestates/typeadapters/BlockPosTypeAdapterFactory.java create mode 100644 src/main/java/com/minecrafttas/tasmod/savestates/typeadapters/EntityClassTypeAdapterFactory.java create mode 100644 src/main/java/com/minecrafttas/tasmod/savestates/typeadapters/EntityTypeAdapterFactory.java create mode 100644 src/main/java/com/minecrafttas/tasmod/savestates/typeadapters/ItemTypeAdapter.java create mode 100644 src/main/java/com/minecrafttas/tasmod/savestates/typeadapters/WorldTypeAdapterFactory.java diff --git a/build.gradle b/build.gradle index 9d749c73..150a6c9d 100644 --- a/build.gradle +++ b/build.gradle @@ -53,7 +53,7 @@ dependencies { // tasmod dependencies embed "com.dselent:bigarraylist:1.6" embed "com.github.KaptainWutax:SeedUtils:b6a383113c" - + // 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 a7ce4939..d6ae3a97 100644 --- a/src/main/java/com/minecrafttas/tasmod/TASmod.java +++ b/src/main/java/com/minecrafttas/tasmod/TASmod.java @@ -43,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; @@ -106,6 +107,7 @@ 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); @@ -246,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/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..26283393 --- /dev/null +++ b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/EntityAiTaskStorage.java @@ -0,0 +1,161 @@ +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.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.storage.SavestateStorageExtensionBase; +import com.minecrafttas.tasmod.savestates.typeadapters.BlockPosTypeAdapterFactory; +import com.minecrafttas.tasmod.savestates.typeadapters.EntityClassTypeAdapterFactory; +import com.minecrafttas.tasmod.savestates.typeadapters.EntityTypeAdapterFactory; +import com.minecrafttas.tasmod.savestates.typeadapters.ItemTypeAdapter; +import com.minecrafttas.tasmod.savestates.typeadapters.WorldTypeAdapterFactory; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.ai.EntityAIBase; +import net.minecraft.entity.ai.EntityAITasks; +import net.minecraft.entity.ai.EntityAITasks.EntityAITaskEntry; +import net.minecraft.item.Item; +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 BlockPosTypeAdapterFactory() + ) + .registerTypeAdapter(Item.class, new ItemTypeAdapter()) + .setExclusionStrategies(new ExclusionStrategy() { + + @Override + public boolean shouldSkipField(FieldAttributes f) { + return f.getAnnotation(Unique.class) != null; + } + + @Override + public boolean shouldSkipClass(Class c) { + return false; + } + }) + .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()); + + 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()); + jsonAiTaskEntry.add("action", gsonInstance.toJsonTree(entry.action)); + 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(); + deserialiseAITasks(entityLiving.tasks, mainObject.get("tasks").getAsJsonObject()); + deserialiseAITasks(entityLiving.targetTasks, mainObject.get("targetTasks").getAsJsonObject()); + } + } + } + + private void deserialiseAITasks(EntityAITasks tasks, JsonObject jsonTasks) { + tasks.taskEntries.clear(); + tasks.taskEntries.addAll(deserialiseAIEntries(tasks, jsonTasks.get("taskEntries").getAsJsonArray())); + tasks.executingTaskEntries.clear(); + tasks.executingTaskEntries.addAll(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(); + 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; + } +} 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/EntityClassTypeAdapterFactory.java b/src/main/java/com/minecrafttas/tasmod/savestates/typeadapters/EntityClassTypeAdapterFactory.java new file mode 100644 index 00000000..bc3d10c8 --- /dev/null +++ b/src/main/java/com/minecrafttas/tasmod/savestates/typeadapters/EntityClassTypeAdapterFactory.java @@ -0,0 +1,49 @@ +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.passive.EntityAnimal; + +public class EntityClassTypeAdapterFactory implements TypeAdapterFactory { + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public TypeAdapter create(Gson gson, TypeToken type) { + if (!new TypeToken>() { + }.equals(type) && !new TypeToken>() { + }.equals(type)) { + 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/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/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..62d1660d 100644 --- a/src/main/resources/tasmod.accesswidener +++ b/src/main/resources/tasmod.accesswidener @@ -35,3 +35,11 @@ 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; + From 230600c48281351e18650e8bfe662b7b4513bd6b Mon Sep 17 00:00:00 2001 From: Scribble Date: Sun, 12 Jul 2026 21:55:58 +0200 Subject: [PATCH 03/10] [Savestates] Store revenge target and last attacked --- .../storage/builtin/EntityAiTaskStorage.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) 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 index 26283393..1362ebf4 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/EntityAiTaskStorage.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/EntityAiTaskStorage.java @@ -22,6 +22,7 @@ import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.ai.EntityAIBase; import net.minecraft.entity.ai.EntityAITasks; import net.minecraft.entity.ai.EntityAITasks.EntityAITaskEntry; @@ -82,6 +83,9 @@ public JsonObject onSavestate(MinecraftServer server, JsonObject dataToSave) { 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)); @@ -123,6 +127,10 @@ public void onLoadstatePost(MinecraftServer server, JsonObject loadedData) { 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()); } @@ -130,10 +138,8 @@ public void onLoadstatePost(MinecraftServer server, JsonObject loadedData) { } private void deserialiseAITasks(EntityAITasks tasks, JsonObject jsonTasks) { - tasks.taskEntries.clear(); - tasks.taskEntries.addAll(deserialiseAIEntries(tasks, jsonTasks.get("taskEntries").getAsJsonArray())); - tasks.executingTaskEntries.clear(); - tasks.executingTaskEntries.addAll(deserialiseAIEntries(tasks, jsonTasks.get("executingTaskEntries").getAsJsonArray())); + tasks.taskEntries = deserialiseAIEntries(tasks, jsonTasks.get("taskEntries").getAsJsonArray()); + tasks.executingTaskEntries = deserialiseAIEntries(tasks, jsonTasks.get("executingTaskEntries").getAsJsonArray()); } private Set deserialiseAIEntries(EntityAITasks tasks, JsonArray jsonEntries) { From 45a03b47ee5d1e4504d6d3b2cf5f7a1dc35d4263 Mon Sep 17 00:00:00 2001 From: Scribble Date: Tue, 28 Jul 2026 21:37:11 +0200 Subject: [PATCH 04/10] [Savestates] Trying to add EntityAiTaskStorage --- .../storage/builtin/EntityAiTaskStorage.java | 73 ++++++++++++++++- ...estAttackableTargetTypeAdapterFactory.java | 48 +++++++++++ .../EntityClassTypeAdapterFactory.java | 8 +- .../typeadapters/EntityLivingTypeAdapter.java | 82 +++++++++++++++++++ .../util/ClassExclusionStrategy.java | 34 ++++++++ .../util/StaggeredTypeAdapterFactory.java | 57 +++++++++++++ src/main/resources/tasmod.accesswidener | 13 +++ 7 files changed, 306 insertions(+), 9 deletions(-) create mode 100644 src/main/java/com/minecrafttas/tasmod/savestates/typeadapters/EntityAINearestAttackableTargetTypeAdapterFactory.java create mode 100644 src/main/java/com/minecrafttas/tasmod/savestates/typeadapters/EntityLivingTypeAdapter.java create mode 100644 src/main/java/com/minecrafttas/tasmod/savestates/typeadapters/util/ClassExclusionStrategy.java create mode 100644 src/main/java/com/minecrafttas/tasmod/savestates/typeadapters/util/StaggeredTypeAdapterFactory.java 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 index 1362ebf4..c9f1dfb2 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/EntityAiTaskStorage.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/EntityAiTaskStorage.java @@ -7,26 +7,34 @@ 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; @@ -46,10 +54,12 @@ public EntityAiTaskStorage() { .registerTypeAdapterFactory( new EntityClassTypeAdapterFactory() ) + .registerTypeAdapterFactory(new EntityAINearestAttackableTargetTypeAdapterFactory()) .registerTypeAdapterFactory( new BlockPosTypeAdapterFactory() ) .registerTypeAdapter(Item.class, new ItemTypeAdapter()) + .registerTypeAdapter(EntityLivingTypeAdapter.class, new EntityLivingTypeAdapter()) .setExclusionStrategies(new ExclusionStrategy() { @Override @@ -61,7 +71,11 @@ public boolean shouldSkipField(FieldAttributes f) { public boolean shouldSkipClass(Class c) { return false; } - }) + }, + new ClassExclusionStrategy(Predicate.class), + new ClassExclusionStrategy(PathNavigate.class), + new ClassExclusionStrategy(EntityLivingBase.class, EntityAINearestAttackableTarget.class) + ) .create()); //@formatter:on } @@ -112,7 +126,12 @@ private JsonArray serialiseAIEntries(Set taskEntries) { jsonAiTaskEntry.addProperty("priority", entry.priority); jsonAiTaskEntry.addProperty("using", entry.using); jsonAiTaskEntry.addProperty("class", entry.action.getClass().getName()); - jsonAiTaskEntry.add("action", gsonInstance.toJsonTree(entry.action)); + 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; @@ -138,8 +157,8 @@ public void onLoadstatePost(MinecraftServer server, JsonObject loadedData) { } private void deserialiseAITasks(EntityAITasks tasks, JsonObject jsonTasks) { - tasks.taskEntries = deserialiseAIEntries(tasks, jsonTasks.get("taskEntries").getAsJsonArray()); - tasks.executingTaskEntries = deserialiseAIEntries(tasks, jsonTasks.get("executingTaskEntries").getAsJsonArray()); + 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) { @@ -148,6 +167,7 @@ private Set deserialiseAIEntries(EntityAITasks tasks, JsonArr 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); @@ -164,4 +184,49 @@ private Set deserialiseAIEntries(EntityAITasks tasks, JsonArr } 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/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 index bc3d10c8..70a59ceb 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/typeadapters/EntityClassTypeAdapterFactory.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/typeadapters/EntityClassTypeAdapterFactory.java @@ -10,18 +10,16 @@ import com.google.gson.stream.JsonWriter; import net.minecraft.entity.Entity; -import net.minecraft.entity.passive.EntityAnimal; public class EntityClassTypeAdapterFactory implements TypeAdapterFactory { @SuppressWarnings({ "unchecked", "rawtypes" }) @Override public TypeAdapter create(Gson gson, TypeToken type) { - if (!new TypeToken>() { - }.equals(type) && !new TypeToken>() { - }.equals(type)) { + + Class rawType = (Class) type.getRawType(); + if (!Class.class.equals(rawType)) return null; - } return new TypeAdapter() { 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/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/resources/tasmod.accesswidener b/src/main/resources/tasmod.accesswidener index 62d1660d..458f8e44 100644 --- a/src/main/resources/tasmod.accesswidener +++ b/src/main/resources/tasmod.accesswidener @@ -43,3 +43,16 @@ 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; From ede97bff8fe9dad7911346964057ec7cfcd45725 Mon Sep 17 00:00:00 2001 From: Scribble Date: Tue, 28 Jul 2026 21:37:11 +0200 Subject: [PATCH 05/10] Trying to add gson again for readability --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index 150a6c9d..bb9fa4de 100644 --- a/build.gradle +++ b/build.gradle @@ -53,6 +53,7 @@ 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}" From 2c0db85bb8b8907a5ffeab3008c07dab549626a5 Mon Sep 17 00:00:00 2001 From: Scribble Date: Sat, 22 Aug 2026 22:51:34 +0200 Subject: [PATCH 06/10] [Savestates] Update EntityAI deserialisation to merge with vanilla actions ...rather than create new action objects entirely. This has the benefit of circumnavigating unserializable fields such as Predicates and Lambdas. --- .../storage/builtin/EntityAiTaskStorage.java | 107 +++++++++--------- ...estAttackableTargetTypeAdapterFactory.java | 48 -------- src/main/resources/tasmod.accesswidener | 4 +- 3 files changed, 58 insertions(+), 101 deletions(-) delete mode 100644 src/main/java/com/minecrafttas/tasmod/savestates/typeadapters/EntityAINearestAttackableTargetTypeAdapterFactory.java 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 index c9f1dfb2..08b60821 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/EntityAiTaskStorage.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/EntityAiTaskStorage.java @@ -1,6 +1,9 @@ package com.minecrafttas.tasmod.savestates.storage.builtin; +import java.lang.reflect.Field; +import java.util.Arrays; import java.util.LinkedHashSet; +import java.util.List; import java.util.Map.Entry; import java.util.Set; import java.util.UUID; @@ -17,7 +20,6 @@ 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; @@ -28,9 +30,7 @@ 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; @@ -54,7 +54,7 @@ public EntityAiTaskStorage() { .registerTypeAdapterFactory( new EntityClassTypeAdapterFactory() ) - .registerTypeAdapterFactory(new EntityAINearestAttackableTargetTypeAdapterFactory()) +// .registerTypeAdapter(EntityAINearestAttackableTarget.class, new EntityAINearestAttackableTargetTypeAdapter()) .registerTypeAdapterFactory( new BlockPosTypeAdapterFactory() ) @@ -73,8 +73,8 @@ public boolean shouldSkipClass(Class c) { } }, new ClassExclusionStrategy(Predicate.class), - new ClassExclusionStrategy(PathNavigate.class), - new ClassExclusionStrategy(EntityLivingBase.class, EntityAINearestAttackableTarget.class) + new ClassExclusionStrategy(PathNavigate.class) +// new ClassExclusionStrategy(EntityLivingBase.class, EntityAINearestAttackableTarget.class) ) .create()); //@formatter:on @@ -157,17 +157,18 @@ public void onLoadstatePost(MinecraftServer server, JsonObject loadedData) { } 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())); + deserialiseAIEntries(tasks.taskEntries, tasks, jsonTasks.get("taskEntries").getAsJsonArray()); + deserialiseAIEntries(tasks.executingTaskEntries, tasks, jsonTasks.get("executingTaskEntries").getAsJsonArray()); } - private Set deserialiseAIEntries(EntityAITasks tasks, JsonArray jsonEntries) { + private Set deserialiseAIEntries(Set taskEntries, EntityAITasks parent, 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()); + + // Deserialise the type of the AI action +// System.out.println(jsonEntry.get("class").getAsString()); Class clazz; try { clazz = Class.forName(jsonEntry.get("class").getAsString(), false, getClass().getClassLoader()).asSubclass(EntityAIBase.class); @@ -176,57 +177,61 @@ private Set deserialiseAIEntries(EntityAITasks tasks, JsonArr return out; } - EntityAIBase action = gsonInstance.fromJson(jsonEntry.get("action"), clazz); + boolean createNew = true; + for (EntityAITaskEntry vanillaEntry : taskEntries) { + EntityAIBase vanillaAction = vanillaEntry.action; + if (vanillaAction.getClass() != clazz) { + continue; + } - EntityAITaskEntry entry = tasks.new EntityAITaskEntry(priority, action); + createNew = false; + vanillaEntry.action = deserialiseAction(vanillaAction, jsonEntry.get("action"), clazz); + out.add(vanillaEntry); + break; + } + + if (!createNew) { + continue; + } + + int priority = jsonEntry.get("priority").getAsInt(); + boolean using = jsonEntry.get("using").getAsBoolean(); + EntityAIBase action = null; + try { + action = gsonInstance.fromJson(jsonEntry, clazz); + } catch (Exception e) { + e.printStackTrace(); + continue; + } + EntityAITaskEntry entry = parent.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; + private EntityAIBase deserialiseAction(EntityAIBase vanillaAction, JsonElement json, Class clazz) { + JsonObject jsonObject = json.getAsJsonObject(); + List vanillaFields = Arrays.asList(vanillaAction.getClass().getDeclaredFields()); - deserialisedAIAvoidEntityAction.avoidTargetSelector = vanillaAIAvoidEntityAction.avoidTargetSelector; - deserialisedAIAvoidEntityAction.canBeSeenSelector = vanillaAIAvoidEntityAction.canBeSeenSelector; - return deserialisedAIAvoidEntityAction; - } + for (Field field : vanillaFields) { + field.setAccessible(true); + String fieldname = field.getName(); + Class fieldClass = field.getType(); - if (vanillaAction instanceof EntityAINearestAttackableTarget) { - EntityAINearestAttackableTarget vanillaAINearestAttackableTarget = (EntityAINearestAttackableTarget) vanillaAction; - EntityAINearestAttackableTarget deserialisedAINearestAttackableTarget = (EntityAINearestAttackableTarget) deserialisedAction; + JsonElement value = jsonObject.get(fieldname); - deserialisedAINearestAttackableTarget.targetEntitySelector = vanillaAINearestAttackableTarget.targetEntitySelector; + if (value == null) + continue; - return deserialisedAINearestAttackableTarget; + Object deserialised = gsonInstance.fromJson(value, fieldClass); + try { + field.set(vanillaAction, deserialised); + } catch (IllegalArgumentException | IllegalAccessException e) { + e.printStackTrace(); + } } - return deserialisedAction; + return vanillaAction; } } diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/typeadapters/EntityAINearestAttackableTargetTypeAdapterFactory.java b/src/main/java/com/minecrafttas/tasmod/savestates/typeadapters/EntityAINearestAttackableTargetTypeAdapterFactory.java deleted file mode 100644 index 8904b729..00000000 --- a/src/main/java/com/minecrafttas/tasmod/savestates/typeadapters/EntityAINearestAttackableTargetTypeAdapterFactory.java +++ /dev/null @@ -1,48 +0,0 @@ -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/resources/tasmod.accesswidener b/src/main/resources/tasmod.accesswidener index 458f8e44..18056f48 100644 --- a/src/main/resources/tasmod.accesswidener +++ b/src/main/resources/tasmod.accesswidener @@ -49,8 +49,8 @@ mutable field net/minecraft/entity/ai/EntityAIAvoidEntity canBeSeenSelector Lcom 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 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; From 7a1d5f5b352cf4dfb210a23ffa61945e9603f62d Mon Sep 17 00:00:00 2001 From: Scribble Date: Wed, 26 Aug 2026 23:25:01 +0200 Subject: [PATCH 07/10] [Savestates] Merge SquidRotation, BatSpawn and TickTimers json into Entity.json --- .../java/com/minecrafttas/tasmod/TASmod.java | 22 +++-- .../EntityBatSpawnPositionStorage.java | 61 ------------- .../builtin/EntitySquidRotationStorage.java | 65 ------------- .../storage/builtin/EntityStorage.java | 88 ++++++++++++++++++ .../builtin/EntityTickTimersStorage.java | 91 ------------------- .../BatSpawnPositionSubStorage.java | 46 ++++++++++ .../LivingTickTimersSubStorage.java | 72 +++++++++++++++ .../SquidRotationSubStorage.java | 48 ++++++++++ 8 files changed, 267 insertions(+), 226 deletions(-) delete mode 100644 src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/EntityBatSpawnPositionStorage.java delete mode 100644 src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/EntitySquidRotationStorage.java create mode 100644 src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/EntityStorage.java delete mode 100644 src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/EntityTickTimersStorage.java create mode 100644 src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entitysubstorage/BatSpawnPositionSubStorage.java create mode 100644 src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entitysubstorage/LivingTickTimersSubStorage.java create mode 100644 src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entitysubstorage/SquidRotationSubStorage.java diff --git a/src/main/java/com/minecrafttas/tasmod/TASmod.java b/src/main/java/com/minecrafttas/tasmod/TASmod.java index d6ae3a97..16480d0d 100644 --- a/src/main/java/com/minecrafttas/tasmod/TASmod.java +++ b/src/main/java/com/minecrafttas/tasmod/TASmod.java @@ -44,10 +44,11 @@ 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; +import com.minecrafttas.tasmod.savestates.storage.builtin.EntityStorage; import com.minecrafttas.tasmod.savestates.storage.builtin.KTRNGSeedStorage; +import com.minecrafttas.tasmod.savestates.storage.builtin.entitysubstorage.BatSpawnPositionSubStorage; +import com.minecrafttas.tasmod.savestates.storage.builtin.entitysubstorage.LivingTickTimersSubStorage; +import com.minecrafttas.tasmod.savestates.storage.builtin.entitysubstorage.SquidRotationSubStorage; import com.minecrafttas.tasmod.tickratechanger.TickrateChangerServer; import com.minecrafttas.tasmod.ticksync.TickSyncServer; import com.minecrafttas.tasmod.util.LoggerMarkers; @@ -104,9 +105,6 @@ public class TASmod implements ModInitializer, EventServerStart, EventServerInit public static GlobalRNG globalRandomness; public static KTRNGSeedStorage seedStorage = new KTRNGSeedStorage(); - 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); @@ -245,9 +243,15 @@ public void onServerStop(MinecraftServer mcserver) { private void registerSavestateStorage() { TASmodAPIRegistry.SAVESTATE_STORAGE.register(motionStorage); TASmodAPIRegistry.SAVESTATE_STORAGE.register(seedStorage); - TASmodAPIRegistry.SAVESTATE_STORAGE.register(entityTickTimers); - TASmodAPIRegistry.SAVESTATE_STORAGE.register(entityBatSpawnPositionStorage); - TASmodAPIRegistry.SAVESTATE_STORAGE.register(entitySquidRotationStorage); + //@formatter:off + TASmodAPIRegistry.SAVESTATE_STORAGE.register( + new EntityStorage( + new BatSpawnPositionSubStorage(), + new SquidRotationSubStorage(), + new LivingTickTimersSubStorage() + ) + ); + //@formatter:on TASmodAPIRegistry.SAVESTATE_STORAGE.register(entityAiTaskStorage); } diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/EntityBatSpawnPositionStorage.java b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/EntityBatSpawnPositionStorage.java deleted file mode 100644 index 307376ea..00000000 --- a/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/EntityBatSpawnPositionStorage.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.minecrafttas.tasmod.savestates.storage.builtin; - -import java.util.UUID; - -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.minecrafttas.tasmod.savestates.storage.SavestateStorageExtensionBase; - -import net.minecraft.entity.Entity; -import net.minecraft.entity.passive.EntityBat; -import net.minecraft.server.MinecraftServer; -import net.minecraft.util.math.BlockPos; -import net.minecraft.world.WorldServer; - -public class EntityBatSpawnPositionStorage extends SavestateStorageExtensionBase { - - public EntityBatSpawnPositionStorage() { - super("entityBatSpawnPosition.json"); - } - - @Override - public String getExtensionName() { - return "EntityBatSpawnPosition"; - } - - @Override - public JsonObject onSavestate(MinecraftServer server, JsonObject dataToSave) { - for (WorldServer worldServer : server.worlds) { - for (Entity entity : worldServer.loadedEntityList) { - if (entity instanceof EntityBat) { - EntityBat bat = (EntityBat) entity; - BlockPos spawnPos = bat.spawnPosition; - if (spawnPos == null) - continue; - UUID entityUUID = entity.getUniqueID(); - dataToSave.addProperty(entityUUID.toString(), Long.toString(bat.spawnPosition.toLong())); - } - } - } - return dataToSave; - } - - @Override - public void onLoadstatePost(MinecraftServer server, JsonObject loadedData) { - if (loadedData == null) - return; - for (WorldServer worldServer : server.worlds) { - for (Entity entity : worldServer.loadedEntityList) { - if (entity instanceof EntityBat) { - EntityBat bat = (EntityBat) entity; - UUID entityUUID = entity.getUniqueID(); - JsonElement element = loadedData.get(entityUUID.toString()); - if (element == null) - continue; - BlockPos spawnPos = BlockPos.fromLong(element.getAsLong()); - bat.spawnPosition = spawnPos; - } - } - } - } -} diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/EntitySquidRotationStorage.java b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/EntitySquidRotationStorage.java deleted file mode 100644 index 0c9e7349..00000000 --- a/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/EntitySquidRotationStorage.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.minecrafttas.tasmod.savestates.storage.builtin; - -import java.util.UUID; - -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.minecrafttas.tasmod.savestates.storage.SavestateStorageExtensionBase; - -import net.minecraft.entity.Entity; -import net.minecraft.entity.passive.EntitySquid; -import net.minecraft.server.MinecraftServer; -import net.minecraft.world.WorldServer; - -/** - *

Stores squid rotation in the savestates - *

Did you know that the rotation of the squid can trigger an RNG call? Did you know Minecraft does not save this by default?
- * Well now you know and it's annoying. - * - * @author Scribble - */ -public class EntitySquidRotationStorage extends SavestateStorageExtensionBase { - - public EntitySquidRotationStorage() { - super("entitySquidRotation.json"); - } - - @Override - public String getExtensionName() { - return "EntitySquidRotation"; - } - - @Override - public JsonObject onSavestate(MinecraftServer server, JsonObject dataToSave) { - for (WorldServer worldServer : server.worlds) { - for (Entity entity : worldServer.loadedEntityList) { - if (entity instanceof EntitySquid) { - EntitySquid squid = (EntitySquid) entity; - float rotation = squid.squidRotation; - UUID entityUUID = entity.getUniqueID(); - dataToSave.addProperty(entityUUID.toString(), Float.toString(rotation)); - } - } - } - return dataToSave; - } - - @Override - public void onLoadstatePost(MinecraftServer server, JsonObject loadedData) { - if (loadedData == null) - return; - for (WorldServer worldServer : server.worlds) { - for (Entity entity : worldServer.loadedEntityList) { - if (entity instanceof EntitySquid) { - EntitySquid squid = (EntitySquid) entity; - UUID entityUUID = entity.getUniqueID(); - JsonElement element = loadedData.get(entityUUID.toString()); - if (element == null) - continue; - float rotation = element.getAsFloat(); - squid.squidRotation = rotation; - } - } - } - } -} diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/EntityStorage.java b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/EntityStorage.java new file mode 100644 index 00000000..ff56024a --- /dev/null +++ b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/EntityStorage.java @@ -0,0 +1,88 @@ +package com.minecrafttas.tasmod.savestates.storage.builtin; + +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; +import com.minecrafttas.tasmod.TASmod; +import com.minecrafttas.tasmod.savestates.storage.SavestateStorageExtensionBase; +import com.minecrafttas.tasmod.util.LoggerMarkers; + +import net.minecraft.entity.Entity; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.WorldServer; + +public class EntityStorage extends SavestateStorageExtensionBase { + + private final List subStorages; + + public EntityStorage(EntitySubStorage... entitySubStorages) { + this(Arrays.asList(entitySubStorages)); + } + + public EntityStorage(List entitySubStorages) { + super("entity.json", new GsonBuilder().setPrettyPrinting().create()); + this.subStorages = entitySubStorages; + } + + @Override + public JsonObject onSavestate(MinecraftServer server, JsonObject dataToSave) { + for (WorldServer worldServer : server.worlds) { + for (Entity entity : worldServer.loadedEntityList) { + UUID uuid = entity.getUniqueID(); + for (EntitySubStorage subStorage : subStorages) { + JsonObject subDataObject = new JsonObject(); + if (subStorage.getEntityClass().isAssignableFrom(entity.getClass())) { + JsonObject subDataToSave = new JsonObject(); + subDataToSave = subStorage.onSavestate(server, entity, subDataToSave); + subDataObject.add(subStorage.getPropertyName(), subDataToSave); + } + dataToSave.add(uuid.toString(), subDataObject); + } + } + } + return dataToSave; + } + + @Override + public void onLoadstatePost(MinecraftServer server, JsonObject loadedData) { + if (loadedData == null) + return; + for (WorldServer worldServer : server.worlds) { + for (Entity entity : worldServer.loadedEntityList) { + UUID uuid = entity.getUniqueID(); + if (!loadedData.has(uuid.toString())) + continue; + + JsonObject subDataObject = loadedData.get(uuid.toString()).getAsJsonObject(); + for (EntitySubStorage subStorage : subStorages) { + if (!subDataObject.has(subStorage.getPropertyName())) { + TASmod.LOGGER.warn(LoggerMarkers.Savestate, "{} is not found in EntityStorage. Things might desync!", subStorage.getPropertyName()); + continue; + } + + if (subStorage.getEntityClass().isAssignableFrom(entity.getClass())) { + entity = subStorage.onLoadstatePost(server, entity, subDataObject.get(subStorage.getPropertyName()).getAsJsonObject()); + } + } + } + } + } + + @Override + public String getExtensionName() { + return "EntityAIStorage"; + } + + public static interface EntitySubStorage { + public String getPropertyName(); + + public Class getEntityClass(); + + public JsonObject onSavestate(MinecraftServer server, Entity entity, JsonObject dataToSave); + + public Entity onLoadstatePost(MinecraftServer server, Entity entity, JsonObject loadedData); + } +} diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/EntityTickTimersStorage.java b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/EntityTickTimersStorage.java deleted file mode 100644 index 7d356430..00000000 --- a/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/EntityTickTimersStorage.java +++ /dev/null @@ -1,91 +0,0 @@ -package com.minecrafttas.tasmod.savestates.storage.builtin; - -import java.util.UUID; - -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.minecrafttas.tasmod.savestates.storage.SavestateStorageExtensionBase; - -import net.minecraft.entity.Entity; -import net.minecraft.entity.EntityLiving; -import net.minecraft.entity.EntityLivingBase; -import net.minecraft.entity.ai.EntityAITasks; -import net.minecraft.server.MinecraftServer; -import net.minecraft.world.WorldServer; - -/** - * Stores the {@link EntityAITasks#tickCount} in a savestate. - * - * AI tasks are only updated every third tick and the tickCount keeps track of that. - * - * @author Scribble - */ -public class EntityTickTimersStorage extends SavestateStorageExtensionBase { - - public EntityTickTimersStorage() { - super("entityTickTimers.json"); - } - - @Override - public String getExtensionName() { - return "EntityTickTimers"; - } - - @Override - public JsonObject onSavestate(MinecraftServer server, JsonObject dataToSave) { - for (WorldServer worldServer : server.worlds) { - for (Entity entity : worldServer.loadedEntityList) { - if (entity instanceof EntityLiving) { - UUID entityUUID = entity.getUniqueID(); - EntityLiving entityLiving = (EntityLiving) entity; - EntityLivingBase entityLivingBase = (EntityLivingBase) entity; - EntityAITasks tasks = entityLiving.tasks; - EntityAITasks targetTasks = entityLiving.targetTasks; - int tickCount = tasks.tickCount; - int tickCountTarget = targetTasks.tickCount; - int tickCountSound = entityLiving.livingSoundTime; - int tickIdle = entityLivingBase.idleTime; - JsonObject tickCountList = new JsonObject(); - tickCountList.addProperty("aitasks", tickCount); - tickCountList.addProperty("aitargetTasks", tickCountTarget); - tickCountList.addProperty("livingSoundTime", tickCountSound); - tickCountList.addProperty("idleTime", tickIdle); - dataToSave.add(entityUUID.toString(), tickCountList); - } - } - } - return dataToSave; - } - - @Override - public void onLoadstatePost(MinecraftServer server, JsonObject loadedData) { - if (loadedData == null) - return; - for (WorldServer worldServer : server.worlds) { - for (Entity entity : worldServer.loadedEntityList) { - UUID entityUUID = entity.getUniqueID(); - - JsonElement tickCountElement = loadedData.get(entityUUID.toString()); - if (tickCountElement == null) - continue; - JsonObject tickCountList = tickCountElement.getAsJsonObject(); - - int tickCount = tickCountList.get("aitasks").getAsInt(); - int tickCountTarget = tickCountList.get("aitargetTasks").getAsInt(); - int tickCountSound = tickCountList.get("livingSoundTime").getAsInt(); - int tickCountIdle = tickCountList.get("idleTime").getAsInt(); - - if (entity instanceof EntityLiving) { - EntityLiving entityLiving = (EntityLiving) entity; - EntityLivingBase entityLivingBase = (EntityLivingBase) entity; - EntityAITasks tasks = entityLiving.tasks; - EntityAITasks targetTasks = entityLiving.targetTasks; - tasks.tickCount = tickCount; - targetTasks.tickCount = tickCountTarget; - entityLiving.livingSoundTime = tickCountSound; - entityLivingBase.idleTime = tickCountIdle; - } - } - } - } -} diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entitysubstorage/BatSpawnPositionSubStorage.java b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entitysubstorage/BatSpawnPositionSubStorage.java new file mode 100644 index 00000000..22cbe1a0 --- /dev/null +++ b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entitysubstorage/BatSpawnPositionSubStorage.java @@ -0,0 +1,46 @@ +package com.minecrafttas.tasmod.savestates.storage.builtin.entitysubstorage; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.minecrafttas.tasmod.savestates.storage.builtin.EntityStorage.EntitySubStorage; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.passive.EntityBat; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.math.BlockPos; + +public class BatSpawnPositionSubStorage implements EntitySubStorage { + + @Override + public String getPropertyName() { + return "batSpawnPositionStorage"; + } + + @Override + public Class getEntityClass() { + return EntityBat.class; + } + + @Override + public JsonObject onSavestate(MinecraftServer server, Entity entity, JsonObject dataToSave) { + EntityBat bat = (EntityBat) entity; + BlockPos spawnPos = bat.spawnPosition; + if (spawnPos == null) + return dataToSave; + + dataToSave.addProperty("spawnPos", Long.toString(bat.spawnPosition.toLong())); + return dataToSave; + + } + + @Override + public Entity onLoadstatePost(MinecraftServer server, Entity entity, JsonObject loadedData) { + EntityBat bat = (EntityBat) entity; + JsonElement element = loadedData.get("spawnPos"); + if (element == null) + return bat; + BlockPos spawnPos = BlockPos.fromLong(element.getAsLong()); + bat.spawnPosition = spawnPos; + return bat; + } +} diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entitysubstorage/LivingTickTimersSubStorage.java b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entitysubstorage/LivingTickTimersSubStorage.java new file mode 100644 index 00000000..ad34d7eb --- /dev/null +++ b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entitysubstorage/LivingTickTimersSubStorage.java @@ -0,0 +1,72 @@ +package com.minecrafttas.tasmod.savestates.storage.builtin.entitysubstorage; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.minecrafttas.tasmod.savestates.storage.builtin.EntityStorage.EntitySubStorage; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.ai.EntityAITasks; +import net.minecraft.server.MinecraftServer; + +/** + * Stores the {@link EntityAITasks#tickCount} in a savestate. + * + * AI tasks are only updated every third tick and the tickCount keeps track of that. + * + * @author Scribble + */ +public class LivingTickTimersSubStorage implements EntitySubStorage { + + @Override + public String getPropertyName() { + return "tickTimers"; + } + + @Override + public Class getEntityClass() { + return EntityLiving.class; + } + + @Override + public JsonObject onSavestate(MinecraftServer server, Entity entity, JsonObject dataToSave) { + EntityLiving entityLiving = (EntityLiving) entity; + EntityLivingBase entityLivingBase = (EntityLivingBase) entity; + EntityAITasks tasks = entityLiving.tasks; + EntityAITasks targetTasks = entityLiving.targetTasks; + int tickCount = tasks.tickCount; + int tickCountTarget = targetTasks.tickCount; + int tickCountSound = entityLiving.livingSoundTime; + int tickIdle = entityLivingBase.idleTime; + dataToSave.addProperty("aitasks", tickCount); + dataToSave.addProperty("aitargetTasks", tickCountTarget); + dataToSave.addProperty("livingSoundTime", tickCountSound); + dataToSave.addProperty("idleTime", tickIdle); + return dataToSave; + } + + @Override + public Entity onLoadstatePost(MinecraftServer server, Entity entity, JsonObject loadedData) { + + JsonElement tickCount = loadedData.get("aitasks"); + JsonElement tickCountTarget = loadedData.get("aitargetTasks"); + JsonElement tickCountSound = loadedData.get("livingSoundTime"); + JsonElement tickCountIdle = loadedData.get("idleTime"); + + EntityLiving entityLiving = (EntityLiving) entity; + EntityLivingBase entityLivingBase = (EntityLivingBase) entity; + EntityAITasks tasks = entityLiving.tasks; + EntityAITasks targetTasks = entityLiving.targetTasks; + if (tickCount != null) + tasks.tickCount = tickCount.getAsInt(); + if (tickCountTarget != null) + targetTasks.tickCount = tickCountTarget.getAsInt(); + if (tickCountSound != null) + entityLiving.livingSoundTime = tickCountSound.getAsInt(); + if (tickCountIdle != null) + entityLivingBase.idleTime = tickCountIdle.getAsInt(); + + return entityLiving; + } +} diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entitysubstorage/SquidRotationSubStorage.java b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entitysubstorage/SquidRotationSubStorage.java new file mode 100644 index 00000000..88adc54a --- /dev/null +++ b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entitysubstorage/SquidRotationSubStorage.java @@ -0,0 +1,48 @@ +package com.minecrafttas.tasmod.savestates.storage.builtin.entitysubstorage; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.minecrafttas.tasmod.savestates.storage.builtin.EntityStorage.EntitySubStorage; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.passive.EntitySquid; +import net.minecraft.server.MinecraftServer; + +/** + *

Stores squid rotation in the savestates + *

Did you know that the rotation of the squid can trigger an RNG call? Did you know Minecraft does not save this by default?
+ * Well now you know and it's annoying. + * + * @author Scribble + */ +public class SquidRotationSubStorage implements EntitySubStorage { + + @Override + public String getPropertyName() { + return "squidRotationStorage"; + } + + @Override + public Class getEntityClass() { + return EntitySquid.class; + } + + @Override + public JsonObject onSavestate(MinecraftServer server, Entity entity, JsonObject dataToSave) { + EntitySquid squid = (EntitySquid) entity; + float rotation = squid.squidRotation; + dataToSave.addProperty("rotation", Float.toString(rotation)); + return dataToSave; + } + + @Override + public Entity onLoadstatePost(MinecraftServer server, Entity entity, JsonObject loadedData) { + EntitySquid squid = (EntitySquid) entity; + JsonElement rotElement = loadedData.get("rotation"); + if (rotElement == null) + return squid; + float rotation = rotElement.getAsFloat(); + squid.squidRotation = rotation; + return squid; + } +} From f8b1ee8751c0fba703bb8d159b50c05f461cd8d0 Mon Sep 17 00:00:00 2001 From: Scribble Date: Sat, 29 Aug 2026 11:26:39 +0200 Subject: [PATCH 08/10] [Savestates] Fix sub storage not working correctly - Rename package entitysubstorage to just entity - Remove unnecessary accesswideners --- .../java/com/minecrafttas/tasmod/TASmod.java | 8 +-- .../storage/builtin/EntityStorage.java | 4 +- .../BatSpawnPositionSubStorage.java | 3 +- .../entity/CreeperDetonateSubStorage.java | 51 +++++++++++++++++++ .../LivingTickTimersSubStorage.java | 2 +- .../SquidRotationSubStorage.java | 2 +- src/main/resources/tasmod.accesswidener | 25 ++++----- 7 files changed, 71 insertions(+), 24 deletions(-) rename src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/{entitysubstorage => entity}/BatSpawnPositionSubStorage.java (98%) create mode 100644 src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entity/CreeperDetonateSubStorage.java rename src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/{entitysubstorage => entity}/LivingTickTimersSubStorage.java (99%) rename src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/{entitysubstorage => entity}/SquidRotationSubStorage.java (99%) diff --git a/src/main/java/com/minecrafttas/tasmod/TASmod.java b/src/main/java/com/minecrafttas/tasmod/TASmod.java index 16480d0d..eaa5c845 100644 --- a/src/main/java/com/minecrafttas/tasmod/TASmod.java +++ b/src/main/java/com/minecrafttas/tasmod/TASmod.java @@ -46,9 +46,10 @@ import com.minecrafttas.tasmod.savestates.storage.builtin.EntityAiTaskStorage; import com.minecrafttas.tasmod.savestates.storage.builtin.EntityStorage; import com.minecrafttas.tasmod.savestates.storage.builtin.KTRNGSeedStorage; -import com.minecrafttas.tasmod.savestates.storage.builtin.entitysubstorage.BatSpawnPositionSubStorage; -import com.minecrafttas.tasmod.savestates.storage.builtin.entitysubstorage.LivingTickTimersSubStorage; -import com.minecrafttas.tasmod.savestates.storage.builtin.entitysubstorage.SquidRotationSubStorage; +import com.minecrafttas.tasmod.savestates.storage.builtin.entity.BatSpawnPositionSubStorage; +import com.minecrafttas.tasmod.savestates.storage.builtin.entity.CreeperDetonateSubStorage; +import com.minecrafttas.tasmod.savestates.storage.builtin.entity.LivingTickTimersSubStorage; +import com.minecrafttas.tasmod.savestates.storage.builtin.entity.SquidRotationSubStorage; import com.minecrafttas.tasmod.tickratechanger.TickrateChangerServer; import com.minecrafttas.tasmod.ticksync.TickSyncServer; import com.minecrafttas.tasmod.util.LoggerMarkers; @@ -247,6 +248,7 @@ private void registerSavestateStorage() { TASmodAPIRegistry.SAVESTATE_STORAGE.register( new EntityStorage( new BatSpawnPositionSubStorage(), + new CreeperDetonateSubStorage(), new SquidRotationSubStorage(), new LivingTickTimersSubStorage() ) diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/EntityStorage.java b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/EntityStorage.java index ff56024a..2aa4763c 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/EntityStorage.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/EntityStorage.java @@ -32,15 +32,15 @@ public JsonObject onSavestate(MinecraftServer server, JsonObject dataToSave) { for (WorldServer worldServer : server.worlds) { for (Entity entity : worldServer.loadedEntityList) { UUID uuid = entity.getUniqueID(); + JsonObject subDataObject = new JsonObject(); for (EntitySubStorage subStorage : subStorages) { - JsonObject subDataObject = new JsonObject(); if (subStorage.getEntityClass().isAssignableFrom(entity.getClass())) { JsonObject subDataToSave = new JsonObject(); subDataToSave = subStorage.onSavestate(server, entity, subDataToSave); subDataObject.add(subStorage.getPropertyName(), subDataToSave); } - dataToSave.add(uuid.toString(), subDataObject); } + dataToSave.add(uuid.toString(), subDataObject); } } return dataToSave; diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entitysubstorage/BatSpawnPositionSubStorage.java b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entity/BatSpawnPositionSubStorage.java similarity index 98% rename from src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entitysubstorage/BatSpawnPositionSubStorage.java rename to src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entity/BatSpawnPositionSubStorage.java index 22cbe1a0..0e668810 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entitysubstorage/BatSpawnPositionSubStorage.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entity/BatSpawnPositionSubStorage.java @@ -1,4 +1,4 @@ -package com.minecrafttas.tasmod.savestates.storage.builtin.entitysubstorage; +package com.minecrafttas.tasmod.savestates.storage.builtin.entity; import com.google.gson.JsonElement; import com.google.gson.JsonObject; @@ -30,7 +30,6 @@ public JsonObject onSavestate(MinecraftServer server, Entity entity, JsonObject dataToSave.addProperty("spawnPos", Long.toString(bat.spawnPosition.toLong())); return dataToSave; - } @Override diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entity/CreeperDetonateSubStorage.java b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entity/CreeperDetonateSubStorage.java new file mode 100644 index 00000000..a388a186 --- /dev/null +++ b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entity/CreeperDetonateSubStorage.java @@ -0,0 +1,51 @@ +package com.minecrafttas.tasmod.savestates.storage.builtin.entity; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.minecrafttas.tasmod.savestates.storage.builtin.EntityStorage.EntitySubStorage; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.monster.EntityCreeper; +import net.minecraft.server.MinecraftServer; + +public class CreeperDetonateSubStorage implements EntitySubStorage { + + @Override + public String getPropertyName() { + return "creeperDetonate"; + } + + @Override + public Class getEntityClass() { + return EntityCreeper.class; + } + + @Override + public JsonObject onSavestate(MinecraftServer server, Entity entity, JsonObject dataToSave) { + EntityCreeper creeper = (EntityCreeper) entity; + dataToSave.addProperty("timeSinceIgnited", creeper.timeSinceIgnited); + dataToSave.addProperty("droppedSkulls", creeper.droppedSkulls); + dataToSave.addProperty("exploding", creeper.getCreeperState()); + return dataToSave; + } + + @Override + public Entity onLoadstatePost(MinecraftServer server, Entity entity, JsonObject loadedData) { + EntityCreeper creeper = (EntityCreeper) entity; + JsonElement boomTime = loadedData.get("timeSinceIgnited"); + JsonElement dropped = loadedData.get("droppedSkulls"); + JsonElement exploding = loadedData.get("exploding"); + + if (boomTime != null) + creeper.timeSinceIgnited = boomTime.getAsInt(); + + if (dropped != null) + creeper.droppedSkulls = dropped.getAsInt(); + + if (exploding != null) + creeper.setCreeperState(exploding.getAsInt()); + + return entity; + } + +} diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entitysubstorage/LivingTickTimersSubStorage.java b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entity/LivingTickTimersSubStorage.java similarity index 99% rename from src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entitysubstorage/LivingTickTimersSubStorage.java rename to src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entity/LivingTickTimersSubStorage.java index ad34d7eb..bcc854fd 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entitysubstorage/LivingTickTimersSubStorage.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entity/LivingTickTimersSubStorage.java @@ -1,4 +1,4 @@ -package com.minecrafttas.tasmod.savestates.storage.builtin.entitysubstorage; +package com.minecrafttas.tasmod.savestates.storage.builtin.entity; import com.google.gson.JsonElement; import com.google.gson.JsonObject; diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entitysubstorage/SquidRotationSubStorage.java b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entity/SquidRotationSubStorage.java similarity index 99% rename from src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entitysubstorage/SquidRotationSubStorage.java rename to src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entity/SquidRotationSubStorage.java index 88adc54a..af36876c 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entitysubstorage/SquidRotationSubStorage.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entity/SquidRotationSubStorage.java @@ -1,4 +1,4 @@ -package com.minecrafttas.tasmod.savestates.storage.builtin.entitysubstorage; +package com.minecrafttas.tasmod.savestates.storage.builtin.entity; import com.google.gson.JsonElement; import com.google.gson.JsonObject; diff --git a/src/main/resources/tasmod.accesswidener b/src/main/resources/tasmod.accesswidener index 18056f48..02166893 100644 --- a/src/main/resources/tasmod.accesswidener +++ b/src/main/resources/tasmod.accesswidener @@ -28,31 +28,26 @@ accessible field net/minecraft/client/settings/KeyBinding CATEGORY_ORDER Ljava/u accessible field net/minecraft/entity/Entity rand Ljava/util/Random; accessible field net/minecraft/world/World updateLCG I mutable field net/minecraft/world/World rand Ljava/util/Random; -accessible field net/minecraft/entity/EntityLiving tasks Lnet/minecraft/entity/ai/EntityAITasks; -accessible field net/minecraft/entity/EntityLiving targetTasks Lnet/minecraft/entity/ai/EntityAITasks; + +# EntityRNG accessible field net/minecraft/entity/ai/EntityAITasks tickCount I accessible field net/minecraft/entity/passive/EntityBat spawnPosition Lnet/minecraft/util/math/BlockPos; accessible field net/minecraft/entity/Entity nextEntityID I accessible field net/minecraft/entity/EntityLivingBase idleTime I +accessible field net/minecraft/entity/monster/EntityCreeper timeSinceIgnited I +accessible field net/minecraft/entity/monster/EntityCreeper droppedSkulls I + +# EntityAI +accessible field net/minecraft/entity/EntityLiving tasks Lnet/minecraft/entity/ai/EntityAITasks; +accessible field net/minecraft/entity/EntityLiving targetTasks Lnet/minecraft/entity/ai/EntityAITasks; 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; +mutable field net/minecraft/entity/ai/EntityAITasks$EntityAITaskEntry action Lnet/minecraft/entity/ai/EntityAIBase; + 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; From fd144a8406acdd21466409e6675f3a3a00fc34f3 Mon Sep 17 00:00:00 2001 From: Scribble Date: Sat, 29 Aug 2026 11:26:39 +0200 Subject: [PATCH 09/10] [Savestates] Fix entity AI not being applied correctly --- .../savestates/storage/builtin/EntityAiTaskStorage.java | 7 ++++--- src/main/resources/tasmod.accesswidener | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) 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 index 08b60821..37d27828 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/EntityAiTaskStorage.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/EntityAiTaskStorage.java @@ -98,6 +98,7 @@ public JsonObject onSavestate(MinecraftServer server, JsonObject dataToSave) { mainObject.addProperty("type", entity.getClass().getSimpleName()); mainObject.add("revengeTarget", gsonInstance.toJsonTree(entityLiving.getRevengeTarget())); + mainObject.add("revengeTimer", gsonInstance.toJsonTree(entityLiving.revengeTimer)); mainObject.add("lastAttackedEntity", gsonInstance.toJsonTree(entityLiving.getLastAttackedEntity())); EntityAITasks tasks = entityLiving.tasks; @@ -157,8 +158,8 @@ public void onLoadstatePost(MinecraftServer server, JsonObject loadedData) { } private void deserialiseAITasks(EntityAITasks tasks, JsonObject jsonTasks) { - deserialiseAIEntries(tasks.taskEntries, tasks, jsonTasks.get("taskEntries").getAsJsonArray()); - deserialiseAIEntries(tasks.executingTaskEntries, tasks, jsonTasks.get("executingTaskEntries").getAsJsonArray()); + tasks.taskEntries = deserialiseAIEntries(tasks.taskEntries, tasks, jsonTasks.get("taskEntries").getAsJsonArray()); + tasks.executingTaskEntries = deserialiseAIEntries(tasks.executingTaskEntries, tasks, jsonTasks.get("executingTaskEntries").getAsJsonArray()); } private Set deserialiseAIEntries(Set taskEntries, EntityAITasks parent, JsonArray jsonEntries) { @@ -198,7 +199,7 @@ private Set deserialiseAIEntries(Set taskE boolean using = jsonEntry.get("using").getAsBoolean(); EntityAIBase action = null; try { - action = gsonInstance.fromJson(jsonEntry, clazz); + action = gsonInstance.fromJson(jsonEntry.get("action"), clazz); } catch (Exception e) { e.printStackTrace(); continue; diff --git a/src/main/resources/tasmod.accesswidener b/src/main/resources/tasmod.accesswidener index 02166893..000ca1e2 100644 --- a/src/main/resources/tasmod.accesswidener +++ b/src/main/resources/tasmod.accesswidener @@ -33,6 +33,7 @@ mutable field net/minecraft/world/World rand Ljava/util/Random; accessible field net/minecraft/entity/ai/EntityAITasks tickCount I accessible field net/minecraft/entity/passive/EntityBat spawnPosition Lnet/minecraft/util/math/BlockPos; accessible field net/minecraft/entity/Entity nextEntityID I +accessible field net/minecraft/entity/EntityLivingBase revengeTimer I accessible field net/minecraft/entity/EntityLivingBase idleTime I accessible field net/minecraft/entity/monster/EntityCreeper timeSinceIgnited I accessible field net/minecraft/entity/monster/EntityCreeper droppedSkulls I From f25ab40a5cd0c6726710a5da8ff59225b73aec05 Mon Sep 17 00:00:00 2001 From: Scribble Date: Mon, 31 Aug 2026 22:26:57 +0200 Subject: [PATCH 10/10] [Savestates] Add PathFinding to sub storage --- .../java/com/minecrafttas/tasmod/TASmod.java | 17 +++++-- .../com/minecrafttas/tasmod/TASmodClient.java | 2 +- .../storage/builtin/EntityAiTaskStorage.java | 5 +- .../storage/builtin/EntityStorage.java | 25 ++++++++-- .../entity/BatSpawnPositionSubStorage.java | 5 +- .../entity/CreeperDetonateSubStorage.java | 5 +- .../entity/LivingNavigatorSubStorage.java | 50 +++++++++++++++++++ .../entity/LivingTickTimersSubStorage.java | 5 +- .../entity/SquidRotationSubStorage.java | 5 +- src/main/resources/tasmod.accesswidener | 12 ++++- 10 files changed, 111 insertions(+), 20 deletions(-) create mode 100644 src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entity/LivingNavigatorSubStorage.java diff --git a/src/main/java/com/minecrafttas/tasmod/TASmod.java b/src/main/java/com/minecrafttas/tasmod/TASmod.java index eaa5c845..67665ed0 100644 --- a/src/main/java/com/minecrafttas/tasmod/TASmod.java +++ b/src/main/java/com/minecrafttas/tasmod/TASmod.java @@ -1,6 +1,7 @@ package com.minecrafttas.tasmod; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; import org.apache.logging.log4j.LogManager; @@ -48,6 +49,7 @@ import com.minecrafttas.tasmod.savestates.storage.builtin.KTRNGSeedStorage; import com.minecrafttas.tasmod.savestates.storage.builtin.entity.BatSpawnPositionSubStorage; import com.minecrafttas.tasmod.savestates.storage.builtin.entity.CreeperDetonateSubStorage; +import com.minecrafttas.tasmod.savestates.storage.builtin.entity.LivingNavigatorSubStorage; import com.minecrafttas.tasmod.savestates.storage.builtin.entity.LivingTickTimersSubStorage; import com.minecrafttas.tasmod.savestates.storage.builtin.entity.SquidRotationSubStorage; import com.minecrafttas.tasmod.tickratechanger.TickrateChangerServer; @@ -250,7 +252,8 @@ private void registerSavestateStorage() { new BatSpawnPositionSubStorage(), new CreeperDetonateSubStorage(), new SquidRotationSubStorage(), - new LivingTickTimersSubStorage() + new LivingTickTimersSubStorage(), + new LivingNavigatorSubStorage() ) ); //@formatter:on @@ -262,11 +265,19 @@ public static MinecraftServer getServerInstance() { } private void loadConfig(MinecraftServer server) { - Path configDir = server.getDataDirectory().toPath(); + Path configDir = server.getDataDirectory().toPath().resolve("config"); + + if (!Files.exists(configDir)) { + try { + Files.createDirectory(configDir); + } catch (IOException e) { + LOGGER.catching(e); + } + } ConfigurationRegistry SERVER_CONFIG_REGISTRY = new ConfigurationRegistry(); SERVER_CONFIG_REGISTRY.register(TASmodServerConfig.values()); - config = new Configuration("TASmod Server Configuration", configDir.resolve("tasmod.cfg"), SERVER_CONFIG_REGISTRY); + config = new Configuration("TASmod Server Configuration", configDir.resolve("tasmod_server.cfg"), SERVER_CONFIG_REGISTRY); config.load(); config.save(); diff --git a/src/main/java/com/minecrafttas/tasmod/TASmodClient.java b/src/main/java/com/minecrafttas/tasmod/TASmodClient.java index fca5e374..1d95a651 100644 --- a/src/main/java/com/minecrafttas/tasmod/TASmodClient.java +++ b/src/main/java/com/minecrafttas/tasmod/TASmodClient.java @@ -366,7 +366,7 @@ private void loadConfig(Minecraft mc) { LOGGER.catching(e); } } - config = new Configuration("TASmod configuration", configDir.resolve("tasmod.cfg"), CLIENT_CONFIG_REGISTRY); + config = new Configuration("TASmod configuration", configDir.resolve("tasmod_client.cfg"), CLIENT_CONFIG_REGISTRY); config.load(); config.save(); 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 index 37d27828..24639101 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/EntityAiTaskStorage.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/EntityAiTaskStorage.java @@ -169,7 +169,7 @@ private Set deserialiseAIEntries(Set taskE JsonObject jsonEntry = jsonEntryElement.getAsJsonObject(); // Deserialise the type of the AI action -// System.out.println(jsonEntry.get("class").getAsString()); + System.out.println(jsonEntry.get("class").getAsString()); Class clazz; try { clazz = Class.forName(jsonEntry.get("class").getAsString(), false, getClass().getClassLoader()).asSubclass(EntityAIBase.class); @@ -179,6 +179,7 @@ private Set deserialiseAIEntries(Set taskE } boolean createNew = true; + boolean using = jsonEntry.get("using").getAsBoolean(); for (EntityAITaskEntry vanillaEntry : taskEntries) { EntityAIBase vanillaAction = vanillaEntry.action; if (vanillaAction.getClass() != clazz) { @@ -187,6 +188,7 @@ private Set deserialiseAIEntries(Set taskE createNew = false; vanillaEntry.action = deserialiseAction(vanillaAction, jsonEntry.get("action"), clazz); + vanillaEntry.using = using; out.add(vanillaEntry); break; } @@ -196,7 +198,6 @@ private Set deserialiseAIEntries(Set taskE } int priority = jsonEntry.get("priority").getAsInt(); - boolean using = jsonEntry.get("using").getAsBoolean(); EntityAIBase action = null; try { action = gsonInstance.fromJson(jsonEntry.get("action"), clazz); diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/EntityStorage.java b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/EntityStorage.java index 2aa4763c..96f65c8b 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/EntityStorage.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/EntityStorage.java @@ -4,14 +4,21 @@ import java.util.List; import java.util.UUID; +import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonObject; import com.minecrafttas.tasmod.TASmod; import com.minecrafttas.tasmod.savestates.storage.SavestateStorageExtensionBase; +import com.minecrafttas.tasmod.savestates.typeadapters.util.ClassExclusionStrategy; import com.minecrafttas.tasmod.util.LoggerMarkers; import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.ai.attributes.IAttributeInstance; +import net.minecraft.pathfinding.NodeProcessor; +import net.minecraft.pathfinding.PathFinder; import net.minecraft.server.MinecraftServer; +import net.minecraft.world.World; import net.minecraft.world.WorldServer; public class EntityStorage extends SavestateStorageExtensionBase { @@ -23,7 +30,15 @@ public EntityStorage(EntitySubStorage... entitySubStorages) { } public EntityStorage(List entitySubStorages) { - super("entity.json", new GsonBuilder().setPrettyPrinting().create()); + //@formatter:off + super("entity.json", new GsonBuilder().setPrettyPrinting().setExclusionStrategies( + new ClassExclusionStrategy(EntityLiving.class), + new ClassExclusionStrategy(World.class), + new ClassExclusionStrategy(NodeProcessor.class), + new ClassExclusionStrategy(PathFinder.class), + new ClassExclusionStrategy(IAttributeInstance.class) + ).create()); + //@formatter:on this.subStorages = entitySubStorages; } @@ -36,7 +51,7 @@ public JsonObject onSavestate(MinecraftServer server, JsonObject dataToSave) { for (EntitySubStorage subStorage : subStorages) { if (subStorage.getEntityClass().isAssignableFrom(entity.getClass())) { JsonObject subDataToSave = new JsonObject(); - subDataToSave = subStorage.onSavestate(server, entity, subDataToSave); + subDataToSave = subStorage.onSavestate(server, entity, subDataToSave, super.gsonInstance); subDataObject.add(subStorage.getPropertyName(), subDataToSave); } } @@ -64,7 +79,7 @@ public void onLoadstatePost(MinecraftServer server, JsonObject loadedData) { } if (subStorage.getEntityClass().isAssignableFrom(entity.getClass())) { - entity = subStorage.onLoadstatePost(server, entity, subDataObject.get(subStorage.getPropertyName()).getAsJsonObject()); + entity = subStorage.onLoadstatePost(server, entity, subDataObject.get(subStorage.getPropertyName()).getAsJsonObject(), super.gsonInstance); } } } @@ -81,8 +96,8 @@ public static interface EntitySubStorage { public Class getEntityClass(); - public JsonObject onSavestate(MinecraftServer server, Entity entity, JsonObject dataToSave); + public JsonObject onSavestate(MinecraftServer server, Entity entity, JsonObject dataToSave, Gson gsonInstance); - public Entity onLoadstatePost(MinecraftServer server, Entity entity, JsonObject loadedData); + public Entity onLoadstatePost(MinecraftServer server, Entity entity, JsonObject loadedData, Gson gsonInstance); } } diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entity/BatSpawnPositionSubStorage.java b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entity/BatSpawnPositionSubStorage.java index 0e668810..afe2073f 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entity/BatSpawnPositionSubStorage.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entity/BatSpawnPositionSubStorage.java @@ -1,5 +1,6 @@ package com.minecrafttas.tasmod.savestates.storage.builtin.entity; +import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.minecrafttas.tasmod.savestates.storage.builtin.EntityStorage.EntitySubStorage; @@ -22,7 +23,7 @@ public Class getEntityClass() { } @Override - public JsonObject onSavestate(MinecraftServer server, Entity entity, JsonObject dataToSave) { + public JsonObject onSavestate(MinecraftServer server, Entity entity, JsonObject dataToSave, Gson gsonInstance) { EntityBat bat = (EntityBat) entity; BlockPos spawnPos = bat.spawnPosition; if (spawnPos == null) @@ -33,7 +34,7 @@ public JsonObject onSavestate(MinecraftServer server, Entity entity, JsonObject } @Override - public Entity onLoadstatePost(MinecraftServer server, Entity entity, JsonObject loadedData) { + public Entity onLoadstatePost(MinecraftServer server, Entity entity, JsonObject loadedData, Gson gsonInstance) { EntityBat bat = (EntityBat) entity; JsonElement element = loadedData.get("spawnPos"); if (element == null) diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entity/CreeperDetonateSubStorage.java b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entity/CreeperDetonateSubStorage.java index a388a186..7b9ae5a2 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entity/CreeperDetonateSubStorage.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entity/CreeperDetonateSubStorage.java @@ -1,5 +1,6 @@ package com.minecrafttas.tasmod.savestates.storage.builtin.entity; +import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.minecrafttas.tasmod.savestates.storage.builtin.EntityStorage.EntitySubStorage; @@ -21,7 +22,7 @@ public Class getEntityClass() { } @Override - public JsonObject onSavestate(MinecraftServer server, Entity entity, JsonObject dataToSave) { + public JsonObject onSavestate(MinecraftServer server, Entity entity, JsonObject dataToSave, Gson gsonInstance) { EntityCreeper creeper = (EntityCreeper) entity; dataToSave.addProperty("timeSinceIgnited", creeper.timeSinceIgnited); dataToSave.addProperty("droppedSkulls", creeper.droppedSkulls); @@ -30,7 +31,7 @@ public JsonObject onSavestate(MinecraftServer server, Entity entity, JsonObject } @Override - public Entity onLoadstatePost(MinecraftServer server, Entity entity, JsonObject loadedData) { + public Entity onLoadstatePost(MinecraftServer server, Entity entity, JsonObject loadedData, Gson gsonInstance) { EntityCreeper creeper = (EntityCreeper) entity; JsonElement boomTime = loadedData.get("timeSinceIgnited"); JsonElement dropped = loadedData.get("droppedSkulls"); diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entity/LivingNavigatorSubStorage.java b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entity/LivingNavigatorSubStorage.java new file mode 100644 index 00000000..cd70ca36 --- /dev/null +++ b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entity/LivingNavigatorSubStorage.java @@ -0,0 +1,50 @@ +package com.minecrafttas.tasmod.savestates.storage.builtin.entity; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.minecrafttas.tasmod.savestates.storage.builtin.EntityStorage.EntitySubStorage; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLiving; +import net.minecraft.pathfinding.PathNavigate; +import net.minecraft.server.MinecraftServer; + +public class LivingNavigatorSubStorage implements EntitySubStorage { + + @Override + public String getPropertyName() { + return "livingNavigator"; + } + + @Override + public Class getEntityClass() { + return EntityLiving.class; + } + + @Override + public JsonObject onSavestate(MinecraftServer server, Entity entity, JsonObject dataToSave, Gson gsonInstance) { + EntityLiving entityLiving = (EntityLiving) entity; + dataToSave.add("navigator", gsonInstance.toJsonTree(entityLiving.navigator)); + return dataToSave; + } + + @Override + public Entity onLoadstatePost(MinecraftServer server, Entity entity, JsonObject loadedData, Gson gsonInstance) { + EntityLiving entityLiving = (EntityLiving) entity; + JsonElement navigatorJson = loadedData.get("navigator"); + if (navigatorJson != null) { + PathNavigate dummyNavigator = entityLiving.createNavigator(entityLiving.world); + PathNavigate navigator = entityLiving.navigator; + navigator = gsonInstance.fromJson(navigatorJson, dummyNavigator.getClass()); + navigator.entity = dummyNavigator.entity; + navigator.world = dummyNavigator.world; + navigator.pathSearchRange = dummyNavigator.pathSearchRange; + navigator.nodeProcessor = dummyNavigator.nodeProcessor; + navigator.pathFinder = dummyNavigator.pathFinder; + } + + return entityLiving; + } + +} diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entity/LivingTickTimersSubStorage.java b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entity/LivingTickTimersSubStorage.java index bcc854fd..4f50b1af 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entity/LivingTickTimersSubStorage.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entity/LivingTickTimersSubStorage.java @@ -1,5 +1,6 @@ package com.minecrafttas.tasmod.savestates.storage.builtin.entity; +import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.minecrafttas.tasmod.savestates.storage.builtin.EntityStorage.EntitySubStorage; @@ -30,7 +31,7 @@ public Class getEntityClass() { } @Override - public JsonObject onSavestate(MinecraftServer server, Entity entity, JsonObject dataToSave) { + public JsonObject onSavestate(MinecraftServer server, Entity entity, JsonObject dataToSave, Gson gsonInstance) { EntityLiving entityLiving = (EntityLiving) entity; EntityLivingBase entityLivingBase = (EntityLivingBase) entity; EntityAITasks tasks = entityLiving.tasks; @@ -47,7 +48,7 @@ public JsonObject onSavestate(MinecraftServer server, Entity entity, JsonObject } @Override - public Entity onLoadstatePost(MinecraftServer server, Entity entity, JsonObject loadedData) { + public Entity onLoadstatePost(MinecraftServer server, Entity entity, JsonObject loadedData, Gson gsonInstance) { JsonElement tickCount = loadedData.get("aitasks"); JsonElement tickCountTarget = loadedData.get("aitargetTasks"); diff --git a/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entity/SquidRotationSubStorage.java b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entity/SquidRotationSubStorage.java index af36876c..0a768eec 100644 --- a/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entity/SquidRotationSubStorage.java +++ b/src/main/java/com/minecrafttas/tasmod/savestates/storage/builtin/entity/SquidRotationSubStorage.java @@ -1,5 +1,6 @@ package com.minecrafttas.tasmod.savestates.storage.builtin.entity; +import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.minecrafttas.tasmod.savestates.storage.builtin.EntityStorage.EntitySubStorage; @@ -28,7 +29,7 @@ public Class getEntityClass() { } @Override - public JsonObject onSavestate(MinecraftServer server, Entity entity, JsonObject dataToSave) { + public JsonObject onSavestate(MinecraftServer server, Entity entity, JsonObject dataToSave, Gson gsonInstance) { EntitySquid squid = (EntitySquid) entity; float rotation = squid.squidRotation; dataToSave.addProperty("rotation", Float.toString(rotation)); @@ -36,7 +37,7 @@ public JsonObject onSavestate(MinecraftServer server, Entity entity, JsonObject } @Override - public Entity onLoadstatePost(MinecraftServer server, Entity entity, JsonObject loadedData) { + public Entity onLoadstatePost(MinecraftServer server, Entity entity, JsonObject loadedData, Gson gsonInstance) { EntitySquid squid = (EntitySquid) entity; JsonElement rotElement = loadedData.get("rotation"); if (rotElement == null) diff --git a/src/main/resources/tasmod.accesswidener b/src/main/resources/tasmod.accesswidener index 000ca1e2..224b0f83 100644 --- a/src/main/resources/tasmod.accesswidener +++ b/src/main/resources/tasmod.accesswidener @@ -38,6 +38,17 @@ accessible field net/minecraft/entity/EntityLivingBase idleTime I accessible field net/minecraft/entity/monster/EntityCreeper timeSinceIgnited I accessible field net/minecraft/entity/monster/EntityCreeper droppedSkulls I +# EntityPathFinding +accessible method net/minecraft/entity/EntityLiving createNavigator (Lnet/minecraft/world/World;)Lnet/minecraft/pathfinding/PathNavigate; +accessible field net/minecraft/entity/EntityLiving navigator Lnet/minecraft/pathfinding/PathNavigate; +accessible field net/minecraft/pathfinding/PathNavigate entity Lnet/minecraft/entity/EntityLiving; +accessible field net/minecraft/pathfinding/PathNavigate world Lnet/minecraft/world/World; +accessible field net/minecraft/pathfinding/PathNavigate pathSearchRange Lnet/minecraft/entity/ai/attributes/IAttributeInstance; +mutable field net/minecraft/pathfinding/PathNavigate pathSearchRange Lnet/minecraft/entity/ai/attributes/IAttributeInstance; +accessible field net/minecraft/pathfinding/PathNavigate nodeProcessor Lnet/minecraft/pathfinding/NodeProcessor; +accessible field net/minecraft/pathfinding/PathNavigate pathFinder Lnet/minecraft/pathfinding/PathFinder; +mutable field net/minecraft/pathfinding/PathNavigate pathFinder Lnet/minecraft/pathfinding/PathFinder; + # EntityAI accessible field net/minecraft/entity/EntityLiving tasks Lnet/minecraft/entity/ai/EntityAITasks; accessible field net/minecraft/entity/EntityLiving targetTasks Lnet/minecraft/entity/ai/EntityAITasks; @@ -51,4 +62,3 @@ mutable field net/minecraft/entity/ai/EntityAITasks$EntityAITaskEntry action Lne accessible field net/minecraft/entity/ai/EntityAITasks executingTaskEntries Ljava/util/Set; mutable field net/minecraft/entity/ai/EntityAITasks executingTaskEntries Ljava/util/Set; -