Skip to content

Commit f0b744f

Browse files
authored
Merge pull request #51 from BTE-France/feat/unlock-osm-json5
Unlock all osm.json5 configuration features
2 parents 6f84338 + 197a98d commit f0b744f

5 files changed

Lines changed: 213 additions & 99 deletions

File tree

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
package de.btegermany.terraplusminus.gen;
2+
3+
import net.buildtheearth.terraminusminus.substitutes.BlockState;
4+
import net.buildtheearth.terraminusminus.substitutes.Identifier;
5+
import net.buildtheearth.terraminusminus.substitutes.TerraBukkit;
6+
import net.buildtheearth.terraminusminus.util.http.Disk;
7+
import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
8+
import org.bukkit.Material;
9+
import org.bukkit.block.data.BlockData;
10+
import org.bukkit.configuration.file.FileConfiguration;
11+
import org.bukkit.plugin.Plugin;
12+
import org.jetbrains.annotations.Contract;
13+
import org.jetbrains.annotations.NotNull;
14+
import org.jetbrains.annotations.Nullable;
15+
16+
import java.util.HashMap;
17+
import java.util.Map;
18+
19+
import static net.buildtheearth.terraminusminus.substitutes.TerraBukkit.toBukkitBlockData;
20+
21+
/**
22+
* Maps Terra-- {@link BlockState block states} to Bukkit {@link BlockData block datas},
23+
* based on material replacements configured in a {@link FileConfiguration}.
24+
* <br>
25+
* This purposefully only supports materials to be defined in the config and not full block data,
26+
* in order to maintain exact backward compatibility,
27+
* and encourage the use of <code>osm.json5</code> instead for complex use cases.
28+
*
29+
* @author Smyler
30+
*/
31+
public class BlockMapper {
32+
33+
private final BlockData genericSurfaceBlock;
34+
private final Map<Identifier, BlockData> mapping;
35+
36+
private BlockMapper(BlockData genericSurfaceBlock, Map<Identifier, BlockData> mapping) {
37+
this.genericSurfaceBlock = genericSurfaceBlock;
38+
this.mapping = mapping;
39+
}
40+
41+
/**
42+
* Maps a {@link BlockState block state} to a {@link BlockData block data} using the defined mappings.
43+
* {@code null} {@link BlockState block states} are mapped to {@code null} {@link BlockData}.
44+
* If there is no explicit mapping for the {@link BlockState block state}'s material,
45+
* it is natively converted to a {@link BlockData block data} with Terra--'s {@link TerraBukkit#toBukkitBlockData(BlockState)}.
46+
*
47+
* @param blockState the {@link BlockState} to map to a {@link BlockData}
48+
* @return the mapped {@link BlockData}, or {@code null}
49+
*/
50+
@Contract(value = "null -> null; !null -> !null", pure = true)
51+
public @Nullable BlockData map(@Nullable BlockState blockState) {
52+
if (blockState == null) {
53+
return null;
54+
}
55+
BlockData data = this.mapping.get(blockState.getBlock());
56+
if (data != null) {
57+
return data;
58+
}
59+
return toBukkitBlockData(blockState);
60+
}
61+
62+
public @Nullable BlockData genericSurfaceBlock() {
63+
return this.genericSurfaceBlock;
64+
}
65+
66+
public static @NotNull Builder fromPlugin(@NotNull Plugin plugin) {
67+
return new Builder(plugin.getConfig(), plugin.getComponentLogger());
68+
}
69+
70+
public static class Builder {
71+
private final FileConfiguration configuration;
72+
private final ComponentLogger logger;
73+
private BlockData genericSurfaceBlock;
74+
private final Map<Identifier, BlockData> mapping = new HashMap<>();
75+
76+
private Builder(FileConfiguration config, ComponentLogger logger) {
77+
this.configuration = config;
78+
this.logger = logger;
79+
}
80+
81+
public Builder withConfiguredMapping(@NotNull Identifier materialId, @NotNull String configPath) {
82+
Material material = this.readMaterialFromConfig(configPath);
83+
if (material == null) {
84+
return this;
85+
}
86+
this.logger.warn(
87+
"Configuration entry {} is set to {}, but is deprecated and may be removed in future versions. " +
88+
"Consider removing it from your configuration and editing {} instead.",
89+
configPath,
90+
material,
91+
Disk.configFile("osm.json5")
92+
);
93+
return this.withStaticMapping(materialId, material);
94+
}
95+
96+
public Builder withConfiguredGenericSurface(@NotNull String configPath) {
97+
Material material = this.readMaterialFromConfig(configPath);
98+
if (material == null) {
99+
return this;
100+
}
101+
return this.withStaticGenericSurface(material);
102+
}
103+
104+
public Builder withStaticGenericSurface(@NotNull Material material) {
105+
this.genericSurfaceBlock = material.createBlockData();
106+
return this;
107+
}
108+
109+
public Builder withConfiguredMapping(@NotNull String materialId, @NotNull String configPath) {
110+
Identifier identifier = Identifier.parse(materialId); // Let this fail if invalid, that means devs are at fault
111+
return this.withConfiguredMapping(identifier, configPath);
112+
}
113+
114+
public Builder withStaticMapping(@NotNull Identifier materialId, @NotNull Material material) {
115+
this.logger.trace(
116+
"Adding material replacement mapping {} -> {}",
117+
materialId, material
118+
);
119+
this.mapping.put(materialId, material.createBlockData());
120+
return this;
121+
}
122+
123+
private @Nullable Material readMaterialFromConfig(@NotNull String configPath) {
124+
String materialName = this.configuration.getString(configPath);
125+
if (materialName == null) {
126+
return null;
127+
}
128+
Material material = Material.getMaterial(materialName);
129+
if (material == null) {
130+
this.logger.warn(
131+
"Configuration entry '{}' has been explicitly set to '{}', but no such material exists. It will be ignored.",
132+
configPath, materialName
133+
);
134+
return null;
135+
}
136+
return material;
137+
}
138+
139+
public @NotNull BlockMapper build() {
140+
return new BlockMapper(this.genericSurfaceBlock, this.mapping);
141+
}
142+
}
143+
144+
}

src/main/java/de/btegermany/terraplusminus/gen/RealWorldGenerator.java

Lines changed: 45 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,13 @@
44
import com.google.common.cache.LoadingCache;
55
import de.btegermany.terraplusminus.Terraplusminus;
66
import de.btegermany.terraplusminus.gen.tree.TreePopulator;
7-
import de.btegermany.terraplusminus.utils.ConfigurationHelper;
87
import de.btegermany.terraplusminus.utils.Properties;
98
import lombok.Getter;
109
import net.buildtheearth.terraminusminus.generator.CachedChunkData;
1110
import net.buildtheearth.terraminusminus.generator.ChunkDataLoader;
1211
import net.buildtheearth.terraminusminus.generator.EarthGeneratorSettings;
1312
import net.buildtheearth.terraminusminus.projection.GeographicProjection;
1413
import net.buildtheearth.terraminusminus.projection.transform.OffsetProjectionTransform;
15-
import net.buildtheearth.terraminusminus.substitutes.BlockState;
1614
import net.buildtheearth.terraminusminus.substitutes.ChunkPos;
1715
import net.buildtheearth.terraminusminus.util.http.Http;
1816
import org.bukkit.HeightMap;
@@ -21,6 +19,7 @@
2119
import org.bukkit.World;
2220
import org.bukkit.block.Biome;
2321
import org.bukkit.block.Block;
22+
import org.bukkit.block.data.BlockData;
2423
import org.bukkit.generator.BiomeProvider;
2524
import org.bukkit.generator.BlockPopulator;
2625
import org.bukkit.generator.ChunkGenerator;
@@ -34,26 +33,38 @@
3433
import java.util.concurrent.TimeUnit;
3534

3635
import static java.lang.Math.min;
36+
import static java.util.Collections.singletonList;
3737
import static net.buildtheearth.terraminusminus.substitutes.ChunkPos.blockToCube;
3838
import static net.buildtheearth.terraminusminus.substitutes.ChunkPos.cubeToMinBlock;
39-
import static net.buildtheearth.terraminusminus.substitutes.TerraBukkit.toBukkitBlockData;
4039
import static org.bukkit.Material.*;
4140
import static org.bukkit.block.Biome.*;
4241

42+
/**
43+
* A world generator using Terra-- as the generation engine.
44+
* It is opinionated and optimized for BTE creative building
45+
* (very bland terrain with no features at all).
46+
*/
4347
public class RealWorldGenerator extends ChunkGenerator {
4448

4549
@Getter
4650
private final EarthGeneratorSettings settings;
4751
@Getter
4852
private final int yOffset;
49-
private Location spawnLocation = null;
5053

51-
private final LoadingCache<ChunkPos, CompletableFuture<CachedChunkData>> cache;
54+
private final LoadingCache<@NotNull ChunkPos, @NotNull CompletableFuture<CachedChunkData>> cache;
5255
private final CustomBiomeProvider customBiomeProvider;
5356

5457

55-
private final Material surfaceMaterial;
56-
private final Map<String, Material> materialMapping;
58+
private final BlockData defaultSurfaceBlock;
59+
private final BlockData mountainSurfaceBlock = STONE.createBlockData();
60+
private final BlockData underwaterBlock = DIRT.createBlockData();
61+
private final Map<Biome, BlockData> defaultBiomeSurfaceBlocks = Map.of(
62+
DESERT, SAND.createBlockData(),
63+
SNOWY_SLOPES, SNOW_BLOCK.createBlockData(),
64+
SNOWY_PLAINS, SNOW_BLOCK.createBlockData(),
65+
FROZEN_PEAKS, SNOW_BLOCK.createBlockData()
66+
);
67+
private final BlockMapper blockMapper;
5768

5869
private static final Set<Material> GRASS_LIKE_MATERIALS = Set.of(
5970
GRASS_BLOCK,
@@ -88,53 +99,32 @@ public RealWorldGenerator(int yOffset, Terraplusminus plugin) {
8899
.softValues()
89100
.build(new ChunkDataLoader(this.settings));
90101

91-
this.surfaceMaterial = ConfigurationHelper.getMaterial(plugin.getConfig(), Properties.SURFACE_MATERIAL, GRASS_BLOCK);
92-
this.materialMapping = Map.of(
93-
"minecraft:bricks", ConfigurationHelper.getMaterial(plugin.getConfig(), Properties.BUILDING_OUTLINES_MATERIAL, BRICKS),
94-
"minecraft:gray_concrete", ConfigurationHelper.getMaterial(plugin.getConfig(), Properties.ROAD_MATERIAL, GRAY_CONCRETE_POWDER),
95-
"minecraft:dirt_path", ConfigurationHelper.getMaterial(plugin.getConfig(), Properties.PATH_MATERIAL, MOSS_BLOCK)
96-
);
97-
102+
// This code is explicitly there for backward compatibility and is legitimate in using the deprecated config keys
103+
this.blockMapper = BlockMapper.fromPlugin(plugin)
104+
.withStaticGenericSurface(GRASS_BLOCK)
105+
.withConfiguredGenericSurface(Properties.SURFACE_MATERIAL) // Overrides the static definition if present
106+
.withConfiguredMapping("minecraft:bricks", Properties.BUILDING_OUTLINES_MATERIAL)
107+
.withConfiguredMapping("minecraft:gray_concrete", Properties.ROAD_MATERIAL)
108+
.withConfiguredMapping("minecraft:dirt_path", Properties.PATH_MATERIAL)
109+
.build();
110+
this.defaultSurfaceBlock = this.blockMapper.genericSurfaceBlock();
98111
}
99112

100113

101114
@Override
102115
public void generateNoise(@NotNull WorldInfo worldInfo, @NotNull Random random, int chunkX, int chunkZ, @NotNull ChunkData chunkData) {
103-
104116
CachedChunkData terraData = this.getTerraChunkData(chunkX, chunkZ);
105117

106118
int minWorldY = worldInfo.getMinHeight();
107119
int maxWorldY = worldInfo.getMaxHeight();
108120

109-
// We start by finding the lowest 16x16x16 cube that's not underground
110-
//TODO expose the minimum surface Y in Terra-- so we don't have to scan this way
121+
// Optimization: if the entire chunk is above the surface, there is nothing to do
111122
int minSurfaceCubeY = blockToCube(minWorldY - this.yOffset);
112-
int maxWorldCubeY = blockToCube(maxWorldY - this.yOffset);
113123
if (terraData.aboveSurface(minSurfaceCubeY)) {
114-
return; // All done, it's all air
115-
}
116-
while (minSurfaceCubeY < maxWorldCubeY && terraData.belowSurface(minSurfaceCubeY)) {
117-
minSurfaceCubeY++;
118-
}
119-
120-
// We can now fill most of the underground in a single call.
121-
// Hopefully the underlying implementation can take advantage of that...
122-
if (minSurfaceCubeY >= maxWorldCubeY) {
123-
chunkData.setRegion(
124-
0, minWorldY, 0,
125-
16, maxWorldY, 16,
126-
STONE
127-
);
128-
return; // All done, everything is underground
129-
} else {
130-
chunkData.setRegion(
131-
0, minWorldY, 0,
132-
0, cubeToMinBlock(minSurfaceCubeY), 0,
133-
STONE
134-
);
124+
return;
135125
}
136126

137-
// And now, we build the actual terrain shape on top of everything
127+
// And now, we build the actual terrain shape
138128
for (int x = 0; x < 16; x++) {
139129
for (int z = 0; z < 16; z++) {
140130
int groundHeight = min(terraData.groundHeight(x, z) + this.yOffset, maxWorldY - 1);
@@ -175,39 +165,24 @@ public void generateSurface(@NotNull WorldInfo worldInfo, @NotNull Random random
175165
continue; // We are not within vertical bounds, continue
176166
}
177167

178-
Material material;
179-
180-
BlockState state = terraData.surfaceBlock(x, z);
181-
if (state != null) {
182-
// Terra--'s OSM config says a feature should be drawn there, let's transform it to respect our config
183-
material = this.materialMapping.get(state.getBlock().toString());
184-
if (material == null) {
185-
// We don't know what material this is, let's respect what the Terra-- configuration says
186-
material = toBukkitBlockData(state).getMaterial();
187-
}
188-
} else if (groundY >= startMountainHeight) {
189-
material = STONE; // Mountains stare bare
190-
} else {
191-
// Fallback to a generic block that matches the biome
192-
Biome biome = chunkData.getBiome(x, groundY, z);
193-
if (biome == DESERT) {
194-
material = Material.SAND;
195-
196-
} else if (biome == SNOWY_SLOPES || biome == SNOWY_PLAINS || biome == FROZEN_PEAKS){
197-
material = SNOW_BLOCK;
198-
168+
BlockData surfaceBlock = this.blockMapper.map(terraData.surfaceBlock(x, z));
169+
if (surfaceBlock == null) {
170+
if (groundY >= startMountainHeight) {
171+
surfaceBlock = this.mountainSurfaceBlock; // Mountains stay bare
199172
} else {
200-
material = this.surfaceMaterial;
173+
// Fallback to a generic block that matches the biome, or to the default block
174+
Biome biome = chunkData.getBiome(x, groundY, z);
175+
surfaceBlock = this.defaultBiomeSurfaceBlocks.getOrDefault(biome, this.defaultSurfaceBlock);
201176
}
202177
}
203178

204-
// We don't want grass, snow, and all underwater
179+
// We don't want grass, snow, and all that underwater
205180
boolean isUnderWater = groundY + 1 >= maxWorldY || chunkData.getBlockData(x, groundY + 1, z).getMaterial().equals(WATER);
206-
if (isUnderWater && GRASS_LIKE_MATERIALS.contains(material)) {
207-
material = DIRT;
181+
if (isUnderWater && GRASS_LIKE_MATERIALS.contains(surfaceBlock.getMaterial())) {
182+
surfaceBlock = this.underwaterBlock;
208183
}
209184

210-
chunkData.setBlock(x, groundY, z, material);
185+
chunkData.setBlock(x, groundY, z, surfaceBlock);
211186

212187
}
213188
}
@@ -264,14 +239,13 @@ public boolean canSpawn(@NotNull World world, int x, int z) {
264239
@Override
265240
@NotNull
266241
public List<BlockPopulator> getDefaultPopulators(@NotNull World world) {
267-
return Collections.singletonList(new TreePopulator(customBiomeProvider, yOffset));
242+
return singletonList(new TreePopulator(this.customBiomeProvider, yOffset));
268243
}
269244

270-
@Override
271245
@Nullable
246+
@Override
272247
public Location getFixedSpawnLocation(@NotNull World world, @NotNull Random random) {
273-
if (spawnLocation == null)
274-
spawnLocation = new Location(world, 3517417, 58, -5288234);
275-
return spawnLocation;
248+
return new Location(world, 3517417, 58, -5288234);
276249
}
250+
277251
}

src/main/java/de/btegermany/terraplusminus/utils/ConfigurationHelper.java

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
package de.btegermany.terraplusminus.utils;
22

33
import de.btegermany.terraplusminus.Terraplusminus;
4-
import org.bukkit.Material;
5-
import org.bukkit.configuration.file.FileConfiguration;
6-
import org.jetbrains.annotations.NotNull;
74
import org.jspecify.annotations.NonNull;
85
import org.jspecify.annotations.Nullable;
96

@@ -13,27 +10,6 @@
1310

1411
public final class ConfigurationHelper {
1512

16-
/**
17-
* Returns a material from the configuration,
18-
* or a default value if the configuration path is either missing or the value is not a valid material identifier.
19-
*
20-
* @param config the configuration file to read from
21-
* @param path the configuration path to retrieve
22-
* @param defaultValue a default value to return if the value is missing from the config or invalid
23-
* @return a {@link Material} from the configuration, or {@code defaultValue} as a fallback
24-
*/
25-
public static Material getMaterial(@NotNull FileConfiguration config, @NotNull String path, Material defaultValue) {
26-
String materialName = config.getString(path);
27-
if (materialName == null) {
28-
return defaultValue;
29-
}
30-
Material material = Material.getMaterial(materialName);
31-
if (material == null) {
32-
return defaultValue;
33-
}
34-
return material;
35-
}
36-
3713
private ConfigurationHelper() {
3814
throw new IllegalStateException();
3915
}

0 commit comments

Comments
 (0)