diff --git a/forge/src/main/java/com/mineagent/ClientEventHandler.java b/forge/src/main/java/com/mineagent/ClientEventHandler.java index b3d26ed..8946b0f 100644 --- a/forge/src/main/java/com/mineagent/ClientEventHandler.java +++ b/forge/src/main/java/com/mineagent/ClientEventHandler.java @@ -4,11 +4,19 @@ import com.mojang.logging.LogUtils; import java.nio.ByteBuffer; import net.minecraft.client.Minecraft; -import net.minecraft.client.player.LocalPlayer; -import net.minecraft.client.gui.screens.TitleScreen; +import net.minecraft.client.gui.screens.AccessibilityOnboardingScreen; import net.minecraft.client.gui.screens.DeathScreen; import net.minecraft.client.gui.screens.PauseScreen; +import net.minecraft.client.gui.screens.TitleScreen; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.world.Difficulty; import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.level.GameRules; +import net.minecraft.world.level.GameType; +import net.minecraft.world.level.LevelSettings; +import net.minecraft.world.level.WorldDataConfiguration; +import net.minecraft.world.level.levelgen.WorldOptions; +import net.minecraft.world.level.levelgen.presets.WorldPresets; import net.minecraftforge.event.TickEvent; import net.minecraftforge.event.entity.living.LivingDeathEvent; import net.minecraftforge.event.entity.living.LivingHurtEvent; @@ -19,7 +27,10 @@ import org.lwjgl.opengl.GL11; import org.slf4j.Logger; -/** Handles client-side game events and coordinates input injection with observations. */ +/** + * Handles client-side game events and coordinates input injection with + * observations. + */ public class ClientEventHandler { private static final Logger LOGGER = LogUtils.getLogger(); private static final DataBridge dataBridge = DataBridge.getInstance(); @@ -42,33 +53,36 @@ public static void onClientTick(TickEvent.ClientTickEvent event) { } Minecraft mc = Minecraft.getInstance(); boolean inWorld = mc.level != null && mc.player != null; + boolean onAccessibilityScreen = mc.screen instanceof AccessibilityOnboardingScreen; boolean onTitleScreen = mc.screen instanceof TitleScreen; boolean onDeathScreen = mc.screen instanceof DeathScreen; boolean onPauseScreen = mc.screen instanceof PauseScreen; boolean isMenu = mc.screen != null; boolean inWorldWithOverlay = mc.level != null && mc.screen != null; - if (onTitleScreen) { - mc.createWorldOpenFlows().openWorld("New World", () -> { - mc.forceSetScreen(new TitleScreen()); - }); - // final ResetInput resetInput = dataBridge.getLatestResetInput(); - // if (resetInput != null) { - // // TODO: Open new world? This currently only opens existing world - // // Minecraft mc = Minecraft.getInstance(); - // // createFreshLevel opens the world creation flow. - // // For a quick programmatic world with defaults: - // // mc.createWorldOpenFlows().createFreshLevel( - // // saveName, // String: folder name for the save - // // displayName, // String: human-readable world name - // // WorldPresets.NORMAL, // or FLAT, AMPLIFIED, etc. -- ResourceKey - // // null, // @Nullable Screen to return to on cancel - // // null // @Nullable WorldCreationContext overrides - // // ); - // } + if (onTitleScreen || onAccessibilityScreen) { + String worldName = Config.WORLD_NAME.get(); + if (Config.CREATE_NEW_WORLD.get()) { + String folderName = worldName.toLowerCase().replace(' ', '-'); + mc.createWorldOpenFlows() + .createFreshLevel( + folderName, + new LevelSettings( + worldName, + GameType.SURVIVAL, + false, + Difficulty.NORMAL, + false, + new GameRules(WorldDataConfiguration.DEFAULT.enabledFeatures()), + WorldDataConfiguration.DEFAULT), + WorldOptions.defaultWithRandomSeed(), + WorldPresets::createNormalWorldDimensions, + new TitleScreen()); + } else { + mc.createWorldOpenFlows().openWorld(worldName, () -> mc.forceSetScreen(new TitleScreen())); + } return; - } - else if (onPauseScreen) { + } else if (onPauseScreen) { mc.setScreen(null); return; } @@ -96,14 +110,17 @@ else if (onPauseScreen) { // This fires press events and sets KeyMapping states for held buttons dataBridge.getInputInjector().maintainButtonState(); - // Observations only while the local player exists (no capture on death screen). Extrinsic - // reward queued during death therefore attaches to the first frame after respawn if needed. + // Observations only while the local player exists (no capture on death screen). + // Extrinsic + // reward queued during death therefore attaches to the first frame after + // respawn if needed. double reward = dataBridge.takeExtrinsicReward(); dataBridge.sendObservation(new Observation(reward, captureFrame())); } /** - * Handles input suppression when a Python client is connected. Disables the system cursor to + * Handles input suppression when a Python client is connected. Disables the + * system cursor to * prevent real mouse input from interfering. */ private static void handleInputSuppression(Minecraft mc) { @@ -146,7 +163,9 @@ public static void onPlayerDeath(LivingDeathEvent event) { } } - /** The player the agent controls on this machine (not other players or mobs). */ + /** + * The player the agent controls on this machine (not other players or mobs). + */ private static boolean isClientControlledPlayer(LivingEntity entity) { return entity instanceof LocalPlayer p && p == Minecraft.getInstance().player; } diff --git a/forge/src/main/java/com/mineagent/Config.java b/forge/src/main/java/com/mineagent/Config.java index c769aa3..fce0510 100644 --- a/forge/src/main/java/com/mineagent/Config.java +++ b/forge/src/main/java/com/mineagent/Config.java @@ -23,6 +23,9 @@ public class Config { public static final ForgeConfigSpec.ConfigValue EXTRINSIC_DAMAGE_PER_POINT; public static final ForgeConfigSpec.ConfigValue DEATH_PENALTY; + public static final ForgeConfigSpec.ConfigValue CREATE_NEW_WORLD; + public static final ForgeConfigSpec.ConfigValue WORLD_NAME; + // Built Configuration Specification public static final ForgeConfigSpec SPEC; @@ -110,6 +113,22 @@ public class Config { BUILDER.pop(); + BUILDER.comment("World Configuration"); + BUILDER.push("world"); + + CREATE_NEW_WORLD = + BUILDER + .comment( + "If true, create a new world on startup; if false, load an existing save with the given name") + .define("create_new_world", false); + + WORLD_NAME = + BUILDER + .comment("Display name of the world to load or create (e.g. \"New World\")") + .define("world_name", "New World"); + + BUILDER.pop(); + // Build the specification after all values are defined SPEC = BUILDER.build(); }