├── .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 |
--------------------------------------------------------------------------------