diff --git a/sponge/pom.xml b/sponge/pom.xml index 8627a4a..217c217 100644 --- a/sponge/pom.xml +++ b/sponge/pom.xml @@ -108,7 +108,7 @@ org.spongepowered - spongeapi + SpongeAPI 1.0.0-SNAPSHOT diff --git a/sponge/src/main/java/com/tommytony/war/WarConfig.java b/sponge/src/main/java/com/tommytony/war/WarConfig.java index 7a6177b..84b4493 100644 --- a/sponge/src/main/java/com/tommytony/war/WarConfig.java +++ b/sponge/src/main/java/com/tommytony/war/WarConfig.java @@ -1,6 +1,7 @@ package com.tommytony.war; import com.google.common.collect.ImmutableList; +import com.tommytony.war.zone.ZoneConfig; import org.spongepowered.api.entity.Player; import java.io.Closeable; @@ -17,6 +18,7 @@ import java.util.UUID; */ public class WarConfig implements Closeable { private final WarPlugin plugin; + private final ZoneConfig zoneDefaults; /** * Database configuration descriptor. */ @@ -39,6 +41,7 @@ public class WarConfig implements Closeable { stmt.executeUpdate("CREATE TABLE IF NOT EXISTS zones (name TEXT)"); stmt.executeUpdate("CREATE TABLE IF NOT EXISTS zonemakers (uuid TEXT)"); } + zoneDefaults = new ZoneConfig(conn, "zone_settings"); } /** @@ -55,12 +58,21 @@ public class WarConfig implements Closeable { if (result.next()) { return result.getInt(1); } else { - return (int) setting.defaultValue; + return (Integer) setting.defaultValue; } } } } + /** + * Get access to zone default settings. + * + * @return controller of server zone defaults. + */ + public ZoneConfig getZoneDefaults() { + return zoneDefaults; + } + /** * Load all the enabled war zones on the server. * diff --git a/sponge/src/main/java/com/tommytony/war/WarPlugin.java b/sponge/src/main/java/com/tommytony/war/WarPlugin.java index 12c8370..70178ec 100644 --- a/sponge/src/main/java/com/tommytony/war/WarPlugin.java +++ b/sponge/src/main/java/com/tommytony/war/WarPlugin.java @@ -4,6 +4,7 @@ import org.apache.logging.log4j.Logger; import org.spongepowered.api.Game; import org.spongepowered.api.event.SpongeEventHandler; import org.spongepowered.api.event.state.PreInitializationEvent; +import org.spongepowered.api.event.state.ServerStartedEvent; import org.spongepowered.api.event.state.ServerStartingEvent; import org.spongepowered.api.plugin.Plugin; @@ -37,6 +38,11 @@ public class WarPlugin { config = new WarConfig(this, new File(dataDir, "war.sl3")); } + @SpongeEventHandler + public void onStart(ServerStartedEvent event) { + // register commands + } + public Game getGame() { return game; } @@ -45,6 +51,10 @@ public class WarPlugin { return logger; } + public File getDataDir() { + return dataDir; + } + public WarConfig getConfig() { return config; } diff --git a/sponge/src/main/java/com/tommytony/war/zone/Warzone.java b/sponge/src/main/java/com/tommytony/war/zone/Warzone.java new file mode 100644 index 0000000..9047a52 --- /dev/null +++ b/sponge/src/main/java/com/tommytony/war/zone/Warzone.java @@ -0,0 +1,54 @@ +package com.tommytony.war.zone; + +import com.tommytony.war.WarPlugin; + +import java.sql.SQLException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Representation of a war zone area, blocks, and settings. + * Each zone is to only have one instance of Warzone.class at any given time while the server is running. + */ +public class Warzone { + private static Pattern zoneName = Pattern.compile("[./%]+"); + private final WarPlugin plugin; + private final String name; + private final ZoneStorage db; + private final ZoneConfig config; + + /** + * Load or create a war zone from the war settings store. + * + * @param plugin Instance of the plugin War. + * @param name Name of the zone. Used to locate the zone data file. + */ + public Warzone(WarPlugin plugin, String name) { + this.plugin = plugin; + this.name = name; + try { + this.db = new ZoneStorage(this, plugin); + this.config = new ZoneConfig(db.getConnection(), "settings", plugin.getConfig().getZoneDefaults()); + } catch (SQLException ex) { + plugin.getLogger().error("Failed to load zone database and settings", ex); + throw new RuntimeException("Can't create/load database for warzone " + name); + } + } + + /** + * Check if the specified name is valid for a zone. + * Specifically, characters that could be part of a file path or web URL. + * '.', '/', and '%' are invalid. + * + * @param name Potential zone name to check. + * @return true if the name can be used. + */ + public static boolean zoneNameValid(String name) { + Matcher m = zoneName.matcher(name); + return m.find(); + } + + public String getName() { + return name; + } +} diff --git a/sponge/src/main/java/com/tommytony/war/zone/ZoneConfig.java b/sponge/src/main/java/com/tommytony/war/zone/ZoneConfig.java new file mode 100644 index 0000000..4722c48 --- /dev/null +++ b/sponge/src/main/java/com/tommytony/war/zone/ZoneConfig.java @@ -0,0 +1,75 @@ +package com.tommytony.war.zone; + +import java.sql.*; + +/** + * The zone configuration settings database. + */ +public class ZoneConfig { + /** + * Database configuration descriptor. + */ + private final Connection conn; + /** + * Table of values to manage. May be a table in a zone database or the main war database. + */ + private final String table; + /** + * Root zone config, for fallback. Null if this is the war main settings. + */ + private final ZoneConfig parent; + + /** + * Manages a zone configuration section. + * + * @param database Active database to use. + * @param table Table name to use in database. Created if it does not exist. Needs to be trusted input. + * @param parent Parent zone config, for fallback. Could be zone config for a team or war global for zones. + * @throws SQLException + */ + public ZoneConfig(Connection database, String table, ZoneConfig parent) throws SQLException { + this.conn = database; + this.table = table; + this.parent = parent; + try (Statement stmt = conn.createStatement()) { + stmt.executeUpdate(String.format("CREATE TABLE IF NOT EXISTS %s (option TEXT, value BLOB)", table)); + } + } + + /** + * Manages a zone configuration section. + * + * @param database Active database to use. + * @param table Table name to use in database. Created if it does not exist. Needs to be trusted input. + * @throws SQLException + */ + public ZoneConfig(Connection database, String table) throws SQLException { + this(database, table, null); + } + + /** + * Get the value of an integer setting. + * + * @param setting The type of setting to look up. + * @return the value of the setting or the default if not found. + * @throws SQLException + */ + public int getInt(ZoneSetting setting) throws SQLException { + try (PreparedStatement stmt = conn.prepareStatement(String.format("SELECT value FROM %s WHERE option = ?", table))) { + stmt.setString(1, setting.name()); + try (ResultSet result = stmt.executeQuery()) { + if (result.next()) { + // found an override for this config level + return result.getInt(1); + } else if (parent != null) { + // look for it in zone/global configs; will be recursive upwards + return parent.getInt(setting); + } else { + // the hard-coded value for fallback + return (Integer) setting.getDefaultValue(); + } + } + } + } + +} diff --git a/sponge/src/main/java/com/tommytony/war/zone/ZoneSetting.java b/sponge/src/main/java/com/tommytony/war/zone/ZoneSetting.java new file mode 100644 index 0000000..9c90f16 --- /dev/null +++ b/sponge/src/main/java/com/tommytony/war/zone/ZoneSetting.java @@ -0,0 +1,25 @@ +package com.tommytony.war.zone; + +/** + * Settings that can be changed on a per-zone basis. + * Some can be overridden per-team. + */ +public enum ZoneSetting { + /** + * Maximum players in a zone or team. Zone max is still observed even if team settings allow for more. + */ + MAXPLAYERS(Integer.class, 10, true); + private final Class dataType; + private final Object defaultValue; + private final boolean perTeam; + + private ZoneSetting(Class dataType, Object defaultValue, boolean perTeam) { + this.dataType = dataType; + this.defaultValue = defaultValue; + this.perTeam = perTeam; + } + + public Object getDefaultValue() { + return defaultValue; + } +} diff --git a/sponge/src/main/java/com/tommytony/war/zone/ZoneStorage.java b/sponge/src/main/java/com/tommytony/war/zone/ZoneStorage.java new file mode 100644 index 0000000..c6e1836 --- /dev/null +++ b/sponge/src/main/java/com/tommytony/war/zone/ZoneStorage.java @@ -0,0 +1,76 @@ +package com.tommytony.war.zone; + +import com.tommytony.war.WarPlugin; + +import java.io.File; +import java.sql.*; + +/** + * Manages the war zone database file, which contains all the data for the war zone. + */ +public class ZoneStorage implements AutoCloseable { + private static int DATABASE_VERSION = 1; + private final Warzone zone; + private final WarPlugin plugin; + private final Connection connection; + private final File dataStore; + + /** + * Initiates a database for a new or existing database. + * + * @param zone The server war zone object for this database. + * @param plugin The war plugin, for storage information and configuration. + * @throws SQLException + */ + ZoneStorage(Warzone zone, WarPlugin plugin) throws SQLException { + this.zone = zone; + this.plugin = plugin; + dataStore = new File(plugin.getDataDir(), String.format("%s.warzone", zone.getName())); + connection = DriverManager.getConnection("jdbc:sqlite:" + dataStore.getPath()); + this.upgradeDatabase(); + } + + Connection getConnection() { + return connection; + } + + /** + * Check the database stored version information and perform upgrade tasks if necessary. + * + * @throws SQLException + */ + private void upgradeDatabase() throws SQLException { + int version; + try ( + Statement stmt = connection.createStatement(); + ResultSet resultSet = stmt.executeQuery("PRAGMA user_version"); + ) { + version = resultSet.getInt("user_version"); + } + if (version > DATABASE_VERSION) { + // version is from a future version + throw new IllegalStateException(String.format("Unsupported zone version: %d. War current version: %d", + version, DATABASE_VERSION)); + } else if (version == 0) { + // brand new database file + } else if (version < DATABASE_VERSION) { + // upgrade + switch (version) { + // none yet + default: + // some odd bug or people messing with their database + throw new IllegalStateException(String.format("Unsupported zone version: %d.", version)); + } + } + } + + /** + * Closes this resource, relinquishing any underlying resources. + * + * @throws Exception if this resource cannot be closed + */ + @Override + public void close() throws Exception { + connection.close(); + } +}