├── .classpath ├── .gitignore ├── .project ├── .settings ├── org.eclipse.jdt.core.prefs ├── org.eclipse.m2e.core.prefs └── org.eclipse.wst.common.project.facet.core.xml ├── build.xml ├── config.json ├── license.txt ├── pom.xml ├── readme.md └── src ├── config ├── CommonConfig.java ├── GlobalConfig.java ├── OreConfig.java └── PlanetConfig.java ├── core ├── Generator.java ├── Main.java └── Stats.java ├── main └── resources │ └── log4j2.xml ├── map ├── MapData.java └── MapHandler.java └── xml └── XMLConfigUpdater.java /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | /target/ 3 | /build/ 4 | **.png 5 | *.sbc 6 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | Procedural Ore Generator 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.m2e.core.maven2Builder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.m2e.core.maven2Nature 22 | 23 | 24 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 3 | org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate 4 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 5 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve 6 | org.eclipse.jdt.core.compiler.compliance=1.8 7 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate 8 | org.eclipse.jdt.core.compiler.debug.localVariable=generate 9 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate 10 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 11 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 12 | org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning 13 | org.eclipse.jdt.core.compiler.source=1.8 14 | -------------------------------------------------------------------------------- /.settings/org.eclipse.m2e.core.prefs: -------------------------------------------------------------------------------- 1 | activeProfiles= 2 | eclipse.preferences.version=1 3 | resolveWorkspaceProjects=true 4 | version=1 5 | -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.common.project.facet.core.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "planetDataPath": "G:\\Steam\\steamapps\\common\\SpaceEngineers\\Content\\Data\\PlanetDataFiles", 3 | "planetGeneratorDefinitionsPathArray": ["G:\\Steam\\steamapps\\common\\SpaceEngineers\\Content\\Data\\PlanetGeneratorDefinitions.sbc", "G:\\Steam\\steamapps\\common\\SpaceEngineers\\Content\\Data\\Triton.sbc", "G:\\Steam\\steamapps\\common\\SpaceEngineers\\Content\\Data\\Pertam.sbc"], 4 | "makeColouredMaps": true, 5 | "surfaceHintMaps": true, 6 | "shape": 1, 7 | "density": 0.9, 8 | "oreTemplates": [ 9 | { 10 | "id": 1, 11 | "type": "Iron_02", 12 | "surfaceArea": 200, 13 | "startDepth": 12, 14 | "depth": 6, 15 | "testColourHex": "E00000", 16 | "centreOreTile": 2 17 | }, 18 | { 19 | "id": 2, 20 | "type": "Iron_02", 21 | "surfaceArea": 200, 22 | "startDepth": 2, 23 | "depth": 6, 24 | "testColourHex": "E00000" 25 | }, 26 | { 27 | "id": 24, 28 | "type": "Nickel_01", 29 | "surfaceArea": 80, 30 | "startDepth": 16, 31 | "depth": 6, 32 | "testColourHex": "FF9741" 33 | }, 34 | { 35 | "id": 48, 36 | "type": "Silicon_01", 37 | "surfaceArea": 80, 38 | "startDepth": 18, 39 | "depth": 6, 40 | "testColourHex": "FFFFFF" 41 | }, 42 | { 43 | "id": 72, 44 | "type": "Cobalt_01", 45 | "surfaceArea": 80, 46 | "startDepth": 30, 47 | "depth": 6, 48 | "testColourHex": "0033FF" 49 | }, 50 | { 51 | "id": 96, 52 | "type": "Silver_01", 53 | "surfaceArea": 15, 54 | "startDepth": 40, 55 | "depth": 5, 56 | "testColourHex": "A0A0A0" 57 | }, 58 | { 59 | "id": 120, 60 | "type": "Magnesium_01", 61 | "surfaceArea": 60, 62 | "startDepth": 32, 63 | "depth": 6, 64 | "testColourHex": "958CFF" 65 | }, 66 | { 67 | "id": 168, 68 | "type": "Gold_01", 69 | "surfaceArea": 5, 70 | "startDepth": 55, 71 | "depth": 4, 72 | "testColourHex": "FFD800" 73 | }, 74 | { 75 | "id": 144, 76 | "type": "Uraninite_01", 77 | "surfaceArea": 7, 78 | "density": 0.7, 79 | "startDepth": 70, 80 | "depth": 4, 81 | "surfaceHintProbability": 0.2, 82 | "testColourHex": "00FF00" 83 | }, 84 | { 85 | "id": 192, 86 | "type": "Platinum_01", 87 | "surfaceArea": 10, 88 | "density": 0.6, 89 | "startDepth": 120, 90 | "depth": 4, 91 | "surfaceHintProbability": 0.2, 92 | "testColourHex": "5D188E" 93 | } 94 | ], 95 | "planets": [ 96 | { 97 | "name": "EarthLike", 98 | "seed": 1, 99 | "surfaceHintColour": 144, 100 | "ores": [ 101 | { 102 | "id": 1, 103 | "p": 0.4 104 | }, 105 | { 106 | "id": 24, 107 | "p": 0.2 108 | }, 109 | { 110 | "id": 48, 111 | "p": 0.2 112 | }, 113 | { 114 | "id": 72, 115 | "p": 0.4 116 | }, 117 | { 118 | "id": 96, 119 | "p": 0.02 120 | }, 121 | { 122 | "id": 120, 123 | "p": 0.15 124 | }, 125 | { 126 | "id": 168 127 | }, 128 | { 129 | "id": 144, 130 | "p": 0.008 131 | }, 132 | { 133 | "id": 192 134 | } 135 | ] 136 | }, 137 | { 138 | "name": "Moon", 139 | "seed": 2, 140 | "surfaceHintColour": 176, 141 | "ores": [ 142 | { 143 | "id": 1, 144 | "p": 0.1 145 | }, 146 | { 147 | "id": 24, 148 | "p": 0.05 149 | }, 150 | { 151 | "id": 48, 152 | "p": 0.3 153 | }, 154 | { 155 | "id": 72 156 | }, 157 | { 158 | "id": 96, 159 | "p": 0.4, 160 | "planetFaces": ["BACK"] 161 | }, 162 | { 163 | "id": 120, 164 | "p": 0.05 165 | }, 166 | { 167 | "id": 168, 168 | "p": 0.2, 169 | "planetFaces": ["FRONT"] 170 | }, 171 | { 172 | "id": 144 173 | }, 174 | { 175 | "id": 192, 176 | "p": 0.005 177 | } 178 | ] 179 | }, 180 | { 181 | "name": "Mars", 182 | "seed": 3, 183 | "ores": [ 184 | { 185 | "id": 1, 186 | "p": 0.5, 187 | "surfaceArea": 300 188 | }, 189 | { 190 | "id": 24, 191 | "p": 0.05, 192 | "surfaceArea": 10 193 | }, 194 | { 195 | "id": 48, 196 | "p": 0.05, 197 | "surfaceArea": 10 198 | }, 199 | { 200 | "id": 72, 201 | "p": 0.005, 202 | "surfaceArea": 10 203 | }, 204 | { 205 | "id": 96 206 | }, 207 | { 208 | "id": 120, 209 | "p": 0.1 210 | }, 211 | { 212 | "id": 168 213 | }, 214 | { 215 | "id": 144 216 | }, 217 | { 218 | "id": 192, 219 | "p": 0.6, 220 | "surfaceArea": 25 221 | } 222 | ] 223 | }, 224 | { 225 | "name": "Europa", 226 | "seed": 4, 227 | "ores": [ 228 | { 229 | "id": 1, 230 | "p": 0.1 231 | }, 232 | { 233 | "id": 24, 234 | "p": 0.05 235 | }, 236 | { 237 | "id": 48, 238 | "p": 0.05 239 | }, 240 | { 241 | "id": 72 242 | }, 243 | { 244 | "id": 96, 245 | "p": 0.2 246 | }, 247 | { 248 | "id": 120, 249 | "p": 0.6 250 | }, 251 | { 252 | "id": 168, 253 | "p": 0.2 254 | }, 255 | { 256 | "id": 144, 257 | "p": 0.01 258 | }, 259 | { 260 | "id": 192 261 | } 262 | ] 263 | }, 264 | { 265 | "name": "Alien", 266 | "seed": 5, 267 | "surfaceHintColour": 160, 268 | "maxOreTiles": 50000, 269 | "ores": [ 270 | { 271 | "id": 1 272 | }, 273 | { 274 | "id": 24 275 | }, 276 | { 277 | "id": 48 278 | }, 279 | { 280 | "id": 72 281 | }, 282 | { 283 | "id": 96 284 | }, 285 | { 286 | "id": 120 287 | }, 288 | { 289 | "id": 168, 290 | "p": 0.4 291 | }, 292 | { 293 | "id": 144, 294 | "p": 1.0, 295 | "surfaceArea": 25 296 | }, 297 | { 298 | "id": 192 299 | } 300 | ] 301 | }, 302 | { 303 | "name": "Titan", 304 | "seed": 6, 305 | "ores": [ 306 | { 307 | "id": 1, 308 | "p": 0.2 309 | }, 310 | { 311 | "id": 24, 312 | "p": 0.7 313 | }, 314 | { 315 | "id": 48, 316 | "p": 0.2 317 | }, 318 | { 319 | "id": 72 320 | }, 321 | { 322 | "id": 96, 323 | "p": 0.2 324 | }, 325 | { 326 | "id": 120 327 | }, 328 | { 329 | "id": 168 330 | }, 331 | { 332 | "id": 144, 333 | "p": 0.1, 334 | "surfaceArea": 5 335 | }, 336 | { 337 | "id": 192, 338 | "p": 0.3 339 | } 340 | ] 341 | }, 342 | { 343 | "name": "Triton", 344 | "seed": 1, 345 | "surfaceHintColour": 144, 346 | "ores": [ 347 | { 348 | "id": 1, 349 | "p": 0.4 350 | }, 351 | { 352 | "id": 24, 353 | "p": 0.2 354 | }, 355 | { 356 | "id": 48, 357 | "p": 0.2 358 | }, 359 | { 360 | "id": 72, 361 | "p": 0.4 362 | }, 363 | { 364 | "id": 96, 365 | "p": 0.02 366 | }, 367 | { 368 | "id": 120, 369 | "p": 0.15 370 | }, 371 | { 372 | "id": 168 373 | }, 374 | { 375 | "id": 144, 376 | "p": 0.008 377 | }, 378 | { 379 | "id": 192 380 | } 381 | ] 382 | }, 383 | { 384 | "name": "Pertam", 385 | "seed": 1, 386 | "surfaceHintColour": 144, 387 | "ores": [ 388 | { 389 | "id": 1, 390 | "p": 0.4 391 | }, 392 | { 393 | "id": 24, 394 | "p": 0.2 395 | }, 396 | { 397 | "id": 48, 398 | "p": 0.2 399 | }, 400 | { 401 | "id": 72, 402 | "p": 0.4 403 | }, 404 | { 405 | "id": 96, 406 | "p": 0.02 407 | }, 408 | { 409 | "id": 120, 410 | "p": 0.15 411 | }, 412 | { 413 | "id": 168 414 | }, 415 | { 416 | "id": 144, 417 | "p": 0.008 418 | }, 419 | { 420 | "id": 192 421 | } 422 | ] 423 | } 424 | ] 425 | } -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright [2019] [Andrew Srbic] 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | OrePatchGenerator 6 | SpaceEngineers 7 | 0.1 8 | 9 | 10 | 11 | com.google.code.gson 12 | gson 13 | 2.8.2 14 | 15 | 16 | 17 | org.apache.commons 18 | commons-math3 19 | 3.3 20 | 21 | 22 | 23 | xom 24 | xom 25 | 1.2.5 26 | 27 | 28 | org.apache.logging.log4j 29 | log4j-api 30 | 2.11.2 31 | 32 | 33 | org.apache.logging.log4j 34 | log4j-core 35 | 2.16.0 36 | 37 | 38 | 39 | com.google.guava 40 | guava 41 | 28.0-jre 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Procedural Ore Generator for Space Engineers 2 | 3 | ## Description 4 | 5 | This program will randomly generate ore patches in PlanetDataFiles for existing planets in Space Engineers. The type, size, shape, denity etc of these ores can be configured for each planet. Ore templates can also be created at the global level and overwritten where needed. Optionally, the ids and other settings for each ore can also be populated in the PlanetGeneratorDefinitions.sbc file. 6 | 7 | 8 | ## How to use 9 | 10 | Requires 64-bit Java version 1.8 or later. 11 | 12 | 1. Locate the PlanetDataFiles directory containing the planet data directories for the planets you want to use as a base 13 | - eg `C:\\Program Files (x86)\\Steam\\steamapps\\common\\SpaceEngineers\\Content\\Data\\PlanetDataFiles\\` 14 | 2. Modify settings in config.json to suit your needs 15 | - planetDataPath should equal the path from step 1 16 | - The "name" for each planet config should be exactly the same as its corresponding directory name in the PlanetDataFiles from step 1 17 | - planetGeneratorDefinitionsPath (or planetGeneratorDefinitionsPathArray) should be the location of PlanetGeneratorDefinitions.sbc you want to use as a base 18 | - This is in the Data directory above the directory from step 1 19 | - All settings will cascade from oreTemplates -> all ores, global -> each planet then each planet -> its ores. Settings will not be overwritten if already set. This can minimise the amount of config you need to write. 20 | - eg. `"id": 1, "type": "Iron_02"` in an ore template will set all ores on all planets which have the id 1 to have the default type Iron_02 21 | - eg. "depth": 12 at the top level will set the default depth of all ores to 12 22 | - Be very careful with the "" {} [] , characters. Make sure brackets are always closed and all elements but the last have a comma at the end of the line 23 | - See https://www.json.org/ 24 | 3. Run the program with run.bat 25 | - You might want to run with `"makeColouredMaps": true` until you get the output you want. Then run again with `"makeColouredMaps": false` or manually remove all of the coloured images from each planet directory 26 | - Writing/compressing pngs can take a surprisingly long time 27 | 4. Copy the contents of the `PlanetDataFiles` directory from step 1. to a new directory in the Space Engineers mod directory (`C:\Users\\AppData\Roaming\SpaceEngineers\Mods\`) 28 | - Space Engineers will not load a mod if it touches a planet but does not contain the heightmaps (`front.png` etc), even if they are present elsewhere. 29 | 5. Copy the outputs of the generator tool, `PlanetDataFiles` and `PlanetGeneratorDefinition.sbc`, to the Space Engineers mod direcory from the step above, overwriting any files. 30 | - There may be multiple `.sbc` files output. All should be copied. 31 | 6. Add the directory you created to the mod list of a game in Space Engineers and test. You do not need to create a new world. 32 | 7. Select the directory containing your mod in the mod list and click Publish in the bottom right to upload it to the Steam Workshop 33 | - The program will print out a formatted table detailing the count of tiles generated for each type of ore (Everything between [table]...[/table]). You can paste this directly into the steam item description. 34 | 35 | tl;dr: run.bat 36 | 37 | 38 | ## Configuration Options 39 | - planetDataPath (default null): Directory of PlanetDataFiles containing data for each planet. If null, no ore generation will occur. 40 | - planetGeneratorDefinitionsPath (default null): Directory of PlanetGeneratorDefinitions.sbc to insert entries for configured ores. If null, no definitions file will be produced. Superseded by planetGeneratorDefinitionsPathArray but kept for older configs. 41 | - planetGeneratorDefinitionsPathArray (default empty): Same as planetGeneratorDefinitionsPath but allows you to specify multiple .sbc files. All of the specified files will be searched for any planet config in config.json. 42 | - planetDataOutputPath (default "./PlanetDataFiles/"): Output path for planet data. Not used if no planetDataPath is not set. Directory will be created if it doesn't exist. 43 | - planetGeneratorDefinitionsOutputPath (default "./"): Output path for specified .sbc file(s). Not used if planetGeneratorDefinitionsPath is not set. Directory will be created if it doesn't exist. 44 | - makeColouredMaps (default true): Used at the global level to determine if colour coded test maps will be generated 45 | - surfaceHintMaps (default true): Used at the global level to determine whether hint maps will be generated 46 | - countExistingTiles (default true): If true, the existing ore tiles on the map will be counted before they get cleared 47 | 48 | - maxOreTiles (default 100000): total maximum ore tiles/pixels to generate on a planet. The actual number could be less than this because overwritten tiles are still counted 49 | - maxOrePatches (default 1000): total maximum ore patches to generate on a planet 50 | 51 | - p (default 0.0): Chance of this ore being selected to spawn. effective chance to spawn is (total of all p on this planet)/p. 52 | - surfaceArea (default 20): Maximum number of tiles in an ore patch. Each tiles is about 30x30 square metres on EarthLike. This will likely be different for different sized planets. 53 | - density (default 1.0): Lower that 1 increases the area and reduces density. Higher is the opposite. 54 | - startDepth (default 10): Starting depth. Ore will fill from this down "depth" metres. 55 | - depth (default 6): Vertical size of the ore patch. 56 | - shape (default 1): Determines the generator which will be used. See the Generator Shapes section for information on each one. 57 | - surfaceAreaVariance (default 0.4): Add random variance to the surfaceArea. 58 | - avoidIce (default true): If true, this ore will not spawn on/under ice lakes. 59 | - centreOreTile (default -1): If set to a positive number, the given id will be used as the centre tile of the ore patch. This can be used to generate a single hint tile of ore close to the surface while the rest of the patch is far below, out of ore detector range. Just add the ore you want to be at the centre to the planet's config with "p": 0.0 so it doesn't spawn elsewhere. 60 | - seed (default 7): Random seed used at the planet level. With the exact same configuration (including seed), the exact same ore patches should be generated. 61 | - surfaceAreaMultiplier (default: 1.0): Multiplier to increase/decrease surface area 62 | - surfaceHintProbability (default 1.0): Chance that surface hints will show above each ore tile. Set to 0.0 to have no surface hint. 63 | - surfaceHintColour (default 128): Colour of surface hints. This seems to change per planet/biome in vanilla. 64 | - testColourHex (default "FFFFFF"): 24-bit hexadecimal rgb colour which this ore will show as in the colour coded test maps. 65 | - mappingFileTargetColour (default "#616c83"): Required in PlanetGeneratorDefinitions.sbc for each ore. I'm not actually sure what this does but most of the vanilla ores have the default for this. 66 | - mappingFileColourInfluence (default 15): Also unsure what this does. Similar usage in PlanetGeneratorDefinitions.sbc but is always 15. 67 | - planetFaces (`default ["FRONT", "LEFT", "RIGHT", "UP", "DOWN", "BACK"]`): If not specified, the ore will generate on all faces of the planet. If specified, the ore will only be generated on a randomly selected face from the provided list. A value of `["UP","UP","LEFT"]` will give a 66% chance for the ore to spawn on the top of the planet and a 33% chance for it to spawn on the left face. A value of `["FRONT"]` will only allow the ore to spawn on the front face of the planet. A sphere only has one face but Space Engineers cuts it up into 6 sides for ore/material definition purposes. This field allows you to only spawn certain ores in certain areas of the planet, forcing the player to go to the far side to get the ore they need. 68 | 69 | ## Generator Shapes 70 | 71 | 1. Random: Will randomly select one of the below for each patch. Excludes 7. because it doesn't quite look natural. 72 | 2. Gaussain random: Will randomly select tiles with a strong tendency towards the centre of the patch. This typically leads to a dense centre with some isolated ore tiles around it. 73 | 3. Snake: Works a bit like the game snake. One of 4 random directions (up, down, left, right) will be chosen. From this new tile, a random direction is chosen again. Repeat. This is the simplest algorithm and leads to shapes most like vanilla. 74 | 4. Step Gaussian lines: Will randomly select tiles with a strong tendency towards the centre of the patch. A line is then drawn to it by stepping up, down, left or right. This is probably my favourite. 75 | 5. Fuzzy linearly interpolated Gaussian lines: Will randomly select tiles with a strong tendency towards the centre of the patch and draw a line from the current tile to it. Leads to some intersting shapes but patches are often smaller than specified. 76 | 6. Circles: Draws circles shapes which tend to be more sparse the further from the centre the tile is. If density is 1.0, the circle will be solid and not sparse at all. 77 | 7. Sparse diamonds: Doesn't look natural, probably don't use. Ignores density. 78 | -------------------------------------------------------------------------------- /src/config/CommonConfig.java: -------------------------------------------------------------------------------- 1 | package config; 2 | 3 | public abstract class CommonConfig { 4 | 5 | 6 | public enum PlanetFace { 7 | FRONT(0, "front"), 8 | LEFT(1, "left"), 9 | RIGHT(2, "right"), 10 | UP(3, "up"), 11 | DOWN(4, "down"), 12 | BACK(5, "back"); 13 | 14 | public static final PlanetFace[] ALL = {PlanetFace.FRONT, PlanetFace.LEFT, PlanetFace.RIGHT, PlanetFace.UP, PlanetFace.DOWN, PlanetFace.BACK}; 15 | 16 | public int index; 17 | public String name; 18 | PlanetFace(int index, String name) { 19 | this.index = index; 20 | this.name = name; 21 | 22 | } 23 | } 24 | 25 | public float surfaceAreaMultiplier = -1; 26 | public float surfaceAreaVariance = -1; 27 | public int maxOreTiles = -1; 28 | public int maxOrePatches = -1; 29 | public Long seed = null; 30 | public double p = -1; 31 | public float density = -1; 32 | public int shape = -1; 33 | public Boolean avoidIce = null; 34 | public int surfaceArea = -1; 35 | public Boolean surfaceHintMaps = null; 36 | public float surfaceHintProbability = -1; 37 | public int surfaceHintColour = -1; 38 | public Boolean makeColouredMaps = null; 39 | public String testColourHex = null; 40 | public int startDepth = -1; 41 | public int depth = -1; 42 | public String mappingFileTargetColour = null; 43 | public int mappingFileColourInfluence = -1; 44 | public int centreOreTile = -1; 45 | public PlanetFace[] planetFaces = null; 46 | 47 | public void cascadeSettings(CommonConfig other) { 48 | if(surfaceAreaMultiplier == -1) { 49 | surfaceAreaMultiplier = other.surfaceAreaMultiplier; 50 | } 51 | if(surfaceAreaVariance == -1) { 52 | surfaceAreaVariance = other.surfaceAreaVariance; 53 | } 54 | if(maxOreTiles == -1) { 55 | maxOreTiles = other.maxOreTiles; 56 | } 57 | if(maxOrePatches == -1) { 58 | maxOrePatches = other.maxOrePatches; 59 | } 60 | if(seed == null) { 61 | seed = other.seed; 62 | } 63 | if(p == -1) { 64 | p = other.p; 65 | } 66 | if(density == -1) { 67 | density = other.density; 68 | } 69 | if(shape == -1) { 70 | shape = other.shape; 71 | } 72 | if(avoidIce == null) { 73 | avoidIce = other.avoidIce; 74 | } 75 | if(surfaceArea == -1) { 76 | surfaceArea = other.surfaceArea; 77 | } 78 | if(surfaceHintMaps == null) { 79 | surfaceHintMaps = other.surfaceHintMaps; 80 | } 81 | if(surfaceHintProbability == -1) { 82 | surfaceHintProbability = other.surfaceHintProbability; 83 | } 84 | if(surfaceHintColour == -1) { 85 | surfaceHintColour = other.surfaceHintColour; 86 | } 87 | if(makeColouredMaps == null) { 88 | makeColouredMaps = other.makeColouredMaps; 89 | } 90 | if(testColourHex == null) { 91 | testColourHex = other.testColourHex; 92 | } 93 | if(startDepth == -1) { 94 | startDepth = other.startDepth; 95 | } 96 | if(depth == -1) { 97 | depth = other.depth; 98 | } 99 | if(mappingFileTargetColour == null) { 100 | mappingFileTargetColour = other.mappingFileTargetColour; 101 | } 102 | if(mappingFileColourInfluence == -1) { 103 | mappingFileColourInfluence = other.mappingFileColourInfluence; 104 | } 105 | if(centreOreTile == -1) { 106 | centreOreTile = other.centreOreTile; 107 | } 108 | if(planetFaces == null) { 109 | planetFaces = other.planetFaces; 110 | } 111 | } 112 | 113 | public void setDefaults() { 114 | surfaceAreaMultiplier = 1.0f; 115 | surfaceAreaVariance = 0.4f; 116 | maxOreTiles = 100000; 117 | maxOrePatches = 1000; 118 | seed = 7l; 119 | p = 0.0; 120 | density = 1.0f; 121 | shape = 1; 122 | avoidIce = true; 123 | surfaceArea = 20; 124 | surfaceHintMaps = true; 125 | surfaceHintProbability = 1.0f; 126 | surfaceHintColour = 128; 127 | makeColouredMaps = true; 128 | testColourHex = "FFFFFF"; 129 | startDepth = 10; 130 | depth = 6; 131 | mappingFileTargetColour = "#616c83"; 132 | mappingFileColourInfluence = 15; 133 | centreOreTile = -1; 134 | planetFaces = PlanetFace.ALL; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/config/GlobalConfig.java: -------------------------------------------------------------------------------- 1 | package config; 2 | 3 | import java.io.IOException; 4 | import java.lang.reflect.Type; 5 | import java.nio.charset.Charset; 6 | import java.nio.file.Files; 7 | import java.nio.file.Paths; 8 | import java.util.ArrayList; 9 | import java.util.HashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | import javax.swing.JOptionPane; 14 | 15 | import org.apache.logging.log4j.LogManager; 16 | import org.apache.logging.log4j.Logger; 17 | 18 | import com.google.gson.Gson; 19 | import com.google.gson.GsonBuilder; 20 | import com.google.gson.reflect.TypeToken; 21 | public class GlobalConfig extends CommonConfig { 22 | public static final Logger logger = LogManager.getLogger("GlobalConfig"); 23 | 24 | public String planetDataPath = null; 25 | public String planetDataOutputPath = null; 26 | public String planetGeneratorDefinitionsOutputPath = null; 27 | public Boolean countExistingTiles = null; 28 | public String planetGeneratorDefinitionsPath = null; 29 | public List planetGeneratorDefinitionsPathArray = new ArrayList(); 30 | public Boolean concurrentImageWrite = true; 31 | public OreConfig[] oreTemplates; 32 | public PlanetConfig[] planets; 33 | 34 | public static GlobalConfig loadConfig(String path) { 35 | Gson gson = new Gson(); 36 | byte[] encoded; 37 | try { 38 | encoded = Files.readAllBytes(Paths.get(path)); 39 | return gson.fromJson(new String(encoded, Charset.defaultCharset()), GlobalConfig.class); 40 | } catch (Exception e) { 41 | // TODO Auto-generated catch block 42 | logger.error("Failed to load config file: " + e.getLocalizedMessage()); 43 | } 44 | return null; 45 | } 46 | 47 | public static List loadConfigs(String path) { 48 | Gson gson = new Gson(); 49 | byte[] encoded; 50 | try { 51 | encoded = Files.readAllBytes(Paths.get(path)); 52 | Type listType = new TypeToken>(){}.getType(); 53 | return gson.fromJson(new String(encoded, Charset.defaultCharset()), listType); 54 | } catch (IOException e) { 55 | // TODO Auto-generated catch block 56 | logger.error(e); 57 | JOptionPane.showMessageDialog(null, "Could not find the specified config file:\n" + path, "File Read Error", JOptionPane.ERROR_MESSAGE); 58 | } 59 | return null; 60 | } 61 | 62 | public GlobalConfig() { 63 | 64 | } 65 | 66 | public String toJSON() { 67 | Gson gson = new GsonBuilder().setPrettyPrinting().create(); 68 | return gson.toJson(this); 69 | } 70 | 71 | public void cascadeSettings() { 72 | //add opacity 73 | Map oreLookup = new HashMap(); 74 | for(OreConfig oreTemplate : oreTemplates) { 75 | oreTemplate.makeColouredMaps = null; 76 | oreTemplate.surfaceHintMaps = null; 77 | oreLookup.put(oreTemplate.id, oreTemplate); 78 | } 79 | surfaceHintColour = 0xFF000000 | (surfaceHintColour << 16); 80 | for(PlanetConfig planetConfig : planets) { 81 | for(OreConfig ore : planetConfig.ores) { 82 | if(oreLookup.containsKey(ore.id)) { 83 | ore.cascadeSettings(oreLookup.get(ore.id)); 84 | } 85 | } 86 | planetConfig.cascadeSettings(this); 87 | } 88 | } 89 | 90 | public void setDefaults() { 91 | super.setDefaults(); 92 | planetDataOutputPath = "./PlanetDataFiles/"; 93 | countExistingTiles = true; 94 | planetGeneratorDefinitionsOutputPath = "./"; 95 | concurrentImageWrite = true; 96 | } 97 | 98 | public void copyDefaults(GlobalConfig other) { 99 | super.cascadeSettings(other); 100 | if(planetDataOutputPath == null) { 101 | planetDataOutputPath = other.planetDataOutputPath; 102 | } 103 | if(planetGeneratorDefinitionsOutputPath == null) { 104 | planetGeneratorDefinitionsOutputPath = other.planetGeneratorDefinitionsOutputPath; 105 | } 106 | if(countExistingTiles == null) { 107 | countExistingTiles = other.countExistingTiles; 108 | } 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /src/config/OreConfig.java: -------------------------------------------------------------------------------- 1 | package config; 2 | 3 | import static map.MapData.OPAQUE; 4 | 5 | public class OreConfig extends CommonConfig { 6 | public String type = null; 7 | public int id; 8 | public transient Integer testColour = null; 9 | 10 | public OreConfig() { 11 | 12 | } 13 | 14 | public void cascadeSettings(CommonConfig other) { 15 | if(surfaceHintColour != -1) { 16 | surfaceHintColour = 0xFF000000 | (surfaceHintColour << 16); 17 | } 18 | super.cascadeSettings(other); 19 | if(other instanceof OreConfig) { 20 | OreConfig otherOre = (OreConfig)other; 21 | if(type == null) { 22 | type = otherOre.type; 23 | } 24 | } 25 | if(testColourHex != null) { 26 | testColour = Integer.parseInt(testColourHex, 16) | OPAQUE; 27 | } 28 | } 29 | 30 | public String getOreName() { 31 | int suffixStartIndex = type.indexOf('_'); 32 | if(suffixStartIndex != -1 && suffixStartIndex > 0) { 33 | return type.substring(0, suffixStartIndex); 34 | } 35 | else { 36 | return type; 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/config/PlanetConfig.java: -------------------------------------------------------------------------------- 1 | package config; 2 | 3 | public class PlanetConfig extends CommonConfig { 4 | public String name; 5 | public OreConfig[] ores; 6 | 7 | public void cascadeSettings(CommonConfig parent) { 8 | if(surfaceHintColour != -1) { 9 | surfaceHintColour = 0xFF000000 | (surfaceHintColour << 16); 10 | } 11 | super.cascadeSettings(parent); 12 | for(OreConfig ore : ores) { 13 | ore.makeColouredMaps = null; 14 | ore.surfaceHintMaps = null; 15 | ore.cascadeSettings(this); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/core/Generator.java: -------------------------------------------------------------------------------- 1 | package core; 2 | 3 | import static map.MapData.ICE_FILTER; 4 | import static map.MapData.ORE_EXCLUDER; 5 | import static map.MapData.ORE_FILTER; 6 | 7 | import java.awt.image.BufferedImage; 8 | import java.util.ArrayList; 9 | import java.util.HashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | import java.util.Random; 13 | import java.util.concurrent.ConcurrentHashMap; 14 | 15 | import org.apache.commons.math3.distribution.EnumeratedDistribution; 16 | import org.apache.commons.math3.random.JDKRandomGenerator; 17 | import org.apache.commons.math3.random.RandomGenerator; 18 | import org.apache.commons.math3.util.Pair; 19 | import org.apache.logging.log4j.LogManager; 20 | import org.apache.logging.log4j.Logger; 21 | 22 | import config.OreConfig; 23 | import config.PlanetConfig; 24 | import map.MapData; 25 | public class Generator { 26 | public static final int[][] GEN_SIDES = {{1,0},{0,1},{-1,0},{0,-1}}; 27 | public static final int[][] GEN_SIDES_ALL = {{1,0},{0,1},{-1,0},{0,-1}, {1,1},{1,-1},{-1,1},{-1,-1}}; 28 | public static final Logger logger = LogManager.getLogger("Generator"); 29 | public static final int RANDOM_SHAPE = 1; 30 | public static final int[] SHAPES = {2,3,4,5,6}; 31 | Map> tileCountMap = new ConcurrentHashMap<>(); 32 | 33 | public long generatePatches(MapData mapData, PlanetConfig planetConfig) { 34 | Map planetTileCountMap = new HashMap(); 35 | List> tempPairedList = new ArrayList>(); 36 | Map oreTileCounts = new HashMap(); 37 | for(OreConfig ore : planetConfig.ores) { 38 | tempPairedList.add(new Pair(ore, ore.p)); 39 | oreTileCounts.put(ore, new Long(0)); 40 | } 41 | EnumeratedDistribution oreDist = new EnumeratedDistribution(tempPairedList); 42 | Random rand = new Random(planetConfig.seed); 43 | oreDist.reseedRandomGenerator(rand.nextLong()); 44 | long generatedTiles = 0; 45 | for(int i = 0; i < planetConfig.maxOrePatches && generatedTiles < planetConfig.maxOreTiles; ++i) { 46 | OreConfig ore = oreDist.sample(); 47 | int generatedTilesForOre = generateOrePatch(mapData, ore, rand); 48 | if(planetTileCountMap.containsKey(ore.getOreName())) { 49 | planetTileCountMap.put(ore.getOreName(), planetTileCountMap.get(ore.getOreName()).longValue() + generatedTilesForOre); 50 | } 51 | else { 52 | planetTileCountMap.put(ore.getOreName(), new Long(generatedTilesForOre)); 53 | } 54 | generatedTiles += generatedTilesForOre; 55 | oreTileCounts.put(ore, oreTileCounts.get(ore).longValue() + generatedTilesForOre); 56 | } 57 | 58 | for(OreConfig ore : oreTileCounts.keySet()) { 59 | logger.info(planetConfig.name + ":\t\tTiles generated for ore " + ore.type + " (id:" + ore.id + ") with testColour " + Integer.toHexString(ore.testColour) + " : " + oreTileCounts.get(ore)); 60 | } 61 | tileCountMap.put(planetConfig.name, planetTileCountMap); 62 | return generatedTiles; 63 | } 64 | 65 | private int generateOrePatch(MapData mapData, OreConfig ore, Random rand) { 66 | // The ugly bit 67 | int tilesAdded = 0; 68 | int mapSize = mapData.getMapSize(); 69 | int mapIndex = ore.planetFaces[rand.nextInt(ore.planetFaces.length)].index; 70 | int startColIndex = rand.nextInt(mapSize); 71 | int startRowIndex = rand.nextInt(mapSize); 72 | Random tileRand = new Random(rand.nextLong()); 73 | 74 | Random hintRand = new Random(rand.nextLong()); 75 | int oreShape = ore.shape; 76 | if(oreShape == RANDOM_SHAPE) { 77 | oreShape = SHAPES[tileRand.nextInt(SHAPES.length)]; 78 | } 79 | int patchSize = Math.round((ore.surfaceArea * ore.surfaceAreaMultiplier) * (1 + (tileRand.nextFloat() * 2 - 1) * ore.surfaceAreaVariance)); 80 | float patchDiameter = Math.round(Math.sqrt((double)patchSize) / (ore.density)); 81 | float patchRadius = patchDiameter / 2f; 82 | float squash = tileRand.nextFloat() * 1.0f + 0.75f; 83 | float horizontalSquash; 84 | float verticalSquash; 85 | if(tileRand.nextBoolean()) { 86 | horizontalSquash = squash; 87 | verticalSquash = 1 / squash; 88 | } 89 | else { 90 | verticalSquash = squash; 91 | horizontalSquash = 1 / squash; 92 | } 93 | int width = Math.round(patchDiameter * horizontalSquash); 94 | int height = Math.round(patchDiameter * verticalSquash); 95 | int oreId = ore.centreOreTile >= 0 ? ore.centreOreTile : ore.id; 96 | 97 | boolean avoidIce = ore.avoidIce; 98 | boolean isSurfaceHint = ore.surfaceHintMaps && ore.surfaceHintProbability > 0; 99 | int colIndex = startColIndex; 100 | int rowIndex = startRowIndex; 101 | int centreOreTile = ore.centreOreTile; 102 | BufferedImage img = mapData.images[mapIndex]; 103 | BufferedImage hintImg = mapData.surfaceHintImages[mapIndex]; 104 | BufferedImage colouredImg = mapData.colouredMaps[mapIndex]; 105 | int iterations = 0; 106 | int maxIterations = patchSize * 30; 107 | //for line shapes 108 | int targetCol = startColIndex; 109 | int targetRow = startRowIndex; 110 | double linearCoefficient = 0; 111 | double linearXDist = 0; 112 | double linearXIncrement = 0; 113 | int sourceColIndex = 0; 114 | int sourceRowIndex = 0; 115 | double colDiff = 0; 116 | double rowDiff = 0; 117 | boolean colMet, rowMet; 118 | int lastColIndex = startColIndex; 119 | int lastRowIndex = startRowIndex; 120 | int halfWidth = width/2; 121 | int halfHeight = height/2; 122 | int x = 0 - halfWidth; 123 | int y = 0 - halfHeight; 124 | int layer = 1; 125 | int foundCount = 0; 126 | boolean found; 127 | do { 128 | boolean paintTile = true; 129 | // add patch tiles 130 | // each tile/pixel corresponds to a 30x30m patch of ore in game - measured on EarthLike 131 | if(rowIndex >= mapSize || rowIndex < 0 || colIndex >= mapSize || colIndex < 0) { 132 | paintTile = false; 133 | } 134 | if(paintTile) { 135 | int pixRGB = img.getRGB(colIndex, rowIndex); 136 | if((pixRGB & ORE_FILTER) == oreId || (centreOreTile != -1 && (pixRGB & ORE_FILTER) == centreOreTile)) { 137 | paintTile = false; 138 | } 139 | if((avoidIce && ((pixRGB & ICE_FILTER) == ICE_FILTER))) { 140 | if(iterations == 0) { 141 | //If starting on an ice lake, abort 142 | return 0; 143 | } 144 | else { 145 | paintTile = false; 146 | } 147 | } 148 | } 149 | if(paintTile) { 150 | img.setRGB(colIndex, rowIndex, (img.getRGB(colIndex, rowIndex) & ORE_EXCLUDER) | oreId); 151 | ++tilesAdded; 152 | if(ore.makeColouredMaps) { 153 | colouredImg.setRGB(colIndex, rowIndex, ore.testColour); 154 | } 155 | if(isSurfaceHint && hintImg != null && hintRand.nextFloat() < ore.surfaceHintProbability) { 156 | hintImg.setRGB(colIndex, rowIndex, ore.surfaceHintColour); 157 | } 158 | } 159 | if(centreOreTile != -1 && iterations == 0) { 160 | oreId = ore.id; 161 | } 162 | 163 | 164 | // Handle different shapes 165 | switch(oreShape) { 166 | case 7: 167 | // diamonds 168 | found = false; 169 | while(!found) { 170 | for(int i = 0; i < GEN_SIDES_ALL.length && !found;++i) { 171 | int colInc = GEN_SIDES_ALL[i][0]; 172 | int rowInc = GEN_SIDES_ALL[i][1]; 173 | colDiff = Math.abs(startColIndex - (colIndex + colInc)); 174 | rowDiff = Math.abs(startRowIndex - (rowIndex + rowInc)); 175 | if(colDiff + rowDiff == layer && !(lastColIndex == colIndex + colInc && lastRowIndex == rowIndex + rowInc)) { 176 | 177 | lastColIndex = colIndex; 178 | lastRowIndex = rowIndex; 179 | //paint it 180 | colIndex += colInc; 181 | rowIndex += rowInc; 182 | 183 | ++foundCount; 184 | double weightedColDiff = colDiff * horizontalSquash; 185 | double weightedRowDiff = rowDiff * verticalSquash; 186 | double crowSquared = weightedColDiff * weightedColDiff + weightedRowDiff * weightedRowDiff; 187 | if(Math.min(crowSquared, patchRadius * patchRadius * 2/3) < (tileRand.nextDouble() * patchRadius * patchRadius)) { 188 | found = true; 189 | } 190 | } 191 | } 192 | if((foundCount >= (4 * layer))) { 193 | foundCount = foundCount % (4 * layer); 194 | ++layer; 195 | } 196 | } 197 | break; 198 | case 6: 199 | // sparse circles 200 | found = false; 201 | while(!found) { 202 | while((x <= halfWidth) && !found) { 203 | while((y <= halfHeight) && !found) { 204 | colDiff = startColIndex - x; 205 | rowDiff = startRowIndex - y; 206 | double crowDist = Math.sqrt((x * x) + (y * y)); 207 | if(Math.abs(crowDist - (double)layer) <= 1.0d) { 208 | if(tileRand.nextDouble() /*- (1f / (float)Math.min(layer, patchRadius))*/ <= ore.density) { 209 | colIndex = startColIndex + x; 210 | rowIndex = startRowIndex + y; 211 | found = true; 212 | } 213 | } 214 | ++y; 215 | } 216 | if(y > halfHeight) { 217 | y = 0 - halfHeight; 218 | ++x; 219 | } 220 | } 221 | // for(; y<=halfHeight && !found; y++) { 222 | // for(; x<=halfWidth && !found; x++) { 223 | // if(x*x*halfHeight*halfHeight+y*y*halfWidth*halfWidth <= halfHeight*halfHeight*halfWidth*halfWidth) { 224 | // colIndex = startColIndex + x; 225 | // rowIndex = startRowIndex + y; 226 | // found = true; 227 | // } 228 | // } 229 | // } 230 | if(!found) { 231 | ++layer; 232 | x = 0 - halfWidth; 233 | y = 0 - halfWidth; 234 | break; 235 | } 236 | } 237 | break; 238 | case 5: 239 | // fuzzy gaussian line 240 | colMet = (colIndex >= targetCol && sourceColIndex <= targetCol) || (colIndex <= targetCol && sourceColIndex >= targetCol); 241 | rowMet = (rowIndex >= targetRow && sourceRowIndex <= targetRow) || (rowIndex <= targetRow && sourceRowIndex >= targetRow); 242 | if(colMet && rowMet) { 243 | //new target 244 | targetCol = startColIndex + Math.round((float)(tileRand.nextFloat() - 0.5d) * width); 245 | targetRow = startRowIndex + Math.round((float)(tileRand.nextFloat() - 0.5d) * height); 246 | sourceColIndex = colIndex; 247 | sourceRowIndex = rowIndex; 248 | colDiff = targetCol - colIndex; 249 | rowDiff = targetRow - rowIndex; 250 | //dodge x/0 251 | linearCoefficient = rowDiff / (colDiff == 0d ? colDiff + 0.4d : colDiff); 252 | linearXIncrement = (colDiff / Math.abs(rowDiff)) * 0.2d; 253 | linearXDist = 0; 254 | } 255 | colIndex = (int)Math.round(linearXDist + (double)tileRand.nextInt(3)-1) + sourceColIndex; 256 | rowIndex = (int)Math.round((linearXDist*linearCoefficient) + (double)tileRand.nextInt(3)-1) + sourceRowIndex; 257 | linearXDist += linearXIncrement; 258 | 259 | break; 260 | case 4: 261 | // step Gaussian lines 262 | colMet = (colIndex >= targetCol && sourceColIndex <= targetCol) || (colIndex <= targetCol && sourceColIndex >= targetCol); 263 | rowMet = (rowIndex >= targetRow && sourceRowIndex <= targetRow) || (rowIndex <= targetRow && sourceRowIndex >= targetRow); 264 | 265 | if(colMet && rowMet) { 266 | // new target 267 | targetCol = startColIndex + Math.round((float)(tileRand.nextGaussian() - 0.5d) * width / 3); 268 | targetRow = startRowIndex + Math.round((float)(tileRand.nextGaussian() - 0.5d) * height / 3); 269 | sourceColIndex = colIndex; 270 | sourceRowIndex = rowIndex; 271 | } 272 | colDiff = targetCol - colIndex; 273 | rowDiff = targetRow - rowIndex; 274 | int colSign = colDiff >= 0 ? 1 : -1; 275 | int rowSign = rowDiff >= 0 ? 1 : -1; 276 | colDiff = Math.abs(colDiff); 277 | rowDiff = Math.abs(rowDiff); 278 | if(colDiff > rowDiff) { 279 | colIndex += colSign; 280 | } 281 | else if(colDiff < rowDiff) { 282 | rowIndex += rowSign; 283 | } 284 | else { 285 | if(tileRand.nextBoolean()) { 286 | colIndex += colSign; 287 | } 288 | else { 289 | rowIndex += rowSign; 290 | } 291 | } 292 | break; 293 | case 3: 294 | // snek 295 | int[] side = GEN_SIDES[tileRand.nextInt(GEN_SIDES.length)]; 296 | 297 | if(colIndex > (startColIndex + width/2) || colIndex < (startColIndex - width/2) || 298 | rowIndex > (startRowIndex + height/2) || rowIndex < (startRowIndex - height/2)) { 299 | colIndex = startColIndex + side[0]; 300 | rowIndex = startRowIndex + side[1]; 301 | } 302 | else { 303 | colIndex = colIndex + side[0]; 304 | rowIndex = rowIndex + side[1]; 305 | } 306 | break; 307 | default: 308 | case 2: 309 | // gaussian 310 | colIndex = startColIndex + Math.round((float)((tileRand.nextGaussian() - 0.5d) * patchDiameter *(1f/3f)) * horizontalSquash); 311 | rowIndex = startRowIndex + Math.round((float)((tileRand.nextGaussian() - 0.5d) * patchDiameter * (1f/3f)) * verticalSquash); 312 | break; 313 | 314 | } 315 | } while(tilesAdded < patchSize && ++iterations < maxIterations); 316 | 317 | return tilesAdded; 318 | } 319 | 320 | 321 | } 322 | -------------------------------------------------------------------------------- /src/core/Main.java: -------------------------------------------------------------------------------- 1 | package core; 2 | 3 | 4 | import java.io.File; 5 | import java.io.FilenameFilter; 6 | import java.io.StringWriter; 7 | import java.nio.file.Paths; 8 | import java.util.*; 9 | import java.util.concurrent.*; 10 | 11 | import org.apache.logging.log4j.LogManager; 12 | import org.apache.logging.log4j.Logger; 13 | 14 | import com.google.gson.Gson; 15 | import com.google.gson.GsonBuilder; 16 | 17 | import config.GlobalConfig; 18 | import config.PlanetConfig; 19 | import map.MapData; 20 | import map.MapHandler; 21 | import xml.XMLConfigUpdater; 22 | 23 | public class Main { 24 | public static final Logger logger = LogManager.getLogger("Main"); 25 | 26 | GlobalConfig config; 27 | MapData mapData; 28 | Generator generator; 29 | XMLConfigUpdater xmlUpdater; 30 | ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); 31 | ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); 32 | Set> generatefutures = new HashSet<>(); 33 | Map imagefutures = new ConcurrentHashMap<>(); 34 | 35 | public static void main(String[] args) throws Exception { 36 | try { 37 | new Main().run(); 38 | } 39 | catch(Exception e) { 40 | logger.error("Failed, exception occurred: ", e); 41 | throw e; 42 | } 43 | finally 44 | { 45 | logger.info("Done. Press the ENTER key to exit"); 46 | Scanner exit = new Scanner(System.in); 47 | exit.nextLine(); 48 | exit.close(); 49 | logger.info("Done."); 50 | } 51 | } 52 | 53 | public Main() { 54 | mapData = new MapData(); 55 | generator = new Generator(); 56 | xmlUpdater = new XMLConfigUpdater(); 57 | } 58 | 59 | public void run() throws Exception { 60 | String configFile = "config.json"; 61 | logger.info("Attempting to load " + configFile); 62 | config = GlobalConfig.loadConfig(configFile); 63 | if(config == null) { 64 | return; 65 | } 66 | GlobalConfig defaultConfig = new GlobalConfig(); 67 | defaultConfig.setDefaults(); 68 | config.copyDefaults(defaultConfig); 69 | config.cascadeSettings(); 70 | if(config.planetGeneratorDefinitionsPath != null || !config.planetGeneratorDefinitionsPathArray.isEmpty()) { 71 | if(!xmlUpdater.updatePlanetGeneratorDefinitions(config)) { 72 | return; 73 | } 74 | } 75 | if(!config.makeColouredMaps) { 76 | deleteColouredTestFiles(); 77 | } 78 | if(config.planetDataPath != null) { 79 | if(!generate()) { 80 | return; 81 | } 82 | logger.info("Steam workshop table summary:\n" + getSteamWorkshopSummary()); 83 | } 84 | logger.info("Waiting for all image compression/writer threads to finish..."); 85 | for (Map.Entry imageWriterFuture : imagefutures.entrySet()) { 86 | imageWriterFuture.getValue().get(); 87 | } 88 | executor.shutdown(); 89 | singleThreadExecutor.shutdown(); 90 | logger.info("All Image compression/writer threads complete"); 91 | } 92 | 93 | 94 | 95 | public boolean generate() throws ExecutionException, InterruptedException { 96 | for(final PlanetConfig planetConfig : config.planets) { 97 | generatefutures.add(executor.submit(() -> { 98 | MapData mapData = new MapData(); 99 | MapHandler handler = new MapHandler(planetConfig.name, mapData, Paths.get(config.planetDataPath, planetConfig.name).toString(), 100 | Paths.get(config.planetDataOutputPath, planetConfig.name).toString(), planetConfig.surfaceHintMaps, planetConfig.makeColouredMaps); 101 | logger.info(planetConfig.name + ": Processing planet \"" + planetConfig.name + "\""); 102 | logger.info(planetConfig.name + ":\tLoading map data from: " + Paths.get(config.planetDataPath, planetConfig.name).toString()); 103 | if(handler.loadMapData() == null) { 104 | return false; 105 | } 106 | 107 | if(config.countExistingTiles) { 108 | mapData.countTiles(); 109 | } 110 | logger.info(planetConfig.name + ":\tClearing existing ore data"); 111 | mapData.clearOreData(); 112 | logger.info(planetConfig.name + ":\tGenerating ore tiles with effective ore configs:\n" + toJSON(planetConfig.ores)); 113 | long tilesGenerated = generator.generatePatches(mapData, planetConfig); 114 | logger.info(planetConfig.name + ":\tTiles generated:" + tilesGenerated); 115 | logger.info(planetConfig.name + ":\tWriting ore data to map images in: " + Paths.get(config.planetDataOutputPath, planetConfig.name).toString()); 116 | if(config.concurrentImageWrite) 117 | imagefutures.put(planetConfig.name, executor.submit(handler)); 118 | else 119 | imagefutures.put(planetConfig.name, singleThreadExecutor.submit(handler)); 120 | return true; 121 | })); 122 | 123 | } 124 | for (Future generatefuture : generatefutures) { 125 | if(false == generatefuture.get()) {return false;} 126 | } 127 | return true; 128 | } 129 | 130 | public String getSteamWorkshopSummary() { 131 | List uniqueOres = new ArrayList(); 132 | for(String planetName : generator.tileCountMap.keySet()) { 133 | for(String oreName : generator.tileCountMap.get(planetName).keySet()) { 134 | if(!uniqueOres.contains(oreName)) { 135 | uniqueOres.add(oreName); 136 | } 137 | } 138 | } 139 | StringWriter w = new StringWriter(); 140 | w.append("[table]\n") 141 | .append("[tr]\n") 142 | .append("[th][/th]\n"); 143 | List planetNames = new ArrayList(); 144 | for(PlanetConfig planet : config.planets) { 145 | if(generator.tileCountMap.containsKey(planet.name)) { 146 | w.append("[th]" + planet.name + "[/th]\n"); 147 | planetNames.add(planet.name); 148 | } 149 | } 150 | w.append("[/tr]\n"); 151 | for(String oreName : uniqueOres) { 152 | 153 | w.append("[tr][th]" + oreName + "[/th]\n"); 154 | for(String planetName : planetNames) { 155 | Long oreCount = generator.tileCountMap.get(planetName).get(oreName); 156 | if(oreCount == null) { 157 | oreCount = 0l; 158 | } 159 | w.append("[td]" + oreCount + "[/td]\n"); 160 | } 161 | w.append("[/tr]\n"); 162 | } 163 | w.append("[/table]\n"); 164 | return w.toString(); 165 | } 166 | 167 | String toJSON(Object o) { 168 | Gson gson = new GsonBuilder().setPrettyPrinting().create(); 169 | return gson.toJson(o); 170 | } 171 | 172 | private void deleteColouredTestFiles() { 173 | for(PlanetConfig planet : config.planets) { 174 | File planetDir = Paths.get(config.planetDataOutputPath, planet.name).toFile(); 175 | if(planetDir.exists()) { 176 | File[] images = planetDir.listFiles((File dir, String name) -> { 177 | return name.contains("coloured"); 178 | }); 179 | for(File image : images) { 180 | image.delete(); 181 | } 182 | } 183 | } 184 | } 185 | 186 | 187 | } 188 | -------------------------------------------------------------------------------- /src/core/Stats.java: -------------------------------------------------------------------------------- 1 | package core; 2 | 3 | import com.google.common.collect.Table; 4 | import com.google.common.collect.HashBasedTable; 5 | 6 | public class Stats { 7 | 8 | Table data; 9 | 10 | public Stats() { 11 | data = HashBasedTable.create(); 12 | 13 | } 14 | 15 | public synchronized void add(String ore, String planet, int tiles) { 16 | Counts counts; 17 | if(tiles > 0) { 18 | if(data.contains(planet, "")) { 19 | counts = data.get(planet, ""); 20 | data.put(planet, "", new Counts(counts, tiles, 1)); 21 | } 22 | else { 23 | data.put(planet, "", new Counts(tiles,1)); 24 | } 25 | 26 | if(data.contains("", ore)) { 27 | counts = data.get("", ore); 28 | data.put("", ore, new Counts(counts, tiles, 1)); 29 | } 30 | else { 31 | data.put("", ore, new Counts(tiles,1)); 32 | } 33 | 34 | if(data.contains(planet, ore)) { 35 | counts = data.get(planet, ore); 36 | data.put(planet, ore, new Counts(counts, tiles, 1)); 37 | } 38 | else { 39 | data.put(planet, ore, new Counts(tiles,1)); 40 | } 41 | } 42 | } 43 | 44 | public synchronized int getPlanetTileTotal(String planet) { 45 | int total = 0; 46 | if(data.contains(planet, "")) { 47 | total = data.get(planet, "").tiles; 48 | } 49 | return total; 50 | } 51 | 52 | public synchronized int getOreTileTotal(String ore) { 53 | int total = 0; 54 | if(data.contains("", ore)) { 55 | total = data.get("", ore).tiles; 56 | } 57 | return total; 58 | } 59 | 60 | public synchronized int getPlanetOreTileTotal(String planet, String ore) { 61 | int total = 0; 62 | if(data.contains(planet, ore)) { 63 | total = data.get(planet, ore).tiles; 64 | } 65 | return total; 66 | } 67 | 68 | public synchronized int getPlanetPatchTotal(String planet) { 69 | int total = 0; 70 | if(data.contains(planet, "")) { 71 | total = data.get(planet, "").patches; 72 | } 73 | return total; 74 | } 75 | 76 | public synchronized int getOrePatchTotal(String ore) { 77 | int total = 0; 78 | if(data.contains("", ore)) { 79 | total = data.get("", ore).patches; 80 | } 81 | return total; 82 | } 83 | 84 | public synchronized int getPlanetOrePatchTotal(String planet, String ore) { 85 | int total = 0; 86 | if(data.contains(planet, ore)) { 87 | total = data.get(planet, ore).patches; 88 | } 89 | return total; 90 | } 91 | 92 | public class Counts { 93 | int tiles; 94 | int patches; 95 | 96 | public Counts() { 97 | tiles = 0; 98 | patches = 0; 99 | } 100 | 101 | public Counts(int tiles, int patches) { 102 | this.tiles = tiles; 103 | this.patches = patches; 104 | } 105 | 106 | public Counts(Counts other, int tilesAdd, int patchesAdd) { 107 | this.tiles = other.tiles + tilesAdd; 108 | this.patches = other.patches + patchesAdd; 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/map/MapData.java: -------------------------------------------------------------------------------- 1 | package map; 2 | 3 | import java.awt.Color; 4 | import java.awt.Graphics2D; 5 | import java.awt.image.BufferedImage; 6 | 7 | import org.apache.logging.log4j.LogManager; 8 | import org.apache.logging.log4j.Logger; 9 | 10 | import config.CommonConfig.PlanetFace; 11 | 12 | public class MapData { 13 | public static final Logger logger = LogManager.getLogger("MapData"); 14 | 15 | public BufferedImage[] images; 16 | public BufferedImage[] surfaceHintImages; 17 | public BufferedImage[] colouredMaps; 18 | 19 | public static final int BACKGROUND_ORE_COLOUR = 255; 20 | public static final int BACKGROUND_ADD_COLOUR = 0xFF000000; 21 | public static final int ORE_EXCLUDER = 0xFFFFFF00; 22 | public static final int ORE_FILTER = 0x000000FF; 23 | public static final int ICE_FILTER = 0x00520000; //82 (colour in R channel for ice in hex) 24 | public static final int OPAQUE = 0xFF000000; 25 | public static final int UNSET_ORE_FILTER = 0x000000FF; 26 | 27 | int mapSize; 28 | 29 | public MapData() { 30 | int mapSides = PlanetFace.ALL.length; 31 | images = new BufferedImage[mapSides]; 32 | surfaceHintImages = new BufferedImage[mapSides]; 33 | colouredMaps = new BufferedImage[mapSides]; 34 | } 35 | 36 | public void clearOreData() { 37 | for(int i = 0; i < images.length; ++i) { 38 | BufferedImage img = images[i]; 39 | if(img != null) { 40 | for(int j = 0; j < img.getWidth(); ++j) { 41 | for(int k = 0; k < img.getHeight(); ++k) { 42 | img.setRGB(j, k, img.getRGB(j, k) | BACKGROUND_ORE_COLOUR); 43 | } 44 | } 45 | } 46 | BufferedImage hintImg = surfaceHintImages[i]; 47 | if(hintImg != null) { 48 | Graphics2D graphics = hintImg.createGraphics(); 49 | graphics.setPaint ( new Color(BACKGROUND_ADD_COLOUR)); 50 | graphics.fillRect (0, 0, hintImg.getWidth(), hintImg.getHeight()); 51 | } 52 | BufferedImage colouredImg = colouredMaps[i]; 53 | if(colouredImg != null) { 54 | for(int j = 0; j < colouredImg.getWidth(); ++j) { 55 | for(int k = 0; k < colouredImg.getHeight(); ++k) { 56 | int pixRGB = colouredImg.getRGB(j, k) & ORE_EXCLUDER; 57 | //convert coloured test image to greyscale 58 | int rgb[] = new int[] { 59 | (pixRGB >> 16) & 0xff, //red 60 | (pixRGB >> 8) & 0xff, //green 61 | (pixRGB ) & 0xff //blue 62 | }; 63 | int avg = (( rgb[0] + rgb[1] + rgb[2]) / 3); 64 | int grey_rgb = 0; 65 | grey_rgb |= avg; 66 | for(int l = 0; l < 3; l++) { 67 | grey_rgb <<= 8; 68 | grey_rgb |= avg; 69 | 70 | } 71 | grey_rgb |= OPAQUE; 72 | colouredImg.setRGB(j, k, grey_rgb); 73 | } 74 | } 75 | } 76 | } 77 | } 78 | 79 | public int getMapSize() { 80 | return mapSize; 81 | } 82 | 83 | 84 | public void setMapSize(int mapSize) { 85 | this.mapSize = mapSize; 86 | } 87 | 88 | public void calculateMapSize() { 89 | if(this.images.length > 0) { 90 | if(this.images[0] != null) { 91 | //assume that all mapes are the same size and are square 92 | mapSize = this.images[0].getWidth(); 93 | } 94 | } 95 | } 96 | 97 | public void countTiles() { 98 | logger.info("\tCounting existing ore tiles..."); 99 | long total = 0; 100 | int mapCount = 0; 101 | for(BufferedImage img : images) { 102 | if(img != null) { 103 | long tileCount = 0; 104 | for(int i = 0; i < img.getWidth(); ++i) { 105 | for(int j = 0; j < img.getHeight(); ++j) { 106 | if((img.getRGB(i, j) & UNSET_ORE_FILTER) != UNSET_ORE_FILTER) { 107 | ++tileCount; 108 | } 109 | } 110 | } 111 | logger.info("\t\t" + PlanetFace.ALL[mapCount].name + " map existing tile count:" + tileCount); 112 | mapCount++; 113 | total += tileCount; 114 | } 115 | } 116 | logger.info("\tTotal existing tiles: " + total); 117 | 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/map/MapHandler.java: -------------------------------------------------------------------------------- 1 | package map; 2 | 3 | import java.io.File; 4 | import java.nio.file.Files; 5 | import java.nio.file.Path; 6 | import java.nio.file.Paths; 7 | 8 | import javax.imageio.ImageIO; 9 | import javax.swing.JOptionPane; 10 | 11 | import org.apache.logging.log4j.LogManager; 12 | import org.apache.logging.log4j.Logger; 13 | 14 | import config.CommonConfig.PlanetFace; 15 | 16 | public class MapHandler implements Runnable { 17 | public static final Logger logger = LogManager.getLogger("MapHandler"); 18 | 19 | public static final String MAT = "_mat.png"; 20 | public static final String ADD = "_add.png"; 21 | public static final String COLOURED = "_coloured.png"; 22 | 23 | String planetName; 24 | MapData mapData; 25 | String inputPath; 26 | String outputPath; 27 | boolean surfaceHintMaps; 28 | boolean colouredMaps; 29 | 30 | public MapHandler(String planetName, MapData mapData, String inputPath, String outputPath, boolean surfaceHintMaps, boolean colouredMaps) { 31 | this.planetName = planetName; 32 | this.mapData = mapData; 33 | this.inputPath = inputPath; 34 | this.outputPath = outputPath; 35 | this.surfaceHintMaps = surfaceHintMaps; 36 | this.colouredMaps = colouredMaps; 37 | } 38 | 39 | public MapData loadMapData() { 40 | for(int i = 0; i < PlanetFace.ALL.length; ++i) { 41 | String mapName = PlanetFace.ALL[i].name; 42 | try { 43 | mapData.images[i] = ImageIO.read(Paths.get(inputPath, mapName + MAT).toFile()); 44 | if(surfaceHintMaps) { 45 | Path imagePath = Paths.get(inputPath, mapName + ADD); 46 | File f = imagePath.toFile(); 47 | if(f.exists()) 48 | mapData.surfaceHintImages[i] = ImageIO.read(f); 49 | else 50 | mapData.surfaceHintImages[i] = null; 51 | } 52 | if(colouredMaps) { 53 | mapData.colouredMaps[i] = ImageIO.read(Paths.get(inputPath, mapName + MAT).toFile()); 54 | } 55 | } 56 | catch(Exception e) { 57 | logger.error("Exception occurred while loading map data (images):", e); 58 | JOptionPane.showMessageDialog(null, "Unable to load image files from directory:\n" + inputPath, "File Read Error", JOptionPane.ERROR_MESSAGE); 59 | return null; 60 | } 61 | } 62 | mapData.calculateMapSize(); 63 | 64 | return mapData; 65 | } 66 | 67 | public void writeMapData() { 68 | try { 69 | 70 | for(int i = 0; i < mapData.images.length; ++i) { 71 | if(mapData.images[i] == null) { 72 | continue; 73 | } 74 | String mapName = PlanetFace.ALL[i].name; 75 | Path path = Paths.get(outputPath); 76 | if(Files.notExists(path)) { 77 | Files.createDirectories(path); 78 | } 79 | ImageIO.write(mapData.images[i], "png" , Paths.get(outputPath, mapName + MAT).toFile()); 80 | if(surfaceHintMaps) { 81 | if(mapData.surfaceHintImages[i] != null) 82 | ImageIO.write(mapData.surfaceHintImages[i], "png" , Paths.get(outputPath, mapName + ADD).toFile()); 83 | } 84 | if(colouredMaps) { 85 | ImageIO.write(mapData.colouredMaps[i], "png" , Paths.get(outputPath, mapName + COLOURED).toFile()); 86 | } 87 | } 88 | } 89 | catch(Exception e) { 90 | logger.error("Exception occurred while writing map data (images): ", e); 91 | JOptionPane.showMessageDialog(null, "Unable to create image files in directory:\n" + outputPath, "File Write Error", JOptionPane.ERROR_MESSAGE); 92 | return; 93 | } 94 | logger.info("Images for " + planetName + " done."); 95 | } 96 | 97 | @Override 98 | public void run() { 99 | //write 100 | writeMapData(); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/xml/XMLConfigUpdater.java: -------------------------------------------------------------------------------- 1 | package xml; 2 | 3 | import java.io.File; 4 | import java.io.PrintWriter; 5 | import java.nio.file.Paths; 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | import javax.swing.JOptionPane; 11 | 12 | import config.GlobalConfig; 13 | import config.OreConfig; 14 | import config.PlanetConfig; 15 | import nu.xom.Attribute; 16 | import nu.xom.Builder; 17 | import nu.xom.Document; 18 | import nu.xom.Element; 19 | import nu.xom.Node; 20 | import nu.xom.Nodes; 21 | 22 | import org.apache.logging.log4j.LogManager; 23 | import org.apache.logging.log4j.Logger; 24 | 25 | public class XMLConfigUpdater { 26 | public static final Logger logger = LogManager.getLogger("XMLConfigUpdater"); 27 | 28 | public boolean updatePlanetGeneratorDefinitions(GlobalConfig config) throws Exception { 29 | logger.info("Attempting to update PlanetGeneratorDefinitions file(s).."); 30 | Map planetLookup = new HashMap(); 31 | for (PlanetConfig pc : config.planets) { 32 | planetLookup.put(pc.name, pc); 33 | } 34 | 35 | List planetGeneratorDefPaths = config.planetGeneratorDefinitionsPathArray; 36 | if(planetGeneratorDefPaths.isEmpty()) 37 | planetGeneratorDefPaths.add(config.planetGeneratorDefinitionsPath); 38 | for(String path : planetGeneratorDefPaths) { 39 | try { 40 | boolean planetModified = false; 41 | File f = new File(path); 42 | String fileName = f.getName(); 43 | if (!f.exists()) { 44 | JOptionPane.showMessageDialog(null, 45 | "Unable to find file: " + path, "Cannot find planet generator definitions file", 46 | JOptionPane.ERROR_MESSAGE); 47 | logger.error("Unable to find file: " + path); 48 | return false; 49 | } 50 | Builder parser = new Builder(false); 51 | Document doc = parser.build(f); 52 | Nodes definitions = doc.query("Definitions/Definition|Definitions/PlanetGeneratorDefinitions/PlanetGeneratorDefinition"); 53 | for (int i = 0; i < definitions.size(); ++i) { 54 | Node def = definitions.get(i); 55 | String subTypeId = def.query("Id/SubtypeId").get(0).getValue(); 56 | if (planetLookup.containsKey(subTypeId)) { 57 | planetModified = true; 58 | PlanetConfig planet = planetLookup.get(subTypeId); 59 | Node oreMappings = def.query("OreMappings").get(0); 60 | Element e = (Element) oreMappings; 61 | e.removeChildren(); 62 | for (int j = 0; j < planet.ores.length; ++j) { 63 | OreConfig ore = planet.ores[j]; 64 | Element oreElement = new Element("Ore"); 65 | oreElement.addAttribute(new Attribute("Value", Integer.toString(ore.id))); 66 | oreElement.addAttribute(new Attribute("Type", ore.type)); 67 | oreElement.addAttribute(new Attribute("Start", Integer.toString(ore.startDepth))); 68 | oreElement.addAttribute(new Attribute("Depth", Integer.toString(ore.depth))); 69 | oreElement.addAttribute(new Attribute("TargetColor", ore.mappingFileTargetColour)); 70 | oreElement.addAttribute( 71 | new Attribute("ColorInfluence", Integer.toString(ore.mappingFileColourInfluence))); 72 | e.appendChild(oreElement); 73 | } 74 | } 75 | } 76 | if(planetModified) { 77 | File out = Paths.get(config.planetGeneratorDefinitionsOutputPath + fileName).toFile(); 78 | PrintWriter writer = new PrintWriter(out); 79 | writer.print(doc.toXML()); 80 | writer.close(); 81 | logger.info("Created PlanetGeneratorDefinitions file: " + path); 82 | } 83 | else { 84 | logger.warn("Not creating PlanetGeneratorDefinitions file:" + path + ". No configs for any planets in this file."); 85 | } 86 | } catch (Exception e) { 87 | JOptionPane.showMessageDialog(null, 88 | "Unable to parse " + path + "\n" + e.toString(), "XML parse error", 89 | JOptionPane.ERROR_MESSAGE); 90 | logger.error("Unable to parse " + path + "\n" + e.toString()); 91 | throw e; 92 | } 93 | } 94 | logger.info("Finished creating all PlanetGeneratorDefinitions files."); 95 | return true; 96 | } 97 | } 98 | --------------------------------------------------------------------------------