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

Add a level screen provider registry #4118

Open
wants to merge 1 commit into
base: 1.21.2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
Copy link
Member

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.

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

Choose a reason for hiding this comment

The 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?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The FlattenableBlockRegistry, LandPathNodeTypesRegistry, SculkSensorFrequencyRegistry, StrippableBlockRegistry, VillagerInteractionRegistries, FabricDefaultAttributeRegistry, MinecartComparatorLogicRegistry, and VillagerTypeHelper classes provide registration methods which log replaced values the same way.

}
}
}
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) {

Choose a reason for hiding this comment

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

Choose a reason for hiding this comment

The 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) {
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
Loading