Skip to content
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

Closed
wants to merge 17 commits into from

Conversation

Foghrye4
Copy link
Contributor

@Foghrye4 Foghrye4 commented Dec 22, 2018

No description provided.

public class TestCustomGeneratorSettingsFixer {
String cc655ExamplePreset = "{\n" +
" \"waterLevel\":63,\n" +
" \"caves\":true,\n" +
Copy link
Member

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());
Copy link
Member

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

Copy link
Contributor Author

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?

Copy link
Member

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@Barteks2x Barteks2x changed the title External generator settings (Full-assed with unit test) External generator settings (new implementation) Jan 19, 2019
CustomGeneratorSettings settings = null;
if (externalGeneratorPresetFile.exists()) {
try (FileReader reader = new FileReader(externalGeneratorPresetFile)){
CharBuffer sb = CharBuffer.allocate((int) externalGeneratorPresetFile.length() * 2);
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

Copy link
Member

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));
Copy link
Member

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)

Copy link
Contributor Author

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?

Copy link
Member

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.

Copy link
Member

@Barteks2x Barteks2x Jan 20, 2019

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}";
Copy link
Member

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)

@Barteks2x
Copy link
Member

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)

@Foghrye4
Copy link
Contributor Author

Tested with 380 Kb preset - level.dat saved fine.

settings = CustomGeneratorSettings.fromJson(CustomCubicWorldType.pendingCustomCubicSettingsJsonString);
CustomCubicWorldType.pendingCustomCubicSettingsJsonString = "";
CustomCubicWorldType.createNewWorld = false;
Copy link
Member

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.

Copy link
Contributor Author

@Foghrye4 Foghrye4 Jan 20, 2019

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());
Copy link
Member

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)
Copy link
Member

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;
Copy link
Member

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;
Copy link
Member

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;
Copy link
Member

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;
Copy link
Member

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) {
Copy link
Member

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));
Copy link
Member

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
Copy link
Member

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.

@Barteks2x
Copy link
Member

Closing, implemented in #11

@Barteks2x Barteks2x closed this Jan 26, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants