diff --git a/src/main/java/dev/zontreck/libzontreck/LibZontreck.java b/src/main/java/dev/zontreck/libzontreck/LibZontreck.java index 803d1f9..af3c290 100644 --- a/src/main/java/dev/zontreck/libzontreck/LibZontreck.java +++ b/src/main/java/dev/zontreck/libzontreck/LibZontreck.java @@ -9,14 +9,18 @@ import java.util.Map; import java.util.UUID; import dev.zontreck.libzontreck.chestgui.ChestGUIRegistry; +import dev.zontreck.libzontreck.config.ServerConfig; import dev.zontreck.libzontreck.currency.Bank; import dev.zontreck.libzontreck.currency.CurrencyHelper; import dev.zontreck.libzontreck.events.BlockRestoreQueueRegistrationEvent; import dev.zontreck.libzontreck.items.ModItems; +import dev.zontreck.libzontreck.memory.world.BlockRestoreQueue; +import dev.zontreck.libzontreck.memory.world.BlockRestoreQueueRegistry; import dev.zontreck.libzontreck.menus.ChestGUIScreen; import dev.zontreck.libzontreck.types.ModMenuTypes; import dev.zontreck.libzontreck.networking.NetworkEvents; import net.minecraft.client.gui.screens.MenuScreens; +import net.minecraft.server.level.ServerLevel; import org.slf4j.Logger; import com.mojang.logging.LogUtils; @@ -106,9 +110,16 @@ public class LibZontreck { public void onServerStarted(final ServerStartedEvent event) { ALIVE=true; + + ServerConfig.init(); CURRENT_SIDE = LogicalSide.SERVER; MinecraftForge.EVENT_BUS.post(new BlockRestoreQueueRegistrationEvent()); + + for(ServerLevel level : event.getServer().getAllLevels()) + { + // Queues have been registered, but we now need to initialize the queue's data from saveddata + } } @SubscribeEvent diff --git a/src/main/java/dev/zontreck/libzontreck/config/ServerConfig.java b/src/main/java/dev/zontreck/libzontreck/config/ServerConfig.java new file mode 100644 index 0000000..3d0c3b8 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/config/ServerConfig.java @@ -0,0 +1,35 @@ +package dev.zontreck.libzontreck.config; + +import dev.zontreck.libzontreck.LibZontreck; +import dev.zontreck.libzontreck.config.sections.DatabaseSection; +import dev.zontreck.libzontreck.util.SNbtIo; +import net.minecraft.nbt.CompoundTag; + +import java.nio.file.Path; + +public class ServerConfig +{ + public static final Path BASE = LibZontreck.BASE_CONFIG.resolve("server.snbt"); + + public static DatabaseSection database; + + public static void init() + { + if(BASE.toFile().exists()) + { + var config = SNbtIo.loadSnbt(BASE); + + database = DatabaseSection.deserialize(config.getCompound(DatabaseSection.TAG_NAME)); + } else { + database = new DatabaseSection(); + } + } + + public static void commit() + { + CompoundTag tag = new CompoundTag(); + tag.put(DatabaseSection.TAG_NAME, database.serialize()); + + SNbtIo.writeSnbt(BASE, tag); + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/config/sections/DatabaseSection.java b/src/main/java/dev/zontreck/libzontreck/config/sections/DatabaseSection.java new file mode 100644 index 0000000..9787ecd --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/config/sections/DatabaseSection.java @@ -0,0 +1,91 @@ +package dev.zontreck.libzontreck.config.sections; + +import dev.zontreck.libzontreck.config.ServerConfig; +import net.minecraft.nbt.CompoundTag; + +public class DatabaseSection +{ + public static final String TAG_NAME = "database"; + public static final String TAG_USER = "username"; + public static final String TAG_PASSWORD = "password"; + public static final String TAG_HOST = "host"; + public static final String TAG_DATABASE = "database"; + public static final String TAG_VERSION = "rev"; + + + public String user = "root"; + public String password = ""; + public String host = "localhost:3306"; // IP:port + public String database = "savedBlocks"; + public int version; + + public static final int VERSION = 1; + + private boolean migrated=false; + + public static DatabaseSection deserialize(CompoundTag tag) + { + int ver = tag.getInt(TAG_VERSION); + DatabaseSection section = new DatabaseSection(); + section.doMigrations(ver, tag); + + + return section; + } + + public void doMigrations(int ver, CompoundTag tag) + { + switch (ver) + { + case 0: + { + ver++; + user = tag.getString(TAG_USER); + password = tag.getString(TAG_PASSWORD); + host = tag.getString(TAG_HOST); + database = tag.getString(TAG_DATABASE); + version = ver; + migrated=true; + break; + } + default:{ + if(migrated) + { + ServerConfig.commit(); + migrated=false; + return; + } + break; + } + } + + doMigrations(ver, tag); + } + + public DatabaseSection(){ + + } + + public DatabaseSection(String user, String password, String host, String database) + { + this.user = user; + this.password = password; + this.host = host; + this.database = database; + } + + public String getAsSQLFileName() + { + return database + ".sql"; + } + + public CompoundTag serialize() + { + CompoundTag tag = new CompoundTag(); + tag.putString(TAG_USER, user); + tag.putString(TAG_PASSWORD, password); + tag.putString(TAG_HOST, host); + tag.putString(TAG_DATABASE, database); + return tag; + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/events/BlockRestoreQueueRegistrationEvent.java b/src/main/java/dev/zontreck/libzontreck/events/BlockRestoreQueueRegistrationEvent.java index 151f007..4ae35c5 100644 --- a/src/main/java/dev/zontreck/libzontreck/events/BlockRestoreQueueRegistrationEvent.java +++ b/src/main/java/dev/zontreck/libzontreck/events/BlockRestoreQueueRegistrationEvent.java @@ -2,6 +2,7 @@ package dev.zontreck.libzontreck.events; import dev.zontreck.libzontreck.memory.world.BlockRestoreQueue; import dev.zontreck.libzontreck.memory.world.BlockRestoreQueueRegistry; +import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.eventbus.api.Event; public class BlockRestoreQueueRegistrationEvent extends Event @@ -13,5 +14,7 @@ public class BlockRestoreQueueRegistrationEvent extends Event public void register(BlockRestoreQueue queue) { BlockRestoreQueueRegistry.addQueue(queue); + + MinecraftForge.EVENT_BUS.register(queue); } } diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java index 11a2129..46612a3 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java @@ -1,13 +1,21 @@ package dev.zontreck.libzontreck.memory.world; import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtIo; +import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.block.Block; +import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.TickEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; +import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.List; + public abstract class BlockRestoreQueue { private List BLOCK_QUEUE = new ArrayList<>(); @@ -109,7 +117,7 @@ public abstract class BlockRestoreQueue /** * Executes a tick. Spawns a thread which will modify 1 block from the queue */ - public void tick() + private void tick() { Thread tx = new Thread(RUNNER); tx.start(); @@ -123,4 +131,28 @@ public abstract class BlockRestoreQueue tick(); } } + + /** + * Initialize a restore Queue for a specific level. This will load and import the blocks in the save data into this queue + * @param level The level to load for + * @throws IOException On failure to read a file + */ + public void initialize(ServerLevel level) throws IOException { + var file = SaveDataFactory.builder().withDimension(level).withQueueID(this).withPosition(null).build(); + + CompoundTag tag = NbtIo.read(file.getSaveDataPath().toFile()); + SaveDataFactory.SaveDataManifest manifest = SaveDataFactory.SaveDataManifest.deserialize(tag); + + List files = manifest.get(); + for(SaveDataCoordinates chunk : files) + { + var saved = SaveDataFactory.builder().withDimension(level).withQueueID(this).withPosition(chunk.toBlockPos()).build(); + var saveData = saved.getInstance(); + for(SavedBlock sb : saveData.blocks) + { + enqueueBlock(sb); + } + } + + } } diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueueRegistry.java b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueueRegistry.java index 11e0cd4..57e18e0 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueueRegistry.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueueRegistry.java @@ -1,5 +1,9 @@ package dev.zontreck.libzontreck.memory.world; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.Level; + +import java.io.IOException; import java.util.*; /** @@ -27,4 +31,35 @@ public class BlockRestoreQueueRegistry public static BlockRestoreQueue getQueue(String restoreQueueName) { return QUEUES.get(restoreQueueName); } + + /** + * Returns a iterator for a list of all the queues. This cannot remove items from the main queue. + * @return + */ + public static Iterator getReadOnlyQueue() { + List queues = new ArrayList<>(); + queues.addAll(QUEUES.values()); + return queues.iterator(); + } + + /** + * Initialize a block restore queue. + *

+ * Block Restore Queues are level independent, but blocks are not. This loads the blocks saved for this queue in that particular level's hierarchy + * @param level The level to load the queue for + */ + public static void init(ServerLevel level) + { + + Iterator it = BlockRestoreQueueRegistry.getReadOnlyQueue(); + while(it.hasNext()) + { + BlockRestoreQueue queue = it.next(); + try { + queue.initialize(level); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } } diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseWrapper.java b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseWrapper.java new file mode 100644 index 0000000..18ace42 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseWrapper.java @@ -0,0 +1,57 @@ +package dev.zontreck.libzontreck.memory.world; + + +import java.sql.*; + +public class DatabaseWrapper { + private Connection connection; + + public DatabaseWrapper() { + connection = null; + } + + public void connect(String url, String username, String password) throws SQLException { + try { + // Try MariaDB JDBC driver + Class.forName("org.mariadb.jdbc.Driver"); + connection = DriverManager.getConnection("jdbc:mariadb://" + url, username, password); + } catch (ClassNotFoundException | SQLException e) { + // MariaDB not found or failed to connect, try MySQL + try { + Class.forName("com.mysql.cj.jdbc.Driver"); + connection = DriverManager.getConnection("jdbc:mysql://" + url, username, password); + } catch (ClassNotFoundException | SQLException ex) { + // MySQL not found or failed to connect, try SQLite + try { + Class.forName("org.sqlite.JDBC"); + connection = DriverManager.getConnection("jdbc:sqlite:" + url); + } catch (ClassNotFoundException | SQLException exc) { + throw new SQLException("Failed to connect to database: " + exc.getMessage()); + } + } + } + } + + public ResultSet executeQuery(String query) throws SQLException { + if (connection == null) { + throw new SQLException("Connection not established."); + } + Statement statement = connection.createStatement(); + return statement.executeQuery(query); + } + + public int executeUpdate(String statement) throws SQLException { + if (connection == null) { + throw new SQLException("Connection not established."); + } + Statement stmt = connection.createStatement(); + return stmt.executeUpdate(statement); + } + + public void disconnect() throws SQLException { + if (connection != null && !connection.isClosed()) { + connection.close(); + } + } + +} diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/SaveDataCoordinates.java b/src/main/java/dev/zontreck/libzontreck/memory/world/SaveDataCoordinates.java index 2d652b0..0b95612 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/SaveDataCoordinates.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/SaveDataCoordinates.java @@ -1,20 +1,47 @@ package dev.zontreck.libzontreck.memory.world; import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtUtils; +import net.minecraft.tags.BlockTags; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraftforge.common.Tags; public class SaveDataCoordinates { public int X; public int Z; + private BlockPos source; public SaveDataCoordinates(BlockPos pos) { int X = pos.getX() >> 4 >> 5; int Z = pos.getZ() >> 4 >> 5; + + source = pos; } public String getFileName() { return "r." + X + "." + Z + ".dat"; } + + public BlockPos toBlockPos() + { + return source; + } + + public static final String TAG_COORDS = "sdc"; + public CompoundTag toNBT() + { + return NbtUtils.writeBlockPos(source); + } + + public static SaveDataCoordinates deserialize(CompoundTag tag) + { + BlockPos pos = NbtUtils.readBlockPos(tag); + + return new SaveDataCoordinates(pos); + } } diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/SaveDataFactory.java b/src/main/java/dev/zontreck/libzontreck/memory/world/SaveDataFactory.java index d4ac680..0effe5f 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/SaveDataFactory.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/SaveDataFactory.java @@ -1,11 +1,9 @@ package dev.zontreck.libzontreck.memory.world; import dev.zontreck.libzontreck.LibZontreck; +import dev.zontreck.libzontreck.vectors.WorldPosition; import net.minecraft.core.BlockPos; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.ListTag; -import net.minecraft.nbt.NbtIo; -import net.minecraft.nbt.Tag; +import net.minecraft.nbt.*; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.Level; @@ -72,6 +70,11 @@ public class SaveDataFactory return this; } + public SaveDataManifest getManifest() + { + return new SaveDataManifest(); + } + public SaveDataFile build() { return new SaveDataFile(modID, levelName, queueID, DBMode, position); @@ -85,6 +88,7 @@ public class SaveDataFactory String queue; boolean database; BlockPos position; + boolean useManifest = false; public SaveDataFile(String modID, String levelName, String queueID, boolean DBMode, BlockPos pos) { @@ -93,8 +97,17 @@ public class SaveDataFactory queue = queueID; database = DBMode; position = pos; + + if(pos == null) + { + useManifest=true; + } } + /** + * config/LibZontreck/block_snapshots/[mod]/[dimension]/[queueNickName]/r.x.z.dat + * @return + */ public Path getSaveDataPath() { Path path = LibZontreck.BASE_CONFIG.resolve("block_snapshots"); @@ -104,6 +117,8 @@ public class SaveDataFactory path.toFile().mkdirs(); + if(useManifest) return path.resolve("manifest.nbt"); + SaveDataCoordinates coordinates = new SaveDataCoordinates(position); path = path.resolve(coordinates.getFileName()); return path; @@ -131,6 +146,52 @@ public class SaveDataFactory } } + static class SaveDataManifest { + private List SAVE_DATA = new ArrayList<>(); + public static final String TAG_MANIFEST = "manifest"; + + private SaveDataManifest() + { + } + + public void add(SaveDataCoordinates pos){ + SAVE_DATA.add(pos); + } + + public List get() + { + return new ArrayList<>(SAVE_DATA); + } + + public CompoundTag save() + { + CompoundTag tag = new CompoundTag(); + ListTag lst = new ListTag(); + for(SaveDataCoordinates str : SAVE_DATA) + { + lst.add(str.toNBT()); + } + tag.put(TAG_MANIFEST, lst); + return tag; + + } + + public static SaveDataManifest deserialize(CompoundTag tag) + { + SaveDataManifest ret = new SaveDataManifest(); + ListTag lst = tag.getList(TAG_MANIFEST, Tag.TAG_COMPOUND); + for(Tag entry : lst) + { + if(entry instanceof CompoundTag ct) + { + ret.add(SaveDataCoordinates.deserialize(ct)); + } + } + + return ret; + } + } + static class SaveData { SaveDataFile myFile; public static final String TAG_SAVED_BLOCKS = "sb"; @@ -183,6 +244,8 @@ public class SaveDataFactory /** * Imports a full queue to the save data file. * ! WARNING ! This method will overwrite the SaveDataFile's Queue ID + * * * * + * This will only import for the correct dimension. This method is invoked automatically for each level for each queue when the server is shutting down prior to level unload. * @param queue Queue to import * @return The current SaveData instance */ @@ -190,7 +253,10 @@ public class SaveDataFactory { for(PrimitiveBlock blk : queue.getQueue()) { - blocks.add(blk.savedBlock); + if(WorldPosition.getDim(blk.level) == this.myFile.dimension) + blocks.add(blk.savedBlock); + else + continue; // We only want to add to a save data file, the blocks for the dimension in question. } myFile.queue = queue.getRestoreQueueName();