-
Notifications
You must be signed in to change notification settings - Fork 417
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
Add a level screen provider registry #4118
base: 1.21.2
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
/* | ||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package net.fabricmc.fabric.api.client.screen.v1; | ||
|
||
import java.util.Objects; | ||
import java.util.Optional; | ||
|
||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import net.minecraft.client.gui.screen.world.LevelScreenProvider; | ||
import net.minecraft.registry.RegistryKey; | ||
import net.minecraft.world.gen.WorldPreset; | ||
|
||
import net.fabricmc.fabric.mixin.screen.LevelScreenProviderAccessor; | ||
|
||
/** | ||
* Adds registration hooks for {@link LevelScreenProvider}s. | ||
*/ | ||
public final class LevelScreenProviderRegistry { | ||
private static final Logger LOGGER = LoggerFactory.getLogger(LevelScreenProviderRegistry.class); | ||
|
||
private LevelScreenProviderRegistry() { | ||
} | ||
|
||
/** | ||
* Registers a provider for a screen that allows users to adjust the generation options for a given world preset. | ||
* | ||
* @param worldPreset the world preset to register the provider for | ||
* @param provider the provider for the screen | ||
*/ | ||
public static void register(RegistryKey<WorldPreset> worldPreset, LevelScreenProvider provider) { | ||
Objects.requireNonNull(worldPreset, "world preset cannot be null"); | ||
Objects.requireNonNull(provider, "level screen provider cannot be null"); | ||
|
||
Optional<RegistryKey<WorldPreset>> key = Optional.of(worldPreset); | ||
LevelScreenProvider old = LevelScreenProviderAccessor.fabric_getWorldPresetToScreenProvider().put(key, provider); | ||
|
||
if (old != null) { | ||
LOGGER.debug("Replaced old level screen provider mapping from {} to {} with {}", worldPreset, old, provider); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Generally we crash if there is a duplicate, is there a specific usecase for overriding them? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
/* | ||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package net.fabricmc.fabric.mixin.screen; | ||
|
||
import java.util.Map; | ||
import java.util.Optional; | ||
|
||
import org.spongepowered.asm.mixin.Mixin; | ||
import org.spongepowered.asm.mixin.gen.Accessor; | ||
|
||
import net.minecraft.client.gui.screen.world.LevelScreenProvider; | ||
import net.minecraft.registry.RegistryKey; | ||
import net.minecraft.world.gen.WorldPreset; | ||
|
||
@Mixin(LevelScreenProvider.class) | ||
public interface LevelScreenProviderAccessor { | ||
@Accessor("WORLD_PRESET_TO_SCREEN_PROVIDER") | ||
static Map<Optional<RegistryKey<WorldPreset>>, LevelScreenProvider> fabric_getWorldPresetToScreenProvider() { | ||
throw new AssertionError("Untransformed @Accessor"); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
/* | ||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package net.fabricmc.fabric.mixin.screen; | ||
|
||
import java.util.HashMap; | ||
import java.util.Map; | ||
import java.util.Optional; | ||
|
||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation; | ||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; | ||
import org.spongepowered.asm.mixin.Mixin; | ||
import org.spongepowered.asm.mixin.injection.At; | ||
|
||
import net.minecraft.client.gui.screen.world.LevelScreenProvider; | ||
import net.minecraft.registry.RegistryKey; | ||
import net.minecraft.world.gen.WorldPreset; | ||
|
||
@Mixin(LevelScreenProvider.class) | ||
public interface LevelScreenProviderMixin { | ||
@WrapOperation(method = "<clinit>", at = @At(value = "INVOKE", target = "Ljava/util/Map;of(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/Map;")) | ||
private static Map<Optional<RegistryKey<WorldPreset>>, LevelScreenProvider> makeMutable(Object k1, Object v1, Object k2, Object v2, Operation<Map<Optional<RegistryKey<WorldPreset>>, LevelScreenProvider>> operation) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ModifyExpressionValue |
||
return new HashMap<>(operation.call(k1, v1, k2, v2)); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
/* | ||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package net.fabricmc.fabric.test.screen; | ||
|
||
import java.util.function.BiFunction; | ||
import java.util.function.Function; | ||
|
||
import net.minecraft.block.BlockState; | ||
import net.minecraft.client.gui.DrawContext; | ||
import net.minecraft.client.gui.screen.Screen; | ||
import net.minecraft.client.gui.screen.world.CreateWorldScreen; | ||
import net.minecraft.client.gui.widget.ButtonWidget; | ||
import net.minecraft.client.world.GeneratorOptionsHolder; | ||
import net.minecraft.registry.Registries; | ||
import net.minecraft.registry.Registry; | ||
import net.minecraft.registry.RegistryKeys; | ||
import net.minecraft.screen.ScreenTexts; | ||
import net.minecraft.text.Text; | ||
import net.minecraft.util.math.random.Random; | ||
import net.minecraft.world.biome.Biome; | ||
import net.minecraft.world.gen.chunk.ChunkGenerator; | ||
|
||
import net.fabricmc.fabric.test.screen.chunk.FabriclandChunkGenerator; | ||
import net.fabricmc.fabric.test.screen.chunk.FabriclandChunkGeneratorConfig; | ||
|
||
public class FabriclandScreen extends Screen { | ||
private static final Text TITLE = Text.literal("Fabricland"); | ||
|
||
private static final int BUTTON_WIDTH = 150; | ||
private static final int LARGE_BUTTON_WIDTH = 210; | ||
private static final int BUTTON_HEIGHT = 20; | ||
|
||
private final CreateWorldScreen parent; | ||
private final Random random = Random.create(); | ||
|
||
private FabriclandChunkGeneratorConfig config; | ||
|
||
public FabriclandScreen(CreateWorldScreen parent, GeneratorOptionsHolder generatorOptionsHolder) { | ||
super(TITLE); | ||
this.parent = parent; | ||
|
||
ChunkGenerator chunkGenerator = generatorOptionsHolder.selectedDimensions().getChunkGenerator(); | ||
this.config = FabriclandChunkGeneratorConfig.from(chunkGenerator); | ||
} | ||
|
||
@Override | ||
protected void init() { | ||
int x = (this.width - LARGE_BUTTON_WIDTH) / 2; | ||
|
||
this.addDrawableChild(createChangeBlockButton("outline", FabriclandChunkGeneratorConfig::outline, (config, outline) -> config.withOutline(outline)).dimensions(x, 80, LARGE_BUTTON_WIDTH, BUTTON_HEIGHT).build()); | ||
this.addDrawableChild(createChangeBlockButton("background", FabriclandChunkGeneratorConfig::background, (config, background) -> config.withBackground(background)).dimensions(x, 105, LARGE_BUTTON_WIDTH, BUTTON_HEIGHT).build()); | ||
|
||
this.addDrawableChild(ButtonWidget.builder(ScreenTexts.DONE, button -> { | ||
this.parent.getWorldCreator().applyModifier((dynamicRegistryManager, dimensionsRegistryHolder) -> { | ||
Registry<Biome> biomeRegistry = dynamicRegistryManager.getOrThrow(RegistryKeys.BIOME); | ||
ChunkGenerator chunkGenerator = new FabriclandChunkGenerator(biomeRegistry, config); | ||
|
||
return dimensionsRegistryHolder.with(dynamicRegistryManager, chunkGenerator); | ||
}); | ||
|
||
this.client.setScreen(this.parent); | ||
}).dimensions((this.width - BUTTON_WIDTH) / 2, this.height - 28, BUTTON_WIDTH, BUTTON_HEIGHT).build()); | ||
} | ||
|
||
@Override | ||
public void render(DrawContext context, int mouseX, int mouseY, float delta) { | ||
super.render(context, mouseX, mouseY, delta); | ||
context.drawCenteredTextWithShadow(this.textRenderer, this.title, this.width / 2, 8, 0xFFFFFF); | ||
} | ||
|
||
@Override | ||
public void close() { | ||
this.client.setScreen(this.parent); | ||
} | ||
|
||
private ButtonWidget.Builder createChangeBlockButton(String suffix, Function<FabriclandChunkGeneratorConfig, BlockState> getter, BiFunction<FabriclandChunkGeneratorConfig, BlockState, FabriclandChunkGeneratorConfig> setter) { | ||
Text title = getChangeBlockButtonMessage(suffix, getter.apply(this.config)); | ||
|
||
return ButtonWidget.builder(title, button -> { | ||
Registries.BLOCK.getRandom(this.random).ifPresent(entry -> { | ||
BlockState next = entry.value().getDefaultState(); | ||
|
||
this.config = setter.apply(this.config, next); | ||
button.setMessage(getChangeBlockButtonMessage(suffix, next)); | ||
}); | ||
}); | ||
} | ||
|
||
private static Text getChangeBlockButtonMessage(String suffix, BlockState state) { | ||
return Text.translatable("generator.fabric-screen-api-v1-testmod.fabricland." + suffix, state.getBlock().getName()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,18 +28,30 @@ | |
import net.minecraft.client.gui.widget.ButtonWidget; | ||
import net.minecraft.client.gui.widget.ClickableWidget; | ||
import net.minecraft.client.render.RenderLayer; | ||
import net.minecraft.registry.Registries; | ||
import net.minecraft.registry.Registry; | ||
import net.minecraft.registry.RegistryKey; | ||
import net.minecraft.registry.RegistryKeys; | ||
import net.minecraft.text.Text; | ||
import net.minecraft.util.Identifier; | ||
import net.minecraft.world.gen.WorldPreset; | ||
|
||
import net.fabricmc.api.ClientModInitializer; | ||
import net.fabricmc.fabric.api.client.screen.v1.LevelScreenProviderRegistry; | ||
import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents; | ||
import net.fabricmc.fabric.api.client.screen.v1.ScreenKeyboardEvents; | ||
import net.fabricmc.fabric.api.client.screen.v1.Screens; | ||
import net.fabricmc.fabric.test.screen.chunk.FabriclandChunkGenerator; | ||
|
||
public final class ScreenTests implements ClientModInitializer { | ||
public static final String MOD_ID = "fabric-screen-api-v1-testmod"; | ||
|
||
public static final Identifier ARMOR_FULL_TEXTURE = Identifier.ofVanilla("hud/armor_full"); | ||
private static final Logger LOGGER = LoggerFactory.getLogger("FabricScreenApiTests"); | ||
|
||
public static final Identifier FABRICLAND_ID = id("fabricland"); | ||
public static final RegistryKey<WorldPreset> FABRICLAND_WORLD_PRESET = RegistryKey.of(RegistryKeys.WORLD_PRESET, FABRICLAND_ID); | ||
|
||
@Override | ||
public void onInitializeClient() { | ||
LOGGER.info("Started Screen Testmod"); | ||
|
@@ -48,6 +60,12 @@ public void onInitializeClient() { | |
}); | ||
|
||
ScreenEvents.AFTER_INIT.register(this::afterInitScreen); | ||
|
||
Registry.register(Registries.CHUNK_GENERATOR, FABRICLAND_ID, FabriclandChunkGenerator.CODEC); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assume its fine to register this only on the client? In a real mod this wouldnt be like this, maybe just put a comment on it for people using it as a reference.. |
||
LevelScreenProviderRegistry.register(FABRICLAND_WORLD_PRESET, (parent, generatorOptionsHolder) -> { | ||
LOGGER.info("Provided level screen provider for Fabricland"); | ||
return new FabriclandScreen(parent, generatorOptionsHolder); | ||
}); | ||
} | ||
|
||
private void afterInitScreen(MinecraftClient client, Screen screen, int windowWidth, int windowHeight) { | ||
|
@@ -96,6 +114,10 @@ private void afterInitScreen(MinecraftClient client, Screen screen, int windowWi | |
} | ||
} | ||
|
||
public static Identifier id(String name) { | ||
return Identifier.of(MOD_ID, name); | ||
} | ||
|
||
// Test that mouseReleased is called | ||
private static final class TestButtonWidget extends ButtonWidget { | ||
private TestButtonWidget() { | ||
|
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.
We generally use interfaces for most public API like this, I would move the logic (with the logger) to an impl class.