From 18743c2ea361f3c61e5ece1e39a2ca2cf180f2cd Mon Sep 17 00:00:00 2001 From: Garrett Allcorn Date: Tue, 8 Aug 2017 22:18:59 -0700 Subject: [PATCH] Code cleanup --- .../wurm/mapgen/FileGeneration.java | 312 ------------------ .../wurm/mapgen/FileManagement.java | 213 ------------ .../imraginbro/wurm/mapgen/MapBuilder.java | 198 +++++++++++ src/com/imraginbro/wurm/mapgen/MapGen.java | 298 +---------------- .../wurm/mapgen/filegen/DBHandler.java | 103 ++++++ .../wurm/mapgen/filegen/FileGeneration.java | 251 ++++++++++++++ .../wurm/mapgen/filegen/FileManagement.java | 168 ++++++++++ .../mapgen/filegen/PropertiesManager.java | 100 ++++++ .../wurm/mapgen/filegen/Structure.java | 157 +++++++++ src/resources/WurmMapGen.properties | 3 + src/resources/required.zip | Bin 32947 -> 30492 bytes 11 files changed, 981 insertions(+), 822 deletions(-) delete mode 100644 src/com/imraginbro/wurm/mapgen/FileGeneration.java delete mode 100644 src/com/imraginbro/wurm/mapgen/FileManagement.java create mode 100644 src/com/imraginbro/wurm/mapgen/MapBuilder.java create mode 100644 src/com/imraginbro/wurm/mapgen/filegen/DBHandler.java create mode 100644 src/com/imraginbro/wurm/mapgen/filegen/FileGeneration.java create mode 100644 src/com/imraginbro/wurm/mapgen/filegen/FileManagement.java create mode 100644 src/com/imraginbro/wurm/mapgen/filegen/PropertiesManager.java create mode 100644 src/com/imraginbro/wurm/mapgen/filegen/Structure.java diff --git a/src/com/imraginbro/wurm/mapgen/FileGeneration.java b/src/com/imraginbro/wurm/mapgen/FileGeneration.java deleted file mode 100644 index 366c389..0000000 --- a/src/com/imraginbro/wurm/mapgen/FileGeneration.java +++ /dev/null @@ -1,312 +0,0 @@ -package com.imraginbro.wurm.mapgen; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.text.DecimalFormat; - -import com.wurmonline.mesh.MeshIO; - -public class FileGeneration { - - final static String newLine = System.lineSeparator(); - final static String separator = java.io.File.separator; - - static int html_nativeZoom = 0; - static int html_mapMinZoom = 0; - static int html_mapMaxZoom = 0; - static int html_actualMapSize = 0; - static int html_maxMapSize = 0; - - public static void generateFiles(MeshIO map) throws IOException, SQLException { - setHTMLvars(map); - generateDeedsFile(MapGen.saveLocation, MapGen.db_wurmZones); - generateGuardTowersFile(MapGen.saveLocation, MapGen.db_wurmItems, MapGen.db_wurmPlayers); - generateStructuresFile(MapGen.saveLocation, MapGen.db_wurmZones, MapGen.db_wurmPlayers); - generateConfigFile(MapGen.saveLocation); - } - - public static void generateStructuresFile(File saveLocation, File db_wurmZones, File db_wurmPlayers) throws IOException, SQLException { - if (!MapGen.showStructures) { - return; - } - if (!db_wurmZones.exists()) { - System.out.println("[ERROR] Could not find wurmzones.db. Skipping structures.js file generation."); - return; - } - if (!db_wurmPlayers.exists()) { - System.out.println("[ERROR] Could not find wurmplayers.db. Skipping structures.js file generation."); - return; - } - System.out.println("Writing structures.js file..."); - BufferedWriter bw = new BufferedWriter(new FileWriter(saveLocation.getAbsolutePath() + separator + "structures.js", false)); - String structBordersString = ""; - System.out.println("Loading structures from wurmzones.db..."); - Connection zonesDBcon = DriverManager.getConnection("jdbc:sqlite:"+db_wurmZones); - Connection playersDBcon = DriverManager.getConnection("jdbc:sqlite:"+db_wurmPlayers); - Statement statement = zonesDBcon.createStatement(); - ResultSet resultSet = statement.executeQuery("SELECT * FROM STRUCTURES WHERE FINISHED='1';"); - - structBordersString += ("function getStructures() {" + newLine); - structBordersString += ("\tvar structureBorders = [];" + newLine); - - int count = 0; - while (resultSet.next()) { - int minX = -1; - int maxX = -1; - int minY = -1; - int maxY = -1; - long structureID = resultSet.getLong("WURMID"); - - Statement structStatement = zonesDBcon.createStatement(); - ResultSet structRes = structStatement.executeQuery("SELECT TILEX, TILEY FROM BUILDTILES WHERE STRUCTUREID='"+structureID+"';"); - if (structRes.next()) { - minX = structRes.getInt("TILEX"); - maxX = structRes.getInt("TILEX"); - minY = structRes.getInt("TILEY"); - maxY = structRes.getInt("TILEY"); - while (structRes.next()) { - final int nX = structRes.getInt("TILEX"); - final int nY = structRes.getInt("TILEY"); - if (nX < minX) { - minX = nX; - } - if (nX > maxX) { - maxX = nX; - } - if (nY < minY) { - minY = nY; - } - if (nY > maxY) { - maxY = nY; - } - } - } - - maxX++; - maxY++; - - structStatement.close(); - - String name = resultSet.getString("NAME"); - long ownerID = resultSet.getLong("OWNERID"); - Statement nameStatement = playersDBcon.createStatement(); - ResultSet nameRes = nameStatement.executeQuery("SELECT NAME FROM PLAYERS WHERE WURMID='"+ownerID+"';"); - String pname = ""; - if (nameRes.next()) { - pname = nameRes.getString("NAME"); - } - nameStatement.close(); - structBordersString += ("\tstructureBorders.push(L.polygon(["); - structBordersString += ("xy("+minX+","+minY+"),"); - structBordersString += ("xy("+maxX+","+minY+"),"); - structBordersString += ("xy("+maxX+","+maxY+"),"); - structBordersString += ("xy("+minX+","+maxY+")]"); - structBordersString += (", {color:'blue',fillOpacity:0.1,weight:1})"); - structBordersString += (".bindPopup(\"
" + name + "
Created by " + pname + "
\"));" + newLine); - - count++; - } - System.out.println("Added "+count+" structures to structures.js..."); - structBordersString += ("\treturn structureBorders;" + newLine); - structBordersString += ("}" + newLine + newLine); - bw.append(structBordersString); - zonesDBcon.close(); - playersDBcon.close(); - bw.close(); - } - - public static void generateGuardTowersFile(File saveLocation, File db_wurmItems, File db_wurmPlayers) throws IOException, SQLException { - if (!MapGen.showGuardTowers) { - return; - } - if (!db_wurmItems.exists()) { - System.out.println("[ERROR] Could not find wurmitems.db. Skipping guardtowers.js file generation."); - return; - } - if (!db_wurmPlayers.exists()) { - System.out.println("[ERROR] Could not find wurmplayers.db. Skipping guardtowers.js file generation."); - return; - } - System.out.println("Writing guardtowers.js file..."); - BufferedWriter bw = new BufferedWriter(new FileWriter(saveLocation.getAbsolutePath() + separator + "guardtowers.js", false)); - String deedBordersString = ""; - String deedMarkersString = ""; - System.out.println("Loading guard towers from wurmitems.db..."); - Connection itemsDBcon = DriverManager.getConnection("jdbc:sqlite:"+db_wurmItems); - Connection playersDBcon = DriverManager.getConnection("jdbc:sqlite:"+db_wurmPlayers); - Statement statement = itemsDBcon.createStatement(); - ResultSet resultSet = statement.executeQuery("SELECT * FROM ITEMS WHERE (TEMPLATEID='384' OR TEMPLATEID='430' OR TEMPLATEID='528' OR TEMPLATEID='638' OR TEMPLATEID='996') AND CREATIONSTATE='0';"); - - deedBordersString += ("function getGuardTowerBorders() {" + newLine); - deedBordersString += ("\tvar guardTowerBorders = [];" + newLine); - - deedMarkersString += ("function getGuardTowers() {" + newLine); - deedMarkersString += ("\tvar guardTower = [];" + newLine); - - int count = 0; - while (resultSet.next()) { - int x = (int) Math.floor(resultSet.getInt("POSX")/4); - int y = (int) Math.floor(resultSet.getInt("POSY")/4); - float ql = resultSet.getFloat("QUALITYLEVEL"); - float dmg = resultSet.getFloat("DAMAGE"); - long ownerID = resultSet.getLong("LASTOWNERID"); - Statement nameStatement = playersDBcon.createStatement(); - ResultSet nameRes = nameStatement.executeQuery("SELECT NAME FROM PLAYERS WHERE WURMID='"+ownerID+"';"); - String pname = ""; - if (nameRes.next()) { - pname = nameRes.getString("NAME"); - } - nameStatement.close(); - deedBordersString += ("\tguardTowerBorders.push(L.polygon(["); - deedBordersString += ("xy("+(x-50)+","+(y-50)+"),"); - deedBordersString += ("xy("+(x+51)+","+(y-50)+"),"); - deedBordersString += ("xy("+(x+51)+","+(y+51)+"),"); - deedBordersString += ("xy("+(x-50)+","+(y+51)+")]"); - deedBordersString += (", {color:'red',fillOpacity:0.1,weight:1}));" + newLine); - - deedMarkersString += ("\tguardTower.push(L.marker("); - deedMarkersString += ("xy("+(x+0.5)+","+(y+0.5)+"),"); - deedMarkersString += ("{icon: guardTowerIcon})"); - DecimalFormat f = new DecimalFormat("0.00"); - deedMarkersString += (".bindPopup(\"
Guard Tower
Created by " + pname + "

QL: " + f.format(ql) + "
DMG: " + f.format(dmg) + "\"));" + newLine); - - count++; - } - System.out.println("Added "+count+" guard towers to guardtowers.js..."); - deedBordersString += ("\treturn guardTowerBorders;" + newLine); - deedBordersString += ("}" + newLine + newLine); - deedMarkersString += ("\treturn guardTower;" + newLine); - deedMarkersString += ("}"); - bw.append(deedBordersString); - bw.append(deedMarkersString); - itemsDBcon.close(); - playersDBcon.close(); - bw.close(); - } - - public static void generateDeedsFile(File saveLocation, File db_wurmZones) throws IOException, SQLException { - if (!MapGen.showDeeds) { - return; - } - if (!db_wurmZones.exists()) { - System.out.println("[ERROR] Could not find wurmzones.db. Skipping deeds.js file generation."); - return; - } - System.out.println("Writing deeds.js file..."); - BufferedWriter bw = new BufferedWriter(new FileWriter(saveLocation.getAbsolutePath() + separator + "deeds.js", false)); - String deedBordersString = ""; - String deedMarkersString = ""; - String mainDeedString = ""; - System.out.println("Loading deeds from wurmzones.db..."); - Connection connection = DriverManager.getConnection("jdbc:sqlite:"+db_wurmZones); - Statement statement = connection.createStatement(); - ResultSet resultSet = statement.executeQuery("SELECT * FROM VILLAGES WHERE DISBANDED=0;"); - - mainDeedString += ("function setViewOnMainDeed(map) {" + newLine); - - deedBordersString += ("function deedBorders() {" + newLine); - deedBordersString += ("\tvar deedBorders = [];" + newLine); - - deedMarkersString += ("function deedMarkers() {" + newLine); - deedMarkersString += ("\tvar deedMarkers = [];" + newLine); - - double mainX = 0; - double mainY = 0; - - int count = 0; - while (resultSet.next()) { - int sx = resultSet.getInt("STARTX"); - int sy = resultSet.getInt("STARTY"); - int ex = resultSet.getInt("ENDX"); - int ey = resultSet.getInt("ENDY"); - double x = (sx + ex + 1) / 2; - double y = (sy + ey + 1) / 2; - deedBordersString += ("\tdeedBorders.push(L.polygon(["); - deedBordersString += ("xy("+sx+","+sy+"),"); - deedBordersString += ("xy("+(ex+1)+","+sy+"),"); - deedBordersString += ("xy("+(ex+1)+","+(ey+1)+"),"); - deedBordersString += ("xy("+sx+","+(ey+1)+")]"); - boolean perm = resultSet.getBoolean("PERMANENT"); - if (perm) { - deedBordersString += (", {color:'orange',fillOpacity:0,weight:1})"); - if (mainX == 0 && mainY == 0) { - mainX = x; - mainY = y; - mainDeedString += ("\tmap.setView(xy("+mainX+","+mainY+"), config.mapMaxZoom-1)" + newLine); - } - } else { - deedBordersString += (", {color:'white',fillOpacity:0,weight:1})"); - } - deedBordersString += (".bindPopup(\"" + resultSet.getString("NAME") + "\"));" + newLine); - - String firstLetter = resultSet.getString("NAME").substring(0, 1).toLowerCase(); - deedMarkersString += ("\tdeedMarkers.push(L.marker("); - deedMarkersString += ("xy("+(x+0.5)+","+(y+0.5)+"),"); - if (perm) { - deedMarkersString += ("{icon: mainIcon})"); - } else { - deedMarkersString += ("{icon: letter_"+firstLetter+"Icon})"); - } - deedMarkersString += (".bindPopup(\""+resultSet.getString("NAME")+"\"));" + newLine); - count++; - } - System.out.println("Added "+count+" deeds to deeds.js..."); - deedBordersString += ("\treturn deedBorders;" + newLine); - deedBordersString += ("}" + newLine + newLine); - deedMarkersString += ("\treturn deedMarkers;" + newLine); - deedMarkersString += ("}"); - mainDeedString += ("}" + newLine + newLine); - bw.append(mainDeedString); - bw.append(deedBordersString); - bw.append(deedMarkersString); - connection.close(); - bw.close(); - } - - public static void generateConfigFile(File saveLocation) throws IOException { - System.out.println("Writing config.js file..."); - BufferedWriter bw = new BufferedWriter(new FileWriter(saveLocation.getAbsolutePath() + separator + "config.js", false)); - bw.append("function Config() {}" + newLine); - bw.append("var config = new Config();" + newLine); - bw.append("config.nativeZoom = "+html_nativeZoom+";" + newLine); - bw.append("config.mapMinZoom = "+html_mapMinZoom+";" + newLine); - bw.append("config.mapMaxZoom = "+html_mapMaxZoom+";" + newLine); - bw.append("config.actualMapSize = "+html_actualMapSize+";" + newLine); - bw.append("config.maxMapSize = "+html_maxMapSize+";" + newLine); - bw.append("" + newLine); - bw.append("var xyMulitiplier = (config.actualMapSize / 256);" + newLine); - bw.append("" + newLine); - bw.append("var yx = L.latLng;" + newLine); - bw.append("var xy = function(x, y) {" + newLine); - bw.append("\treturn yx(-(y / xyMulitiplier), (x / xyMulitiplier));" + newLine); - bw.append("};"); - bw.close(); - } - - public static void setHTMLvars(MeshIO map) { - System.out.println("Generating config.js variables..."); - html_actualMapSize = map.getSize(); - html_maxMapSize = html_actualMapSize * 8; - int count = 0; - for (int i = html_actualMapSize; i > 256; i++) { - i = (i/2); - html_nativeZoom = count; - count++; - } - count = 0; - for (int i = html_maxMapSize; i > 256; i++) { - i = (i/2); - html_mapMaxZoom = count; - count++; - } - html_mapMinZoom = 1; - } - -} diff --git a/src/com/imraginbro/wurm/mapgen/FileManagement.java b/src/com/imraginbro/wurm/mapgen/FileManagement.java deleted file mode 100644 index 7150f80..0000000 --- a/src/com/imraginbro/wurm/mapgen/FileManagement.java +++ /dev/null @@ -1,213 +0,0 @@ -package com.imraginbro.wurm.mapgen; - -import java.awt.image.BufferedImage; -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.channels.FileChannel; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; -import java.util.Iterator; -import java.util.Properties; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; - -import javax.imageio.IIOImage; -import javax.imageio.ImageIO; -import javax.imageio.ImageWriteParam; -import javax.imageio.ImageWriter; -import javax.imageio.plugins.jpeg.JPEGImageWriteParam; -import javax.imageio.stream.ImageOutputStream; - -public class FileManagement { - - final static String separator = java.io.File.separator; - - public static void relocateFileVars() { - MapGen.map_topLayer = new File(MapGen.saveLocation.getAbsolutePath() + separator + "tmp" + separator + MapGen.map_topLayer.getName()); - MapGen.db_wurmZones = new File(MapGen.saveLocation.getAbsolutePath() + separator + "tmp" + separator + MapGen.db_wurmZones.getName()); - MapGen.db_wurmItems = new File(MapGen.saveLocation.getAbsolutePath() + separator + "tmp" + separator + MapGen.db_wurmItems.getName()); - MapGen.db_wurmPlayers = new File(MapGen.saveLocation.getAbsolutePath() + separator + "tmp" + separator + MapGen.db_wurmPlayers.getName()); - } - - public static void saveToFile(BufferedImage newImg, File file) throws IOException { - ImageWriter writer = null; - Iterator iter = ImageIO.getImageWritersByFormatName("png"); - if (iter.hasNext()) { - writer = (ImageWriter)iter.next(); - } - ImageOutputStream ios = ImageIO.createImageOutputStream(file); - writer.setOutput(ios); - ImageWriteParam param = new JPEGImageWriteParam(java.util.Locale.getDefault()); - param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT) ; - param.setCompressionQuality(1f); - writer.write(null, new IIOImage( newImg, null, null ), param); - } - - public static void copy(InputStream source , String destination) { - try { - Files.copy(source, Paths.get(destination), StandardCopyOption.REPLACE_EXISTING); - } catch (IOException ex) { - ex.printStackTrace(); - } - } - - public static void extractRescources(String zipFileLocation, File saveLocation) throws Exception { - System.out.println("Copying "+zipFileLocation+" file from jar..."); - InputStream in = FileManagement.class.getResourceAsStream(zipFileLocation); - copy(in, saveLocation.getAbsolutePath() + separator + "tmp" + separator + "required.zip"); - in.close(); - System.out.println("Extracting resources from "+zipFileLocation+"..."); - unzip(saveLocation.getAbsolutePath() + separator + "tmp" + separator + "required.zip", saveLocation.getAbsolutePath()); - } - - public static void extractFile(ZipInputStream zipIn, String filePath) throws IOException { - if (!MapGen.replaceFiles && new File(filePath).exists()) { - return; - } - BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath)); - byte[] bytesIn = new byte[4096]; - int read = 0; - while ((read = zipIn.read(bytesIn)) != -1) { - bos.write(bytesIn, 0, read); - } - bos.close(); - } - - public static void unzip(String zipFilePath, String destDirectory) throws IOException { - File destDir = new File(destDirectory); - if (!destDir.exists()) { - destDir.mkdir(); - } - ZipInputStream zipIn = new ZipInputStream(new FileInputStream(zipFilePath)); - ZipEntry entry = zipIn.getNextEntry(); - while (entry != null) { - String filePath = destDirectory + separator + entry.getName(); - File checkDir = new File(destDirectory + separator + entry.getName()).getParentFile(); - if (!checkDir.exists()) { - checkDir.mkdir(); - } - new File(destDirectory).mkdirs(); - if (!entry.isDirectory()) { - extractFile(zipIn, filePath); - } else { - new File(filePath).mkdirs(); - } - zipIn.closeEntry(); - entry = zipIn.getNextEntry(); - } - zipIn.close(); - } - - @SuppressWarnings("resource") - public static void copyFile(File sourceFile, File destFile) throws IOException { - if(!destFile.exists()) { - destFile.createNewFile(); - } - FileChannel source = null; - FileChannel destination = null; - try { - source = new FileInputStream(sourceFile).getChannel(); - destination = new FileOutputStream(destFile).getChannel(); - destination.transferFrom(source, 0, source.size()); - } - finally { - if(source != null) { - source.close(); - } - if(destination != null) { - destination.close(); - } - } - } - - public static void makeTempCopies(File[] files, File saveLocation) throws IOException { - new File(saveLocation.getAbsolutePath() + separator + "tmp").mkdirs(); - for (int i = 0; i < files.length; i++) { - File old = files[i]; - System.out.println("Creating a temp copy of "+old.getName()+"..."); - copyFile(old, new File(saveLocation.getAbsolutePath() + separator + "tmp" + separator + old.getName())); - } - } - - public static void deleteDir(File file) { - File[] contents = file.listFiles(); - if (contents != null) { - for (File f : contents) { - deleteDir(f); - } - } - file.delete(); - } - - public static void copyPropertiesFile() { - try { - InputStream in = FileManagement.class.getResourceAsStream("/resources/WurmMapGen.properties"); - copy(in, "WurmMapGen.properties"); - in.close(); - } catch(Exception e) { - System.out.println("Error copying properties file from jar - " + e.getMessage()); - } - } - - public static boolean loadPropValues() { - System.out.println("Loading WurmMapGen.properties file!"); - Properties prop = new Properties(); - InputStream input = null; - try { - input = new FileInputStream("WurmMapGen.properties"); - } catch (Exception e) { - System.out.println("[ERROR] problem loading properties FileInputStream - " + e.getMessage()); - System.out.println("Copying properties file from jar... please configure and restart program."); - copyPropertiesFile(); - return false; - } - if (input != null) { - try { - prop.load(input); - } catch (Exception e) { - System.out.println("Error loading properties file - " + e.getMessage()); - } - } - - String maploc = prop.getProperty("wurmMapLocation", "C:/location/to/map/folder"); - String saveloc = prop.getProperty("saveLocation", "C:/location/to/save/folder"); - - System.out.println("[INFO] Map location: " + maploc); - System.out.println("[INFO] Save location: " + saveloc); - - if (maploc.equals("C:/location/to/map/folder") || saveloc.equals("C:/location/to/save/folder")) { - System.out.println("[ERROR] Looks like you are using the default map or save location. Please change in your config file."); - return false; - } - - MapGen.showDeeds = Boolean.parseBoolean(prop.getProperty("showDeeds", Boolean.toString(MapGen.showDeeds))); - MapGen.showGuardTowers = Boolean.parseBoolean(prop.getProperty("showGuardTowers", Boolean.toString(MapGen.showGuardTowers))); - MapGen.showStructures = Boolean.parseBoolean(prop.getProperty("showStructures", Boolean.toString(MapGen.showStructures))); - - MapGen.gen_map_shading = Boolean.parseBoolean(prop.getProperty("mapGenerateShading", Boolean.toString(MapGen.gen_map_shading))); - MapGen.gen_map_shade_paths = Boolean.parseBoolean(prop.getProperty("mapShadePaths", Boolean.toString(MapGen.gen_map_shade_paths))); - MapGen.gen_map_water = Boolean.parseBoolean(prop.getProperty("mapGenerateWater", Boolean.toString(MapGen.gen_map_water))); - MapGen.gen_map_bridges = Boolean.parseBoolean(prop.getProperty("mapGenerateBridges", Boolean.toString(MapGen.gen_map_bridges))); - - MapGen.wurmMapLocation = new File(maploc); - MapGen.saveLocation = new File(saveloc); - - MapGen.replaceFiles = Boolean.parseBoolean(prop.getProperty("replaceFiles", Boolean.toString(MapGen.replaceFiles))); - - - if (input != null) { - try { - input.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - return true; - } - -} diff --git a/src/com/imraginbro/wurm/mapgen/MapBuilder.java b/src/com/imraginbro/wurm/mapgen/MapBuilder.java new file mode 100644 index 0000000..0029f76 --- /dev/null +++ b/src/com/imraginbro/wurm/mapgen/MapBuilder.java @@ -0,0 +1,198 @@ +package com.imraginbro.wurm.mapgen; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.stream.IntStream; + +import com.imraginbro.wurm.mapgen.filegen.DBHandler; +import com.imraginbro.wurm.mapgen.filegen.FileGeneration; +import com.imraginbro.wurm.mapgen.filegen.FileManagement; +import com.imraginbro.wurm.mapgen.filegen.PropertiesManager; +import com.wurmonline.mesh.MeshIO; +import com.wurmonline.mesh.Tiles; +import com.wurmonline.mesh.Tiles.Tile; + +public class MapBuilder { + + private final String separator = File.separator; + private int threadCounter = 0; + private int bridgeTileCount = 0; + + public final static FileManagement fileManager = new FileManagement(); + public final static PropertiesManager propertiesManager = new PropertiesManager(); + public final static FileGeneration fileGenerator = new FileGeneration(); + public final static DBHandler dbhandler = new DBHandler(); + + public static MeshIO map; + + public MapBuilder() throws Exception { + if (!propertiesManager.load()) { + return; + } + fileManager.load(); + fileManager.makeTempCopies(); + fileManager.relocateFileVars(); + map = MeshIO.open(fileManager.map_topLayer.getAbsolutePath()); + dbhandler.load(); + System.out.println("Starting map generation..."); + start(); + fileManager.extractRescources("/resources/required.zip"); + fileGenerator.generateFiles(); + dbhandler.closeConnections(); + map.close(); + System.out.println("Removing temporary files..."); + fileManager.deleteDir(new File(propertiesManager.saveLocation.getAbsolutePath() + separator + "tmp")); + } + + private void start() { + final int tileCount = (getMapSize() / 256); + final int totalProcesses = (tileCount * tileCount); + ExecutorService executor = Executors.newFixedThreadPool(propertiesManager.threadLimit); + for (int x = 0; x < tileCount; x++) { + for (int y = 0; y < tileCount; y++) { + threadCounter++; + Runnable mtt = new MapTileThreader(x, y); + executor.execute(mtt); + } + } + executor.shutdown(); + Object obj = new Object(); + while (!executor.isTerminated()) { + int percent = (int)((float)(totalProcesses - threadCounter) / (float)(totalProcesses) * 100.0f); + System.out.print("Completion percent: " + percent + "%\r"); + try { + synchronized (obj) { + obj.wait(100); + } + } catch (InterruptedException ex) { } + } + System.out.println("Completion percent: 100%"); + System.out.println("Map generation complete!"); + System.out.println("Found " + bridgeTileCount + " bridge tiles to draw!"); + } + + private class MapTileThreader implements Runnable { + + private final int x; + private final int y; + + private MapTileThreader(int x, int y) { + this.x = x; + this.y = y; + } + + @Override + public void run() { + generateImageTile(x, y); + threadCounter--; + } + + } + + private int getMapSize() { + return map.getSize(); + } + + private void generateImageTile(final int imageTileX, final int imageTileY) { + final BufferedImage imageTile = new BufferedImage(256, 256, BufferedImage.TYPE_INT_ARGB); + Graphics2D imageTileGraphics = imageTile.createGraphics(); + for (int x = 0; x < 256; x++) { + for (int y = 0; y < 256; y++) { + final int tileX = (imageTileX * 256) + x; + final int tileY = (imageTileY * 256) + y; + int tileEncoded = map.getTile(tileX, tileY); + byte tileType = Tiles.decodeType(tileEncoded); + short tileHeight = Tiles.decodeHeight(tileEncoded); + Tile thisTile = Tiles.getTile(tileType); + Color tileColor = thisTile.getColor(); + imageTileGraphics.setColor(tileColor); + imageTileGraphics.fillRect(x, y, 1, 1); + if (propertiesManager.gen_map_shading) { + boolean checkPath = false; + if (!propertiesManager.gen_map_shade_paths) { + final int[] path_tile_types = { + Tiles.TILE_TYPE_COBBLESTONE, Tiles.TILE_TYPE_COBBLESTONE_ROUND, + Tiles.TILE_TYPE_MARBLE_BRICKS, Tiles.TILE_TYPE_MARBLE_SLABS, + Tiles.TILE_TYPE_SANDSTONE_BRICKS, Tiles.TILE_TYPE_SANDSTONE_SLABS, + Tiles.TILE_TYPE_SLATE_BRICKS, Tiles.TILE_TYPE_SLATE_SLABS, + Tiles.TILE_TYPE_STONE_SLABS}; + checkPath = IntStream.of(path_tile_types).anyMatch(n -> n == tileType); + } + if (!checkPath) { + if (tileX < (getMapSize()-1) && tileY < (getMapSize()-1)) { + int lastTileEncoded = map.getTile(tileX + 1, tileY + 1); + short nextTileHeight = Tiles.decodeHeight(lastTileEncoded); + int calc = tileHeight - nextTileHeight; + if (calc > 0) { + int alpha = (int) Math.round(Math.pow(calc, 0.95)); + if (alpha > 255) { + alpha = 255; + } + imageTileGraphics.setColor(new Color(0,0,0,alpha)); + imageTileGraphics.fillRect(x, y, 1, 1); + } else { + calc = -calc; + int alpha = (int) Math.round(Math.pow(calc, 0.8)); + if (alpha > 255) { + alpha = 255; + } + imageTileGraphics.setColor(new Color(255,255,255,alpha)); + imageTileGraphics.fillRect(x, y, 1, 1); + } + } + } + } + if (propertiesManager.gen_map_water && tileHeight < 0) { + imageTileGraphics.setColor(new Color(20,80,180,210)); + imageTileGraphics.fillRect(x, y, 1, 1); + } + } + } + try { + drawBridges(imageTile, imageTileX, imageTileY); + } catch (SQLException e) { + e.printStackTrace(); + } + new File(propertiesManager.saveLocation.getAbsolutePath() + separator + "images").mkdirs(); + try { + fileManager.saveToFile(imageTile, new File(propertiesManager.saveLocation.getAbsolutePath() + separator + "images" + separator + imageTileX + "-" + imageTileY + ".png")); + } catch (IOException e) { + e.printStackTrace(); + } + imageTileGraphics.dispose(); + imageTile.flush(); + } + + private void drawBridges(BufferedImage imageTile, int imageTileX, int imageTileY) throws SQLException { + if (propertiesManager.gen_map_bridges) { + final int minX = (imageTileX * 256); + final int minY = (imageTileY * 256); + final int maxX = minX + 256; + final int maxY = minY + 256; + Graphics2D imageTileGraphics = imageTile.createGraphics(); + Tile thisTile = Tiles.getTile(9); + Color tileColor = thisTile.getColor(); + imageTileGraphics.setColor(tileColor); + Statement statement = dbhandler.getZonesConnection().createStatement(); + ResultSet resultSet = statement.executeQuery("SELECT TILEX, TILEY FROM BRIDGEPARTS WHERE TILEX >= "+minX+" AND TILEY >= "+minY+" AND TILEX < "+maxX+" AND TILEY < "+maxY+";"); + while (resultSet.next()) { + int tileX = resultSet.getInt("TILEX"); + int tileY = resultSet.getInt("TILEY"); + imageTileGraphics.fillRect((tileX - minX), (tileY - minY), 1, 1); + bridgeTileCount++; + } + resultSet.close(); + statement.close(); + imageTileGraphics.dispose(); + } + } + +} diff --git a/src/com/imraginbro/wurm/mapgen/MapGen.java b/src/com/imraginbro/wurm/mapgen/MapGen.java index f2feb48..99d1cde 100644 --- a/src/com/imraginbro/wurm/mapgen/MapGen.java +++ b/src/com/imraginbro/wurm/mapgen/MapGen.java @@ -1,311 +1,15 @@ package com.imraginbro.wurm.mapgen; -import java.awt.Color; -import java.awt.Graphics2D; -import java.awt.image.BufferedImage; -import java.awt.image.ImagingOpException; -import java.io.File; -import java.io.IOException; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.stream.IntStream; - -import com.wurmonline.mesh.MeshIO; -import com.wurmonline.mesh.Tiles; -import com.wurmonline.mesh.Tiles.Tile; - public class MapGen { - public final static String newLine = System.lineSeparator(); - public final static String separator = java.io.File.separator; - - public static int threadCounter = 0; - - public static File map_topLayer = null; - public static File db_wurmZones = null; - public static File db_wurmItems = null; - public static File db_wurmPlayers = null; - - public static File[] fileBackupArray = new File[4]; - - //vars for map gen - public static boolean gen_map_shading = true; - public static boolean gen_map_shade_paths = true; - public static boolean gen_map_water = true; - public static boolean gen_map_bridges = true; - - //marker generation - public static boolean showDeeds = true; - public static boolean showGuardTowers = true; - public static boolean showStructures = true; - - //config settings - public static boolean replaceFiles = true; - public static File wurmMapLocation = null; - public static File saveLocation = null; - public static void main(String[] args) throws Exception { - - if (!FileManagement.loadPropValues()) { - return; - } - - if (wurmMapLocation == null || saveLocation == null) { - System.out.println("[ERROR] Wurm map location or save location was not set."); - return; - } - - map_topLayer = new File(wurmMapLocation.getAbsolutePath() + separator + "top_layer.map"); - db_wurmZones = new File(wurmMapLocation.getAbsolutePath() + separator + "sqlite" + separator + "wurmzones.db"); - db_wurmItems = new File(wurmMapLocation.getAbsolutePath() + separator + "sqlite" + separator + "wurmitems.db"); - db_wurmPlayers = new File(wurmMapLocation.getAbsolutePath() + separator + "sqlite" + separator + "wurmplayers.db"); - - if (!map_topLayer.exists()) { - System.out.println("[ERROR] Could not find top_layer.map! Stopping program."); - return; - } - - fileBackupArray[0] = map_topLayer; - fileBackupArray[1] = db_wurmZones; - fileBackupArray[2] = db_wurmItems; - fileBackupArray[3] = db_wurmPlayers; - final long startTime = System.currentTimeMillis(); - - FileManagement.makeTempCopies(fileBackupArray, saveLocation); - FileManagement.relocateFileVars(); - - System.out.println("Loading top_layer.map file..."); - MeshIO map = MeshIO.open(map_topLayer.getAbsolutePath()); - - genImages(map); - map.close(); - - FileManagement.extractRescources("/resources/required.zip", saveLocation); - FileGeneration.generateFiles(map); - - System.out.println("Removing temporary files..."); - FileManagement.deleteDir(new File(saveLocation.getAbsolutePath() + separator + "tmp")); - + new MapBuilder(); final long endTime = System.currentTimeMillis(); final long totalTime = (endTime - startTime)/1000; System.out.println("Finished with map generation! " + totalTime + " seconds."); } - public static void genImages(MeshIO map) throws IllegalArgumentException, ImagingOpException, IOException, SQLException, InterruptedException { - System.out.println("Generating image for map"); - BufferedImage original = genMap(map, 1); - genTileMap(original); - original.flush(); - } - - public static BufferedImage genMap(MeshIO map, int PIXEL_SIZE) throws SQLException, InterruptedException { - - int MAP_SIZE = map.getSize(); - int IMAGE_SIZE = PIXEL_SIZE * MAP_SIZE; - - BufferedImage mapImage = new BufferedImage(IMAGE_SIZE, IMAGE_SIZE, BufferedImage.TYPE_INT_ARGB); - - ExecutorService executor = Executors.newFixedThreadPool(5); - System.out.println("Starting multi-thread image generation"); - - for (int y = 0; y < MAP_SIZE; y++) { - threadCounter++; - Runnable mt = new MapThreader(y, PIXEL_SIZE, map, mapImage); - executor.execute(mt); - } - - executor.shutdown(); - - Object obj = new Object(); - - while (!executor.isTerminated()) { - int percent = (int)((float)(MAP_SIZE - threadCounter) / (float)MAP_SIZE * 100.0f); - System.out.print("Completion percent: " + percent + "%\r"); - try { - synchronized (obj) { - obj.wait(1000); - } - } catch (InterruptedException ex) { - } - } - threadCounter = 0; - System.out.println(""); - System.out.println("Image generation complete"); - drawBridges(mapImage, PIXEL_SIZE); - return mapImage; - } - - public static class MapThreader implements Runnable { - - private final int y; - private final int PIXEL_SIZE; - private final MeshIO map; - private final BufferedImage mapImage; - - MapThreader(int y, int PIXEL_SIZE, MeshIO map, BufferedImage mapImage) { - this.y = y; - this.PIXEL_SIZE = PIXEL_SIZE; - this.map = map; - this.mapImage = mapImage; - } - - @Override - public void run() { - genMapThread(y, PIXEL_SIZE, map, mapImage); - } - } - - public static void genMapThread(int y, int PIXEL_SIZE, MeshIO map, BufferedImage mapImage) { - Graphics2D g_mapImage = mapImage.createGraphics(); - for (int x = 0; x < map.getSize(); x++) { - int newX = x * PIXEL_SIZE; - int newY = y * PIXEL_SIZE; - int tileEncoded = map.getTile(x, y); - byte tileType = Tiles.decodeType(tileEncoded); - short tileHeight = Tiles.decodeHeight(tileEncoded); - Tile thisTile = Tiles.getTile(tileType); - Color tileColor = thisTile.getColor(); - g_mapImage.setColor(tileColor); - g_mapImage.fillRect(newX, newY, PIXEL_SIZE, PIXEL_SIZE); - if (gen_map_shading) { - boolean checkPath = false; - if (!gen_map_shade_paths) { - final int[] path_tile_types = { - Tiles.TILE_TYPE_COBBLESTONE, Tiles.TILE_TYPE_COBBLESTONE_ROUND, - Tiles.TILE_TYPE_MARBLE_BRICKS, Tiles.TILE_TYPE_MARBLE_SLABS, - Tiles.TILE_TYPE_SANDSTONE_BRICKS, Tiles.TILE_TYPE_SANDSTONE_SLABS, - Tiles.TILE_TYPE_SLATE_BRICKS, Tiles.TILE_TYPE_SLATE_SLABS, - Tiles.TILE_TYPE_STONE_SLABS}; - checkPath = IntStream.of(path_tile_types).anyMatch(n -> n == tileType); - } - if (!checkPath) { - if (x < (map.getSize()-1) && y < (map.getSize()-1)) { - int lastTileEncoded = map.getTile(x + 1, y + 1); - short nextTileHeight = Tiles.decodeHeight(lastTileEncoded); - int calc = tileHeight - nextTileHeight; - if (calc > 0) { - int alpha = (int) Math.round(Math.pow(calc, 0.95)); - if (alpha > 255) { - alpha = 255; - } - g_mapImage.setColor(new Color(0,0,0,alpha)); - g_mapImage.fillRect(newX, newY, PIXEL_SIZE, PIXEL_SIZE); - } else { - calc = -calc; - int alpha = (int) Math.round(Math.pow(calc, 0.8)); - if (alpha > 255) { - alpha = 255; - } - g_mapImage.setColor(new Color(255,255,255,alpha)); - g_mapImage.fillRect(newX, newY, PIXEL_SIZE, PIXEL_SIZE); - } - } - } - } - if (gen_map_water && tileHeight < 0) { - g_mapImage.setColor(new Color(20,80,180,210)); - g_mapImage.fillRect(newX, newY, PIXEL_SIZE, PIXEL_SIZE); - } - } - g_mapImage.dispose(); - threadCounter--; - } - - public static void drawBridges(BufferedImage mapImage, int PIXEL_SIZE) throws SQLException { - if (gen_map_bridges) { - if (!db_wurmZones.exists()) { - System.out.println("[ERROR] Could not find zones.db. Skipping bridge generation."); - return; - } - Graphics2D g_mapImage = mapImage.createGraphics(); - System.out.println("Loading bridges from wurmzones.db..."); - Tile thisTile = Tiles.getTile(9); - Color tileColor = thisTile.getColor(); - g_mapImage.setColor(tileColor); - Connection connection = DriverManager.getConnection("jdbc:sqlite:"+db_wurmZones); - Statement statement = connection.createStatement(); - ResultSet resultSet = statement.executeQuery("SELECT * FROM BRIDGEPARTS;"); - int count = 0; - System.out.println("Drawing bridges..."); - while (resultSet.next()) { - int tileX = resultSet.getInt("TILEX") * PIXEL_SIZE; - int tileY = resultSet.getInt("TILEY") * PIXEL_SIZE; - g_mapImage.fillRect(tileX, tileY, PIXEL_SIZE, PIXEL_SIZE); - count++; - } - g_mapImage.dispose(); - connection.close(); - System.out.println("Finished drawing " + count + " bridge tiles"); - } - } - - public static void genTileMap(BufferedImage img) throws IOException { - System.out.println("Creating tile map from image..."); - int subImg = img.getWidth() / 256; - ExecutorService executor = Executors.newFixedThreadPool(2); - for (int x = 0; x < subImg; x++) { - for (int y = 0; y < subImg; y++) { - /*new File(saveLocation.getAbsolutePath() + separator + "images").mkdirs(); - BufferedImage newImg = img.getSubimage(x * 256, y * 256, 256, 256); - FileManagement.saveToFile(newImg, new File(saveLocation.getAbsolutePath() + separator + "images" + separator + x + "-" + y + ".png")); - newImg.flush();*/ - threadCounter++; - Runnable mt = new MapTileThreader(x, y, img); - executor.execute(mt); - } - } - - executor.shutdown(); - Object obj = new Object(); - - while (!executor.isTerminated()) { - int percent = (int)((float)((subImg*subImg) - threadCounter) / (float)(subImg*subImg) * 100.0f); - System.out.print("Completion percent: " + percent + "%\r"); - try { - synchronized (obj) { - obj.wait(1000); - } - } catch (InterruptedException ex) { - } - } - - System.out.println(""); - img.flush(); - threadCounter = 0; - } - - public static class MapTileThreader implements Runnable { - - private final int x; - private final int y; - private final BufferedImage img; - - MapTileThreader(int x, int y, BufferedImage img) { - this.x = x; - this.y = y; - this.img = img; - } - - @Override - public void run() { - new File(saveLocation.getAbsolutePath() + separator + "images").mkdirs(); - BufferedImage newImg = img.getSubimage(x * 256, y * 256, 256, 256); - try { - FileManagement.saveToFile(newImg, new File(saveLocation.getAbsolutePath() + separator + "images" + separator + x + "-" + y + ".png")); - } catch (IOException e) { - e.printStackTrace(); - } - newImg.flush(); - threadCounter--; - } - } - } diff --git a/src/com/imraginbro/wurm/mapgen/filegen/DBHandler.java b/src/com/imraginbro/wurm/mapgen/filegen/DBHandler.java new file mode 100644 index 0000000..068dcae --- /dev/null +++ b/src/com/imraginbro/wurm/mapgen/filegen/DBHandler.java @@ -0,0 +1,103 @@ +package com.imraginbro.wurm.mapgen.filegen; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; + +import com.imraginbro.wurm.mapgen.MapBuilder; + +public class DBHandler { + + private Connection wurmZonesConnection = null; + private Connection wurmItemsConnection = null; + private Connection wurmPlayersConnection = null; + + public void load() { + this.openConnections(); + } + + public Connection getZonesConnection() { + return this.wurmZonesConnection; + } + + public Connection getItemsConnection() { + return this.wurmItemsConnection; + } + + public Connection getPlayersConnection() { + return this.wurmPlayersConnection; + } + + public boolean checkZonesConnection() { + if (this.wurmZonesConnection != null) { + return true; + } + return false; + } + + public boolean checkItemsConnection() { + if (this.wurmItemsConnection != null) { + return true; + } + return false; + } + + public boolean checkPlayersConnection() { + if (this.wurmPlayersConnection != null) { + return true; + } + return false; + } + + public void openConnections() { + try { + if (MapBuilder.fileManager.db_wurmZones.exists() && !this.checkZonesConnection()) { + this.wurmZonesConnection = DriverManager.getConnection("jdbc:sqlite:" + MapBuilder.fileManager.db_wurmZones); + } + } catch (SQLException e) { + System.out.println("[ERROR] connecting wurmzones.db - " + e.getMessage()); + } + try { + if (MapBuilder.fileManager.db_wurmItems.exists() && !this.checkItemsConnection()) { + this.wurmItemsConnection = DriverManager.getConnection("jdbc:sqlite:" + MapBuilder.fileManager.db_wurmItems); + } + } catch (SQLException e) { + System.out.println("[ERROR] connecting wurmitems.db - " + e.getMessage()); + } + try { + if (MapBuilder.fileManager.db_wurmPlayers.exists() && !this.checkPlayersConnection()) { + this.wurmPlayersConnection = DriverManager.getConnection("jdbc:sqlite:" + MapBuilder.fileManager.db_wurmPlayers); + } + } catch (SQLException e) { + System.out.println("[ERROR] connecting wurmplayers.db - " + e.getMessage()); + } + } + + public void closeConnections() { + if (this.wurmZonesConnection != null) { + try { + this.wurmZonesConnection.close(); + } catch (SQLException e) { + e.printStackTrace(); + } + this.wurmZonesConnection = null; + } + if (this.wurmItemsConnection != null) { + try { + this.wurmItemsConnection.close(); + } catch (SQLException e) { + e.printStackTrace(); + } + this.wurmItemsConnection = null; + } + if (this.wurmPlayersConnection != null) { + try { + this.wurmPlayersConnection.close(); + } catch (SQLException e) { + e.printStackTrace(); + } + this.wurmPlayersConnection = null; + } + } + +} diff --git a/src/com/imraginbro/wurm/mapgen/filegen/FileGeneration.java b/src/com/imraginbro/wurm/mapgen/filegen/FileGeneration.java new file mode 100644 index 0000000..28a7897 --- /dev/null +++ b/src/com/imraginbro/wurm/mapgen/filegen/FileGeneration.java @@ -0,0 +1,251 @@ +package com.imraginbro.wurm.mapgen.filegen; + +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.io.IOException; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.text.DecimalFormat; + +import com.imraginbro.wurm.mapgen.MapBuilder; +import com.wurmonline.mesh.MeshIO; + +public class FileGeneration { + + final static String newLine = System.lineSeparator(); + final static String separator = java.io.File.separator; + + private int html_nativeZoom = 0; + private int html_mapMinZoom = 0; + private int html_mapMaxZoom = 0; + private int html_actualMapSize = 0; + private int html_maxMapSize = 0; + + public void generateFiles() throws IOException, SQLException { + setHTMLvars(MapBuilder.map); + generateDeedsFile(); + generateGuardTowersFile(); + generateStructuresFile(); + generateConfigFile(); + } + + public void generateStructuresFile() throws IOException, SQLException { + if (!MapBuilder.propertiesManager.showStructures || !MapBuilder.dbhandler.checkZonesConnection() || !MapBuilder.dbhandler.checkPlayersConnection()) { + System.out.println("Skipping structures.js generation."); + return; + } + + System.out.println("Writing structures.js file..."); + BufferedWriter bw = new BufferedWriter(new FileWriter(MapBuilder.propertiesManager.saveLocation.getAbsolutePath() + separator + "includes" + separator + "structures.js", false)); + + System.out.println("Loading structures from wurmzones.db..."); + + Statement statement = MapBuilder.dbhandler.getZonesConnection().createStatement(); + ResultSet resultSet = statement.executeQuery("SELECT WURMID FROM STRUCTURES WHERE FINISHED='1';"); + + bw.append("function getStructures() {" + newLine); + bw.append("\tvar structureBorders = [];" + newLine); + int count = 0; + while (resultSet.next()) { + long structureID = resultSet.getLong("WURMID"); + Structure structure = new Structure(MapBuilder.dbhandler, structureID); + bw.append("\tstructureBorders.push(L.polygon([" + + "xy("+structure.getMinX()+","+structure.getMinY()+")," + + "xy("+structure.getMaxX()+","+structure.getMinY()+")," + + "xy("+structure.getMaxX()+","+structure.getMaxY()+")," + + "xy("+structure.getMinX()+","+structure.getMaxY()+")]" + + ", {color:'blue',fillOpacity:0.1,weight:1})" + + ".bindPopup(\"
" + structure.getStructureName() + "
" + + "Created by " + structure.getOwnerName() + "
\"));" + newLine); + count++; + } + System.out.println("Added " + count + " structures to structures.js..."); + bw.append("\treturn structureBorders;" + newLine); + bw.append("}"); + bw.close(); + } + + public void generateGuardTowersFile() throws IOException, SQLException { + if (!MapBuilder.propertiesManager.showGuardTowers || !MapBuilder.dbhandler.checkItemsConnection() || !MapBuilder.dbhandler.checkPlayersConnection()) { + System.out.println("Skipping guardtowers.js generation."); + return; + } + + System.out.println("Writing guardtowers.js file..."); + BufferedWriter bw = new BufferedWriter(new FileWriter(MapBuilder.propertiesManager.saveLocation.getAbsolutePath() + separator + "includes" + separator + "guardtowers.js", false)); + StringBuilder deedBordersString = new StringBuilder(); + StringBuilder deedMarkersString = new StringBuilder(); + System.out.println("Loading guard towers from wurmitems.db..."); + Statement statement = MapBuilder.dbhandler.getItemsConnection().createStatement(); + ResultSet resultSet = statement.executeQuery("SELECT * FROM ITEMS WHERE (TEMPLATEID='384' OR TEMPLATEID='430' OR TEMPLATEID='528' OR TEMPLATEID='638' OR TEMPLATEID='996') AND CREATIONSTATE='0';"); + + deedBordersString.append("function getGuardTowerBorders() {" + newLine); + deedBordersString.append("\tvar guardTowerBorders = [];" + newLine); + + deedMarkersString.append("function getGuardTowers() {" + newLine); + deedMarkersString.append("\tvar guardTower = [];" + newLine); + + int count = 0; + while (resultSet.next()) { + + + int x = (int) Math.floor(resultSet.getInt("POSX")/4); + int y = (int) Math.floor(resultSet.getInt("POSY")/4); + float ql = resultSet.getFloat("QUALITYLEVEL"); + float dmg = resultSet.getFloat("DAMAGE"); + long ownerID = resultSet.getLong("LASTOWNERID"); + Statement nameStatement = MapBuilder.dbhandler.getPlayersConnection().createStatement(); + ResultSet nameRes = nameStatement.executeQuery("SELECT NAME FROM PLAYERS WHERE WURMID='" + ownerID + "';"); + String pname = ""; + if (nameRes.next()) { + pname = nameRes.getString("NAME"); + } + nameStatement.close(); + + + deedBordersString.append("\tguardTowerBorders.push(L.polygon(["); + deedBordersString.append("xy("+(x-50)+","+(y-50)+"),"); + deedBordersString.append("xy("+(x+51)+","+(y-50)+"),"); + deedBordersString.append("xy("+(x+51)+","+(y+51)+"),"); + deedBordersString.append("xy("+(x-50)+","+(y+51)+")]"); + deedBordersString.append(", {color:'red',fillOpacity:0.1,weight:1}));" + newLine); + + deedMarkersString.append("\tguardTower.push(L.marker("); + deedMarkersString.append("xy("+(x+0.5)+","+(y+0.5)+"),"); + deedMarkersString.append("{icon: guardTowerIcon})"); + DecimalFormat f = new DecimalFormat("0.00"); + deedMarkersString.append(".bindPopup(\"
Guard Tower
Created by " + pname + "

QL: " + f.format(ql) + "
DMG: " + f.format(dmg) + "\"));" + newLine); + + count++; + } + System.out.println("Added " + count + " guard towers to guardtowers.js..."); + + deedBordersString.append("\treturn guardTowerBorders;" + newLine); + deedBordersString.append("}" + newLine + newLine); + + deedMarkersString.append("\treturn guardTower;" + newLine); + deedMarkersString.append("}"); + + bw.append(deedBordersString); + bw.append(deedMarkersString); + + bw.close(); + } + + public void generateDeedsFile() throws IOException, SQLException { + if (!MapBuilder.propertiesManager.showDeeds || !MapBuilder.dbhandler.checkZonesConnection()) { + System.out.println("Skipping deeds.js generation."); + return; + } + System.out.println("Writing deeds.js file..."); + BufferedWriter bw = new BufferedWriter(new FileWriter(MapBuilder.propertiesManager.saveLocation.getAbsolutePath() + separator + "includes" + separator + "deeds.js", false)); + String deedBordersString = ""; + String deedMarkersString = ""; + String mainDeedString = ""; + System.out.println("Loading deeds from wurmzones.db..."); + + Statement statement = MapBuilder.dbhandler.getZonesConnection().createStatement(); + ResultSet resultSet = statement.executeQuery("SELECT * FROM VILLAGES WHERE DISBANDED=0;"); + + mainDeedString += ("function setViewOnMainDeed(map) {" + newLine); + + deedBordersString += ("function deedBorders() {" + newLine); + deedBordersString += ("\tvar deedBorders = [];" + newLine); + + deedMarkersString += ("function deedMarkers() {" + newLine); + deedMarkersString += ("\tvar deedMarkers = [];" + newLine); + + double mainX = 0; + double mainY = 0; + + int count = 0; + while (resultSet.next()) { + int sx = resultSet.getInt("STARTX"); + int sy = resultSet.getInt("STARTY"); + int ex = resultSet.getInt("ENDX"); + int ey = resultSet.getInt("ENDY"); + double x = (sx + ex + 1) / 2; + double y = (sy + ey + 1) / 2; + deedBordersString += ("\tdeedBorders.push(L.polygon(["); + deedBordersString += ("xy("+sx+","+sy+"),"); + deedBordersString += ("xy("+(ex+1)+","+sy+"),"); + deedBordersString += ("xy("+(ex+1)+","+(ey+1)+"),"); + deedBordersString += ("xy("+sx+","+(ey+1)+")]"); + boolean perm = resultSet.getBoolean("PERMANENT"); + if (perm) { + deedBordersString += (", {color:'orange',fillOpacity:0,weight:1})"); + if (mainX == 0 && mainY == 0) { + mainX = x; + mainY = y; + mainDeedString += ("\tmap.setView(xy("+mainX+","+mainY+"), config.mapMaxZoom-1)" + newLine); + } + } else { + deedBordersString += (", {color:'white',fillOpacity:0,weight:1})"); + } + deedBordersString += (".bindPopup(\"" + resultSet.getString("NAME") + "\"));" + newLine); + + String firstLetter = resultSet.getString("NAME").substring(0, 1).toLowerCase(); + deedMarkersString += ("\tdeedMarkers.push(L.marker("); + deedMarkersString += ("xy("+(x+0.5)+","+(y+0.5)+"),"); + if (perm) { + deedMarkersString += ("{icon: mainIcon})"); + } else { + deedMarkersString += ("{icon: letter_"+firstLetter+"Icon})"); + } + deedMarkersString += (".bindPopup(\""+resultSet.getString("NAME")+"\"));" + newLine); + count++; + } + System.out.println("Added "+count+" deeds to deeds.js..."); + deedBordersString += ("\treturn deedBorders;" + newLine); + deedBordersString += ("}" + newLine + newLine); + deedMarkersString += ("\treturn deedMarkers;" + newLine); + deedMarkersString += ("}"); + mainDeedString += ("}" + newLine + newLine); + bw.append(mainDeedString); + bw.append(deedBordersString); + bw.append(deedMarkersString); + + bw.close(); + } + + public void generateConfigFile() throws IOException { + System.out.println("Writing config.js file..."); + BufferedWriter bw = new BufferedWriter(new FileWriter(MapBuilder.propertiesManager.saveLocation.getAbsolutePath() + separator + "includes" + separator + "config.js", false)); + bw.append("function Config() {}" + newLine); + bw.append("var config = new Config();" + newLine); + bw.append("config.nativeZoom = "+html_nativeZoom+";" + newLine); + bw.append("config.mapMinZoom = "+html_mapMinZoom+";" + newLine); + bw.append("config.mapMaxZoom = "+html_mapMaxZoom+";" + newLine); + bw.append("config.actualMapSize = "+html_actualMapSize+";" + newLine); + bw.append("config.maxMapSize = "+html_maxMapSize+";" + newLine); + bw.append("" + newLine); + bw.append("var xyMulitiplier = (config.actualMapSize / 256);" + newLine); + bw.append("" + newLine); + bw.append("var yx = L.latLng;" + newLine); + bw.append("var xy = function(x, y) {" + newLine); + bw.append("\treturn yx(-(y / xyMulitiplier), (x / xyMulitiplier));" + newLine); + bw.append("};"); + bw.close(); + } + + public void setHTMLvars(MeshIO map) { + System.out.println("Generating config.js variables..."); + html_actualMapSize = map.getSize(); + html_maxMapSize = html_actualMapSize * 8; + int count = 0; + for (int i = html_actualMapSize; i > 256; i++) { + i = (i/2); + html_nativeZoom = count; + count++; + } + count = 0; + for (int i = html_maxMapSize; i > 256; i++) { + i = (i/2); + html_mapMaxZoom = count; + count++; + } + html_mapMinZoom = 1; + } + +} diff --git a/src/com/imraginbro/wurm/mapgen/filegen/FileManagement.java b/src/com/imraginbro/wurm/mapgen/filegen/FileManagement.java new file mode 100644 index 0000000..2c34413 --- /dev/null +++ b/src/com/imraginbro/wurm/mapgen/filegen/FileManagement.java @@ -0,0 +1,168 @@ +package com.imraginbro.wurm.mapgen.filegen; + +import java.awt.image.BufferedImage; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.channels.FileChannel; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.Iterator; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import javax.imageio.IIOImage; +import javax.imageio.ImageIO; +import javax.imageio.ImageWriteParam; +import javax.imageio.ImageWriter; +import javax.imageio.plugins.jpeg.JPEGImageWriteParam; +import javax.imageio.stream.ImageOutputStream; + +import com.imraginbro.wurm.mapgen.MapBuilder; + +public class FileManagement { + + final static String separator = java.io.File.separator; + + public File map_topLayer = null; + public File db_wurmZones = null; + public File db_wurmItems = null; + public File db_wurmPlayers = null; + + File[] fileBackupArray = null; + + public void load() { + map_topLayer = new File(MapBuilder.propertiesManager.wurmMapLocation.getAbsolutePath() + separator + "top_layer.map"); + db_wurmZones = new File(MapBuilder.propertiesManager.wurmMapLocation.getAbsolutePath() + separator + "sqlite" + separator + "wurmzones.db"); + db_wurmItems = new File(MapBuilder.propertiesManager.wurmMapLocation.getAbsolutePath() + separator + "sqlite" + separator + "wurmitems.db"); + db_wurmPlayers = new File(MapBuilder.propertiesManager.wurmMapLocation.getAbsolutePath() + separator + "sqlite" + separator + "wurmplayers.db"); + fileBackupArray = new File[]{map_topLayer, db_wurmZones, db_wurmItems, db_wurmPlayers}; + } + + public void relocateFileVars() { + map_topLayer = new File(MapBuilder.propertiesManager.saveLocation.getAbsolutePath() + separator + "tmp" + separator + map_topLayer.getName()); + db_wurmZones = new File(MapBuilder.propertiesManager.saveLocation.getAbsolutePath() + separator + "tmp" + separator + db_wurmZones.getName()); + db_wurmItems = new File(MapBuilder.propertiesManager.saveLocation.getAbsolutePath() + separator + "tmp" + separator + db_wurmItems.getName()); + db_wurmPlayers = new File(MapBuilder.propertiesManager.saveLocation.getAbsolutePath() + separator + "tmp" + separator + db_wurmPlayers.getName()); + } + + public void saveToFile(BufferedImage newImg, File file) throws IOException { + ImageWriter writer = null; + Iterator iter = ImageIO.getImageWritersByFormatName("png"); + if (iter.hasNext()) { + writer = (ImageWriter)iter.next(); + } + ImageOutputStream ios = ImageIO.createImageOutputStream(file); + writer.setOutput(ios); + ImageWriteParam param = new JPEGImageWriteParam(java.util.Locale.getDefault()); + param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT) ; + param.setCompressionQuality(1f); + writer.write(null, new IIOImage( newImg, null, null ), param); + } + + public void copy(InputStream source , String destination) { + try { + Files.copy(source, Paths.get(destination), StandardCopyOption.REPLACE_EXISTING); + } catch (IOException ex) { + ex.printStackTrace(); + } + } + + public void extractRescources(String zipFileLocation) throws Exception { + System.out.println("Copying "+zipFileLocation+" file from jar..."); + InputStream in = FileManagement.class.getResourceAsStream(zipFileLocation); + copy(in, MapBuilder.propertiesManager.saveLocation.getAbsolutePath() + separator + "tmp" + separator + "required.zip"); + in.close(); + System.out.println("Extracting resources from "+zipFileLocation+"..."); + unzip(MapBuilder.propertiesManager.saveLocation.getAbsolutePath() + separator + "tmp" + separator + "required.zip", MapBuilder.propertiesManager.saveLocation.getAbsolutePath()); + } + + public void extractFile(ZipInputStream zipIn, String filePath) throws IOException { + if (!MapBuilder.propertiesManager.replaceFiles && new File(filePath).exists()) { + return; + } + BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath)); + byte[] bytesIn = new byte[4096]; + int read = 0; + while ((read = zipIn.read(bytesIn)) != -1) { + bos.write(bytesIn, 0, read); + } + bos.close(); + } + + public void unzip(String zipFilePath, String destDirectory) throws IOException { + File destDir = new File(destDirectory); + if (!destDir.exists()) { + destDir.mkdir(); + } + ZipInputStream zipIn = new ZipInputStream(new FileInputStream(zipFilePath)); + ZipEntry entry = zipIn.getNextEntry(); + while (entry != null) { + String filePath = destDirectory + separator + entry.getName(); + File checkDir = new File(destDirectory + separator + entry.getName()).getParentFile(); + if (!checkDir.exists()) { + checkDir.mkdir(); + } + new File(destDirectory).mkdirs(); + if (!entry.isDirectory()) { + extractFile(zipIn, filePath); + } else { + new File(filePath).mkdirs(); + } + zipIn.closeEntry(); + entry = zipIn.getNextEntry(); + } + zipIn.close(); + } + + @SuppressWarnings("resource") + public void copyFile(File sourceFile, File destFile) throws IOException { + if(!destFile.exists()) { + destFile.createNewFile(); + } + FileChannel source = null; + FileChannel destination = null; + try { + source = new FileInputStream(sourceFile).getChannel(); + destination = new FileOutputStream(destFile).getChannel(); + destination.transferFrom(source, 0, source.size()); + } + finally { + if(source != null) { + source.close(); + } + if(destination != null) { + destination.close(); + } + } + } + + public void makeTempCopies() { + new File(MapBuilder.propertiesManager.saveLocation.getAbsolutePath() + separator + "tmp").mkdirs(); + for (int i = 0; i < fileBackupArray.length; i++) { + final File old = fileBackupArray[i]; + System.out.println("Creating a temp copy of "+old.getName()+"..."); + try { + copyFile(old, new File(MapBuilder.propertiesManager.saveLocation.getAbsolutePath() + separator + "tmp" + separator + old.getName())); + } catch (IOException e) { + e.printStackTrace(); + } + } + relocateFileVars(); + } + + public void deleteDir(File file) { + File[] contents = file.listFiles(); + if (contents != null) { + for (File f : contents) { + deleteDir(f); + } + } + file.delete(); + } + +} diff --git a/src/com/imraginbro/wurm/mapgen/filegen/PropertiesManager.java b/src/com/imraginbro/wurm/mapgen/filegen/PropertiesManager.java new file mode 100644 index 0000000..a941c48 --- /dev/null +++ b/src/com/imraginbro/wurm/mapgen/filegen/PropertiesManager.java @@ -0,0 +1,100 @@ +package com.imraginbro.wurm.mapgen.filegen; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +import com.imraginbro.wurm.mapgen.MapBuilder; + +public class PropertiesManager { + + private final String propertiesFile = "WurmMapGen.properties"; + + public Boolean showDeeds = true; + public Boolean showGuardTowers = true; + public Boolean showStructures = true; + + public int threadLimit = 2; + + public Boolean gen_map_shading = true; + public Boolean gen_map_shade_paths = true; + public Boolean gen_map_water = true; + public Boolean gen_map_bridges = true; + + public Boolean replaceFiles = true; + + public File wurmMapLocation; + public File saveLocation; + + private void copyFromJar() { + try { + InputStream in = this.getClass().getResourceAsStream("/resources/" + this.propertiesFile); + MapBuilder.fileManager.copy(in, this.propertiesFile); + in.close(); + } catch(Exception e) { + System.out.println("Error copying properties file from jar - " + e.getMessage()); + } + } + + public boolean load() { + System.out.println("Loading " + propertiesFile + " file!"); + Properties prop = new Properties(); + InputStream input = null; + + try { + input = new FileInputStream(propertiesFile); + } catch (Exception e) { + System.out.println("[ERROR] problem loading properties FileInputStream - " + e.getMessage()); + System.out.println("Copying properties file from jar... please configure and restart program."); + copyFromJar(); + return false; + } + + if (input != null) { + try { + prop.load(input); + } catch (Exception e) { + System.out.println("Error loading properties file - " + e.getMessage()); + } + } + + final String maploc = prop.getProperty("wurmMapLocation", "C:/location/to/map/folder"); + final String saveloc = prop.getProperty("saveLocation", "C:/location/to/save/folder"); + + System.out.println("[INFO] Map location: " + maploc); + System.out.println("[INFO] Save location: " + saveloc); + + if (maploc.equals("C:/location/to/map/folder") || saveloc.equals("C:/location/to/save/folder")) { + System.out.println("[ERROR] Looks like you are using the default map or save location. Please change in your config file."); + return false; + } + + this.showDeeds = Boolean.parseBoolean(prop.getProperty("showDeeds", Boolean.toString(this.showDeeds))); + this.showGuardTowers = Boolean.parseBoolean(prop.getProperty("showGuardTowers", Boolean.toString(this.showGuardTowers))); + this.showStructures = Boolean.parseBoolean(prop.getProperty("showStructures", Boolean.toString(this.showStructures))); + + this.threadLimit = Integer.parseInt(prop.getProperty("threads", Integer.toString(this.threadLimit))); + + this.gen_map_shading = Boolean.parseBoolean(prop.getProperty("mapGenerateShading", Boolean.toString(this.gen_map_shading))); + this.gen_map_shade_paths = Boolean.parseBoolean(prop.getProperty("mapShadePaths", Boolean.toString(this.gen_map_shade_paths))); + this.gen_map_water = Boolean.parseBoolean(prop.getProperty("mapGenerateWater", Boolean.toString(this.gen_map_water))); + this.gen_map_bridges = Boolean.parseBoolean(prop.getProperty("mapGenerateBridges", Boolean.toString(this.gen_map_bridges))); + + this.wurmMapLocation = new File(maploc); + this.saveLocation = new File(saveloc); + + this.replaceFiles = Boolean.parseBoolean(prop.getProperty("replaceFiles", Boolean.toString(this.replaceFiles))); + + if (input != null) { + try { + input.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + return true; + } + +} diff --git a/src/com/imraginbro/wurm/mapgen/filegen/Structure.java b/src/com/imraginbro/wurm/mapgen/filegen/Structure.java new file mode 100644 index 0000000..a65b5ea --- /dev/null +++ b/src/com/imraginbro/wurm/mapgen/filegen/Structure.java @@ -0,0 +1,157 @@ +package com.imraginbro.wurm.mapgen.filegen; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +public class Structure { + + private DBHandler dbhandler; + private String structureName; + private long structureID; + private String ownerName; + private long ownerID; + private int minX = -1; + private int maxX = -1; + private int minY = -1; + private int maxY = -1; + + public Structure(DBHandler dbhandler, Long structureID) { + this.dbhandler = dbhandler; + this.structureID = structureID; + populateStructure(); + populateOwnerName(); + generateMinMax(); + } + + public String getStructureName() { + return this.structureName; + } + + public long getStructureID() { + return this.structureID; + } + + public String getOwnerName() { + return this.ownerName; + } + + public long getOwnerID() { + return this.ownerID; + } + + public int getMinX() { + return this.minX; + } + + public int getMaxX() { + return this.maxX; + } + + public int getMinY() { + return this.minY; + } + + public int getMaxY() { + return this.maxY; + } + + private void populateStructure() { + Statement statement = null; + ResultSet result = null; + try { + statement = dbhandler.getZonesConnection().createStatement(); + result = statement.executeQuery("SELECT * FROM STRUCTURES WHERE WURMID='"+this.structureID+"';"); + if (result.next()) { + this.ownerID = result.getLong("OWNERID"); + this.structureName = result.getString("NAME"); + } + } catch(SQLException e) { + System.out.println("[ERROR] " + e.getMessage()); + e.printStackTrace(); + } finally { + if (result != null) { + try { + result.close(); + } catch (SQLException e) { } + } + if (statement != null) { + try { + statement.close(); + } catch (SQLException e) { } + } + } + } + + private void populateOwnerName() { + Statement statement = null; + ResultSet result = null; + try { + statement = dbhandler.getPlayersConnection().createStatement(); + result = statement.executeQuery("SELECT NAME FROM PLAYERS WHERE WURMID='"+this.ownerID+"';"); + if (result.next()) { + this.ownerName = result.getString("NAME"); + } + } catch(SQLException e) { + System.out.println("[ERROR] " + e.getMessage()); + e.printStackTrace(); + } finally { + if (result != null) { + try { + result.close(); + } catch (SQLException e) { } + } + if (statement != null) { + try { + statement.close(); + } catch (SQLException e) { } + } + } + } + + private void generateMinMax() { + Statement statement = null; + ResultSet result = null; + try { + statement = dbhandler.getZonesConnection().createStatement(); + result = statement.executeQuery("SELECT TILEX, TILEY FROM BUILDTILES WHERE STRUCTUREID='"+this.structureID+"';"); + if (result.next()) { + minX = result.getInt("TILEX"); + maxX = result.getInt("TILEX"); + minY = result.getInt("TILEY"); + maxY = result.getInt("TILEY"); + while (result.next()) { + final int nX = result.getInt("TILEX"); + final int nY = result.getInt("TILEY"); + if (nX < minX) { + minX = nX; + } else if (nX > maxX) { + maxX = nX; + } + if (nY < minY) { + minY = nY; + } else if (nY > maxY) { + maxY = nY; + } + } + maxX++; + maxY++; + } + } catch(SQLException e) { + System.out.println("[ERROR] " + e.getMessage()); + e.printStackTrace(); + } finally { + if (result != null) { + try { + result.close(); + } catch (SQLException e) { } + } + if (statement != null) { + try { + statement.close(); + } catch (SQLException e) { } + } + } + } + +} diff --git a/src/resources/WurmMapGen.properties b/src/resources/WurmMapGen.properties index 51aa6aa..d92b396 100644 --- a/src/resources/WurmMapGen.properties +++ b/src/resources/WurmMapGen.properties @@ -15,6 +15,9 @@ replaceFiles=true ###Map generation variables +#How many concurrent threads to process? Lower=slower,less CPU - Higher=faster,more CPU usage +threads=2 + #Generate shading on map based on tile height mapGenerateShading=true diff --git a/src/resources/required.zip b/src/resources/required.zip index 96300887e047accdc7d70a07fd26dc7366978ce9..cc6169d7c369988a0f46ae63e298f84df4667f41 100644 GIT binary patch delta 10836 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 z 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 Jp3bN5{s%==21Wn?