-
Notifications
You must be signed in to change notification settings - Fork 23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
External generator settings (new implementation) #10
Conversation
public class TestCustomGeneratorSettingsFixer { | ||
String cc655ExamplePreset = "{\n" + | ||
" \"waterLevel\":63,\n" + | ||
" \"caves\":true,\n" + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use all settings non-defaults in tests
@Test | ||
public void testCustomGeneratorSettingsFixer() { | ||
CustomGeneratorSettings settings = CustomGeneratorSettings.fromJson(cc655ExamplePreset); | ||
assertEquals(13,settings.standardOres.size()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't verify that the preset actually has all the right data
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So what I should assert?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First, test conversion between specific versions, and second, just put the expected json and assert that they make equal JsonObject
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
CustomGeneratorSettings settings = null; | ||
if (externalGeneratorPresetFile.exists()) { | ||
try (FileReader reader = new FileReader(externalGeneratorPresetFile)){ | ||
CharBuffer sb = CharBuffer.allocate((int) externalGeneratorPresetFile.length() * 2); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What... why twice the length?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CharBuffer.allocate(int)
consume char buffer capacity in chars, File.length()
return size in bytes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, I'm derping a little. I should divide by 2.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually no. Files and java chars use completely different character encoding. Files are UTF-8 and java uses UTF-16. For the ASCII subset, 1 byte in file corresponds to 1 java character. There can be multiple bytes in file that also correspond to a single java char. But not the other way around.
for (JsonElement entry : array) { | ||
JsonArray mapEntry = entry.getAsJsonArray(); | ||
JsonElement key = mapEntry.get(0); | ||
JsonObject value = stringToJson(fixGeneratorOptions(mapEntry.get(1).getAsJsonObject(), true)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs the "parent" one as context, and use parent preset as defaults (this also means getCubeAreas
needs to be called last, so that the parent preset is otherwise complete)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This means that every cube area will be as huge as full list of settings. And manual editing of preset would be slightly harder. Are you sure it should be that way?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this is the way it should be done. No magic inheritance.
The goal is that layers as they are now are eventually made irrelevant and I know what I need to do to make that happen.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To explain in more detail the reasoning:
- This mechanism is just confusing and extremely hard to explain. So far there has been one person who used it explicitly, and that's in the exact place where it doesn't actually work the way it's intended to...
- Which bring the "it doesn't work" part. For big presets, the single biggest part is ores. And this is also the exact place where it just doesn't work and can't work.
- It's a pain to handle in code, especially as preset format evolves. If each layer doesn't depend on ANYTHING else, then you can put a valid preset (or layer from that preset) from ANY format version (with the exception of compressed v3 and earlier, so anything without "version" specified is not going to work) and put it as a layer in preset of any other version.
With inheritance, mixing versions just can't work without either special handling for different version combinations, or some kind of intermediate representation, where old fixers would have to be kept up to date with. Fixers are supposed to be write once and never touch it again things, so this is obviously not going to work that way. For v3 and newer, you would just plug a layer into the whole fixer system starting from the beginning, it would figure out the versions and convert as needed, and you wouldn't need any kind of special context. It would also be increasingly difficult to handle and document all the possible version interactions - The layer system will get more or less phased out. It will remain there as a supported use case, but for what it's used right now, there will be a far superior system. So preset size and editing convenience is going to be less of a concern. I'm just waiting on this to actually start implementing these things because I really want this before I start messing around with presets again, to avoid making handling the "legacy" preset format even more of a pain.
" \"highNoiseOctaves\":3\n" + | ||
"}"; | ||
|
||
String expectedConversionResult = "{\"waterLevel\":100,\"caves\":false,\"strongholds\":false,\"alternateStrongholdsPositions\":false,\"villages\":false,\"mineshafts\":false,\"temples\":false,\"oceanMonuments\":false,\"woodlandMansions\":false,\"ravines\":false,\"dungeons\":false,\"dungeonCount\":101,\"waterLakes\":false,\"waterLakeRarity\":102,\"lavaLakes\":false,\"lavaLakeRarity\":103,\"aboveSeaLavaLakeRarity\":104,\"lavaOceans\":true,\"biome\":1,\"biomeSize\":104,\"riverSize\":105,\"standardOres\":[{\"blockstate\":{\"Properties\":{\"variant\":\"dirt\",\"snowy\":\"false\"},\"Name\":\"minecraft:dirt\"},\"spawnSize\":134,\"spawnTries\":135,\"spawnProbability\":136.0,\"minHeight\":-Infinity,\"maxHeight\":Infinity},{\"blockstate\":{\"Name\":\"minecraft:gravel\"},\"spawnSize\":137,\"spawnTries\":138,\"spawnProbability\":139.0,\"minHeight\":-Infinity,\"maxHeight\":Infinity},{\"blockstate\":{\"Properties\":{\"variant\":\"granite\"},\"Name\":\"minecraft:stone\"},\"spawnSize\":140,\"spawnTries\":141,\"spawnProbability\":142.0,\"minHeight\":-Infinity,\"maxHeight\":143.0},{\"blockstate\":{\"Properties\":{\"variant\":\"diorite\"},\"Name\":\"minecraft:stone\"},\"spawnSize\":144,\"spawnTries\":145,\"spawnProbability\":146.0,\"minHeight\":-Infinity,\"maxHeight\":147.0},{\"blockstate\":{\"Properties\":{\"variant\":\"andesite\"},\"Name\":\"minecraft:stone\"},\"spawnSize\":148,\"spawnTries\":149,\"spawnProbability\":150.0,\"minHeight\":-Infinity,\"maxHeight\":151.0},{\"blockstate\":{\"Name\":\"minecraft:coal_ore\"},\"spawnSize\":152,\"spawnTries\":153,\"spawnProbability\":154.0,\"minHeight\":-Infinity,\"maxHeight\":155.0},{\"blockstate\":{\"Name\":\"minecraft:iron_ore\"},\"spawnSize\":156,\"spawnTries\":157,\"spawnProbability\":158.0,\"minHeight\":-Infinity,\"maxHeight\":159.0},{\"blockstate\":{\"Name\":\"minecraft:gold_ore\"},\"spawnSize\":160,\"spawnTries\":161,\"spawnProbability\":162.0,\"minHeight\":-Infinity,\"maxHeight\":-163.0},{\"blockstate\":{\"Name\":\"minecraft:redstone_ore\"},\"spawnSize\":164,\"spawnTries\":165,\"spawnProbability\":166.0,\"minHeight\":-Infinity,\"maxHeight\":-167.0},{\"blockstate\":{\"Name\":\"minecraft:diamond_ore\"},\"spawnSize\":168,\"spawnTries\":169,\"spawnProbability\":170.0,\"minHeight\":-Infinity,\"maxHeight\":-178.0},{\"blockstate\":{\"Name\":\"minecraft:emerald_ore\"},\"biomes\":[\"minecraft:extreme_hills\",\"minecraft:smaller_extreme_hills\",\"minecraft:extreme_hills_with_trees\",\"minecraft:mutated_extreme_hills\",\"minecraft:mutated_extreme_hills_with_trees\"],\"spawnSize\":3,\"spawnTries\":184,\"spawnProbability\":185.0,\"minHeight\":-Infinity,\"maxHeight\":-186.0},{\"blockstate\":{\"Properties\":{\"variant\":\"stone\"},\"Name\":\"minecraft:monster_egg\"},\"biomes\":[\"minecraft:extreme_hills\",\"minecraft:smaller_extreme_hills\",\"minecraft:extreme_hills_with_trees\",\"minecraft:mutated_extreme_hills\",\"minecraft:mutated_extreme_hills_with_trees\"],\"spawnSize\":187,\"spawnTries\":188,\"spawnProbability\":189.0,\"minHeight\":-Infinity,\"maxHeight\":190.0},{\"blockstate\":{\"Name\":\"minecraft:gold_ore\"},\"biomes\":[\"minecraft:mesa\",\"minecraft:mesa_clear_rock\",\"minecraft:mesa_rock\",\"minecraft:mutated_mesa\",\"minecraft:mutated_mesa_clear_rock\",\"minecraft:mutated_mesa_rock\"],\"spawnSize\":191,\"spawnTries\":192,\"spawnProbability\":193.0,\"minHeight\":-194.0,\"maxHeight\":196.0}],\"periodicGaussianOres\":[{\"blockstate\":{\"Name\":\"minecraft:lapis_ore\"},\"spawnSize\":179,\"spawnTries\":180,\"spawnProbability\":181.0,\"heightMean\":182.0,\"heightStdDeviation\":183.0,\"heightSpacing\":0.0,\"minHeight\":0.0,\"maxHeight\":0.0}],\"expectedBaseHeight\":110.0,\"expectedHeightVariation\":109.0,\"actualHeight\":544.0,\"heightVariationFactor\":106.0,\"specialHeightVariationFactorBelowAverageY\":107.0,\"heightVariationOffset\":108.0,\"heightFactor\":109.0,\"heightOffset\":110.0,\"depthNoiseFactor\":111.0,\"depthNoiseOffset\":112.0,\"depthNoiseFrequencyX\":113.0,\"depthNoiseFrequencyZ\":114.0,\"depthNoiseOctaves\":5,\"selectorNoiseFactor\":116.0,\"selectorNoiseOffset\":117.0,\"selectorNoiseFrequencyX\":118.0,\"selectorNoiseFrequencyY\":119.0,\"selectorNoiseFrequencyZ\":120.0,\"selectorNoiseOctaves\":1,\"lowNoiseFactor\":122.0,\"lowNoiseOffset\":123.0,\"lowNoiseFrequencyX\":124.0,\"lowNoiseFrequencyY\":125.0,\"lowNoiseFrequencyZ\":126.0,\"lowNoiseOctaves\":7,\"highNoiseFactor\":128.0,\"highNoiseOffset\":129.0,\"highNoiseFrequencyX\":130.0,\"highNoiseFrequencyY\":131.0,\"highNoiseFrequencyZ\":132.0,\"highNoiseOctaves\":3,\"cubeAreas\":[],\"replacerConfig\":{\"defaults\":{\"cubicgen:biome_fill_noise_octaves\":4.0,\"cubicgen:ocean_block\":{\"Properties\":{\"level\":\"0\"},\"Name\":\"minecraft:water\"},\"cubicgen:height_scale\":64.0,\"cubicgen:biome_fill_noise_freq\":0.0078125,\"cubicgen:water_level\":63.0,\"cubicgen:biome_fill_depth_factor\":2.3333333333333335,\"cubicgen:terrain_fill_block\":{\"Properties\":{\"variant\":\"stone\"},\"Name\":\"minecraft:stone\"},\"cubicgen:mesa_depth\":16.0,\"cubicgen:biome_fill_depth_offset\":3.0,\"cubicgen:horizontal_gradient_depth_decrease_weight\":1.0,\"cubicgen:height_offset\":64.0},\"overrides\":{}},\"version\":3}"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Split into multiple lines (without \n
)
I also still don't think it actually works with presets bigger than 64kB correctly. Just try such preset. Last time I tested it (and I didn't see any relevant changes since then), MC would throw an exception when writing level.dat (the exception is then ignored, but it means level.dat never gets updated) |
Tested with 380 Kb preset - |
… new world creation
settings = CustomGeneratorSettings.fromJson(CustomCubicWorldType.pendingCustomCubicSettingsJsonString); | ||
CustomCubicWorldType.pendingCustomCubicSettingsJsonString = ""; | ||
CustomCubicWorldType.createNewWorld = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There should be a better way to determine if it's a new world that is used in common event handler.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can suggest to do a same thing but in a AttachCapabilitiesEvent<World>
handler with high priority. Another option is to retrieve saveDirName
from GuiCreateWorld
and create generator settings file before World instance creation on CustomCubicWorldType.onGUICreateWorldPress()
.
|
||
@Override | ||
public void preInit() { | ||
MinecraftForge.EVENT_BUS.register(new GuiEventHandler()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Proxy not needed. use @Mod.EventBusSubscriber(modid = modid, value = Side.CLIENT)
if (!action.getButton().enabled) | ||
return; | ||
// "Recreate world" has button id = 5 | ||
if (action.getButton().id != 5) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why a comment instead of making a private static final int
field?
String json = CustomGeneratorSettings.loadJsonStringFromSaveFolder(isavehandler); | ||
isavehandler.flush(); | ||
if (json != null) { | ||
CustomCubicWorldType.pendingCustomCubicSettingsJsonString = json; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really don't like that static field here. Verifying that this field has the right value at the right time is nearly impossible without actually trying every possibility.
Instead I would suggest just mixing into WorldSettings
and WorldInfo
(same as what I did in CC itself, with MixinWorldSettings
and MixinWorldInfo
).
This also should remove the need for createNewWorld
variable.
This would also allow to actually store the loaded preset text somewhere instead of loading it from disk each time it's needed.
If you actually inject in the right place, you can almost completely hide the fact that the preset is not saved in level.dat, by just not saving that part into level.dat for CustomCubic worlds, and instead have code somewhere else that saves it.
import java.util.Map; | ||
import java.util.Set; | ||
|
||
import javax.annotation.Nullable; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why change import order?
|
||
public static boolean isOutdated(String settingsJsonString) { | ||
JsonObject root = CustomGeneratorSettingsFixer.stringToJson(settingsJsonString); | ||
return !root.has("version") || root.get("version").getAsInt() != CustomGeneratorSettingsFixer.VERSION; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
<
instead of !=
. Don't touch the settings if the stored version is newer. Ideally, CWG wouldn't even let you open such world without a warning, but I'm not sure how to do that.
}; | ||
|
||
private static boolean isActual(JsonObject root) { | ||
return root.has("version") && root.get("version").getAsInt() == 3; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actual
is most likely the wrong word here.
Gson gson = CustomGeneratorSettings.gson(); | ||
JsonObject oldRoot = CustomGeneratorSettingsFixer.stringToJson(json); | ||
boolean rootIsActual = isActual(oldRoot); | ||
if (rootIsActual) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same
} | ||
JsonObject newRoot = stringToJson("{}"); | ||
newRoot.add("version", new JsonPrimitive(3)); | ||
newRoot.add("waterLevel", getWaterLevel(oldRoot,parent)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Formatting
newRoot.add("highNoiseFrequencyZ", getHighNoiseFrequencyZ(oldRoot,parent)); | ||
newRoot.add("highNoiseOctaves", getHighNoiseOctaves(oldRoot,parent)); | ||
newRoot.add("replacerConfig", getReplacerConfig(oldRoot,parent)); | ||
if (parent == null) // cube areas allowed only for root |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually no. Any sub-layer can contain more CubeAreas. Any valid preset can be used as a cube area.
Closing, implemented in #11 |
No description provided.