Skip to content

Commit

Permalink
Add a level screen provider registry
Browse files Browse the repository at this point in the history
Fixes #3926
  • Loading branch information
haykam821 committed Sep 24, 2024
1 parent e1e5711 commit 2934b46
Show file tree
Hide file tree
Showing 12 changed files with 526 additions and 0 deletions.
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);
}
}
}
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) {
return new HashMap<>(operation.call(k1, v1, k2, v2));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
"client": [
"GameRendererMixin",
"HandledScreenMixin",
"LevelScreenProviderAccessor",
"LevelScreenProviderMixin",
"MinecraftClientMixin",
"ScreenMixin"
],
Expand Down
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
Expand Up @@ -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");
Expand All @@ -48,6 +60,12 @@ public void onInitializeClient() {
});

ScreenEvents.AFTER_INIT.register(this::afterInitScreen);

Registry.register(Registries.CHUNK_GENERATOR, FABRICLAND_ID, FabriclandChunkGenerator.CODEC);
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) {
Expand Down Expand Up @@ -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() {
Expand Down
Loading

0 comments on commit 2934b46

Please sign in to comment.