Merge pull request #846 from taoneill/feature/113savefiles

Zone database structure update
This commit is contained in:
Connor Monahan 2018-11-27 18:10:52 -06:00 committed by GitHub
commit 3470a0cc54
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 686 additions and 1255 deletions

View File

@ -18,6 +18,7 @@ import net.milkbowl.vault.economy.Economy;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.enchantments.Enchantment;
@ -50,10 +51,10 @@ public class War extends JavaPlugin {
static final boolean HIDE_BLANK_MESSAGES = true;
public static War war;
private static ResourceBundle messages = ResourceBundle.getBundle("messages");
private final List<String> zoneMakerNames = new ArrayList<String>();
private final List<OfflinePlayer> zoneMakerNames = new ArrayList<>();
private final List<String> commandWhitelist = new ArrayList<String>();
private final List<Warzone> incompleteZones = new ArrayList<Warzone>();
private final List<String> zoneMakersImpersonatingPlayers = new ArrayList<String>();
private final List<OfflinePlayer> zoneMakersImpersonatingPlayers = new ArrayList<>();
private final HashMap<String, String> wandBearers = new HashMap<String, String>(); // playername to zonename
private final List<String> deadlyAdjectives = new ArrayList<String>();
private final List<String> killerVerbs = new ArrayList<String>();
@ -221,7 +222,6 @@ public class War extends JavaPlugin {
this.getDefaultInventories().setReward(reward);
this.getCommandWhitelist().add("who");
this.getZoneMakerNames().add("tommytony");
this.setKillstreakReward(new KillstreakReward());
this.setMysqlConfig(new MySQLConfig());
@ -1066,14 +1066,14 @@ public class War extends JavaPlugin {
*/
public boolean isZoneMaker(Player player) {
// sort out disguised first
for (String disguised : this.zoneMakersImpersonatingPlayers) {
if (disguised.equals(player.getName())) {
for (OfflinePlayer disguised : this.zoneMakersImpersonatingPlayers) {
if (disguised.isOnline() && disguised.getPlayer().equals(player)) {
return false;
}
}
for (String zoneMaker : this.zoneMakerNames) {
if (zoneMaker.equals(player.getName())) {
for (OfflinePlayer zoneMaker : this.zoneMakerNames) {
if (zoneMaker.isOnline() && zoneMaker.getPlayer().equals(player)) {
return true;
}
}
@ -1107,7 +1107,7 @@ public class War extends JavaPlugin {
// lost his sword, or new warzone
if (zoneName.equals(alreadyHaveWand)) {
// same zone, give him a new sword
player.getInventory().addItem(new ItemStack(Material.WOODEN_SWORD, 1, (byte) 8));
player.getInventory().addItem(Compat.createDamagedIS(Material.WOODEN_SWORD, 1, 8));
this.msg(player, "Here's a new sword for zone " + zoneName + ".");
}
}
@ -1116,8 +1116,7 @@ public class War extends JavaPlugin {
this.badMsg(player, "Your inventory is full. Please drop an item and try again.");
} else {
this.wandBearers.put(player.getName(), zoneName);
player.getInventory().addItem(new ItemStack(Material.WOODEN_SWORD, 1, (byte) 8));
// player.getWorld().dropItem(player.getLocation(), new ItemStack(Material.WOOD_SWORD));
player.getInventory().addItem(Compat.createDamagedIS(Material.WOODEN_SWORD, 1, 8));
this.msg(player, "You now have a wand for zone " + zoneName + ". Left-click with wooden sword for corner 1. Right-click for corner 2.");
War.war.log(player.getName() + " now has a wand for warzone " + zoneName, Level.INFO);
}
@ -1136,9 +1135,7 @@ public class War extends JavaPlugin {
}
public void removeWandBearer(Player player) {
if (this.wandBearers.containsKey(player.getName())) {
this.wandBearers.remove(player.getName());
}
this.wandBearers.remove(player.getName());
}
public Warzone zoneOfZoneWallAtProximity(Location location) {
@ -1150,7 +1147,7 @@ public class War extends JavaPlugin {
return null;
}
public List<String> getZoneMakerNames() {
public List<OfflinePlayer> getZoneMakerNames() {
return this.zoneMakerNames;
}
@ -1162,7 +1159,7 @@ public class War extends JavaPlugin {
return ZoneLobby.getLobbyByLocation(location) != null;
}
public List<String> getZoneMakersImpersonatingPlayers() {
public List<OfflinePlayer> getZoneMakersImpersonatingPlayers() {
return this.zoneMakersImpersonatingPlayers;
}

View File

@ -12,7 +12,6 @@ import com.tommytony.war.job.LogKillsDeathsJob;
import com.tommytony.war.job.LogKillsDeathsJob.KillsDeathsRecord;
import com.tommytony.war.job.ZoneTimeJob;
import com.tommytony.war.mapper.LoadoutYmlMapper;
import com.tommytony.war.mapper.VolumeMapper;
import com.tommytony.war.mapper.ZoneVolumeMapper;
import com.tommytony.war.structure.*;
import com.tommytony.war.utility.*;
@ -2087,7 +2086,7 @@ public class Warzone {
}
public Volume loadStructure(String volName, World world) throws SQLException {
return loadStructure(volName, world, ZoneVolumeMapper.getZoneConnection(volume, name, world));
return loadStructure(volName, world, ZoneVolumeMapper.getZoneConnection(volume, name));
}
public Volume loadStructure(String volName, Connection zoneConnection) throws SQLException {
@ -2096,12 +2095,6 @@ public class Warzone {
public Volume loadStructure(String volName, World world, Connection zoneConnection) throws SQLException {
Volume volume = new Volume(volName, world);
if (!containsTable(String.format("structure_%d_corners", volName.hashCode() & Integer.MAX_VALUE), zoneConnection)) {
volume = VolumeMapper.loadVolume(volName, name, world);
ZoneVolumeMapper.saveStructure(volume, zoneConnection);
War.war.getLogger().log(Level.INFO, "Stuffed structure {0} into database for warzone {1}", new Object[] {volName, name});
return volume;
}
ZoneVolumeMapper.loadStructure(volume, zoneConnection);
return volume;
}

View File

@ -1,15 +1,13 @@
package com.tommytony.war.command;
import java.util.logging.Level;
import org.bukkit.command.CommandSender;
import com.tommytony.war.War;
import com.tommytony.war.Warzone;
import com.tommytony.war.mapper.VolumeMapper;
import com.tommytony.war.mapper.WarYmlMapper;
import com.tommytony.war.structure.WarHub;
import org.bukkit.command.CommandSender;
import java.util.logging.Level;
/**
* Deletes the warhub.
@ -30,7 +28,7 @@ public class DeleteWarhubCommand extends AbstractWarAdminCommand {
if (War.war.getWarHub() != null) {
// reset existing hub
War.war.getWarHub().getVolume().resetBlocks();
VolumeMapper.delete(War.war.getWarHub().getVolume());
VolumeMapper.deleteSimpleVolume(War.war.getWarHub().getVolume());
War.war.setWarHub((WarHub) null);
for (Warzone zone : War.war.getWarzones()) {
if (zone.getLobby() != null) {

View File

@ -1,13 +1,13 @@
package com.tommytony.war.command;
import java.util.logging.Level;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import com.tommytony.war.War;
import com.tommytony.war.mapper.WarYmlMapper;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.logging.Level;
/**
* Makes a player zonemaker and other way round.
@ -21,8 +21,8 @@ public class ZoneMakerCommand extends AbstractWarCommand {
if (sender instanceof Player) { // i hate java for this.
if (!War.war.isZoneMaker((Player) sender)) {
for (String name : War.war.getZoneMakersImpersonatingPlayers()) {
if (((Player) sender).getName().equals(name)) {
for (OfflinePlayer offlinePlayer : War.war.getZoneMakersImpersonatingPlayers()) {
if (offlinePlayer.isOnline() && sender.equals(offlinePlayer.getPlayer())) {
return;
}
}
@ -41,30 +41,29 @@ public class ZoneMakerCommand extends AbstractWarCommand {
if (War.war.isZoneMaker(player)) {
if (this.args.length == 0) {
War.war.getZoneMakersImpersonatingPlayers().add(player.getName());
War.war.getZoneMakersImpersonatingPlayers().add(player);
this.msg("You are now impersonating a regular player. Type /zonemaker again to toggle back to war maker mode.");
} else if (this.args.length == 1) {
OfflinePlayer other = Bukkit.getOfflinePlayer(this.args[0]);
// make someone zonemaker or remove the right
if (War.war.getZoneMakerNames().contains(this.args[0])) {
if (War.war.getZoneMakerNames().contains(other)) {
// kick
War.war.getZoneMakerNames().remove(this.args[0]);
War.war.getZoneMakerNames().remove(other);
this.msg(this.args[0] + " is not a zone maker anymore.");
// TODO store zone makers using UUIDs
Player kickedMaker = War.war.getServer().getPlayer(this.args[0]);
if (kickedMaker != null) {
War.war.msg(kickedMaker, player.getName() + " took away your warzone maker priviledges.");
War.war.log(player.getName() + " took away zonemaker rights from " + kickedMaker, Level.INFO);
if (other.isOnline()) {
War.war.msg(other.getPlayer(), player.getName() + " took away your warzone maker priviledges.");
War.war.log(player.getName() + " took away zonemaker rights from " + other.getName(), Level.INFO);
}
} else {
// add
War.war.getZoneMakerNames().add(this.args[0]);
War.war.getZoneMakerNames().add(other);
this.msg(this.args[0] + " is now a zone maker.");
Player newMaker = War.war.getServer().getPlayer(this.args[0]);
if (newMaker != null) {
War.war.msg(newMaker, player.getName() + " made you warzone maker.");
War.war.log(player.getName() + " made " + newMaker + " a zonemaker", Level.INFO);
if (other.isOnline()) {
War.war.msg(other.getPlayer(), player.getName() + " made you warzone maker.");
War.war.log(player.getName() + " made " + other.getName() + " a zonemaker", Level.INFO);
}
}
WarYmlMapper.save();
} else {
return false;
}
@ -73,7 +72,7 @@ public class ZoneMakerCommand extends AbstractWarCommand {
return false;
}
War.war.getZoneMakersImpersonatingPlayers().remove(player.getName());
War.war.getZoneMakersImpersonatingPlayers().remove(player);
this.msg("You are back as a zone maker.");
WarYmlMapper.save();
}

View File

@ -1,24 +1,25 @@
package com.tommytony.war.job;
import java.sql.Connection;
import java.sql.SQLException;
import java.text.DecimalFormat;
import java.text.MessageFormat;
import java.text.NumberFormat;
import java.util.*;
import java.util.logging.Level;
import com.tommytony.war.War;
import com.tommytony.war.Warzone;
import com.tommytony.war.mapper.ZoneVolumeMapper;
import com.tommytony.war.structure.ZoneLobby;
import com.tommytony.war.volume.ZoneVolume;
import org.bukkit.Material;
import org.bukkit.block.BlockState;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import com.tommytony.war.War;
import com.tommytony.war.Warzone;
import com.tommytony.war.structure.ZoneLobby;
import com.tommytony.war.volume.ZoneVolume;
import java.sql.Connection;
import java.sql.SQLException;
import java.text.DecimalFormat;
import java.text.MessageFormat;
import java.text.NumberFormat;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
public class PartialZoneResetJob extends BukkitRunnable implements Cloneable {
@ -61,7 +62,7 @@ public class PartialZoneResetJob extends BukkitRunnable implements Cloneable {
public void run() {
try {
if (conn == null || conn.isClosed()) {
conn = ZoneVolumeMapper.getZoneConnection(volume, zone.getName(), volume.getWorld());
conn = ZoneVolumeMapper.getZoneConnection(volume, zone.getName());
}
if (completed >= total) {
int airChanges = 0;

View File

@ -1,69 +0,0 @@
package com.tommytony.war.job;
import java.sql.SQLException;
import java.util.logging.Level;
import org.bukkit.Location;
import org.bukkit.World;
import com.tommytony.war.War;
import com.tommytony.war.Warzone;
import com.tommytony.war.mapper.VolumeMapper;
import com.tommytony.war.structure.WarHub;
import com.tommytony.war.volume.Volume;
public class RestoreWarhubJob implements Runnable {
private final String hubStr;
public RestoreWarhubJob(String hubStr) {
this.hubStr = hubStr;
}
public void run() {
String[] hubStrSplit = this.hubStr.split(",");
int hubX = Integer.parseInt(hubStrSplit[0]);
int hubY = Integer.parseInt(hubStrSplit[1]);
int hubZ = Integer.parseInt(hubStrSplit[2]);
World world = null;
String worldName;
String hubOrientation = "west";
if (hubStrSplit.length > 3) {
worldName = hubStrSplit[3];
world = War.war.getServer().getWorld(worldName);
if (hubStrSplit.length > 4) {
hubOrientation = hubStrSplit[4];
}
} else {
worldName = "DEFAULT";
world = War.war.getServer().getWorlds().get(0); // default to first world
}
if (world != null) {
Location hubLocation = new Location(world, hubX, hubY, hubZ);
WarHub hub = new WarHub(hubLocation, hubOrientation);
War.war.setWarHub(hub);
Volume vol;
try {
vol = VolumeMapper.loadVolume("warhub", "", world);
} catch (SQLException e) {
throw new RuntimeException(e);
}
hub.setVolume(vol);
hub.getVolume().resetBlocks();
hub.initialize();
// In the previous job started by the mapper, warzones were created, but their lobbies are missing the war hub gate (because it didn't exist yet)
for (Warzone zone : War.war.getWarzones()) {
if (zone.getLobby() != null) {
zone.getLobby().getVolume().resetBlocks();
zone.getLobby().initialize();
}
}
War.war.log("Warhub ready.", Level.INFO);
} else {
War.war.log("Failed to restore warhub. The specified world (name: " + worldName + ") does not exist!", Level.WARNING);
}
}
}

View File

@ -1,18 +1,16 @@
package com.tommytony.war.job;
import java.sql.SQLException;
import java.util.logging.Level;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.inventory.ItemStack;
import com.tommytony.war.War;
import com.tommytony.war.Warzone;
import com.tommytony.war.mapper.VolumeMapper;
import com.tommytony.war.structure.WarHub;
import com.tommytony.war.volume.Volume;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.configuration.ConfigurationSection;
import java.sql.SQLException;
import java.util.logging.Level;
public class RestoreYmlWarhubJob implements Runnable {
@ -54,7 +52,7 @@ public class RestoreYmlWarhubJob implements Runnable {
War.war.setWarHub(hub);
Volume vol;
try {
vol = VolumeMapper.loadVolume("warhub", "", world);
vol = VolumeMapper.loadSimpleVolume("warhub", world);
} catch (SQLException e) {
throw new RuntimeException(e);
}

View File

@ -1,29 +0,0 @@
package com.tommytony.war.job;
import com.tommytony.war.War;
import com.tommytony.war.mapper.ZoneVolumeMapper;
import com.tommytony.war.volume.Volume;
import java.sql.SQLException;
import java.util.logging.Level;
import org.bukkit.scheduler.BukkitRunnable;
public class ZoneVolumeSaveJob extends BukkitRunnable {
private final Volume volume;
private final String zoneName;
public ZoneVolumeSaveJob(Volume volume, String zoneName) {
this.volume = volume;
this.zoneName = zoneName;
}
@Override
public void run() {
try {
ZoneVolumeMapper.save(this.volume, this.zoneName);
} catch (SQLException ex) {
War.war.log(ex.getMessage(), Level.SEVERE);
}
}
}

View File

@ -1,407 +0,0 @@
package com.tommytony.war.mapper;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.logging.Logger;
import java.io.FileInputStream;
import java.util.Map;
import java.util.Properties;
/**
* Used for accessing and creating .[properties] files, reads them as utf-8, saves as utf-8. Internationalization is key importance especially for character codes.
*
* @author Nijikokun
* @version 1.0.4, %G%
*/
public final class PropertiesFile {
private static final Logger log = Logger.getLogger("Minecraft");
private String fileName;
private Properties props = new Properties();
private FileInputStream inputStream;
private FileOutputStream outputStream;
// private List<String> lines = new ArrayList<String>();
// private Map<String, String> props = new HashMap<String, String>();
/**
* Creates or opens a properties file using specified filename
*
* @param fileName
*/
public PropertiesFile(String fileName) {
this.fileName = fileName;
File file = new File(fileName);
try {
if (file.exists()) {
this.load();
} else {
this.save();
}
} catch (IOException ex) {
PropertiesFile.log.severe("[PropertiesFile] Unable to load " + fileName + "!");
}
}
/**
* The loader for property files, it reads the file as UTF8 or converts the string into UTF8. Used for simple runthrough's, loading, or reloading of the file.
*
* @throws IOException
*/
public void load() throws IOException {
this.inputStream = new FileInputStream(this.fileName);
this.props.load(this.inputStream);
}
/**
* Writes out the <code>key=value</code> properties that were changed into a .[properties] file in UTF8.
*/
public void save() {
try {
this.outputStream = new FileOutputStream(this.fileName);
this.props.store(this.outputStream, null);
} catch (IOException ex) {
PropertiesFile.log.severe("[PropertiesFile] Unable to save " + this.fileName + "!");
}
}
public void close() {
if (this.outputStream != null) {
try {
this.outputStream.close();
} catch (IOException e) {
PropertiesFile.log.severe("[PropertiesFile] Failed to close " + this.fileName + " writer!");
}
} else if (this.inputStream != null) {
try {
this.inputStream.close();
} catch (IOException e) {
PropertiesFile.log.severe("[PropertiesFile] Failed to close " + this.fileName + " reader!");
}
}
}
/**
* Returns a Map of all <code>key=value</code> properties in the file as <code>&lt;key (java.lang.String), value (java.lang.String)></code> <br />
* <br />
* Example: <blockquote>
*
* <pre>
* PropertiesFile settings = new PropertiesFile(&quot;settings.properties&quot;);
* Map&lt;String, String&gt; mappedSettings;
*
* try {
* mappedSettings = settings.returnMap();
* } catch (Exception ex) {
* log.info(&quot;Failed mapping settings.properties&quot;);
* }
* </pre>
*
* </blockquote>
*
* @return <code>map</code> - Simple Map HashMap of the entire <code>key=value</code> as <code>&lt;key (java.lang.String), value (java.lang.String)></code>
* @throws Exception
* If the properties file doesn't exist.
*/
@SuppressWarnings("unchecked")
public Map<String, String> returnMap() throws Exception {
return (Map<String, String>) this.props.clone();
}
/**
* Checks to see if the .[properties] file contains the given <code>key</code>.
*
* @param var
* The key we are going to be checking the existance of.
* @return <code>Boolean</code> - True if the <code>key</code> exists, false if it cannot be found.
*/
public boolean containsKey(String var) {
return this.props.containsKey(var);
}
/**
* Checks to see if this <code>key</code> exists in the .[properties] file.
*
* @param var
* The key we are grabbing the value of.
* @return <code>java.lang.String</code> - True if the <code>key</code> exists, false if it cannot be found.
*/
public String getProperty(String var) {
return this.props.getProperty(var);
}
/**
* Remove a key from the file if it exists. This will save() which will invoke a load() on the file.
*
* @see #save()
* @param var
* The <code>key</code> that will be removed from the file
*/
public void removeKey(String var) {
if (this.props.containsKey(var)) {
this.props.remove(var);
this.save();
}
}
/**
* Checks the existance of a <code>key</code>.
*
* @see #containsKey(java.lang.String)
* @param key
* The <code>key</code> in question of existance.
* @return <code>Boolean</code> - True for existance, false for <code>key</code> found.
*/
public boolean keyExists(String key) {
return this.containsKey(key);
}
/**
* Returns the value of the <code>key</code> given as a <code>String</code>, however we do not set a string if no <code>key</code> is found.
*
* @see #getProperty(java.lang.String)
* @param key
* The <code>key</code> we will retrieve the property from, if no <code>key</code> is found default to "" or empty.
*/
public String getString(String key) {
if (this.containsKey(key)) {
return this.getProperty(key);
}
return "";
}
/**
* Returns the value of the <code>key</code> given as a <code>String</code>. If it is not found, it will invoke saving the default <code>value</code> to the properties file.
*
* @see #setString(java.lang.String, java.lang.String)
* @see #getProperty(java.lang.String)
* @param key
* The key that we will be grabbing the value from, if no value is found set and return <code>value</code>
* @param value
* The default value that we will be setting if no prior <code>key</code> is found.
* @return java.lang.String Either we will return the default value or a prior existing value depending on existance.
*/
public String getString(String key, String value) {
if (this.containsKey(key)) {
return this.getProperty(key);
}
this.setString(key, value);
return value;
}
/**
* Save the value given as a <code>String</code> on the specified key.
*
* @see #save()
* @param key
* The <code>key</code> that we will be addressing the <code>value</code> to.
* @param value
* The <code>value</code> we will be setting inside the <code>.[properties]</code> file.
*/
public void setString(String key, String value) {
this.props.put(key, value);
this.save();
}
/**
* Returns the value of the <code>key</code> given in a Integer, however we do not set a string if no <code>key</code> is found.
*
* @see #getProperty(String var)
* @param key
* The <code>key</code> we will retrieve the property from, if no <code>key</code> is found default to 0
*/
public int getInt(String key) {
if (this.containsKey(key)) {
return Integer.parseInt(this.getProperty(key));
}
return 0;
}
/**
* Returns the int value of a key
*
* @see #setInt(String key, int value)
* @param key
* The key that we will be grabbing the value from, if no value is found set and return <code>value</code>
* @param value
* The default value that we will be setting if no prior <code>key</code> is found.
* @return <code>Integer</code> - Either we will return the default value or a prior existing value depending on existance.
*/
public int getInt(String key, int value) {
if (this.containsKey(key)) {
return Integer.parseInt(this.getProperty(key));
}
this.setInt(key, value);
return value;
}
/**
* Save the value given as a <code>int</code> on the specified key.
*
* @see #save()
* @param key
* The <code>key</code> that we will be addressing the <code>value</code> to.
* @param value
* The <code>value</code> we will be setting inside the <code>.[properties]</code> file.
*/
public void setInt(String key, int value) {
this.props.put(key, String.valueOf(value));
this.save();
}
/**
* Returns the value of the <code>key</code> given in a Double, however we do not set a string if no <code>key</code> is found.
*
* @see #getProperty(String var)
* @param key
* The <code>key</code> we will retrieve the property from, if no <code>key</code> is found default to 0.0
*/
public double getDouble(String key) {
if (this.containsKey(key)) {
return Double.parseDouble(this.getProperty(key));
}
return 0;
}
/**
* Returns the double value of a key
*
* @see #setDouble(String key, double value)
* @param key
* The key that we will be grabbing the value from, if no value is found set and return <code>value</code>
* @param value
* The default value that we will be setting if no prior <code>key</code> is found.
* @return <code>Double</code> - Either we will return the default value or a prior existing value depending on existance.
*/
public double getDouble(String key, double value) {
if (this.containsKey(key)) {
return Double.parseDouble(this.getProperty(key));
}
this.setDouble(key, value);
return value;
}
/**
* Save the value given as a <code>double</code> on the specified key.
*
* @see #save()
* @param key
* The <code>key</code> that we will be addressing the <code>value</code> to.
* @param value
* The <code>value</code> we will be setting inside the <code>.[properties]</code> file.
*/
public void setDouble(String key, double value) {
this.props.put(key, String.valueOf(value));
this.save();
}
/**
* Returns the value of the <code>key</code> given in a Long, however we do not set a string if no <code>key</code> is found.
*
* @see #getProperty(String var)
* @param key
* The <code>key</code> we will retrieve the property from, if no <code>key</code> is found default to 0L
*/
public long getLong(String key) {
if (this.containsKey(key)) {
return Long.parseLong(this.getProperty(key));
}
return 0;
}
/**
* Returns the long value of a key
*
* @see #setLong(String key, long value)
* @param key
* The key that we will be grabbing the value from, if no value is found set and return <code>value</code>
* @param value
* The default value that we will be setting if no prior <code>key</code> is found.
* @return <code>Long</code> - Either we will return the default value or a prior existing value depending on existance.
*/
public long getLong(String key, long value) {
if (this.containsKey(key)) {
return Long.parseLong(this.getProperty(key));
}
this.setLong(key, value);
return value;
}
/**
* Save the value given as a <code>long</code> on the specified key.
*
* @see #save()
* @param key
* The <code>key</code> that we will be addressing the <code>value</code> to.
* @param value
* The <code>value</code> we will be setting inside the <code>.[properties]</code> file.
*/
public void setLong(String key, long value) {
this.props.put(key, String.valueOf(value));
this.save();
}
/**
* Returns the value of the <code>key</code> given in a Boolean, however we do not set a string if no <code>key</code> is found.
*
* @see #getProperty(String var)
* @param key
* The <code>key</code> we will retrieve the property from, if no <code>key</code> is found default to false
*/
public boolean getBoolean(String key) {
if (this.containsKey(key)) {
return Boolean.parseBoolean(this.getProperty(key));
}
return false;
}
/**
* Returns the boolean value of a key
*
* @see #setBoolean(String key, boolean value)
* @param key
* The key that we will be grabbing the value from, if no value is found set and return <code>value</code>
* @param value
* The default value that we will be setting if no prior <code>key</code> is found.
* @return <code>Boolean</code> - Either we will return the default value or a prior existing value depending on existance.
*/
public boolean getBoolean(String key, boolean value) {
if (this.containsKey(key)) {
return Boolean.parseBoolean(this.getProperty(key));
}
this.setBoolean(key, value);
return value;
}
/**
* Save the value given as a <code>boolean</code> on the specified key.
*
* @see #save()
* @param key
* The <code>key</code> that we will be addressing the <code>value</code> to.
* @param value
* The <code>value</code> we will be setting inside the <code>.[properties]</code> file.
*/
public void setBoolean(String key, boolean value) {
this.props.put(key, String.valueOf(value));
this.save();
}
}

View File

@ -2,15 +2,21 @@ package com.tommytony.war.mapper;
import com.tommytony.war.War;
import com.tommytony.war.volume.Volume;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.bukkit.*;
import org.bukkit.block.*;
import org.bukkit.block.data.BlockData;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.*;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import java.io.File;
import java.sql.*;
import java.text.DecimalFormat;
import java.util.*;
import java.util.logging.Level;
/**
@ -20,43 +26,17 @@ import java.util.logging.Level;
*/
public class VolumeMapper {
public static Volume loadVolume(String volumeName, String zoneName, World world) throws SQLException {
Volume volume = new Volume(volumeName, world);
VolumeMapper.load(volume, zoneName, world);
return volume;
protected static final String delim = "-------mcwar iSdgraIyMvOanTEJjZgocczfuG------";
protected static final int DATABASE_VERSION = 3;
private static String getBlockDescriptor(Location loc, String type, String data, String metadata) {
return String.format("<%d,%d,%d> type: %s, data: %s, meta: %s", loc.getBlockX(), loc.getBlockY(), loc.getBlockZ(), type, data, metadata);
}
public static void load(Volume volume, String zoneName, World world) throws SQLException {
File databaseFile = new File(War.war.getDataFolder(), String.format(
"/dat/volume-%s.sl3", volume.getName()));
if (!zoneName.isEmpty()) {
databaseFile = new File(War.war.getDataFolder(),
String.format("/dat/warzone-%s/volume-%s.sl3", zoneName,
volume.getName()));
}
if (!databaseFile.exists()) {
// dropped nimitz compatibility with the MC 1.13 update
War.war.log("Volume " + volume.getName() + " for zone " + zoneName + " not found. Will not attempt converting legacy War version formats.", Level.WARNING);
return;
}
Connection databaseConnection = DriverManager.getConnection("jdbc:sqlite:" + databaseFile.getPath());
public static void loadCorners(Connection databaseConnection, Volume volume, World world, String prefix) throws SQLException {
Validate.isTrue(!databaseConnection.isClosed());
Statement stmt = databaseConnection.createStatement();
ResultSet versionQuery = stmt.executeQuery("PRAGMA user_version");
int version = versionQuery.getInt("user_version");
versionQuery.close();
if (version > DATABASE_VERSION) {
try {
throw new IllegalStateException("Unsupported zone format " + version);
} finally {
stmt.close();
databaseConnection.close();
}
} else if (version < DATABASE_VERSION) {
switch (version) {
// Run some update SQL for each old version
}
}
ResultSet cornerQuery = stmt.executeQuery("SELECT * FROM corners");
ResultSet cornerQuery = stmt.executeQuery("SELECT * FROM " + prefix + "corners");
cornerQuery.next();
final Block corner1 = world.getBlockAt(cornerQuery.getInt("x"), cornerQuery.getInt("y"), cornerQuery.getInt("z"));
cornerQuery.next();
@ -64,40 +44,217 @@ public class VolumeMapper {
cornerQuery.close();
volume.setCornerOne(corner1);
volume.setCornerTwo(corner2);
ResultSet query = stmt.executeQuery("SELECT * FROM blocks");
stmt.close();
}
/**
* Loads the given volume
*
* @param databaseConnection Open connection to zone database
* @param volume Volume to load
* @param start Starting position to load blocks at
* @param total Amount of blocks to read
* @return Changed blocks
* @throws SQLException Error communicating with SQLite3 database
*/
public static int loadBlocks(Connection databaseConnection, Volume volume, int start, int total, boolean[][][] changes, boolean inMemory, String prefix) throws SQLException {
Validate.isTrue(!databaseConnection.isClosed());
if (inMemory) {
volume.getBlocks().clear();
}
final Block corner1 = volume.getCornerOne().getBlock();
Statement stmt = databaseConnection.createStatement();
Map<Integer, String> stringCache = new HashMap<>();
stringCache.put(0, null);
ResultSet cacheQuery = stmt.executeQuery("SELECT * FROM "+ prefix +"strings");
while (cacheQuery.next()) {
stringCache.put(cacheQuery.getInt("id"), cacheQuery.getString("type"));
}
cacheQuery.close();
int minX = volume.getMinX(), minY = volume.getMinY(), minZ = volume.getMinZ();
int changed = 0;
ResultSet query = stmt.executeQuery("SELECT * FROM "+ prefix + "blocks ORDER BY rowid LIMIT " + start + ", " + total);
while (query.next()) {
int x = query.getInt("x"), y = query.getInt("y"), z = query.getInt("z");
BlockState modify = corner1.getRelative(x, y, z).getState();
ItemStack data = new ItemStack(Material.valueOf(query.getString("type")), 0, query.getShort("data"));
modify.setType(data.getType());
modify.setData(data.getData());
volume.getBlocks().add(modify);
changed++;
Block relative = corner1.getRelative(x, y, z);
int xi = relative.getX() - minX, yi = relative.getY() - minY, zi = relative.getZ() - minZ;
if (changes != null) {
changes[xi][yi][zi] = true;
}
BlockState modify = relative.getState();
// Load information from database, or null if not set
String type = stringCache.get(query.getInt("type"));
String data = stringCache.get(query.getInt("data"));
String metadata = stringCache.get(query.getInt("metadata"));
// Try to look up the material. May fail due to mods or MC updates.
Material mat = Material.getMaterial(type);
if (mat == null) {
War.war.getLogger().log(Level.WARNING, "Failed to parse block type. " + getBlockDescriptor(modify.getLocation(), type, data, metadata));
continue;
}
// Try to get the block data (damage, non-tile information) using the 1.13 functions
BlockData bdata = null;
try {
if (data != null) {
bdata = Bukkit.createBlockData(data);
}
} catch (IllegalArgumentException iae) {
War.war.getLogger().log(Level.WARNING, "Exception loading some block data. " + getBlockDescriptor(modify.getLocation(), type, data, metadata), iae);
}
// Update the block type/data in memory if they have changed
boolean updatedType = false;
if (modify.getType() != mat) {
modify.setType(mat);
updatedType = true;
}
boolean updatedData = false;
if (bdata != null && !modify.getBlockData().equals(bdata)) {
modify.setBlockData(bdata);
updatedData = true;
}
if (!inMemory && (updatedType || updatedData)) {
// Update the type & data if it has changed, needed here for tile entity check
modify.update(true, false); // No-physics update, preventing the need for deferring blocks
relative = corner1.getRelative(x, y, z);
modify = relative.getState();
}
// Try to update the tile entity data
if (metadata != null) {
try {
if (modify instanceof Sign) {
final String[] lines = metadata.split("\n");
for (int i = 0; i < lines.length; i++) {
((Sign) modify).setLine(i, lines[i]);
}
}
// Containers
if (modify instanceof Container) {
YamlConfiguration config = new YamlConfiguration();
config.loadFromString(metadata);
Inventory inv = ((Container) modify).getSnapshotInventory();
inv.clear();
int slot = 0;
for (Object obj : config.getList("items")) {
if (obj instanceof ItemStack) {
inv.setItem(slot, (ItemStack) obj);
}
++slot;
}
}
// Records
if (modify instanceof Jukebox) {
((Jukebox) modify).setPlaying(Material.valueOf(metadata));
}
// Skulls
if (modify instanceof Skull) {
UUID playerId = UUID.fromString(metadata);
OfflinePlayer player = Bukkit.getOfflinePlayer(playerId);
((Skull) modify).setOwningPlayer(player);
}
// Command blocks
if (modify instanceof CommandBlock) {
final String[] commandArray = metadata.split("\n");
((CommandBlock) modify).setName(commandArray[0]);
((CommandBlock) modify).setCommand(commandArray[1]);
}
// Creature spawner
if (modify instanceof CreatureSpawner) {
((CreatureSpawner) modify).setSpawnedType(EntityType.valueOf(metadata));
}
if (!inMemory) {
modify.update(true, false);
}
} catch (Exception ex) {
War.war.getLogger().log(Level.WARNING, "Exception loading some tile entity data. " + getBlockDescriptor(modify.getLocation(), type, data, metadata), ex);
}
}
if (inMemory) {
volume.getBlocks().add(modify);
}
}
query.close();
stmt.close();
databaseConnection.close();
return changed;
}
public static final int DATABASE_VERSION = 1;
public static void save(Volume volume, String zoneName) throws SQLException {
File databaseFile = new File(War.war.getDataFolder(), String.format(
"/dat/volume-%s.sl3", volume.getName()));
if (!zoneName.isEmpty()) {
databaseFile = new File(War.war.getDataFolder(),
String.format("/dat/warzone-%s/volume-%s.sl3", zoneName,
volume.getName()));
/**
* Load saved entities.
*
* @param databaseConnection Open databaseConnection to warzone DB file.
* @param volume Volume for warzone.
* @return number affected
* @throws SQLException SQLite error
*/
public static int loadEntities(Connection databaseConnection, Volume volume) throws SQLException {
Validate.isTrue(!databaseConnection.isClosed());
// first, clear entities from the area
for (Entity e : volume.getWorld().getEntitiesByClass(Hanging.class)) {
if (volume.contains(e.getLocation())) {
e.remove();
}
}
Connection databaseConnection = DriverManager
.getConnection("jdbc:sqlite:" + databaseFile.getPath());
int changed = 0;
Statement stmt = databaseConnection.createStatement();
final Block corner1 = volume.getCornerOne().getBlock();
Location test = new Location(volume.getWorld(), 0, 253, 0); // admins pls don't build stuff here kthx
ResultSet query = stmt.executeQuery("SELECT * FROM entities ORDER BY rowid");
while (query.next()) {
double x = query.getDouble("x"), y = query.getDouble("y"), z = query.getDouble("z");
changed++;
// translate from relative DB location to absolute
Location absolute = corner1.getLocation().clone().add(x, y, z);
int type = query.getInt("type");
String facing = query.getString("facing");
String metadata = query.getString("metadata");
BlockFace face = BlockFace.valueOf(facing);
// Spawn the paintings in the sky somewhere, works because I can only get them to spawn north/south
test.getBlock().setType(Material.AIR);
test.add(2, 0, 0).getBlock().setType(Material.STONE);
try {
if (type == 1) {
Painting p = (Painting) volume.getWorld().spawnEntity(test.clone().add(0, 0, 1), EntityType.PAINTING);
Art art = Art.valueOf(metadata);
p.teleport(calculatePainting(art, face, absolute));
p.setFacingDirection(face, true);
p.setArt(art, true);
} else if (type == 2) {
ItemFrame itemFrame = (ItemFrame) volume.getWorld().spawnEntity(test.clone().add(0, 0, 1), EntityType.ITEM_FRAME);
itemFrame.teleport(absolute);
itemFrame.setFacingDirection(face, true);
String[] args = metadata.split(delim);
itemFrame.setRotation(Rotation.valueOf(args[0]));
YamlConfiguration config = new YamlConfiguration();
config.loadFromString(args[1]);
itemFrame.setItem(config.getItemStack("item"));
}
} catch (Exception ex) {
War.war.getLogger().log(Level.WARNING, "Exception loading entity. x:" + x + " y:" + y + " z:" + z + " type:" + type, ex);
}
}
test.getBlock().setType(Material.AIR);
query.close();
stmt.close();
return changed;
}
public static void saveCorners(Connection databaseConnection, Volume volume, String prefix) throws SQLException {
Statement stmt = databaseConnection.createStatement();
stmt.executeUpdate("PRAGMA user_version = " + DATABASE_VERSION);
stmt.executeUpdate("CREATE TABLE IF NOT EXISTS blocks (x BIGINT, y BIGINT, z BIGINT, type TEXT, data SMALLINT)");
stmt.executeUpdate("CREATE TABLE IF NOT EXISTS corners (pos INTEGER PRIMARY KEY NOT NULL UNIQUE, x INTEGER NOT NULL, y INTEGER NOT NULL, z INTEGER NOT NULL)");
stmt.executeUpdate("DELETE FROM blocks");
stmt.executeUpdate("DELETE FROM corners");
stmt.executeUpdate("CREATE TABLE IF NOT EXISTS "+prefix+"corners (pos INTEGER PRIMARY KEY NOT NULL UNIQUE, x INTEGER NOT NULL, y INTEGER NOT NULL, z INTEGER NOT NULL)");
stmt.executeUpdate("DELETE FROM "+prefix+"corners");
stmt.close();
PreparedStatement cornerStmt = databaseConnection
.prepareStatement("INSERT INTO corners SELECT 1 AS pos, ? AS x, ? AS y, ? AS z UNION SELECT 2, ?, ?, ?");
PreparedStatement cornerStmt = databaseConnection.prepareStatement("INSERT INTO " + prefix + "corners SELECT 1 AS pos, ? AS x, ? AS y, ? AS z UNION SELECT 2, ?, ?, ?");
cornerStmt.setInt(1, volume.getCornerOne().getBlockX());
cornerStmt.setInt(2, volume.getCornerOne().getBlockY());
cornerStmt.setInt(3, volume.getCornerOne().getBlockZ());
@ -106,37 +263,296 @@ public class VolumeMapper {
cornerStmt.setInt(6, volume.getCornerTwo().getBlockZ());
cornerStmt.executeUpdate();
cornerStmt.close();
PreparedStatement dataStmt = databaseConnection
.prepareStatement("INSERT INTO blocks VALUES (?, ?, ?, ?, ?)");
databaseConnection.setAutoCommit(false);
final int batchSize = 1000;
}
public static int saveEntities(Connection databaseConnection, Volume volume) throws SQLException {
Statement stmt = databaseConnection.createStatement();
stmt.executeUpdate("CREATE TABLE IF NOT EXISTS entities (x NUMERIC, y NUMERIC, z NUMERIC, type SMALLINT, facing TEXT, metadata TEXT)");
stmt.executeUpdate("DELETE FROM entities");
stmt.close();
PreparedStatement entityStmt = databaseConnection.prepareStatement("INSERT INTO entities (x, y, z, type, facing, metadata) VALUES (?, ?, ?, ?, ?, ?)");
int i = 0;
for (Entity e : volume.getWorld().getEntities()) {
if (volume.contains(e.getLocation()) && e instanceof Hanging) {
entityStmt.setDouble(1, e.getLocation().getX() - volume.getCornerOne().getBlockX());
entityStmt.setDouble(2, e.getLocation().getY() - volume.getCornerOne().getBlockY());
entityStmt.setDouble(3, e.getLocation().getZ() - volume.getCornerOne().getBlockZ());
entityStmt.setString(5, ((Hanging) e).getFacing().name());
if (e instanceof Painting) {
Painting p = (Painting) e;
entityStmt.setInt(4, 1);
entityStmt.setString(6, p.getArt().name());
} else if (e instanceof ItemFrame) {
ItemFrame itemFrame = (ItemFrame) e;
YamlConfiguration config = new YamlConfiguration();
config.set("item", itemFrame.getItem());
entityStmt.setInt(4, 2);
entityStmt.setString(6, itemFrame.getRotation().name() + delim + config.saveToString());
} else {
entityStmt.setInt(4, 0);
entityStmt.setString(6, "");
}
entityStmt.addBatch();
++i;
}
}
entityStmt.executeBatch();
entityStmt.close();
return i;
}
public static int saveBlocks(Connection databaseConnection, Volume volume, String prefix) throws SQLException {
Statement stmt = databaseConnection.createStatement();
stmt.executeUpdate("CREATE TABLE IF NOT EXISTS "+prefix+"blocks (x BIGINT, y BIGINT, z BIGINT, type BIGINT, data BIGINT, metadata BIGINT)");
stmt.executeUpdate("CREATE TABLE IF NOT EXISTS "+prefix+"strings (id INTEGER PRIMARY KEY NOT NULL UNIQUE, type TEXT)");
stmt.executeUpdate("DELETE FROM "+prefix+"blocks");
stmt.executeUpdate("DELETE FROM "+prefix+"strings");
stmt.close();
Map<String, Integer> stringCache = new HashMap<>();
int cachei = 1;
int changed = 0;
for (BlockState block : volume.getBlocks()) {
final Location relLoc = ZoneVolumeMapper.rebase(
volume.getCornerOne(), block.getLocation());
dataStmt.setInt(1, relLoc.getBlockX());
dataStmt.setInt(2, relLoc.getBlockY());
dataStmt.setInt(3, relLoc.getBlockZ());
dataStmt.setString(4, block.getType().toString());
dataStmt.setShort(5, block.getData().toItemStack(1).getDurability());
dataStmt.addBatch();
if (++changed % batchSize == 0) {
dataStmt.executeBatch();
long startTime = System.currentTimeMillis();
PreparedStatement dataStmt = databaseConnection.prepareStatement("INSERT INTO "+prefix+"blocks (x, y, z, type, data, metadata) VALUES (?, ?, ?, ?, ?, ?)");
databaseConnection.setAutoCommit(false);
final int batchSize = 10000;
for (int i = 0, x = volume.getMinX(); i < volume.getSizeX(); i++, x++) {
for (int j = 0, y = volume.getMinY(); j < volume.getSizeY(); j++, y++) {
for (int k = 0, z = volume.getMinZ(); k < volume.getSizeZ(); k++, z++) {
// Make sure we are using zone volume-relative coords
final Block block = volume.getWorld().getBlockAt(x, y, z);
if (block.getType() == Material.AIR) {
continue; // Do not save air blocks to the file anymore.
}
int typeid, dataid, metaid;
// Save even more space by writing each string only once
if (stringCache.containsKey(block.getType().name())) {
typeid = stringCache.get(block.getType().name());
} else {
typeid = cachei;
stringCache.put(block.getType().name(), cachei++);
}
// Save new-style data
if (BlockData.class.isAssignableFrom(block.getType().data)) {
String data = block.getBlockData().getAsString();
if (stringCache.containsKey(data)) {
dataid = stringCache.get(data);
} else {
dataid = cachei;
stringCache.put(data, cachei++);
}
} else {
dataid = 0;
}
// Save tile entities
BlockState state = block.getState();
String metadata = "";
if (state instanceof Sign) {
metadata = StringUtils.join(((Sign) state).getLines(), "\n");
} else if (state instanceof InventoryHolder) {
List<ItemStack> items = Arrays.asList(((InventoryHolder) state).getInventory().getContents());
YamlConfiguration config = new YamlConfiguration();
// Serialize to config, then store config in database
config.set("items", items);
metadata = config.saveToString();
} else if (state instanceof Jukebox) {
metadata = ((Jukebox) state).getPlaying().toString();
} else if (state instanceof Skull) {
OfflinePlayer player = ((Skull) state).getOwningPlayer();
metadata = player == null ? "" : player.getUniqueId().toString();
} else if (state instanceof CommandBlock) {
metadata = ((CommandBlock) state).getName() + "\n" + ((CommandBlock) state).getCommand();
} else if (state instanceof CreatureSpawner) {
metadata = ((CreatureSpawner) state).getSpawnedType().toString();
}
if (metadata.isEmpty()) {
metaid = 0;
} else if (stringCache.containsKey(metadata)) {
metaid = stringCache.get(metadata);
} else {
metaid = cachei;
stringCache.put(metadata, cachei++);
}
dataStmt.setInt(1, block.getX() - volume.getCornerOne().getBlockX());
dataStmt.setInt(2, block.getY() - volume.getCornerOne().getBlockY());
dataStmt.setInt(3, block.getZ() - volume.getCornerOne().getBlockZ());
dataStmt.setInt(4, typeid);
dataStmt.setInt(5, dataid);
dataStmt.setInt(6, metaid);
dataStmt.addBatch();
if (++changed % batchSize == 0) {
dataStmt.executeBatch();
if ((System.currentTimeMillis() - startTime) >= 5000L) {
String seconds = new DecimalFormat("#0.00").format((double) (System.currentTimeMillis() - startTime) / 1000.0D);
War.war.getLogger().log(Level.FINE, "Still saving volume {0} , {1} seconds elapsed.", new Object[] {volume.getName(), seconds});
}
}
}
}
}
dataStmt.executeBatch(); // insert remaining records
databaseConnection.commit();
dataStmt.close();
databaseConnection.close();
PreparedStatement stringStmt = databaseConnection.prepareStatement("INSERT INTO "+prefix+"strings (id, type) VALUES (?, ?)");
for (Map.Entry<String, Integer> mapping : stringCache.entrySet()) {
stringStmt.setInt(1, mapping.getValue());
stringStmt.setString(2, mapping.getKey());
stringStmt.addBatch();
}
stringStmt.executeBatch();
databaseConnection.commit();
databaseConnection.setAutoCommit(true);
return changed;
}
public static void delete(Volume volume) {
File volFile = new File(War.war.getDataFolder(), String.format(
"/dat/volume-%s.sl3", volume.getName()));
/**
* Save a simple volume, like the WarHub.
*
* @param volume Volume to save (takes corner data and loads from world).
* @return amount of changed blocks
* @throws SQLException
*/
public static int saveSimpleVolume(Volume volume) throws SQLException {
File volFile = new File(War.war.getDataFolder(), String.format("/dat/volume-%s.sl3", volume.getName()));
Connection databaseConnection = getConnection(volFile);
int changed = 0;
saveCorners(databaseConnection, volume, "");
saveBlocks(databaseConnection, volume, "");
databaseConnection.close();
return changed;
}
public static boolean deleteSimpleVolume(Volume volume) {
File volFile = new File(War.war.getDataFolder(), String.format("/dat/volume-%s.sl3", volume.getName()));
boolean deletedData = volFile.delete();
if (!deletedData) {
War.war.log("Failed to delete file " + volFile.getName(), Level.WARNING);
}
return deletedData;
}
public static Volume loadSimpleVolume(String volumeName, World world) throws SQLException {
File volFile = new File(War.war.getDataFolder(), String.format("/dat/volume-%s.sl3", volumeName));
Connection databaseConnection = getConnection(volFile);
int version = checkConvert(databaseConnection);
Volume v = new Volume(volumeName, world);
switch (version) {
case 1:
case 2:
War.war.log(volumeName + " cannot be migrated from War 1.9 due to breaking MC1.13 changes - please resave.", Level.WARNING);
loadCorners(databaseConnection, v, world, "");
convertSchema2_3(databaseConnection, "", true);
return v;
case 3:
break;
default:
throw new IllegalStateException(String.format("Unsupported volume format (was already converted to version: %d, current format: %d)", version, DATABASE_VERSION));
}
loadCorners(databaseConnection, v, world, "");
loadBlocks(databaseConnection, v, 0, 0, null, true, "");
return v;
}
protected static Connection getConnection(File filename) throws SQLException {
return DriverManager.getConnection("jdbc:sqlite:" + filename.getPath());
}
protected static int checkConvert(Connection databaseConnection) throws SQLException {
Statement stmt = databaseConnection.createStatement();
ResultSet versionQuery = stmt.executeQuery("PRAGMA user_version");
int version = versionQuery.getInt("user_version");
versionQuery.close();
stmt.close();
return version;
}
protected static void convertSchema2_3(Connection databaseConnection, String prefix, boolean isSimple) throws SQLException {
Statement stmt = databaseConnection.createStatement();
stmt.executeUpdate("DROP TABLE " + prefix + "blocks");
stmt.executeUpdate("CREATE TABLE IF NOT EXISTS "+prefix+"blocks (x BIGINT, y BIGINT, z BIGINT, type BIGINT, data BIGINT, metadata BIGINT)");
stmt.executeUpdate("CREATE TABLE IF NOT EXISTS "+prefix+"strings (id INTEGER PRIMARY KEY NOT NULL UNIQUE, type TEXT)");
stmt.executeUpdate("PRAGMA user_version = " + DATABASE_VERSION);
stmt.close();
}
/**
* Finds the correct location to place a painting based on its characteristics.
* Credit goes to whatever forum I got this from.
*
* @param art Painting type
* @param facing Block face
* @param loc Desired location
* @return Corrected location
*/
private static Location calculatePainting(Art art, BlockFace facing, Location loc) {
switch(art) {
// 1x1
case ALBAN:
case AZTEC:
case AZTEC2:
case BOMB:
case KEBAB:
case PLANT:
case WASTELAND:
return loc; // No calculation needed.
// 1x2
case GRAHAM:
case WANDERER:
return loc.getBlock().getLocation().add(0, -1, 0);
// 2x1
case CREEBET:
case COURBET:
case POOL:
case SEA:
case SUNSET: // Use same as 4x3
// 4x3
case DONKEY_KONG:
case SKELETON:
if(facing == BlockFace.WEST)
return loc.getBlock().getLocation().add(0, 0, -1);
else if(facing == BlockFace.SOUTH)
return loc.getBlock().getLocation().add(-1, 0, 0);
else
return loc;
// 2x2
case BUST:
case MATCH:
case SKULL_AND_ROSES:
case STAGE:
case VOID:
case WITHER: // Use same as 4x2
// 4x2
case FIGHTERS: // Use same as 4x4
// 4x4
case BURNING_SKULL:
case PIGSCENE:
case POINTER:
if(facing == BlockFace.WEST)
return loc.getBlock().getLocation().add(0, -1, -1);
else if(facing == BlockFace.SOUTH)
return loc.getBlock().getLocation().add(-1, -1, 0);
else
return loc.add(0, -1, 0);
// Unsupported artwork
default:
return loc;
}
}
}

View File

@ -1,18 +1,5 @@
package com.tommytony.war.mapper;
import java.io.File;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.logging.Level;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.inventory.ItemStack;
import com.tommytony.war.War;
import com.tommytony.war.Warzone;
import com.tommytony.war.config.KillstreakReward;
@ -21,6 +8,20 @@ import com.tommytony.war.job.RestoreYmlWarhubJob;
import com.tommytony.war.job.RestoreYmlWarzonesJob;
import com.tommytony.war.structure.WarHub;
import com.tommytony.war.utility.Direction;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.inventory.ItemStack;
import java.io.File;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
import java.util.logging.Level;
public class WarYmlMapper {
@ -56,9 +57,14 @@ public class WarYmlMapper {
List<String> makers = warRootSection.getStringList("war.info.zonemakers");
War.war.getZoneMakerNames().clear();
for (String makerName : makers) {
if (makerName != null && !makerName.equals("")) {
War.war.getZoneMakerNames().add(makerName);
OfflinePlayer player;
try {
UUID id = UUID.fromString(makerName);
player = Bukkit.getOfflinePlayer(id);
} catch (IllegalArgumentException iae) {
player = Bukkit.getOfflinePlayer(makerName);
}
War.war.getZoneMakerNames().add(player);
}
// command whitelist
@ -148,7 +154,11 @@ public class WarYmlMapper {
warInfoSection.set("warzones", warzones);
// zone makers
warInfoSection.set("zonemakers", War.war.getZoneMakerNames());
List<String> zonemakers = new ArrayList<>();
for (OfflinePlayer zonemaker : War.war.getZoneMakerNames()) {
zonemakers.add(zonemaker.getUniqueId().toString());
}
warInfoSection.set("zonemakers", zonemakers);
// whitelisted commands during a game
warInfoSection.set("commandwhitelist", War.war.getCommandWhitelist());
@ -190,7 +200,7 @@ public class WarYmlMapper {
hubConfigSection.set("materials.light", War.war.getWarhubMaterials().getLightBlock());
try {
VolumeMapper.save(hub.getVolume(), "");
VolumeMapper.saveSimpleVolume(hub.getVolume());
} catch (SQLException e) {
// who really even cares
War.war.getLogger().log(Level.WARNING, "Failed to save warhub volume blocks", e);

View File

@ -1,5 +1,21 @@
package com.tommytony.war.mapper;
import com.tommytony.war.Team;
import com.tommytony.war.War;
import com.tommytony.war.Warzone;
import com.tommytony.war.config.TeamConfig;
import com.tommytony.war.config.TeamKind;
import com.tommytony.war.config.WarzoneConfig;
import com.tommytony.war.structure.*;
import com.tommytony.war.utility.Direction;
import com.tommytony.war.volume.Volume;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.block.BlockFace;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.inventory.ItemStack;
import java.io.File;
import java.io.IOException;
import java.sql.Connection;
@ -10,24 +26,6 @@ import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import com.tommytony.war.config.WarzoneConfig;
import com.tommytony.war.structure.*;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.block.BlockFace;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.inventory.ItemStack;
import com.tommytony.war.Team;
import com.tommytony.war.War;
import com.tommytony.war.Warzone;
import com.tommytony.war.config.TeamConfig;
import com.tommytony.war.config.TeamKind;
import com.tommytony.war.utility.Direction;
import com.tommytony.war.volume.Volume;
import com.tommytony.war.volume.ZoneVolume;
public class WarzoneYmlMapper {
public static Warzone load(String name) { // removed createNewVolume, as it did nothing
@ -304,7 +302,7 @@ public class WarzoneYmlMapper {
}
Connection connection = null;
try {
connection = ZoneVolumeMapper.getZoneConnection(warzone.getVolume(), warzone.getName(), warzone.getWorld());
connection = ZoneVolumeMapper.getZoneConnection(warzone.getVolume(), warzone.getName());
} catch (SQLException e) {
War.war.getLogger().log(Level.WARNING, "Failed to load warzone structures volume", e);
}
@ -658,7 +656,7 @@ public class WarzoneYmlMapper {
}
Connection connection = null;
try {
connection = ZoneVolumeMapper.getZoneConnection(warzone.getVolume(), warzone.getName(), warzone.getWorld());
connection = ZoneVolumeMapper.getZoneConnection(warzone.getVolume(), warzone.getName());
} catch (SQLException e) {
War.war.getLogger().log(Level.WARNING, "Failed to load warzone structures volume", e);
}

View File

@ -1,38 +1,19 @@
package com.tommytony.war.mapper;
import java.io.File;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.DecimalFormat;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.bukkit.*;
import org.bukkit.Note.Tone;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockState;
import org.bukkit.block.CommandBlock;
import org.bukkit.block.CreatureSpawner;
import org.bukkit.block.Jukebox;
import org.bukkit.block.NoteBlock;
import org.bukkit.block.Sign;
import org.bukkit.block.Skull;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.*;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import com.tommytony.war.War;
import com.tommytony.war.volume.Volume;
import com.tommytony.war.volume.ZoneVolume;
import org.apache.commons.lang.Validate;
import org.bukkit.World;
import java.io.File;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
/**
* Loads and saves zone blocks to SQLite3 database.
@ -40,387 +21,84 @@ import com.tommytony.war.volume.ZoneVolume;
* @author cmastudios
* @since 1.8
*/
public class ZoneVolumeMapper {
private static final int DATABASE_VERSION = 2;
private static final String delim = "-------mcwar iSdgraIyMvOanTEJjZgocczfuG------";
public class ZoneVolumeMapper extends VolumeMapper {
/**
* Get a connection to the warzone database, converting blocks if not found.
* @param volume zone volume to load
* @param zoneName warzone to load
* @param world world containing this warzone
* @return an open connection to the sqlite file
* @throws SQLException
*/
public static Connection getZoneConnection(ZoneVolume volume, String zoneName, World world) throws SQLException {
public static Connection getZoneConnection(ZoneVolume volume, String zoneName) throws SQLException {
File databaseFile = new File(War.war.getDataFolder(), String.format("/dat/warzone-%s/volume-%s.sl3", zoneName, volume.getName()));
if (!databaseFile.exists()) {
// dropped nimitz compatibility with the MC 1.13 update
War.war.log("Warzone " + zoneName + " not found - creating new file. Will not attempt converting legacy War version formats.", Level.WARNING);
}
Connection databaseConnection = DriverManager.getConnection("jdbc:sqlite:" + databaseFile.getPath());
Statement stmt = databaseConnection.createStatement();
ResultSet versionQuery = stmt.executeQuery("PRAGMA user_version");
int version = versionQuery.getInt("user_version");
versionQuery.close();
if (version > DATABASE_VERSION) {
stmt.close();
databaseConnection.close();
// Can't load this too-recent format
throw new IllegalStateException(String.format("Unsupported zone format (was already converted to version: %d, current format: %d)", version, DATABASE_VERSION));
} else if (version < DATABASE_VERSION) {
stmt.close();
// We need to migrate to newest schema
switch (version) {
// Run some update SQL for each old version
case 1:
// Run update from 1 to 2
updateFromVersionOneToTwo(zoneName, databaseConnection);
// How to continue this pattern: (@tommytony multiple in one shouldn't be needed, just don't put a break in the switch)
// case 2:
// // Run update from 2 to 3
// updateFromVersionTwoToTree(zoneName, databaseConnection);
}
Connection databaseConnection = getConnection(databaseFile);
int version = checkConvert(databaseConnection);
switch (version) {
case 0: // new file
break;
case 1:
case 2:
War.war.log(zoneName + " cannot be migrated from War 1.9 due to breaking MC1.13 changes - please resave.", Level.WARNING);
convertSchema2_3(databaseConnection, "", false);
for (String prefix : getStructures(databaseConnection)) {
convertSchema2_3(databaseConnection, prefix, false);
}
break;
case 3:
break;
default:
throw new IllegalStateException(String.format("Unsupported volume format (was already converted to version: %d, current format: %d)", version, DATABASE_VERSION));
}
return databaseConnection;
}
private static List<String> getStructures(Connection databaseConnection) throws SQLException {
List<String> structures = new ArrayList<>();
Statement stmt = databaseConnection.createStatement();
ResultSet q = stmt.executeQuery("SELECT name FROM sqlite_master WHERE type='table'");
while (q.next()) {
String name = q.getString("name");
if (name.contains("structure") && name.contains("corners")) {
structures.add(name.replace("corners", ""));
}
}
q.close();
stmt.close();
return structures;
}
/**
* Loads the given volume
*
* @param databaseConnection Open connection to zone database
* @param volume Volume to load
* @param world The world the zone is located
* @param onlyLoadCorners Should only the corners be loaded
* @param start Starting position to load blocks at
* @param total Amount of blocks to read
* @return Changed blocks
* @throws SQLException Error communicating with SQLite3 database
*/
public static int load(Connection databaseConnection, ZoneVolume volume, World world, boolean onlyLoadCorners, int start, int total, boolean[][][] changes) throws SQLException {
Validate.isTrue(!databaseConnection.isClosed());
Statement stmt = databaseConnection.createStatement();
ResultSet cornerQuery = stmt.executeQuery("SELECT * FROM corners");
cornerQuery.next();
final Block corner1 = world.getBlockAt(cornerQuery.getInt("x"), cornerQuery.getInt("y"), cornerQuery.getInt("z"));
cornerQuery.next();
final Block corner2 = world.getBlockAt(cornerQuery.getInt("x"), cornerQuery.getInt("y"), cornerQuery.getInt("z"));
cornerQuery.close();
volume.setCornerOne(corner1);
volume.setCornerTwo(corner2);
if (onlyLoadCorners) {
stmt.close();
return 0;
}
int minX = volume.getMinX(), minY = volume.getMinY(), minZ = volume.getMinZ();
int changed = 0;
ResultSet query = stmt.executeQuery("SELECT * FROM blocks ORDER BY rowid LIMIT " + start + ", " + total);
while (query.next()) {
int x = query.getInt("x"), y = query.getInt("y"), z = query.getInt("z");
changed++;
Block relative = corner1.getRelative(x, y, z);
int xi = relative.getX() - minX, yi = relative.getY() - minY, zi = relative.getZ() - minZ;
if (changes != null) {
changes[xi][yi][zi] = true;
}
BlockState modify = relative.getState();
ItemStack data = new ItemStack(Material.valueOf(query.getString("type")), 0, query.getShort("data"));
if (modify.getType() != data.getType() || !modify.getData().equals(data.getData())) {
// Update the type & data if it has changed
modify.setType(data.getType());
modify.setData(data.getData());
modify.update(true, false); // No-physics update, preventing the need for deferring blocks
modify = corner1.getRelative(x, y, z).getState(); // Grab a new instance
}
if (query.getString("metadata") == null || query.getString("metadata").isEmpty()) {
continue;
}
try {
if (modify instanceof Sign) {
final String[] lines = query.getString("metadata").split("\n");
for (int i = 0; i < lines.length; i++) {
((Sign) modify).setLine(i, lines[i]);
}
modify.update(true, false);
}
// Containers
if (modify instanceof InventoryHolder) {
YamlConfiguration config = new YamlConfiguration();
config.loadFromString(query.getString("metadata"));
((InventoryHolder) modify).getInventory().clear();
for (Object obj : config.getList("items")) {
if (obj instanceof ItemStack) {
((InventoryHolder) modify).getInventory().addItem((ItemStack) obj);
}
}
modify.update(true, false);
}
// Notes
if (modify instanceof NoteBlock) {
String[] split = query.getString("metadata").split("\n");
Note note = new Note(Integer.parseInt(split[1]), Tone.valueOf(split[0]), Boolean.parseBoolean(split[2]));
((NoteBlock) modify).setNote(note);
modify.update(true, false);
}
// Records
if (modify instanceof Jukebox) {
((Jukebox) modify).setPlaying(Material.valueOf(query.getString("metadata")));
modify.update(true, false);
}
// Skulls
if (modify instanceof Skull) {
String[] opts = query.getString("metadata").split("\n");
if (!opts[0].isEmpty()) {
// TODO upgrade to store owning players by UUID
((Skull) modify).setOwningPlayer(Bukkit.getOfflinePlayer(opts[0]));
}
((Skull) modify).setSkullType(SkullType.valueOf(opts[1]));
((Skull) modify).setRotation(BlockFace.valueOf(opts[2]));
modify.update(true, false);
}
// Command blocks
if (modify instanceof CommandBlock) {
final String[] commandArray = query.getString("metadata").split("\n");
((CommandBlock) modify).setName(commandArray[0]);
((CommandBlock) modify).setCommand(commandArray[1]);
modify.update(true, false);
}
// Creature spawner
if (modify instanceof CreatureSpawner) {
((CreatureSpawner) modify).setSpawnedType(EntityType.valueOf(query.getString("metadata")));
modify.update(true, false);
}
} catch (Exception ex) {
War.war.getLogger().log(Level.WARNING, "Exception loading some tile data. x:" + x + " y:" + y + " z:" + z + " type:" + modify.getType().toString() + " data:" + modify.getData().toString(), ex);
}
}
query.close();
stmt.close();
return changed;
public static int reloadZoneBlocks(Connection databaseConnection, ZoneVolume volume, int start, int total, boolean[][][] changes) throws SQLException {
return loadBlocks(databaseConnection, volume, start, total, changes, false, "");
}
/**
* Load saved entities.
*
* @param connection Open connection to warzone DB file.
* @param volume Volume for warzone.
* @return number affected
* @throws SQLException SQLite error
*/
public static int loadEntities(Connection connection, ZoneVolume volume) throws SQLException {
Validate.isTrue(!connection.isClosed());
// first, clear entities from the area
for (Entity e : volume.getWorld().getEntitiesByClass(Hanging.class)) {
if (volume.contains(e.getLocation())) {
e.remove();
}
}
int changed = 0;
Statement stmt = connection.createStatement();
ResultSet cornerQuery = stmt.executeQuery("SELECT * FROM corners");
cornerQuery.next();
final Block corner1 = volume.getWorld().getBlockAt(cornerQuery.getInt("x"), cornerQuery.getInt("y"), cornerQuery.getInt("z"));
cornerQuery.close();
Location test = new Location(volume.getWorld(), 0, 253, 0); // admins pls don't build stuff here kthx
// TODO move this to a migration step
stmt.executeUpdate("CREATE TABLE IF NOT EXISTS entities (x NUMERIC, y NUMERIC, z NUMERIC, type SMALLINT, facing TEXT, metadata TEXT)");
ResultSet query = stmt.executeQuery("SELECT * FROM entities ORDER BY rowid");
while (query.next()) {
double x = query.getDouble("x"), y = query.getDouble("y"), z = query.getDouble("z");
changed++;
// translate from relative DB location to absolute
Location absolute = corner1.getLocation().clone().add(x, y, z);
int type = query.getInt("type");
String facing = query.getString("facing");
String metadata = query.getString("metadata");
BlockFace face = BlockFace.valueOf(facing);
// Spawn the paintings in the sky somewhere, works because I can only get them to spawn north/south
test.getBlock().setType(Material.AIR);
test.add(2, 0, 0).getBlock().setType(Material.STONE);
try {
if (type == 1) {
Painting p = (Painting) volume.getWorld().spawnEntity(test.clone().add(0, 0, 1), EntityType.PAINTING);
Art art = Art.valueOf(metadata);
p.teleport(calculatePainting(art, face, absolute));
p.setFacingDirection(face, true);
p.setArt(art, true);
} else if (type == 2) {
ItemFrame itemFrame = (ItemFrame) volume.getWorld().spawnEntity(test.clone().add(0, 0, 1), EntityType.ITEM_FRAME);
itemFrame.teleport(absolute);
itemFrame.setFacingDirection(face, true);
String[] args = metadata.split(delim);
itemFrame.setRotation(Rotation.valueOf(args[0]));
YamlConfiguration config = new YamlConfiguration();
config.loadFromString(args[1]);
itemFrame.setItem(config.getItemStack("item"));
}
} catch (Exception ex) {
War.war.getLogger().log(Level.WARNING, "Exception loading entity. x:" + x + " y:" + y + " z:" + z + " type:" + type, ex);
}
}
test.getBlock().setType(Material.AIR);
query.close();
stmt.close();
return changed;
}
/**
* Finds the correct location to place a painting based on its characteristics.
* Credit goes to whatever forum I got this from.
*
* @param art Painting type
* @param facing Block face
* @param loc Desired location
* @return Corrected location
*/
private static Location calculatePainting(Art art, BlockFace facing, Location loc) {
switch(art) {
// 1x1
case ALBAN:
case AZTEC:
case AZTEC2:
case BOMB:
case KEBAB:
case PLANT:
case WASTELAND:
return loc; // No calculation needed.
// 1x2
case GRAHAM:
case WANDERER:
return loc.getBlock().getLocation().add(0, -1, 0);
// 2x1
case CREEBET:
case COURBET:
case POOL:
case SEA:
case SUNSET: // Use same as 4x3
// 4x3
case DONKEY_KONG:
case SKELETON:
if(facing == BlockFace.WEST)
return loc.getBlock().getLocation().add(0, 0, -1);
else if(facing == BlockFace.SOUTH)
return loc.getBlock().getLocation().add(-1, 0, 0);
else
return loc;
// 2x2
case BUST:
case MATCH:
case SKULL_AND_ROSES:
case STAGE:
case VOID:
case WITHER: // Use same as 4x2
// 4x2
case FIGHTERS: // Use same as 4x4
// 4x4
case BURNING_SKULL:
case PIGSCENE:
case POINTER:
if(facing == BlockFace.WEST)
return loc.getBlock().getLocation().add(0, -1, -1);
else if(facing == BlockFace.SOUTH)
return loc.getBlock().getLocation().add(-1, -1, 0);
else
return loc.add(0, -1, 0);
// Unsupported artwork
default:
return loc;
}
}
public static int saveStructure(Volume volume, Connection databaseConnection) throws SQLException {
Statement stmt = databaseConnection.createStatement();
stmt.executeUpdate("PRAGMA user_version = " + DATABASE_VERSION);
// Storing zonemaker-inputted name could result in injection or undesirable behavior.
String prefix = String.format("structure_%d", volume.getName().hashCode() & Integer.MAX_VALUE);
stmt.executeUpdate("CREATE TABLE IF NOT EXISTS " + prefix +
"_blocks (x BIGINT, y BIGINT, z BIGINT, type TEXT, data SMALLINT)");
stmt.executeUpdate("CREATE TABLE IF NOT EXISTS " + prefix +
"_corners (pos INTEGER PRIMARY KEY NOT NULL UNIQUE, x INTEGER NOT NULL, y INTEGER NOT NULL, z INTEGER NOT NULL)");
stmt.executeUpdate("DELETE FROM " + prefix + "_blocks");
stmt.executeUpdate("DELETE FROM " + prefix + "_corners");
stmt.close();
PreparedStatement cornerStmt = databaseConnection
.prepareStatement("INSERT INTO " + prefix + "_corners SELECT 1 AS pos, ? AS x, ? AS y, ? AS z UNION SELECT 2, ?, ?, ?");
cornerStmt.setInt(1, volume.getCornerOne().getBlockX());
cornerStmt.setInt(2, volume.getCornerOne().getBlockY());
cornerStmt.setInt(3, volume.getCornerOne().getBlockZ());
cornerStmt.setInt(4, volume.getCornerTwo().getBlockX());
cornerStmt.setInt(5, volume.getCornerTwo().getBlockY());
cornerStmt.setInt(6, volume.getCornerTwo().getBlockZ());
cornerStmt.executeUpdate();
cornerStmt.close();
PreparedStatement dataStmt = databaseConnection
.prepareStatement("INSERT INTO " + prefix + "_blocks VALUES (?, ?, ?, ?, ?)");
databaseConnection.setAutoCommit(false);
final int batchSize = 1000;
int changed = 0;
for (BlockState block : volume.getBlocks()) {
final Location relLoc = ZoneVolumeMapper.rebase(
volume.getCornerOne(), block.getLocation());
dataStmt.setInt(1, relLoc.getBlockX());
dataStmt.setInt(2, relLoc.getBlockY());
dataStmt.setInt(3, relLoc.getBlockZ());
dataStmt.setString(4, block.getType().toString());
dataStmt.setShort(5, block.getData().toItemStack(1).getDurability());
dataStmt.addBatch();
if (++changed % batchSize == 0) {
dataStmt.executeBatch();
}
}
dataStmt.executeBatch(); // insert remaining records
databaseConnection.commit();
databaseConnection.setAutoCommit(true);
dataStmt.close();
return changed;
String prefix = String.format("structure_%d_", volume.getName().hashCode() & Integer.MAX_VALUE);
saveCorners(databaseConnection, volume, prefix);
return saveBlocks(databaseConnection, volume, prefix);
}
public static void loadStructure(Volume volume, Connection databaseConnection) throws SQLException {
String prefix = String.format("structure_%d", volume.getName().hashCode() & Integer.MAX_VALUE);
String prefix = String.format("structure_%d_", volume.getName().hashCode() & Integer.MAX_VALUE);
World world = volume.getWorld();
Validate.notNull(world, String.format("Cannot find the warzone for %s", prefix));
Statement stmt = databaseConnection.createStatement();
ResultSet cornerQuery = stmt.executeQuery("SELECT * FROM " + prefix + "_corners");
cornerQuery.next();
final Block corner1 = world.getBlockAt(cornerQuery.getInt("x"), cornerQuery.getInt("y"), cornerQuery.getInt("z"));
cornerQuery.next();
final Block corner2 = world.getBlockAt(cornerQuery.getInt("x"), cornerQuery.getInt("y"), cornerQuery.getInt("z"));
cornerQuery.close();
volume.setCornerOne(corner1);
volume.setCornerTwo(corner2);
volume.getBlocks().clear();
ResultSet query = stmt.executeQuery("SELECT * FROM " + prefix + "_blocks");
while (query.next()) {
int x = query.getInt("x"), y = query.getInt("y"), z = query.getInt("z");
BlockState modify = corner1.getRelative(x, y, z).getState();
ItemStack data = new ItemStack(Material.valueOf(query.getString("type")), 0, query.getShort("data"));
modify.setType(data.getType());
modify.setData(data.getData());
volume.getBlocks().add(modify);
}
query.close();
stmt.close();
loadCorners(databaseConnection, volume, world, prefix);
loadBlocks(databaseConnection, volume, 0, 0, null, true, prefix);
}
/**
@ -431,8 +109,7 @@ public class ZoneVolumeMapper {
* @throws SQLException
*/
public static int getTotalSavedBlocks(ZoneVolume volume, String zoneName) throws SQLException {
File databaseFile = new File(War.war.getDataFolder(), String.format("/dat/warzone-%s/volume-%s.sl3", zoneName, volume.getName()));
Connection databaseConnection = DriverManager.getConnection("jdbc:sqlite:" + databaseFile.getPath());
Connection databaseConnection = getZoneConnection(volume, zoneName);
Statement stmt = databaseConnection.createStatement();
ResultSet sizeQuery = stmt.executeQuery("SELECT COUNT(*) AS total FROM blocks");
int size = sizeQuery.getInt("total");
@ -450,166 +127,18 @@ public class ZoneVolumeMapper {
* @return amount of changed blocks
* @throws SQLException
*/
public static int save(Volume volume, String zoneName) throws SQLException {
long startTime = System.currentTimeMillis();
int changed = 0;
public static int saveZoneBlocksAndEntities(ZoneVolume volume, String zoneName) throws SQLException {
File warzoneDir = new File(War.war.getDataFolder().getPath() + "/dat/warzone-" + zoneName);
if (!warzoneDir.exists() && !warzoneDir.mkdirs()) {
throw new RuntimeException("Failed to create warzone data directory");
}
File databaseFile = new File(War.war.getDataFolder(), String.format("/dat/warzone-%s/volume-%s.sl3", zoneName, volume.getName()));
Connection databaseConnection = DriverManager.getConnection("jdbc:sqlite:" + databaseFile.getPath());
Statement stmt = databaseConnection.createStatement();
stmt.executeUpdate("PRAGMA user_version = " + DATABASE_VERSION);
stmt.executeUpdate("CREATE TABLE IF NOT EXISTS blocks (x BIGINT, y BIGINT, z BIGINT, type TEXT, data SMALLINT, metadata BLOB)");
stmt.executeUpdate("CREATE TABLE IF NOT EXISTS entities (x NUMERIC, y NUMERIC, z NUMERIC, type SMALLINT, facing TEXT, metadata TEXT)");
stmt.executeUpdate("CREATE TABLE IF NOT EXISTS corners (pos INTEGER PRIMARY KEY NOT NULL UNIQUE, x INTEGER NOT NULL, y INTEGER NOT NULL, z INTEGER NOT NULL)");
stmt.executeUpdate("DELETE FROM blocks");
stmt.executeUpdate("DELETE FROM corners");
stmt.executeUpdate("DELETE FROM entities");
stmt.close();
PreparedStatement cornerStmt = databaseConnection.prepareStatement("INSERT INTO corners SELECT 1 AS pos, ? AS x, ? AS y, ? AS z UNION SELECT 2, ?, ?, ?");
cornerStmt.setInt(1, volume.getCornerOne().getBlockX());
cornerStmt.setInt(2, volume.getCornerOne().getBlockY());
cornerStmt.setInt(3, volume.getCornerOne().getBlockZ());
cornerStmt.setInt(4, volume.getCornerTwo().getBlockX());
cornerStmt.setInt(5, volume.getCornerTwo().getBlockY());
cornerStmt.setInt(6, volume.getCornerTwo().getBlockZ());
cornerStmt.executeUpdate();
cornerStmt.close();
PreparedStatement entityStmt = databaseConnection.prepareStatement("INSERT INTO entities (x, y, z, type, facing, metadata) VALUES (?, ?, ?, ?, ?, ?)");
for (Entity e : volume.getWorld().getEntities()) {
if (volume.contains(e.getLocation()) && e instanceof Hanging) {
entityStmt.setDouble(1, e.getLocation().getX() - volume.getCornerOne().getBlockX());
entityStmt.setDouble(2, e.getLocation().getY() - volume.getCornerOne().getBlockY());
entityStmt.setDouble(3, e.getLocation().getZ() - volume.getCornerOne().getBlockZ());
entityStmt.setString(5, ((Hanging) e).getFacing().name());
if (e instanceof Painting) {
Painting p = (Painting) e;
entityStmt.setInt(4, 1);
entityStmt.setString(6, p.getArt().name());
} else if (e instanceof ItemFrame) {
ItemFrame itemFrame = (ItemFrame) e;
YamlConfiguration config = new YamlConfiguration();
config.set("item", itemFrame.getItem());
entityStmt.setInt(4, 2);
entityStmt.setString(6, itemFrame.getRotation().name() + delim + config.saveToString());
} else {
entityStmt.setInt(4, 0);
entityStmt.setString(6, "");
}
entityStmt.addBatch();
}
}
entityStmt.executeBatch();
entityStmt.close();
PreparedStatement dataStmt = databaseConnection.prepareStatement("INSERT INTO blocks (x, y, z, type, data, metadata) VALUES (?, ?, ?, ?, ?, ?)");
databaseConnection.setAutoCommit(false);
final int batchSize = 10000;
for (int i = 0, x = volume.getMinX(); i < volume.getSizeX(); i++, x++) {
for (int j = 0, y = volume.getMinY(); j < volume.getSizeY(); j++, y++) {
for (int k = 0, z = volume.getMinZ(); k < volume.getSizeZ(); k++, z++) {
// Make sure we are using zone volume-relative coords
final Block block = volume.getWorld().getBlockAt(x, y, z);
if (block.getType() == Material.AIR) {
continue; // Do not save air blocks to the file anymore.
}
final BlockState state = block.getState();
dataStmt.setInt(1, block.getX() - volume.getCornerOne().getBlockX());
dataStmt.setInt(2, block.getY() - volume.getCornerOne().getBlockY());
dataStmt.setInt(3, block.getZ() - volume.getCornerOne().getBlockZ());
dataStmt.setString(4, block.getType().name());
dataStmt.setShort(5, state.getData().toItemStack(1).getDurability());
if (state instanceof Sign) {
final String signText = StringUtils.join(((Sign) block.getState()).getLines(), "\n");
dataStmt.setString(6, signText);
} else if (state instanceof InventoryHolder) {
List<ItemStack> items = Arrays.asList(((InventoryHolder) block.getState()).getInventory().getContents());
YamlConfiguration config = new YamlConfiguration();
// Serialize to config, then store config in database
config.set("items", items);
dataStmt.setString(6, config.saveToString());
} else if (state instanceof NoteBlock) {
Note note = ((NoteBlock) block.getState()).getNote();
dataStmt.setString(6, note.getTone().toString() + '\n' + note.getOctave() + '\n' + note.isSharped());
} else if (state instanceof Jukebox) {
dataStmt.setString(6, ((Jukebox) block.getState()).getPlaying().toString());
} else if (state instanceof Skull) {
// TODO upgrade to store owning player by UUID
dataStmt.setString(6, String.format("%s\n%s\n%s",
((Skull) block.getState()).hasOwner() ? ((Skull) block.getState()).getOwningPlayer().getName() : "",
// TODO remove deprecation when Spigot updates their docs about the replacement
((Skull) block.getState()).getSkullType().toString(),
((Skull) block.getState()).getRotation().toString()));
} else if (state instanceof CommandBlock) {
dataStmt.setString(6, ((CommandBlock) block.getState()).getName()
+ "\n" + ((CommandBlock) block.getState()).getCommand());
} else if (state instanceof CreatureSpawner) {
dataStmt.setString(6, ((CreatureSpawner) block.getState()).getSpawnedType().toString());
} else {
dataStmt.setString(6, "");
}
dataStmt.addBatch();
if (++changed % batchSize == 0) {
dataStmt.executeBatch();
if ((System.currentTimeMillis() - startTime) >= 5000L) {
String seconds = new DecimalFormat("#0.00").format((double) (System.currentTimeMillis() - startTime) / 1000.0D);
War.war.getLogger().log(Level.FINE, "Still saving warzone {0} , {1} seconds elapsed.", new Object[] {zoneName, seconds});
}
}
}
}
}
dataStmt.executeBatch(); // insert remaining records
databaseConnection.commit();
dataStmt.close();
databaseConnection.setAutoCommit(true);
Connection databaseConnection = getZoneConnection(volume, zoneName);
int changed = 0;
saveCorners(databaseConnection, volume, "");
saveBlocks(databaseConnection, volume, "");
saveEntities(databaseConnection, volume);
databaseConnection.close();
String seconds = new DecimalFormat("#0.00").format((double) (System.currentTimeMillis() - startTime) / 1000.0D);
War.war.getLogger().log(Level.INFO, "Saved warzone {0} in {1} seconds.", new Object[] {zoneName, seconds});
return changed;
}
static Location rebase(final Location base, final Location exact) {
return new Location(base.getWorld(),
exact.getBlockX() - base.getBlockX(),
exact.getBlockY() - base.getBlockY(),
exact.getBlockZ() - base.getBlockZ());
}
private static void updateFromVersionOneToTwo(String zoneName, Connection connection) throws SQLException {
War.war.log("Migrating warzone " + zoneName + " from v1 to v2 of schema...", Level.INFO);
// We want to do this in a transaction
connection.setAutoCommit(false);
Statement stmt = connection.createStatement();
// We want to combine all extra columns into a single metadata BLOB one. To delete some columns we need to drop the table so we use a temp one.
stmt.executeUpdate("CREATE TEMPORARY TABLE blocks_backup(x BIGINT, y BIGINT, z BIGINT, type TEXT, data SMALLINT, sign TEXT, container BLOB, note INT, record TEXT, skull TEXT, command TEXT, mobid TEXT)");
stmt.executeUpdate("INSERT INTO blocks_backup SELECT x, y, z, type, data, sign, container, note, record, skull, command, mobid FROM blocks");
stmt.executeUpdate("DROP TABLE blocks");
stmt.executeUpdate("CREATE TABLE blocks(x BIGINT, y BIGINT, z BIGINT, type TEXT, data SMALLINT, metadata BLOB)");
stmt.executeUpdate("INSERT INTO blocks SELECT x, y, z, type, data, coalesce(container, sign, note, record, skull, command, mobid) FROM blocks_backup");
stmt.executeUpdate("DROP TABLE blocks_backup");
stmt.executeUpdate("PRAGMA user_version = 2");
stmt.close();
// Commit transaction
connection.commit();
connection.setAutoCommit(true);
War.war.log("Warzone " + zoneName + " converted! Compacting database...", Level.INFO);
// Pack the database otherwise we won't get any space savings.
// This rebuilds the database completely and takes some time.
stmt = connection.createStatement();
stmt.execute("VACUUM");
stmt.close();
War.war.log("Migration of warzone " + zoneName + " to v2 of schema finished.", Level.INFO);
}
}

View File

@ -1,17 +1,5 @@
package com.tommytony.war.structure;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockState;
import org.bukkit.material.Sign;
import com.tommytony.war.Team;
import com.tommytony.war.War;
import com.tommytony.war.Warzone;
@ -19,6 +7,17 @@ import com.tommytony.war.config.TeamConfig;
import com.tommytony.war.config.WarzoneConfig;
import com.tommytony.war.utility.Direction;
import com.tommytony.war.volume.Volume;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockState;
import org.bukkit.material.Sign;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
/**
*
@ -236,8 +235,7 @@ public class WarHub {
/**
* Resets the sign of the given warzone
*
* @param Warzone
* zone
* @param zone
*/
public void resetZoneSign(Warzone zone) {
BlockFace left;

View File

@ -1,13 +1,5 @@
package com.tommytony.war.volume;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.logging.Level;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.block.Block;
import com.tommytony.war.Team;
import com.tommytony.war.War;
import com.tommytony.war.Warzone;
@ -15,6 +7,13 @@ import com.tommytony.war.config.WarConfig;
import com.tommytony.war.job.PartialZoneResetJob;
import com.tommytony.war.mapper.ZoneVolumeMapper;
import com.tommytony.war.structure.Monument;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.block.Block;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.logging.Level;
/**
*
@ -36,7 +35,7 @@ public class ZoneVolume extends Volume {
// Save blocks directly to disk (i.e. don't put everything in memory)
int saved = 0;
try {
saved = ZoneVolumeMapper.save(this, this.zone.getName());
saved = ZoneVolumeMapper.saveZoneBlocksAndEntities(this, this.zone.getName());
} catch (SQLException ex) {
War.war.log("Failed to save warzone " + zone.getName() + ": " + ex.getMessage(), Level.WARNING);
ex.printStackTrace();
@ -51,8 +50,8 @@ public class ZoneVolume extends Volume {
}
public void loadCorners() throws SQLException {
Connection conn = ZoneVolumeMapper.getZoneConnection(this, this.zone.getName(), this.getWorld());
ZoneVolumeMapper.load(conn, this, this.getWorld(), true, 0, 0, null);
Connection conn = ZoneVolumeMapper.getZoneConnection(this, this.zone.getName());
ZoneVolumeMapper.loadCorners(conn, this, this.getWorld(), "");
this.isSaved = true;
}
@ -60,8 +59,8 @@ public class ZoneVolume extends Volume {
public void resetBlocks() {
// Load blocks directly from disk and onto the map (i.e. no more in-memory warzone blocks)
try {
Connection conn = ZoneVolumeMapper.getZoneConnection(this, this.zone.getName(), this.getWorld());
ZoneVolumeMapper.load(conn, this, this.getWorld(), false, 0, Integer.MAX_VALUE, null);
Connection conn = ZoneVolumeMapper.getZoneConnection(this, this.zone.getName());
ZoneVolumeMapper.reloadZoneBlocks(conn, this, 0, Integer.MAX_VALUE, null);
} catch (SQLException ex) {
War.war.log("Failed to load warzone " + zone.getName() + ": " + ex.getMessage(), Level.WARNING);
ex.printStackTrace();
@ -82,7 +81,7 @@ public class ZoneVolume extends Volume {
* @throws SQLException
*/
public int resetSection(Connection conn, int start, int total, boolean[][][] changes) throws SQLException {
return ZoneVolumeMapper.load(conn, this, this.getWorld(), false, start, total, changes);
return ZoneVolumeMapper.reloadZoneBlocks(conn, this, start, total, changes);
}
/**