Merge pull request #718 from taoneill/nimitz-v2

Optimize nimitz file store

Tested on bukkit server 1.7.2 with a warzone originally created with war 1.7.4, therefore upgrade works properly.
This commit is contained in:
Connor 2013-12-26 17:11:11 -08:00
commit 59779eb0e5
13 changed files with 495 additions and 204 deletions

View File

@ -7,7 +7,6 @@ import java.util.*;
import java.util.logging.FileHandler;
import java.util.logging.Formatter;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.bukkit.ChatColor;
import org.bukkit.Location;
@ -116,16 +115,16 @@ public class War extends JavaPlugin {
}
/**
* @see JavaPlugin.onEnable()
* @see War.loadWar()
* @see JavaPlugin#onEnable()
* @see War#loadWar()
*/
public void onEnable() {
this.loadWar();
}
/**
* @see JavaPlugin.onDisable()
* @see War.unloadWar()
* @see JavaPlugin#onDisable()
* @see War#unloadWar()
*/
public void onDisable() {
this.unloadWar();
@ -207,6 +206,7 @@ public class War extends JavaPlugin {
warzoneDefaultConfig.put(WarzoneConfig.XPKILLMETER, false);
warzoneDefaultConfig.put(WarzoneConfig.SOUPHEALING, false);
warzoneDefaultConfig.put(WarzoneConfig.ALLOWENDER, true);
warzoneDefaultConfig.put(WarzoneConfig.RESETBLOCKS, true);
teamDefaultConfig.put(TeamConfig.FLAGMUSTBEHOME, true);
teamDefaultConfig.put(TeamConfig.FLAGPOINTSONLY, false);
@ -342,7 +342,7 @@ public class War extends JavaPlugin {
}
/**
* @see JavaPlugin.onCommand()
* @see org.bukkit.command.CommandExecutor#onCommand(org.bukkit.command.CommandSender, org.bukkit.command.Command, String, String[])
*/
public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args) {
return this.commandHandler.handle(sender, cmd, args);
@ -379,11 +379,6 @@ public class War extends JavaPlugin {
}
}
@Deprecated
public ItemStack copyStack(ItemStack originalStack) {
return originalStack.clone();
}
public void safelyEnchant(ItemStack target, Enchantment enchantment, int level) {
if (level > enchantment.getMaxLevel()) {
target.addUnsafeEnchantment(enchantment, level);
@ -983,10 +978,8 @@ public class War extends JavaPlugin {
/**
* Colors the teams and examples in messages
*
* @param String
* str message-string
* @param String
* msgColor current message-color
* @param str message-string
* @param msgColor current message-color
* @return String Message with colored teams
*/
private String colorKnownTokens(String str, ChatColor msgColor) {
@ -1014,18 +1007,11 @@ public class War extends JavaPlugin {
/**
* Logs a specified message with a specified level
*
* @param String
* str message to log
* @param Level
* lvl level to use
* @param str message to log
* @param lvl level to use
*/
public void log(String str, Level lvl) {
// Log to Bukkit console
this.getLogger().log(lvl, str);
// if (this.warLogger != null) {
// this.warLogger.log(lvl, str);
// }
}
// the only way to find a zone that has only one corner

View File

@ -1,5 +1,9 @@
package com.tommytony.war;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
@ -10,6 +14,8 @@ import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import com.tommytony.war.mapper.VolumeMapper;
import com.tommytony.war.mapper.ZoneVolumeMapper;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.bukkit.Bukkit;
@ -1028,7 +1034,11 @@ public class Warzone {
} else {
// A new battle starts. Reset the zone but not the teams.
this.broadcast("zone.battle.reset");
this.reinitialize();
if (this.getWarzoneConfig().getBoolean(WarzoneConfig.RESETBLOCKS)) {
this.reinitialize();
} else {
this.initializeZone();
}
}
}
} else {
@ -1146,9 +1156,10 @@ public class Warzone {
team.resetPoints();
team.setRemainingLives(team.getTeamConfig().resolveInt(TeamConfig.LIFEPOOL));
}
this.getVolume().resetBlocksAsJob();
this.initializeZoneAsJob();
War.war.log("Last player left warzone " + this.getName() + ". Warzone blocks resetting automatically...", Level.INFO);
if (!this.isReinitializing()) {
this.reinitialize();
War.war.getLogger().log(Level.INFO, "Last player left warzone {0}. Warzone blocks resetting automatically...", new Object[] {this.getName()});
}
}
WarPlayerLeaveEvent event1 = new WarPlayerLeaveEvent(player.getName());
@ -1342,7 +1353,11 @@ public class Warzone {
t.getPlayers().clear(); // empty the team
t.resetSign();
}
this.reinitialize();
if (this.getWarzoneConfig().getBoolean(WarzoneConfig.RESETBLOCKS)) {
this.reinitialize();
} else {
this.initializeZone();
}
}
public void rewardPlayer(Player player, Map<Integer, ItemStack> reward) {
@ -1827,6 +1842,39 @@ public class Warzone {
return this.getTeleport();
}
public Volume loadStructure(String volName, World world) throws SQLException {
return loadStructure(volName, world, ZoneVolumeMapper.getZoneConnection(volume, name, world));
}
public Volume loadStructure(String volName, Connection zoneConnection) throws SQLException {
return loadStructure(volName, world, zoneConnection);
}
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;
}
private boolean containsTable(String table, Connection connection) throws SQLException {
PreparedStatement stmt = connection.prepareStatement("SELECT COUNT(*) AS ct FROM sqlite_master WHERE type = ? AND name = ?");
stmt.setString(1, "table");
stmt.setString(2, table);
ResultSet resultSet = stmt.executeQuery();
try {
return resultSet.next() && resultSet.getInt("ct") > 0;
} finally {
resultSet.close();
stmt.close();
}
}
/**
* Check if a player has stolen from a warzone flag, bomb, or cake.
* @param suspect Player to check.

View File

@ -53,6 +53,8 @@ public class JoinCommand extends AbstractWarCommand {
}
if (zone.getWarzoneConfig().getBoolean(WarzoneConfig.DISABLED)) {
this.badMsg("join.disabled");
} else if (zone.isReinitializing()) {
this.badMsg("join.disabled");
} else if (zone.getWarzoneConfig().getBoolean(WarzoneConfig.AUTOASSIGN)) {
this.badMsg("join.aarequired");
} else if (!zone.getWarzoneConfig().getBoolean(WarzoneConfig.JOINMIDBATTLE) && zone.isEnoughPlayers()) {

View File

@ -78,7 +78,7 @@ public class RenameZoneCommand extends AbstractZoneMakerCommand {
// Load new warzone
War.war.log("Loading zone " + newName + "...", Level.INFO);
Warzone newZone = WarzoneYmlMapper.load(newName, false);
Warzone newZone = WarzoneYmlMapper.load(newName);
War.war.getWarzones().add(newZone);
try {
newZone.getVolume().loadCorners();

View File

@ -25,7 +25,8 @@ public enum WarzoneConfig {
SCOREBOARD (ScoreboardType.class),
XPKILLMETER (Boolean.class),
SOUPHEALING (Boolean.class),
ALLOWENDER (Boolean.class);
ALLOWENDER (Boolean.class),
RESETBLOCKS (Boolean.class);
private final Class<?> configType;

View File

@ -20,6 +20,7 @@ import org.bukkit.event.block.Action;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.event.player.*;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import org.getspout.spoutapi.SpoutManager;
import org.getspout.spoutapi.player.SpoutPlayer;
@ -47,7 +48,6 @@ import java.util.logging.Level;
/**
* @author tommytony, Tim Düsterhus
* @package bukkit.tommytony.war
*/
public class WarPlayerListener implements Listener {
private java.util.Random random = new java.util.Random();
@ -56,7 +56,7 @@ public class WarPlayerListener implements Listener {
/**
* Correctly removes quitting players from warzones
*
* @see PlayerListener.onPlayerQuit()
* @see PlayerQuitEvent
*/
@EventHandler
public void onPlayerQuit(final PlayerQuitEvent event) {
@ -155,7 +155,6 @@ public class WarPlayerListener implements Listener {
player.getInventory().containsAtLeast(team.getKind().getBlockHead(), MINIMUM_TEAM_BLOCKS)) {
// Can't pick up a second precious block
event.setCancelled(true);
return;
}
}
}
@ -236,20 +235,26 @@ public class WarPlayerListener implements Listener {
War.war.badMsg(player, "use.ender");
}
Team team = Team.getTeamByPlayerName(player.getName());
if (team != null && event.getAction() == Action.RIGHT_CLICK_BLOCK && event.getClickedBlock().getType() == Material.ENCHANTMENT_TABLE && team.getTeamConfig().resolveBoolean(TeamConfig.XPKILLMETER)) {
if (zone != null && team != null && event.getAction() == Action.RIGHT_CLICK_BLOCK && event.getClickedBlock().getType() == Material.ENCHANTMENT_TABLE && team.getTeamConfig().resolveBoolean(TeamConfig.XPKILLMETER)) {
event.setCancelled(true);
War.war.badMsg(player, "use.enchant");
if (zone.getAuthors().contains(player.getName())) {
War.war.badMsg(player, "use.xpkillmeter");
}
}
if (team != null && event.getAction() == Action.RIGHT_CLICK_BLOCK && event.getClickedBlock().getType() == Material.ANVIL && team.getTeamConfig().resolveBoolean(TeamConfig.XPKILLMETER)) {
if (zone != null && team != null && event.getAction() == Action.RIGHT_CLICK_BLOCK && event.getClickedBlock().getType() == Material.ANVIL && team.getTeamConfig().resolveBoolean(TeamConfig.XPKILLMETER)) {
event.setCancelled(true);
War.war.badMsg(player, "use.anvil");
if (zone.getAuthors().contains(player.getName())) {
War.war.badMsg(player, "use.xpkillmeter");
}
}
if (zone != null && team != null && event.getAction() == Action.RIGHT_CLICK_BLOCK
&& event.getClickedBlock().getState() instanceof InventoryHolder
&& zone.isFlagThief(player.getName())) {
event.setCancelled(true);
War.war.badMsg(player, "drop.flag.disabled");
}
}
if (event.getAction() == Action.RIGHT_CLICK_BLOCK
@ -337,7 +342,7 @@ public class WarPlayerListener implements Listener {
if (locLobby != null && currentTeam == null && locLobby.isInAnyGate(playerLoc)) {
Warzone zone = locLobby.getZone();
Team locTeamGate = locLobby.getTeamGate(playerLoc);
if (zone.getWarzoneConfig().getBoolean(WarzoneConfig.DISABLED)) {
if (zone.getWarzoneConfig().getBoolean(WarzoneConfig.DISABLED) || zone.isReinitializing()) {
War.war.badMsg(player, "join.disabled");
event.setTo(zone.getTeleport());
} else if (!zone.getWarzoneConfig().getBoolean(WarzoneConfig.JOINMIDBATTLE) && zone.isEnoughPlayers()) {
@ -376,7 +381,7 @@ public class WarPlayerListener implements Listener {
if (zone != null && zone.getTeleport() != null) {
if (zone.getWarzoneConfig().getBoolean(WarzoneConfig.AUTOJOIN)
&& zone.getTeams().size() >= 1 && currentTeam == null) {
if (zone.getWarzoneConfig().getBoolean(WarzoneConfig.DISABLED)) {
if (zone.getWarzoneConfig().getBoolean(WarzoneConfig.DISABLED) || zone.isReinitializing()) {
War.war.badMsg(player, "join.disabled");
event.setTo(hub.getLocation());
} else if (!zone.getWarzoneConfig().getBoolean(WarzoneConfig.JOINMIDBATTLE) && zone.isEnoughPlayers()) {
@ -787,7 +792,6 @@ public class WarPlayerListener implements Listener {
Warzone zone = Warzone.getZoneByLocation(playerLoc);
event.setTo(zone.getTeleport());
War.war.badMsg(player, "zone.noteamnotice");
return;
}
}
@ -803,13 +807,9 @@ public class WarPlayerListener implements Listener {
List<Loadout> loadouts = new ArrayList<Loadout>(playerTeam.getInventories().resolveNewLoadouts());
for (Iterator<Loadout> it = loadouts.iterator(); it.hasNext();) {
Loadout ldt = it.next();
if ("first".equals(ldt.getName())) {
if (ldt.getName().equals("first") ||
(ldt.requiresPermission() && !event.getPlayer().hasPermission(ldt.getPermission()))) {
it.remove();
continue;
}
if (ldt.requiresPermission() && !event.getPlayer().hasPermission(ldt.getPermission())) {
it.remove();
continue;
}
}
int currentIndex = (selection.getSelectedIndex() + 1) % loadouts.size();

View File

@ -1,12 +1,16 @@
package com.tommytony.war.job;
import java.sql.Connection;
import java.sql.SQLException;
import java.text.DecimalFormat;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.text.NumberFormat;
import java.util.*;
import java.util.logging.Level;
import com.tommytony.war.mapper.ZoneVolumeMapper;
import org.bukkit.Material;
import org.bukkit.block.BlockState;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
@ -29,55 +33,82 @@ public class PartialZoneResetJob extends BukkitRunnable implements Cloneable {
private int completed = 0;
private final long startTime = System.currentTimeMillis();
private long messageCounter = System.currentTimeMillis();
boolean[][][] changes;
public static final long MESSAGE_INTERVAL = 7500;
// Ticks between job runs
public static final int JOB_INTERVAL = 1;
private int totalChanges = 0;
private NumberFormat formatter = new DecimalFormat("#0.00");
private Connection conn;
/**
* Reset a warzone's blocks at a certain speed.
*
* @param volume
* @param zone
* Warzone to reset.
* @param speed
* Blocks to modify per #INTERVAL.
*/
public PartialZoneResetJob(Warzone zone, int speed) {
public PartialZoneResetJob(Warzone zone, int speed) throws SQLException {
this.zone = zone;
this.volume = zone.getVolume();
this.speed = speed;
this.total = volume.size();
this.total = volume.getTotalSavedBlocks();
this.changes = new boolean[volume.getSizeX()][volume.getSizeY()][volume.getSizeZ()];
}
@Override
public void run() {
try {
volume.resetSection(completed, speed);
completed += speed;
if (completed < total) {
if (System.currentTimeMillis() - messageCounter > MESSAGE_INTERVAL) {
messageCounter = System.currentTimeMillis();
int percent = (int) (((double) completed / (double) total) * 100);
long seconds = (System.currentTimeMillis() - startTime) / 1000;
String message = MessageFormat.format(
War.war.getString("zone.battle.resetprogress"),
percent, seconds);
this.sendMessageToAllWarzonePlayers(message);
if (conn == null || conn.isClosed()) {
conn = ZoneVolumeMapper.getZoneConnection(volume, zone.getName(), volume.getWorld());
}
if (completed >= total) {
int airChanges = 0;
int minX = volume.getMinX(), minY = volume.getMinY(), minZ = volume.getMinZ();
air: for (int x = volume.getMinX(); x <= volume.getMaxX(); x++) {
for (int y = volume.getMinY(); y <= volume.getMaxY(); y++) {
for (int z = volume.getMinZ(); z <= volume.getMaxZ(); z++) {
int xi = x - minX, yi = y - minY, zi = z - minZ;
if (!changes[xi][yi][zi]) {
changes[xi][yi][zi] = true;
airChanges++;
BlockState state = volume.getWorld().getBlockAt(x, y, z).getState();
if (state.getType() != Material.AIR) {
state.setType(Material.AIR);
state.update(true, false);
}
if (airChanges >= speed) {
this.displayStatusMessage();
break air;
}
}
}
}
}
totalChanges += airChanges;
if (this.doneAir()) {
String secondsAsText = formatter.format(((double)(System.currentTimeMillis() - startTime)) / 1000);
String message = MessageFormat.format(
War.war.getString("zone.battle.resetcomplete"), secondsAsText);
this.sendMessageToAllWarzonePlayers(message);
PartialZoneResetJob.setSenderToNotify(zone, null); // stop notifying for this zone
zone.initializeZone();
War.war.getLogger().log(Level.INFO, "Finished reset cycle for warzone {0} (took {1} seconds)",
new Object[]{volume.getName(), secondsAsText});
conn.close();
} else {
War.war.getServer().getScheduler().runTaskLater(War.war, this.clone(), JOB_INTERVAL);
}
War.war.getServer().getScheduler()
.runTaskLater(War.war, this.clone(), JOB_INTERVAL);
} else {
long seconds = (System.currentTimeMillis() - startTime) / 1000;
String message = MessageFormat.format(
War.war.getString("zone.battle.resetcomplete"), seconds);
this.sendMessageToAllWarzonePlayers(message);
PartialZoneResetJob.setSenderToNotify(zone, null); // stop notifying for this zone
zone.initializeZone();
War.war.getLogger().info(
"Finished reset cycle for warzone " + volume.getName() + " (took " + seconds + " seconds)");
int solidChanges = volume.resetSection(conn, completed, speed, changes);
completed += solidChanges;
totalChanges += solidChanges;
this.displayStatusMessage();
War.war.getServer().getScheduler().runTaskLater(War.war, this.clone(), JOB_INTERVAL);
}
} catch (SQLException e) {
War.war.getLogger().log(Level.WARNING,
"Failed to load zone during reset loop", e);
War.war.getLogger().log(Level.WARNING, "Failed to load zone during reset loop", e);
}
}
@ -96,6 +127,18 @@ public class PartialZoneResetJob extends BukkitRunnable implements Cloneable {
}
}
private void displayStatusMessage() {
if (System.currentTimeMillis() - messageCounter > MESSAGE_INTERVAL) {
String secondsAsText = formatter.format(((double)(System.currentTimeMillis() - startTime)) / 1000);
messageCounter = System.currentTimeMillis();
int percent = (int) (((double) totalChanges / (double) volume.size()) * 100);
String message = MessageFormat.format(
War.war.getString("zone.battle.resetprogress"),
percent, secondsAsText);
this.sendMessageToAllWarzonePlayers(message);
}
}
@Override
protected PartialZoneResetJob clone() {
try {
@ -108,4 +151,19 @@ public class PartialZoneResetJob extends BukkitRunnable implements Cloneable {
public static void setSenderToNotify(Warzone warzone, CommandSender sender) {
PartialZoneResetJob.sendersToNotify.put(warzone, sender);
}
private boolean doneAir() {
int minX = volume.getMinX(), minY = volume.getMinY(), minZ = volume.getMinZ();
for (int x = volume.getMinX(); x <= volume.getMaxX(); x++) {
for (int y = volume.getMinY(); y <= volume.getMaxY(); y++) {
for (int z = volume.getMinZ(); z <= volume.getMaxZ(); z++) {
int xi = x - minX, yi = y - minY, zi = z - minZ;
if (!changes[xi][yi][zi]) {
return false;
}
}
}
}
return true;
}
}

View File

@ -12,11 +12,9 @@ import com.tommytony.war.mapper.WarzoneYmlMapper;
public class RestoreYmlWarzonesJob implements Runnable {
private final List<String> warzones;
private final boolean newWarInstall;
public RestoreYmlWarzonesJob(List<String> warzones, boolean newWarInstall) {
public RestoreYmlWarzonesJob(List<String> warzones) {
this.warzones = warzones;
this.newWarInstall = newWarInstall;
}
public void run() {
@ -25,7 +23,7 @@ public class RestoreYmlWarzonesJob implements Runnable {
for (String warzoneName : this.warzones) {
if (warzoneName != null && !warzoneName.equals("")) {
War.war.log("Loading zone " + warzoneName + "...", Level.INFO);
Warzone zone = WarzoneYmlMapper.load(warzoneName, !this.newWarInstall);
Warzone zone = WarzoneYmlMapper.load(warzoneName);
if (zone != null) { // could have failed, would've been logged already
War.war.getWarzones().add(zone);
try {

View File

@ -48,7 +48,7 @@ public class WarYmlMapper {
// warzones
List<String> warzones = warRootSection.getStringList("war.info.warzones");
RestoreYmlWarzonesJob restoreWarzones = new RestoreYmlWarzonesJob(warzones, newWar); // during conversion, this should execute just after the RestoreTxtWarzonesJob
RestoreYmlWarzonesJob restoreWarzones = new RestoreYmlWarzonesJob(warzones); // during conversion, this should execute just after the RestoreTxtWarzonesJob
if (War.war.getServer().getScheduler().scheduleSyncDelayedTask(War.war, restoreWarzones) == -1) {
War.war.log("Failed to schedule warzone-restore job. No warzone was loaded.", Level.WARNING);
}

View File

@ -2,6 +2,7 @@ package com.tommytony.war.mapper;
import java.io.File;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
@ -32,7 +33,7 @@ import com.tommytony.war.volume.ZoneVolume;
public class WarzoneYmlMapper {
@SuppressWarnings("deprecation")
public static Warzone load(String name, boolean createNewVolume) {
public static Warzone load(String name) { // removed createNewVolume, as it did nothing
File warzoneTxtFile = new File(War.war.getDataFolder().getPath() + "/warzone-" + name + ".txt");
File warzoneYmlFile = new File(War.war.getDataFolder().getPath() + "/warzone-" + name + ".yml");
@ -275,16 +276,16 @@ public class WarzoneYmlMapper {
}
}
}
if (createNewVolume) {
ZoneVolume zoneVolume = new ZoneVolume(warzone.getName(), world, warzone);
warzone.setVolume(zoneVolume);
Connection connection = null;
try {
connection = ZoneVolumeMapper.getZoneConnection(warzone.getVolume(), warzone.getName(), warzone.getWorld());
} catch (SQLException e) {
War.war.getLogger().log(Level.WARNING, "Failed to load warzone structures volume", e);
}
// monument blocks
for (Monument monument : warzone.getMonuments()) {
try {
monument.setVolume(VolumeMapper.loadVolume(monument.getName(), warzone.getName(), world));
monument.setVolume(warzone.loadStructure(monument.getName(), connection));
} catch (SQLException e) {
War.war.getLogger().log(Level.WARNING, "Failed to load warzone structures volume", e);
}
@ -293,7 +294,7 @@ public class WarzoneYmlMapper {
// bomb blocks
for (Bomb bomb : warzone.getBombs()) {
try {
bomb.setVolume(VolumeMapper.loadVolume("bomb-" + bomb.getName(), warzone.getName(), world));
bomb.setVolume(warzone.loadStructure("bomb-" + bomb.getName(), connection));
} catch (SQLException e) {
War.war.getLogger().log(Level.WARNING, "Failed to load warzone structures volume", e);
}
@ -302,7 +303,7 @@ public class WarzoneYmlMapper {
// cake blocks
for (Cake cake : warzone.getCakes()) {
try {
cake.setVolume(VolumeMapper.loadVolume("cake-" + cake.getName(), warzone.getName(), world));
cake.setVolume(warzone.loadStructure("cake-" + cake.getName(), connection));
} catch (SQLException e) {
War.war.getLogger().log(Level.WARNING, "Failed to load warzone structures volume", e);
}
@ -312,14 +313,14 @@ public class WarzoneYmlMapper {
for (Team team : warzone.getTeams()) {
for (Location teamSpawn : team.getTeamSpawns()) {
try {
team.setSpawnVolume(teamSpawn, VolumeMapper.loadVolume(team.getName() + team.getTeamSpawns().indexOf(teamSpawn), warzone.getName(), world));
team.setSpawnVolume(teamSpawn, warzone.loadStructure(team.getName() + team.getTeamSpawns().indexOf(teamSpawn), connection));
} catch (SQLException e) {
War.war.getLogger().log(Level.WARNING, "Failed to load warzone structures volume", e);
}
}
if (team.getTeamFlag() != null) {
try {
team.setFlagVolume(VolumeMapper.loadVolume(team.getName() + "flag", warzone.getName(), world));
team.setFlagVolume(warzone.loadStructure(team.getName() + "flag", connection));
} catch (SQLException e) {
War.war.getLogger().log(Level.WARNING, "Failed to load warzone structures volume", e);
}
@ -399,7 +400,7 @@ public class WarzoneYmlMapper {
// create the lobby
Volume lobbyVolume = null;
try {
lobbyVolume = VolumeMapper.loadVolume("lobby", warzone.getName(), lobbyWorld);
lobbyVolume = warzone.loadStructure("lobby", lobbyWorld, connection);
} catch (SQLException e) {
War.war.getLogger().log(Level.WARNING, "Failed to load warzone lobby", e);
}
@ -443,6 +444,10 @@ public class WarzoneYmlMapper {
(short) floorMaterialSection.getInt("data")));
}
}
try {
connection.close();
} catch (SQLException ignored) {
}
return warzone;
}
@ -649,11 +654,16 @@ public class WarzoneYmlMapper {
flagSection.set("yaw", toIntYaw(teamFlag.getYaw()));
}
}
Connection connection = null;
try {
connection = ZoneVolumeMapper.getZoneConnection(warzone.getVolume(), warzone.getName(), warzone.getWorld());
} catch (SQLException e) {
War.war.getLogger().log(Level.WARNING, "Failed to load warzone structures volume", e);
}
// monument blocks
for (Monument monument : warzone.getMonuments()) {
try {
VolumeMapper.save(monument.getVolume(), warzone.getName());
ZoneVolumeMapper.saveStructure(monument.getVolume(), connection);
} catch (SQLException e) {
War.war.getLogger().log(Level.WARNING, "Failed to save warzone structures volume", e);
}
@ -662,7 +672,7 @@ public class WarzoneYmlMapper {
// bomb blocks
for (Bomb bomb : warzone.getBombs()) {
try {
VolumeMapper.save(bomb.getVolume(), warzone.getName());
ZoneVolumeMapper.saveStructure(bomb.getVolume(), connection);
} catch (SQLException e) {
War.war.getLogger().log(Level.WARNING, "Failed to save warzone structures volume", e);
}
@ -671,7 +681,7 @@ public class WarzoneYmlMapper {
// cake blocks
for (Cake cake : warzone.getCakes()) {
try {
VolumeMapper.save(cake.getVolume(), warzone.getName());
ZoneVolumeMapper.saveStructure(cake.getVolume(), connection);
} catch (SQLException e) {
War.war.getLogger().log(Level.WARNING, "Failed to save warzone structures volume", e);
}
@ -681,14 +691,14 @@ public class WarzoneYmlMapper {
for (Team team : teams) {
for (Volume volume : team.getSpawnVolumes().values()) {
try {
VolumeMapper.save(volume, warzone.getName());
ZoneVolumeMapper.saveStructure(volume, connection);
} catch (SQLException e) {
War.war.getLogger().log(Level.WARNING, "Failed to save warzone structures volume", e);
}
}
if (team.getFlagVolume() != null) {
try {
VolumeMapper.save(team.getFlagVolume(), warzone.getName());
ZoneVolumeMapper.saveStructure(team.getFlagVolume(), connection);
} catch (SQLException e) {
War.war.getLogger().log(Level.WARNING, "Failed to save warzone structures volume", e);
}
@ -697,11 +707,15 @@ public class WarzoneYmlMapper {
if (warzone.getLobby() != null) {
try {
VolumeMapper.save(warzone.getLobby().getVolume(), warzone.getName());
ZoneVolumeMapper.saveStructure(warzone.getLobby().getVolume(), connection);
} catch (SQLException e) {
War.war.getLogger().log(Level.WARNING, "Failed to save warzone structures volume", e);
}
}
try {
connection.close();
} catch (SQLException ignored) {
}
// Save to disk
try {

View File

@ -7,7 +7,7 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.text.DecimalFormat;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
@ -46,29 +46,23 @@ import com.tommytony.war.volume.ZoneVolume;
*/
public class ZoneVolumeMapper {
public static final int DATABASE_VERSION = 1;
public static final int DATABASE_VERSION = 2;
/**
* Loads the given volume
*
* @param ZoneVolume volume Volume to load
* @param String zoneName Zone to load the volume from
* @param World world The world the zone is located
* @param boolean onlyLoadCorners Should only the corners be loaded
* @param start Starting position to load blocks at
* @param total Amount of blocks to read
* @return integer Changed blocks
* @throws SQLException Error communicating with SQLite3 database
* 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 int load(ZoneVolume volume, String zoneName, World world, boolean onlyLoadCorners, int start, int total) throws SQLException {
int changed = 0;
public static Connection getZoneConnection(ZoneVolume volume, String zoneName, World world) throws SQLException {
File databaseFile = new File(War.war.getDataFolder(), String.format("/dat/warzone-%s/volume-%s.sl3", zoneName, volume.getName()));
if (!databaseFile.exists()) {
// Convert warzone to nimitz file format.
changed = PreNimitzZoneVolumeMapper.load(volume, zoneName, world, onlyLoadCorners);
PreNimitzZoneVolumeMapper.load(volume, zoneName, world, false);
ZoneVolumeMapper.save(volume, zoneName);
War.war.log("Warzone " + zoneName + " converted to nimitz format!", Level.INFO);
return changed;
}
Connection databaseConnection = DriverManager.getConnection("jdbc:sqlite:" + databaseFile.getPath());
Statement stmt = databaseConnection.createStatement();
@ -76,17 +70,46 @@ public class ZoneVolumeMapper {
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();
}
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);
}
}
return databaseConnection;
}
/**
* 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"));
@ -97,69 +120,190 @@ public class ZoneVolumeMapper {
volume.setCornerTwo(corner2);
if (onlyLoadCorners) {
stmt.close();
databaseConnection.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");
BlockState modify = corner1.getRelative(x, y, z).getState();
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"));
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 (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("sign").split("\n");
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);
}
if (modify instanceof InventoryHolder && query.getString("container") != null) {
// Containers
if (modify instanceof InventoryHolder) {
YamlConfiguration config = new YamlConfiguration();
config.loadFromString(query.getString("container"));
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("note").split("\n");
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("record")));
((Jukebox) modify).setPlaying(Material.valueOf(query.getString("metadata")));
modify.update(true, false);
}
if (modify instanceof Skull && query.getString("skull") != null) {
String[] opts = query.getString("skull").split("\n");
// Skulls
if (modify instanceof Skull) {
String[] opts = query.getString("metadata").split("\n");
((Skull) modify).setOwner(opts[0]);
((Skull) modify).setSkullType(SkullType.valueOf(opts[1]));
((Skull) modify).setRotation(BlockFace.valueOf(opts[2]));
modify.update(true, false);
}
if (modify instanceof CommandBlock && query.getString("command") != null) {
final String[] commandArray = query.getString("command").split("\n");
// 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("mobid")));
((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", 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);
}
modify.update(true, false);
changed++;
}
query.close();
stmt.close();
databaseConnection.close();
return changed;
}
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().getDurability());
dataStmt.addBatch();
if (++changed % batchSize == 0) {
dataStmt.executeBatch();
}
}
dataStmt.executeBatch(); // insert remaining records
databaseConnection.commit();
databaseConnection.setAutoCommit(true);
dataStmt.close();
return changed;
}
public static void loadStructure(Volume volume, Connection databaseConnection) throws SQLException {
String prefix = String.format("structure_%d", volume.getName().hashCode() & Integer.MAX_VALUE);
World world = volume.getWorld();
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();
}
/**
* Get total saved blocks for a warzone. This should only be called on nimitz-format warzones.
* @param volume Warzone volume
* @param zoneName Name of zone file
* @return Total saved blocks
* @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());
Statement stmt = databaseConnection.createStatement();
ResultSet sizeQuery = stmt.executeQuery("SELECT COUNT(*) AS total FROM blocks");
int size = sizeQuery.getInt("total");
sizeQuery.close();
stmt.close();
databaseConnection.close();
return size;
}
/**
* Save all war zone blocks to a SQLite3 database file.
*
@ -169,14 +313,17 @@ public class ZoneVolumeMapper {
* @throws SQLException
*/
public static int save(Volume volume, String zoneName) throws SQLException {
long startTime = System.currentTimeMillis();
int changed = 0;
File warzoneDir = new File(War.war.getDataFolder().getPath() + "/dat/warzone-" + zoneName);
warzoneDir.mkdirs();
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, sign TEXT, container BLOB, note INT, record TEXT, skull TEXT, command TEXT, mobid TEXT)");
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 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");
@ -190,67 +337,57 @@ public class ZoneVolumeMapper {
cornerStmt.setInt(6, volume.getCornerTwo().getBlockZ());
cornerStmt.executeUpdate();
cornerStmt.close();
PreparedStatement dataStmt = databaseConnection.prepareStatement("INSERT INTO blocks VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
PreparedStatement dataStmt = databaseConnection.prepareStatement("INSERT INTO blocks (x, y, z, type, data, metadata) VALUES (?, ?, ?, ?, ?, ?)");
databaseConnection.setAutoCommit(false);
final int batchSize = 1000;
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);
final Location relLoc = 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.getState().getData().toItemStack().getDurability());
if (block.getState() instanceof Sign) {
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().getDurability());
if (state instanceof Sign) {
final String signText = StringUtils.join(((Sign) block.getState()).getLines(), "\n");
dataStmt.setString(6, signText);
} else {
dataStmt.setNull(6, Types.VARCHAR);
}
if (block.getState() instanceof InventoryHolder) {
} 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(7, config.saveToString());
} else {
dataStmt.setNull(7, Types.BLOB);
}
if (block.getState() instanceof NoteBlock) {
dataStmt.setString(6, config.saveToString());
} else if (state instanceof NoteBlock) {
Note note = ((NoteBlock) block.getState()).getNote();
dataStmt.setString(8, note.getTone().toString() + '\n' + note.getOctave() + '\n' + note.isSharped());
} else {
dataStmt.setNull(8, Types.VARCHAR);
}
if (block.getState() instanceof Jukebox) {
dataStmt.setString(9, ((Jukebox) block.getState()).getPlaying().toString());
} else {
dataStmt.setNull(9, Types.VARCHAR);
}
if (block.getState() instanceof Skull) {
dataStmt.setString(10, String.format("%s\n%s\n%s",
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) {
dataStmt.setString(6, String.format("%s\n%s\n%s",
((Skull) block.getState()).getOwner(),
((Skull) block.getState()).getSkullType().toString(),
((Skull) block.getState()).getRotation().toString()));
} else {
dataStmt.setNull(10, Types.VARCHAR);
}
if (block.getState() instanceof CommandBlock) {
dataStmt.setString(11, ((CommandBlock) block.getState()).getName()
} else if (state instanceof CommandBlock) {
dataStmt.setString(6, ((CommandBlock) block.getState()).getName()
+ "\n" + ((CommandBlock) block.getState()).getCommand());
} else {
dataStmt.setNull(11, Types.VARCHAR);
}
if (block.getState() instanceof CreatureSpawner) {
dataStmt.setString(12, ((CreatureSpawner) block.getState()).getSpawnedType().toString());
} else {
dataStmt.setNull(12, Types.VARCHAR);
} else if (state instanceof CreatureSpawner) {
dataStmt.setString(6, ((CreatureSpawner) block.getState()).getSpawnedType().toString());
}
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});
}
}
}
}
@ -259,15 +396,49 @@ public class ZoneVolumeMapper {
databaseConnection.commit();
dataStmt.close();
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;
}
public static Location rebase(final Location base, final Location exact) {
Validate.isTrue(base.getWorld().equals(exact.getWorld()),
"Locations must be in the same world");
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

@ -15,7 +15,7 @@ public class WarLogFormatter extends Formatter {
b.append(" [");
b.append(arg0.getLevel());
b.append("] ");
b.append(arg0.getMessage());
b.append(formatMessage(arg0));
b.append(System.getProperty("line.separator"));
return b.toString();
}

View File

@ -1,5 +1,6 @@
package com.tommytony.war.volume;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.logging.Level;
@ -50,35 +51,47 @@ public class ZoneVolume extends Volume {
}
public void loadCorners() throws SQLException {
ZoneVolumeMapper.load(this, this.zone.getName(), this.getWorld(), true, 0, 0);
Connection conn = ZoneVolumeMapper.getZoneConnection(this, this.zone.getName(), this.getWorld());
ZoneVolumeMapper.load(conn, this, this.getWorld(), true, 0, 0, null);
this.isSaved = true;
}
@Override
public void resetBlocks() {
// Load blocks directly from disk and onto the map (i.e. no more in-memory warzone blocks)
int reset = 0;
try {
reset = ZoneVolumeMapper.load(this, this.zone.getName(), this.getWorld(), false, 0, Integer.MAX_VALUE);
Connection conn = ZoneVolumeMapper.getZoneConnection(this, this.zone.getName(), this.getWorld());
ZoneVolumeMapper.load(conn, this, this.getWorld(), false, 0, Integer.MAX_VALUE, null);
} catch (SQLException ex) {
War.war.log("Failed to load warzone " + zone.getName() + ": " + ex.getMessage(), Level.WARNING);
ex.printStackTrace();
}
War.war.log("Reset " + reset + " blocks in warzone " + this.zone.getName() + ".", java.util.logging.Level.INFO);
War.war.log("Reset warzone " + this.zone.getName() + ".", java.util.logging.Level.INFO);
this.isSaved = true;
}
/**
* Reset a section of blocks in the warzone.
*
* @param conn Open connection to warzone database file.
* @param start
* Starting position for reset.
* @param total
* Amount of blocks to reset.
* @return Changed block count.
* @throws SQLException
*/
public void resetSection(int start, int total) throws SQLException {
ZoneVolumeMapper.load(this, this.zone.getName(), this.getWorld(), false, start, total);
public int resetSection(Connection conn, int start, int total, boolean[][][] changes) throws SQLException {
return ZoneVolumeMapper.load(conn, this, this.getWorld(), false, start, total, changes);
}
/**
* Get total saved blocks for this warzone. This should only be called on nimitz-format warzones.
* @return Total saved blocks
* @throws SQLException
*/
public int getTotalSavedBlocks() throws SQLException {
return ZoneVolumeMapper.getTotalSavedBlocks(this, this.zone.getName());
}
@Override
@ -87,9 +100,12 @@ public class ZoneVolume extends Volume {
* The job will automatically spawn new instances of itself to run every tick until it is done resetting all blocks.
*/
public void resetBlocksAsJob() {
PartialZoneResetJob job = new PartialZoneResetJob(zone, War.war
.getWarConfig().getInt(WarConfig.RESETSPEED));
War.war.getServer().getScheduler().runTask(War.war, job);
try {
PartialZoneResetJob job = new PartialZoneResetJob(zone, War.war.getWarConfig().getInt(WarConfig.RESETSPEED));
War.war.getServer().getScheduler().runTask(War.war, job);
} catch (SQLException e) {
War.war.getLogger().log(Level.WARNING, "Failed to reset warzone - cannot get count of saved blocks", e);
}
}
public void setNorthwest(Location block) throws NotNorthwestException, TooSmallException, TooBigException {
@ -239,10 +255,7 @@ public class ZoneVolume extends Volume {
private static final int MIN_SIZE = 10;
public boolean tooSmall() {
if (!this.hasTwoCorners()) {
return false;
}
return this.getSizeX() < MIN_SIZE || this.getSizeY() < MIN_SIZE || this.getSizeZ() < MIN_SIZE;
return this.hasTwoCorners() && (this.getSizeX() < MIN_SIZE || this.getSizeY() < MIN_SIZE || this.getSizeZ() < MIN_SIZE);
}
private static final int MAX_SIZE_DEFAULT = 750;