diff --git a/.github/workflows/publish_1.21.2.yml b/.github/workflows/publish_1.21.2.yml index ec6c400..fa3a7a3 100644 --- a/.github/workflows/publish_1.21.2.yml +++ b/.github/workflows/publish_1.21.2.yml @@ -50,4 +50,4 @@ jobs: 1.21.2 1.21.3 - version: mc1.21.2+mc1.21.3-1.2.4 + version: mc1.21.2+mc1.21.3-1.3.0 diff --git a/.github/workflows/publish_1.21.4.yml b/.github/workflows/publish_1.21.4.yml index b96b83c..91d6040 100644 --- a/.github/workflows/publish_1.21.4.yml +++ b/.github/workflows/publish_1.21.4.yml @@ -49,4 +49,4 @@ jobs: game-versions: | 1.21.4 - version: mc1.21.4-1.2.4 + version: mc1.21.4-1.3.0 diff --git a/.github/workflows/publish_1.21.5.yml b/.github/workflows/publish_1.21.5.yml index ab7e23f..5264105 100644 --- a/.github/workflows/publish_1.21.5.yml +++ b/.github/workflows/publish_1.21.5.yml @@ -49,4 +49,4 @@ jobs: game-versions: | 1.21.5 - version: mc1.21.5-1.2.4 \ No newline at end of file + version: mc1.21.5-1.3.0 \ No newline at end of file diff --git a/.github/workflows/publish_1.21.6.yml b/.github/workflows/publish_1.21.6.yml new file mode 100644 index 0000000..c414a42 --- /dev/null +++ b/.github/workflows/publish_1.21.6.yml @@ -0,0 +1,54 @@ +name: publish_1.21.6.yml + +on: [ pull_request, workflow_dispatch ] + +permissions: write-all + +jobs: + build: + runs-on: ubuntu-latest + permissions: write-all + steps: + - name: 检查环境变量 + run: env + + - name: 签出存储库 + uses: actions/checkout@v3 + with: + submodules: true + + - name: 设置 Java + uses: actions/setup-java@v2 + with: + distribution: "temurin" + java-version: 21 + + - name: 使 Gradle Wrapper 可执行 + if: ${{ runner.os != 'Windows' }} + run: chmod +x ./gradlew + + - name: 构建 + run: ./gradlew build -Ptarget_version=1.21.6 + + - name: 发布 + uses: Kir-Antipov/mc-publish@v3.3 + with: + curseforge-id: ${{ vars.CURSEFORGE_ID }} + curseforge-token: ${{ secrets.CURSEFORGE_TOKEN }} + + modrinth-id: ${{ vars.MODRINTH_ID }} + modrinth-token: ${{ secrets.MODRINTH_TOKEN }} + + version-type: release + changelog-file: CHANGELOG.md + + java: 21 + + loaders: fabric + + game-versions: | + 1.21.6 + 1.21.7 + 1.21.8 + + version: mc1.21.6+mc1.21.7+mc1.21.8-1.3.0 \ No newline at end of file diff --git a/.github/workflows/publish_1.21.9.yml b/.github/workflows/publish_1.21.9.yml new file mode 100644 index 0000000..40f381f --- /dev/null +++ b/.github/workflows/publish_1.21.9.yml @@ -0,0 +1,53 @@ +name: publish_1.21.9.yml + +on: [ pull_request, workflow_dispatch ] + +permissions: write-all + +jobs: + build: + runs-on: ubuntu-latest + permissions: write-all + steps: + - name: 检查环境变量 + run: env + + - name: 签出存储库 + uses: actions/checkout@v3 + with: + submodules: true + + - name: 设置 Java + uses: actions/setup-java@v2 + with: + distribution: "temurin" + java-version: 21 + + - name: 使 Gradle Wrapper 可执行 + if: ${{ runner.os != 'Windows' }} + run: chmod +x ./gradlew + + - name: 构建 + run: ./gradlew build -Ptarget_version=1.21.9 + + - name: 发布 + uses: Kir-Antipov/mc-publish@v3.3 + with: + curseforge-id: ${{ vars.CURSEFORGE_ID }} + curseforge-token: ${{ secrets.CURSEFORGE_TOKEN }} + + modrinth-id: ${{ vars.MODRINTH_ID }} + modrinth-token: ${{ secrets.MODRINTH_TOKEN }} + + version-type: release + changelog-file: CHANGELOG.md + + java: 21 + + loaders: fabric + + game-versions: | + 1.21.9 + 1.21.10 + + version: mc1.21.9+mc1.21.10-1.3.0 \ No newline at end of file diff --git a/.github/workflows/publish_1.21.yml b/.github/workflows/publish_1.21.yml index 72bbdec..fde5ac7 100644 --- a/.github/workflows/publish_1.21.yml +++ b/.github/workflows/publish_1.21.yml @@ -50,4 +50,4 @@ jobs: 1.21 1.21.1 - version: mc1.21+mc1.21.1-1.2.4 + version: mc1.21+mc1.21.1-1.3.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index 8060030..0beb321 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ -# 1.2.4 -* 修正部分项目结构以准备向后支持更新的版本 -* 测试 MC-Publish 的自动部署(QwQ \ No newline at end of file +# 1.3.0 +* 支持至 MC 1.21.10~ +* 支持在退出世界时与退出游戏时进行二次确认~ + * 可在模组设置中开关 + +# 1.3.0 +* Supports up to MC 1.21.10~ +* Supports secondary confirmation when exiting the world and exiting the game~ + * Can be toggled in mod settings \ No newline at end of file diff --git a/README.md b/README.md index ad7430a..172d1b6 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ Have a different Title Screen ! 拥有一个与众不同的标题屏幕! +**芒果窗的主要功能已经完成更新,目前不考虑在此模组中增加新的功能,今后的工作仅在于问题修复与兼容后续版本。** + ## 获取 Modrinth: diff --git a/build.gradle b/build.gradle index dd0e01d..7a31b5f 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'fabric-loom' version '1.10-SNAPSHOT' + id 'fabric-loom' version '1.13-SNAPSHOT' id 'maven-publish' } @@ -69,6 +69,7 @@ repositories { // See https://docs.gradle.org/current/userguide/declaring_repositories.html // for more information about repositories. maven { + name = "fanfan" url = "https://maven.fanfan.moe/repository/maven-public/" } } diff --git a/gradle.properties b/gradle.properties index b3a1925..bf55582 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,8 +3,8 @@ org.gradle.jvmargs=-Xmx1G # Fabric Properties # check these on https://modmuss50.me/fabric.html -# 1.21 | 1.21.5 -target_version=1.21.5 +# 1.21 | 1.21.10 +target_version=1.21.10 v121_yarn_mappings=1.21+build.1 v1211_yarn_mappings=1.21.1+build.1 @@ -12,6 +12,11 @@ v1212_yarn_mappings=1.21.2+build.1 v1213_yarn_mappings=1.21.3+build.1 v1214_yarn_mappings=1.21.4+build.1 v1215_yarn_mappings=1.21.5+build.1 +v1216_yarn_mappings=1.21.6+build.1 +v1217_yarn_mappings=1.21.7+build.8 +v1218_yarn_mappings=1.21.8+build.1 +v1219_yarn_mappings=1.21.9+build.1 +v12110_yarn_mappings=1.21.10+build.2 v121_modmenu_version=11.0.3 v1211_modmenu_version=11.0.3 @@ -19,6 +24,11 @@ v1212_modmenu_version=12.0.0 v1213_modmenu_version=12.0.0 v1214_modmenu_version=13.0.3 v1215_modmenu_version=14.0.0-rc.2 +v1216_modmenu_version=15.0.0 +v1217_modmenu_version=15.0.0 +v1218_modmenu_version=15.0.0 +v1219_modmenu_version=16.0.0-rc.1 +v12110_modmenu_version=16.0.0-rc.1 v121_cloth_config_api_version=15.0.140 v1211_cloth_config_api_version=15.0.140 @@ -26,6 +36,11 @@ v1212_cloth_config_api_version=16.0.143 v1213_cloth_config_api_version=16.0.143 v1214_cloth_config_api_version=17.0.144 v1215_cloth_config_api_version=18.0.145 +v1216_cloth_config_api_version=19.0.147 +v1217_cloth_config_api_version=19.0.147 +v1218_cloth_config_api_version=19.0.147 +v1219_cloth_config_api_version=20.0.149 +v12110_cloth_config_api_version=20.0.149 v121_versioned_scr=1.21 v1211_versioned_scr=1.21 @@ -33,16 +48,16 @@ v1212_versioned_scr=1.21.2 v1213_versioned_scr=1.21.2 v1214_versioned_scr=1.21.4 v1215_versioned_scr=1.21.5 +v1216_versioned_scr=1.21.6 +v1217_versioned_scr=1.21.6 +v1218_versioned_scr=1.21.6 +v1219_versioned_scr=1.21.9 +v12110_versioned_scr=1.21.9 loader_version=0.16.14 -loom_version=1.10-SNAPSHOT +loom_version=1.13-SNAPSHOT -mod_version=1.2.4 +mod_version=1.3.0 maven_group=cn.mangofanfan archives_base_name=fanwindow -# loom_libraries_base=https://bmclapi2.bangbang93.com/maven/ -loom_resources_base=https://bmclapi2.bangbang93.com/assets/ -loom_version_manifests=https://bmclapi2.bangbang93.com/mc/game/version_manifest.json -loom_experimental_versions=https://maven.fabricmc.net/net/minecraft/experimental_versions.json -# loom_fabric_repository=https://repository.hanbings.io/proxy/ diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 9bbc975..1b33c55 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 37f853b..ca025c8 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index faf9300..23d15a9 100644 --- a/gradlew +++ b/gradlew @@ -114,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -213,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 9d21a21..db3a6ac 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/settings.gradle b/settings.gradle index f91a4fe..ea99f0a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,5 +1,9 @@ pluginManagement { repositories { + maven { + name = "fanfan" + url = "https://maven.fanfan.moe/repository/maven-public/" + } maven { name = 'Fabric' url = 'https://maven.fabricmc.net/' diff --git a/src/client/java/cn/mangofanfan/fanwindow/client/FanwindowClient.java b/src/client/java/cn/mangofanfan/fanwindow/client/FanwindowClient.java index 0e8e020..f96ecf1 100644 --- a/src/client/java/cn/mangofanfan/fanwindow/client/FanwindowClient.java +++ b/src/client/java/cn/mangofanfan/fanwindow/client/FanwindowClient.java @@ -1,5 +1,6 @@ package cn.mangofanfan.fanwindow.client; +import cn.mangofanfan.fanwindow.client.function.MinecraftVersionGetter; import net.fabricmc.api.ClientModInitializer; import net.fabricmc.loader.api.FabricLoader; import org.slf4j.Logger; @@ -12,6 +13,7 @@ public void onInitializeClient() { Logger logger = LoggerFactory.getLogger("FanWindowClient"); GlobalState globalState = GlobalState.getInstance(); + logger.info("Running on Minecraft Version: {}", MinecraftVersionGetter.getMinecraftVersion()); if (FabricLoader.getInstance().isModLoaded("modmenu")) { logger.info("ModMenu is loaded. Specifical support enabled."); diff --git a/src/client/java/cn/mangofanfan/fanwindow/client/config/BgPicture.java b/src/client/java/cn/mangofanfan/fanwindow/client/config/BgPicture.java index 864b358..4ea08ff 100644 --- a/src/client/java/cn/mangofanfan/fanwindow/client/config/BgPicture.java +++ b/src/client/java/cn/mangofanfan/fanwindow/client/config/BgPicture.java @@ -24,13 +24,16 @@ public enum BgPicture { * 棘巧试炼的官方艺术作品 1.21 */ Tricky_Trials_Artwork_png("Tricky_Trials_Artwork.png"), + /** + * 铜器时代 1.21.9 + */ + The_Copper_Age_Key_Art_png("The_Copper_Age_Key_Art.png"), /** * 末影龙 */ Ender_Dragon_png("Ender_Dragon.png"); - // TODO 重构自定义模式 private final String picName; BgPicture(String image) { @@ -94,6 +97,10 @@ public int[] getPicSize() { size[0] = 1600; size[1] = 900; break; + case The_Copper_Age_Key_Art_png: + size[0] = 1920; + size[1] = 1350; + break; } return size; } diff --git a/src/client/java/cn/mangofanfan/fanwindow/client/config/ConfigManager.java b/src/client/java/cn/mangofanfan/fanwindow/client/config/ConfigManager.java index 2f02109..6c333bb 100644 --- a/src/client/java/cn/mangofanfan/fanwindow/client/config/ConfigManager.java +++ b/src/client/java/cn/mangofanfan/fanwindow/client/config/ConfigManager.java @@ -1,6 +1,7 @@ package cn.mangofanfan.fanwindow.client.config; import cn.mangofanfan.fanwindow.client.function.LocalBackgroundTextureIdentifier; +import cn.mangofanfan.fanwindow.client.function.MinecraftVersionGetter; import com.google.gson.Gson; import me.shedaniel.clothconfig2.api.ConfigBuilder; import me.shedaniel.clothconfig2.api.ConfigCategory; @@ -32,6 +33,7 @@ public class ConfigManager { private final ConfigBuilder configBuilder; private ConfigCategory generalCategory; private ConfigCategory customBackgroundCategory; + private ConfigCategory functionalCategory; private LocalBackgroundTextureIdentifier localBackgroundTextureIdentifier; /** @@ -99,12 +101,14 @@ private void initConfigOptions() { if (generalCategory != null && customBackgroundCategory != null) { generalCategory.removeCategory(); customBackgroundCategory.removeCategory(); + functionalCategory.removeCategory(); } // 加载配置项目 ConfigEntryBuilder entryBuilder = ConfigEntryBuilder.create(); generalCategory = configBuilder.getOrCreateCategory(Text.translatable("fanwindow.config.general")); customBackgroundCategory = configBuilder.getOrCreateCategory(Text.translatable("fanwindow.config.customBackground")); + functionalCategory = configBuilder.getOrCreateCategory(Text.translatable("fanwindow.config.functional")); generalCategory.addEntry(entryBuilder.startTextDescription(Text.translatable("fanwindow.config.description")).build()); generalCategory.addEntry(entryBuilder.startBooleanToggle( Text.translatable("fanwindow.config.useNewTitleScreen"), @@ -168,11 +172,30 @@ private void initConfigOptions() { this::bgPictureToText ) ) - .setDefaultValue(BgPicture.Tricky_Trials_Artwork_png) + .setDefaultValue(DefaultBgPictureGetter.getDefaultBgPicture()) .setTooltip(Text.translatable("fanwindow.config.background.description")) .setSelections(List.of(BgPicture.values())) .setSaveConsumer(this::saveBgPicture) .build()); + customBackgroundCategory.addEntry( + entryBuilder.startTextDescription( + Text.translatable("fanwindow.config.background.versionFeaturedDescription", + DefaultBgPictureGetter.getDefaultBgPicture().getPicName(), + MinecraftVersionGetter.getMinecraftVersion()) + ).build()); + functionalCategory.addEntry( + entryBuilder.startBooleanToggle(Text.translatable("fanwindow.config.exitMinecraftConfirm"), + config.isExitMinecraftConfirm()) + .setDefaultValue(config.isExitMinecraftConfirm()) + .setTooltip(Text.translatable("fanwindow.config.exitMinecraftConfirm.description")) + .setSaveConsumer(newValue -> config.setExitMinecraftConfirm(newValue)) + .build()); + functionalCategory.addEntry( + entryBuilder.startBooleanToggle(Text.translatable("fanwindow.config.exitWorldConfirm"), + config.isExitWorldConfirm()) + .setDefaultValue(config.isExitWorldConfirm()) + .setSaveConsumer(newValue -> config.setExitWorldConfirm(newValue)) + .build()); } private void saveBgPicture(BgPicture bgPicture) { diff --git a/src/client/java/cn/mangofanfan/fanwindow/client/config/DefaultBgPictureGetter.java b/src/client/java/cn/mangofanfan/fanwindow/client/config/DefaultBgPictureGetter.java new file mode 100644 index 0000000..873d995 --- /dev/null +++ b/src/client/java/cn/mangofanfan/fanwindow/client/config/DefaultBgPictureGetter.java @@ -0,0 +1,20 @@ +package cn.mangofanfan.fanwindow.client.config; + +import cn.mangofanfan.fanwindow.client.function.MinecraftVersionGetter; + +public class DefaultBgPictureGetter { + /** + * 根据运行的 Minecraft 版本获取对应主题的官方艺术图片作为背景。 + * 默认回退图片为最新版本的艺术图片,即 铜器时代。 + * @return BgPicture + */ + public static BgPicture getDefaultBgPicture() { + return switch (MinecraftVersionGetter.getMinecraftVersion()) { + case "1.21", "1.21.1" -> BgPicture.Tricky_Trials_Artwork_png; + case "1.21.2", "1.21.3" -> BgPicture.Bundles_of_Bravery_Artwork_png; + case "1.21.4" -> BgPicture.The_Garden_Awakens_Artwork_png; + case "1.21.5" -> BgPicture.Spring_to_Life_Artwork_png; + default -> BgPicture.The_Copper_Age_Key_Art_png; + }; + } +} diff --git a/src/client/java/cn/mangofanfan/fanwindow/client/config/FanWindowConfig.java b/src/client/java/cn/mangofanfan/fanwindow/client/config/FanWindowConfig.java index e784b38..0c7bc6e 100644 --- a/src/client/java/cn/mangofanfan/fanwindow/client/config/FanWindowConfig.java +++ b/src/client/java/cn/mangofanfan/fanwindow/client/config/FanWindowConfig.java @@ -13,7 +13,9 @@ public class FanWindowConfig { private boolean useNewBackgroundInNewScreen = true; private boolean useNewCreateWorldScreen = true; private CustomPictureMode customPictureMode = CustomPictureMode.Disabled; - private BgPicture bgPicture = BgPicture.Spring_to_Life_Artwork_png; + private BgPicture bgPicture = DefaultBgPictureGetter.getDefaultBgPicture(); + private boolean exitMinecraftConfirm = true; + private boolean exitWorldConfirm = true; public boolean isUseNewTitleScreen() { return useNewTitleScreen; @@ -70,4 +72,20 @@ public boolean isDisableVanillaTitleScreen() { public void setDisableVanillaTitleScreen(boolean disableVanillaTitleScreen) { this.disableVanillaTitleScreen = disableVanillaTitleScreen; } + + public boolean isExitMinecraftConfirm() { + return exitMinecraftConfirm; + } + + public void setExitMinecraftConfirm(boolean exitMinecraftConfirm) { + this.exitMinecraftConfirm = exitMinecraftConfirm; + } + + public boolean isExitWorldConfirm() { + return exitWorldConfirm; + } + + public void setExitWorldConfirm(boolean exitWorldConfirm) { + this.exitWorldConfirm = exitWorldConfirm; + } } diff --git a/src/client/java/cn/mangofanfan/fanwindow/client/function/LocalBackgroundTextureIdentifier.java b/src/client/java/cn/mangofanfan/fanwindow/client/function/LocalBackgroundTextureIdentifier.java index 382534d..9407d77 100644 --- a/src/client/java/cn/mangofanfan/fanwindow/client/function/LocalBackgroundTextureIdentifier.java +++ b/src/client/java/cn/mangofanfan/fanwindow/client/function/LocalBackgroundTextureIdentifier.java @@ -32,7 +32,7 @@ public LocalBackgroundTextureIdentifier(Path filePath) { // 未找到图片的错误处理 logger.warn("Error loading Local Background Texture in Config Dir at {}", filePath); logger.warn("If your Custom Background Picture Mode is ConfigDir, you will get a default image as background."); - toastBuilder.show(Text.translatable("fanwindow.toast.localIdentifierNotFound.title"), Text.translatable("fanwindow.toast.localIdentifierNotFound.description")); + toastBuilder.show(Text.translatable("gui.fanwindow.local_identifier_not_found.title"), Text.translatable("gui.fanwindow.local_identifier_not_found.description")); identifier = Identifier.of("fanwindow", "textures/artwork/custom.png"); textureSize = new int[]{1600, 900}; } diff --git a/src/client/java/cn/mangofanfan/fanwindow/client/function/MinecraftVersionGetter.java b/src/client/java/cn/mangofanfan/fanwindow/client/function/MinecraftVersionGetter.java new file mode 100644 index 0000000..91ccd8a --- /dev/null +++ b/src/client/java/cn/mangofanfan/fanwindow/client/function/MinecraftVersionGetter.java @@ -0,0 +1,14 @@ +package cn.mangofanfan.fanwindow.client.function; + +import net.fabricmc.loader.api.FabricLoader; + +import java.util.Optional; + +public class MinecraftVersionGetter { + public static String getMinecraftVersion() { + Optional version = FabricLoader.getInstance() + .getModContainer("minecraft") + .map(modContainer -> modContainer.getMetadata().getVersion().getFriendlyString()); + return version.orElse("Unknown"); + } +} diff --git a/src/client/java/cn/mangofanfan/fanwindow/client/function/RenderBackground.java b/src/client/java/cn/mangofanfan/fanwindow/client/function/VersionedRender.java similarity index 63% rename from src/client/java/cn/mangofanfan/fanwindow/client/function/RenderBackground.java rename to src/client/java/cn/mangofanfan/fanwindow/client/function/VersionedRender.java index f930644..6b60754 100644 --- a/src/client/java/cn/mangofanfan/fanwindow/client/function/RenderBackground.java +++ b/src/client/java/cn/mangofanfan/fanwindow/client/function/VersionedRender.java @@ -3,7 +3,10 @@ import net.minecraft.client.gui.DrawContext; import net.minecraft.util.Identifier; -public abstract class RenderBackground { +/** + * 因为 Minecraft 版本变化而需要差异化的方法 + */ +public abstract class VersionedRender { public abstract void renderBackground(DrawContext context, int[] textureSize, int width, int height, Identifier bgTexture); /** @@ -13,4 +16,8 @@ public abstract class RenderBackground { * @param height 游戏屏幕高度 */ public abstract void renderBackground(DrawContext context, int width, int height); + + public abstract void fillBackgroundColor(DrawContext context, int x1, int y1, int x2, int y2, int color); + + public abstract void fillBackgroundColor(DrawContext context, int x1, int y1, int x2, int y2); } diff --git a/src/client/java/cn/mangofanfan/fanwindow/client/screen/ExitConfirmScreen.java b/src/client/java/cn/mangofanfan/fanwindow/client/screen/ExitConfirmScreen.java new file mode 100644 index 0000000..5ac88c2 --- /dev/null +++ b/src/client/java/cn/mangofanfan/fanwindow/client/screen/ExitConfirmScreen.java @@ -0,0 +1,57 @@ +package cn.mangofanfan.fanwindow.client.screen; + +import cn.mangofanfan.fanwindow.client.function.VersionedRenderImpl; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.TitleScreen; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.text.Text; + +/** + * 退出游戏前再次确认使用的确认屏幕 + */ +public class ExitConfirmScreen extends Screen { + + private final MinecraftClient client = MinecraftClient.getInstance(); + + protected ExitConfirmScreen(Text title) { + super(title); + } + + @Override + protected void init() { + super.init(); + + ButtonWidget yesButton = ButtonWidget.builder(Text.translatable("gui.yes"), + button -> client.scheduleStop()) + .dimensions(this.width / 2 - 155, this.height / 4 + 96, 150, 20) + .build(); + ButtonWidget noButton = ButtonWidget.builder(Text.translatable("gui.no"), + button -> client.setScreen(new TitleScreen())) + .dimensions(this.width / 2 + 5, this.height / 4 + 96, 150, 20) + .build(); + + addDrawableChild(yesButton); + addDrawableChild(noButton); + } + + @Override + public void render(DrawContext context, int mouseX, int mouseY, float deltaTicks) { + super.render(context, mouseX, mouseY, deltaTicks); + + new VersionedRenderImpl().fillBackgroundColor(context, this.width / 2 - 155, this.height / 4 + 64, this.width / 2 + 155, this.height / 4 + 90); + + context.drawText(client.textRenderer, Text.translatable("gui.fanwindow.exit_confirm"), this.width / 2 - 150, this.height / 4 + 70, 0xFFFFFFFF, false); + } + + @Override + public boolean shouldPause() { + return false; + } + + @Override + public boolean shouldCloseOnEsc() { + return false; + } +} diff --git a/src/client/java/cn/mangofanfan/fanwindow/client/screen/NewTitleScreen.java b/src/client/java/cn/mangofanfan/fanwindow/client/screen/NewTitleScreen.java index e01d65d..d1fb74f 100644 --- a/src/client/java/cn/mangofanfan/fanwindow/client/screen/NewTitleScreen.java +++ b/src/client/java/cn/mangofanfan/fanwindow/client/screen/NewTitleScreen.java @@ -2,7 +2,7 @@ import cn.mangofanfan.fanwindow.client.GlobalState; import cn.mangofanfan.fanwindow.client.config.ConfigManager; -import cn.mangofanfan.fanwindow.client.function.RenderBackgroundImpl; +import cn.mangofanfan.fanwindow.client.function.VersionedRenderImpl; import cn.mangofanfan.fanwindow.client.function.SimpleToastBuilder; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; @@ -89,7 +89,12 @@ protected void init() { .dimensions(cenX, cenY + 65, 60, 60) .build(); this.quitButton = ButtonWidget.builder(Text.translatable("menu.quit"), - button -> client.scheduleStop()) + button -> { + if (configManager.config.isExitMinecraftConfirm()) + client.setScreen(new ExitConfirmScreen(Text.of("T-T"))); + else + client.scheduleStop(); + }) .dimensions(cenX + 65, cenY + 65, 60, 60) .build(); this.toggleButton = this.getToggleButtonBuilder() @@ -221,17 +226,17 @@ public void renderBackground(DrawContext context, int mouseX, int mouseY, float super.renderBackground(context, mouseX, mouseY, deltaTicks); } else { - new RenderBackgroundImpl().renderBackground(context, width, height); + new VersionedRenderImpl().renderBackground(context, width, height); } } @Override - protected void renderDarkening(DrawContext context) {} + public boolean shouldPause() { + return false; + } @Override - public void close() { - if (client != null) { - client.setScreen(new TitleScreen()); - } + public boolean shouldCloseOnEsc() { + return false; } } \ No newline at end of file diff --git a/src/client/java/cn/mangofanfan/fanwindow/mixin/client/ClothConfigScreenMixin.java b/src/client/java/cn/mangofanfan/fanwindow/mixin/client/ClothConfigScreenMixin.java index 0fe40c8..5ed355a 100644 --- a/src/client/java/cn/mangofanfan/fanwindow/mixin/client/ClothConfigScreenMixin.java +++ b/src/client/java/cn/mangofanfan/fanwindow/mixin/client/ClothConfigScreenMixin.java @@ -1,6 +1,6 @@ package cn.mangofanfan.fanwindow.mixin.client; -import cn.mangofanfan.fanwindow.client.function.RenderBackgroundImpl; +import cn.mangofanfan.fanwindow.client.function.VersionedRenderImpl; import cn.mangofanfan.fanwindow.client.config.ConfigManager; import com.llamalad7.mixinextras.sugar.Local; import me.shedaniel.clothconfig2.gui.ClothConfigScreen; @@ -26,7 +26,7 @@ protected ClothConfigScreenMixin(Text title) { ) public void onRenderBackground(ClothConfigScreen instance, DrawContext context, float v, @Local(argsOnly = true) float deltaTick) { if (ConfigManager.getInstance().config.isUseNewBackgroundGlobally()) { - new RenderBackgroundImpl().renderBackground(context, instance.width, instance.height); + new VersionedRenderImpl().renderBackground(context, instance.width, instance.height); } else { this.renderPanoramaBackground(context, deltaTick); diff --git a/src/client/java/cn/mangofanfan/fanwindow/mixin/client/MessageScreenMixin.java b/src/client/java/cn/mangofanfan/fanwindow/mixin/client/MessageScreenMixin.java index b924509..900fab1 100644 --- a/src/client/java/cn/mangofanfan/fanwindow/mixin/client/MessageScreenMixin.java +++ b/src/client/java/cn/mangofanfan/fanwindow/mixin/client/MessageScreenMixin.java @@ -1,6 +1,6 @@ package cn.mangofanfan.fanwindow.mixin.client; -import cn.mangofanfan.fanwindow.client.function.RenderBackgroundImpl; +import cn.mangofanfan.fanwindow.client.function.VersionedRenderImpl; import cn.mangofanfan.fanwindow.client.config.ConfigManager; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.MessageScreen; @@ -20,7 +20,7 @@ protected MessageScreenMixin(Text title) { @Inject(method = "renderBackground", at = @At("HEAD"), cancellable = true) public void onRenderBackground(DrawContext context, int mouseX, int mouseY, float deltaTicks, CallbackInfo ci) { if (ConfigManager.getInstance().config.isUseNewBackgroundGlobally()) { - new RenderBackgroundImpl().renderBackground(context, this.width, this.height); + new VersionedRenderImpl().renderBackground(context, this.width, this.height); ci.cancel(); } } diff --git a/src/client/java/cn/mangofanfan/fanwindow/mixin/client/ScreenMixin.java b/src/client/java/cn/mangofanfan/fanwindow/mixin/client/ScreenMixin.java index 87fdea0..7be8412 100644 --- a/src/client/java/cn/mangofanfan/fanwindow/mixin/client/ScreenMixin.java +++ b/src/client/java/cn/mangofanfan/fanwindow/mixin/client/ScreenMixin.java @@ -1,6 +1,6 @@ package cn.mangofanfan.fanwindow.mixin.client; -import cn.mangofanfan.fanwindow.client.function.RenderBackgroundImpl; +import cn.mangofanfan.fanwindow.client.function.VersionedRenderImpl; import cn.mangofanfan.fanwindow.client.config.ConfigManager; import com.llamalad7.mixinextras.sugar.Local; import net.minecraft.client.MinecraftClient; @@ -39,7 +39,7 @@ private void onRenderBackground(CallbackInfo ci, Logger logger = LoggerFactory.getLogger("ScreenMixin"); logger.debug("ScreenMixin is ready to change global background."); if (configManager.config.isUseNewBackgroundGlobally()) { - new RenderBackgroundImpl().renderBackground(context, width, height); + new VersionedRenderImpl().renderBackground(context, width, height); logger.debug("ScreenMixin background has been rendered."); ci.cancel(); } diff --git a/src/client/resources/assets/fanwindow/lang/en_us.json b/src/client/resources/assets/fanwindow/lang/en_us.json index d436fe8..3daa102 100644 --- a/src/client/resources/assets/fanwindow/lang/en_us.json +++ b/src/client/resources/assets/fanwindow/lang/en_us.json @@ -13,6 +13,7 @@ "fanwindow.config.description": "FanWindow is a mod only for redesigning your client screen. Contact with me for any problem.\nYou might need to restart Minecraft to make your changes in use.", "fanwindow.config.background": "Choose background picture", "fanwindow.config.background.description": "If in Disabled Mode, Pictures here will be shown as the New Background. Otherwise this option will be ignored.\nAt the end of the filename, use '_png' to replace with '.png'.", + "fanwindow.config.background.versionFeaturedDescription": "Default picture will be decided from your running Minecraft version. Now, default picture is %1$s because of Minecraft %2$s ~", "fanwindow.config.useNewBackgroundGlobally": "Use new Background instead of vanilla screen background", "fanwindow.config.useNewBackgroundGlobally.description": "New background will replace all screen that use vanilla background globally.", "fanwindow.config.useNewBackgroundInNewScreen": "Use new Background in New Screen", @@ -22,9 +23,16 @@ "fanwindow.config.customBackground": "Background Config", "fanwindow.config.customBackgroundImageMode": "Custom Background Picture Mode", "fanwindow.config.customBackgroundImageMode.description": "Disabled: using Picture chosen below, provided by Fan Window mod.\nResourcePack: using Picture provided in resourcepack.\nConfigDir: using Picture in Config Dir.\nSee document below for guidance to change your custom background.", - "fanwindow.config.customBackground.description": "In Disabled mode, you can use one picture chosen below as background. In ResourcePack mode and ConfigDir mode you can change the background image through your heart.\nFor most player, you can toggle mode to ConfigDir, and move your beloved image (png) into 「.minecraft/config/fanwindow」 dir, rename it to 「custom.png」, and save your changes. Soon you can enjoy a new background image.\nFor support of ResourcePack mode, please see the GitHub of Fan Window mod.\nBTW, if anything in Config Screen goes wrong, you might need to restart Minecraft to make it great again =w", - "fanwindow.toast.localIdentifierNotFound.title": "Local picture in config dir NOT FOUND.", - "fanwindow.toast.localIdentifierNotFound.description": "Fall back to Default picture. Please check your config dir to make sure custom.png is existed and restart Minecraft.", + "fanwindow.config.customBackground.description": "In Disabled mode, you can use one picture chosen below as background. In ResourcePack mode and ConfigDir mode you can change the background image through your heart.\nFor most player, you can toggle mode to ConfigDir, and move your beloved image (png) into 「.minecraft/config/fanwindow」 dir, rename it to 「custom.png」, and save your changes. Soon you can enjoy a new background image.\nIf anything in Config Screen goes wrong, you might need to restart Minecraft to make it great again =w\nFor more help, please visit Github or fanfan.moe ~", + "gui.fanwindow.local_identifier_not_found.title": "Local picture in config dir NOT FOUND.", + "gui.fanwindow.local_identifier_not_found.description": "Fall back to Default picture. Please check your config dir to make sure custom.png is existed and restart Minecraft.", "fanwindow.config.disableVanillaTitleScreen": "Disable vanilla Title Screen", - "fanwindow.config.disableVanillaTitleScreen.description": "Remove the button to toggle Title Screen in New Title Screen to disable vanilla Title Screen in fact.\nIf 「Use New FanWindow Title Screen」 is FALSE, this will be ignored." + "fanwindow.config.disableVanillaTitleScreen.description": "Remove the button to toggle Title Screen in New Title Screen to disable vanilla Title Screen in fact.\nIf 「Use New FanWindow Title Screen」 is FALSE, this will be ignored.", + "fanwindow.config.functional": "Functional Config", + "fanwindow.config.exitMinecraftConfirm": "Need confirm when exit Minecraft client", + "fanwindow.config.exitWorldConfirm": "Need confirm when exit world", + "fanwindow.config.exitMinecraftConfirm.description": "Need to enable UseNewTitleScreen.", + "gui.fanwindow.exit_confirm": "Confirm to EXIT ?", + "gui.fanwindow.exit_world_confirm.title": "Really exit from the world?", + "gui.fanwindow.exit_world_confirm.description": "Re-click the button in 30s to confirm it." } \ No newline at end of file diff --git a/src/client/resources/assets/fanwindow/lang/zh_cn.json b/src/client/resources/assets/fanwindow/lang/zh_cn.json index 0c4e287..b7591a7 100644 --- a/src/client/resources/assets/fanwindow/lang/zh_cn.json +++ b/src/client/resources/assets/fanwindow/lang/zh_cn.json @@ -13,6 +13,7 @@ "fanwindow.config.description": "芒果窗是一个只负责修改客户端屏幕的模组。如有任何问题,请与我联系。\n您可能需要重新启动 Minecraft 来确保更改生效。", "fanwindow.config.background": "选择背景图片", "fanwindow.config.background.description": "在 Disabled 模式下,这里的图片将会作为新背景图片;否则此设置会被忽略。\n在文件名的末尾需要用‘_png’代替‘.png’。", + "fanwindow.config.background.versionFeaturedDescription": "默认图片由当前运行的 Minecraft 版本决定。目前的默认图片是 %1$s 因为目前正在游玩 Minecraft %2$s ~", "fanwindow.config.useNewBackgroundGlobally": "使用新背景图片替换原版背景", "fanwindow.config.useNewBackgroundGlobally.description": "新背景图片会全局替换任何使用原版背景的屏幕。", "fanwindow.config.useNewBackgroundInNewScreen": "在新屏幕中使用新背景图片", @@ -22,9 +23,16 @@ "fanwindow.config.customBackground": "背景设置", "fanwindow.config.customBackgroundImageMode": "自定义背景模式", "fanwindow.config.customBackgroundImageMode.description": "Disabled: 使用下方选择的、由芒果窗模组提供的背景。\nResourcePack: 使用由资源包提供的背景。\nConfigDir: 使用配置目录中的背景。\n查看下方的文档以获取自定义你的背景的指南。", - "fanwindow.config.customBackground.description": "在 Disabled 模式下,你可以从下方选择一张图片作为背景。在 ResourcePack 和 ConfigDir 模式下,你可以任意更改背景图片。\n普遍来说,你可以将模式更改为 ConfigDir,然后将你心爱的图片放入「.minecraft/config/fanwindow」目录,并重命名为「custom.png」,保存更改,然后即可得到新的背景。\n对于 ResourcePack 模式,请参阅本模组的 GitHub 获取更多帮助。\n顺便,如果在配置屏幕中出现奇怪的问题,你可能需要重新启动 Minecraft 来再试试 =w", - "fanwindow.toast.localIdentifierNotFound.title": "配置目录中未找到本地图片。", - "fanwindow.toast.localIdentifierNotFound.description": "已回退至默认图片。请确保 custom.png 存在在配置目录中,并重新启动 Minecraft。", + "fanwindow.config.customBackground.description": "在 Disabled 模式下,你可以从下方选择一张图片作为背景。在 ResourcePack 和 ConfigDir 模式下,你可以任意更改背景图片。\n普遍来说,你可以将模式更改为 ConfigDir,然后将你心爱的图片放入 「.minecraft/config/fanwindow」 目录,并重命名为 「custom.png」 ,保存更改,然后即可得到新的背景。\n如果在配置屏幕中出现奇怪的问题,你可能需要重新启动 Minecraft 来再试试 =w\n请参阅 Github 或 fanfan.moe 来获取更多帮助~", + "gui.fanwindow.local_identifier_not_found.title": "配置目录中未找到本地图片。", + "gui.fanwindow.local_identifier_not_found.description": "已回退至默认图片。请确保 custom.png 存在在配置目录中,并重新启动 Minecraft。", "fanwindow.config.disableVanillaTitleScreen": "禁用原版标题屏幕", - "fanwindow.config.disableVanillaTitleScreen.description": "移除新标题屏幕中切换标题屏幕的按钮,从而在事实上禁用原版标题屏幕。\n若「使用新的芒果窗标题屏幕」启用,此配置无效。" + "fanwindow.config.disableVanillaTitleScreen.description": "移除新标题屏幕中切换标题屏幕的按钮,从而在事实上禁用原版标题屏幕。\n若「使用新的芒果窗标题屏幕」启用,此配置无效。", + "fanwindow.config.functional": "功能性设置", + "fanwindow.config.exitMinecraftConfirm": "退出 Minecraft 时需要确认", + "fanwindow.config.exitWorldConfirm": "退出世界时需要确认", + "fanwindow.config.exitMinecraftConfirm.description": "需要启用 使用新标题屏幕。", + "gui.fanwindow.exit_confirm": "确认退出?", + "gui.fanwindow.exit_world_confirm.title": "确认退出世界?", + "gui.fanwindow.exit_world_confirm.description": "在30秒内再次点击退出按钮以确认。" } \ No newline at end of file diff --git a/src/client/resources/assets/fanwindow/textures/artwork/the_copper_age_key_art.png b/src/client/resources/assets/fanwindow/textures/artwork/the_copper_age_key_art.png new file mode 100644 index 0000000..7743dcb Binary files /dev/null and b/src/client/resources/assets/fanwindow/textures/artwork/the_copper_age_key_art.png differ diff --git a/src/client/resources/fanwindow.accesswidener b/src/client/resources/fanwindow.accesswidener index 6905f5a..9c6c2fe 100644 --- a/src/client/resources/fanwindow.accesswidener +++ b/src/client/resources/fanwindow.accesswidener @@ -10,4 +10,7 @@ accessible method net/minecraft/client/gui/screen/world/CreateWorldScreen$GameTa accessible method net/minecraft/client/gui/screen/world/CreateWorldScreen$WorldTab (Lnet/minecraft/client/gui/screen/world/CreateWorldScreen;)V accessible method net/minecraft/client/gui/screen/world/CreateWorldScreen$MoreTab (Lnet/minecraft/client/gui/screen/world/CreateWorldScreen;)V -accessible method net/minecraft/client/gui/screen/world/CreateWorldScreen openExperimentsScreen (Lnet/minecraft/resource/DataConfiguration;)V \ No newline at end of file +accessible method net/minecraft/client/gui/screen/world/CreateWorldScreen openExperimentsScreen (Lnet/minecraft/resource/DataConfiguration;)V + +# 访问加宽 - 字段 +accessible field net/minecraft/client/gui/screen/GameMenuScreen exitButton Lnet/minecraft/client/gui/widget/ButtonWidget; \ No newline at end of file diff --git a/src/client/resources/fanwindow.client.mixins.json b/src/client/resources/fanwindow.client.mixins.json index f909310..d7ed329 100644 --- a/src/client/resources/fanwindow.client.mixins.json +++ b/src/client/resources/fanwindow.client.mixins.json @@ -6,7 +6,9 @@ "client": [ "ClothConfigScreenMixin", "CreateWorldScreenMixin", + "GameMenuScreenMixin", "MessageScreenMixin", + "MinecraftClientMixin", "ScreenMixin", "TitleScreenMixin" ], diff --git a/src/versioned/1.21.4/client/java/cn/mangofanfan/fanwindow/client/function/RenderBackgroundImpl.java b/src/versioned/1.21.2/client/java/cn/mangofanfan/fanwindow/client/function/VersionedRenderImpl.java similarity index 79% rename from src/versioned/1.21.4/client/java/cn/mangofanfan/fanwindow/client/function/RenderBackgroundImpl.java rename to src/versioned/1.21.2/client/java/cn/mangofanfan/fanwindow/client/function/VersionedRenderImpl.java index caee449..a2340ae 100644 --- a/src/versioned/1.21.4/client/java/cn/mangofanfan/fanwindow/client/function/RenderBackgroundImpl.java +++ b/src/versioned/1.21.2/client/java/cn/mangofanfan/fanwindow/client/function/VersionedRenderImpl.java @@ -5,7 +5,7 @@ import net.minecraft.client.render.RenderLayer; import net.minecraft.util.Identifier; -public class RenderBackgroundImpl extends RenderBackground { +public class VersionedRenderImpl extends VersionedRender { public void renderBackground(DrawContext context, int[] textureSize, int width, int height, Identifier bgTexture) { // 确保渲染背景图片正确,即图片比例不变,且在裁切时总是保持中心位置。 int[] regionSize = {0, 0}; @@ -35,4 +35,17 @@ public void renderBackground(DrawContext context, int width, int height) { ConfigManager configManager = ConfigManager.getInstance(); renderBackground(context, configManager.getBackgroundTextureSize(), width, height, configManager.getBackgroundTexture()); } + + @Override + public void fillBackgroundColor(DrawContext context, int x1, int y1, int x2, int y2, int color) { + context.fill(x1, y1, x2, y2, color); + } + + /** + * @param context DrawContext + */ + @Override + public void fillBackgroundColor(DrawContext context, int x1, int y1, int x2, int y2) { + fillBackgroundColor(context, x1, y1, x2, y2, 0x88000000); + } } diff --git a/src/versioned/1.21.2/client/java/cn/mangofanfan/fanwindow/mixin/client/GameMenuScreenMixin.java b/src/versioned/1.21.2/client/java/cn/mangofanfan/fanwindow/mixin/client/GameMenuScreenMixin.java new file mode 100644 index 0000000..af69e64 --- /dev/null +++ b/src/versioned/1.21.2/client/java/cn/mangofanfan/fanwindow/mixin/client/GameMenuScreenMixin.java @@ -0,0 +1,74 @@ +package cn.mangofanfan.fanwindow.mixin.client; + +import cn.mangofanfan.fanwindow.client.config.ConfigManager; +import cn.mangofanfan.fanwindow.client.function.SimpleToastBuilder; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.GameMenuScreen; +import net.minecraft.client.gui.screen.MessageScreen; +import net.minecraft.client.gui.screen.TitleScreen; +import net.minecraft.client.gui.screen.multiplayer.MultiplayerScreen; +import net.minecraft.client.network.ServerInfo; +import net.minecraft.client.realms.gui.screen.RealmsMainScreen; +import net.minecraft.text.Text; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(GameMenuScreen.class) +public abstract class GameMenuScreenMixin { + @Unique + private static long lastDisconnectTime = 0; + + @Inject(method = "disconnect", at = @At("HEAD"), cancellable = true) + private static void onDisconnect(CallbackInfo ci) { + // 如果没有启用退出世界确认,则无事发生 + if (!ConfigManager.getInstance().config.isExitWorldConfirm()) return; + + // 否则,需要连续尝试退出两次才允许退出 + if (System.currentTimeMillis() - lastDisconnectTime > 30000) { + lastDisconnectTime = System.currentTimeMillis(); + new SimpleToastBuilder().show( + Text.translatable("gui.fanwindow.exit_world_confirm.title"), + Text.translatable("gui.fanwindow.exit_world_confirm.description") + ); + if (MinecraftClient.getInstance().currentScreen != null) { + GameMenuScreen screen = (GameMenuScreen) MinecraftClient.getInstance().currentScreen; + if (screen.exitButton != null) { + screen.exitButton.active = true; + } + } + } + else { + normalDisconnect(); + } + ci.cancel(); + } + + @Unique + private static void normalDisconnect() { + // 首先获取 client 对象 + MinecraftClient client = MinecraftClient.getInstance(); + + boolean bl = client.isInSingleplayer(); + ServerInfo serverInfo = client.getCurrentServerEntry(); + if (client.world != null) { + client.world.disconnect(); + } + if (bl) { + client.disconnect(new MessageScreen(Text.translatable("menu.savingLevel"))); // SAVING_LEVEL_TEXT + } else { + client.disconnect(); + } + + TitleScreen titleScreen = new TitleScreen(); + if (bl) { + client.setScreen(titleScreen); + } else if (serverInfo != null && serverInfo.isRealm()) { + client.setScreen(new RealmsMainScreen(titleScreen)); + } else { + client.setScreen(new MultiplayerScreen(titleScreen)); + } + } +} diff --git a/src/versioned/1.21.2/client/java/cn/mangofanfan/fanwindow/mixin/client/MinecraftClientMixin.java b/src/versioned/1.21.2/client/java/cn/mangofanfan/fanwindow/mixin/client/MinecraftClientMixin.java new file mode 100644 index 0000000..bdc605f --- /dev/null +++ b/src/versioned/1.21.2/client/java/cn/mangofanfan/fanwindow/mixin/client/MinecraftClientMixin.java @@ -0,0 +1,9 @@ +package cn.mangofanfan.fanwindow.mixin.client; + +import net.minecraft.client.MinecraftClient; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(MinecraftClient.class) +public class MinecraftClientMixin { + // nothing to do +} diff --git a/src/versioned/1.21.5/client/java/cn/mangofanfan/fanwindow/client/function/RenderBackgroundImpl.java b/src/versioned/1.21.4/client/java/cn/mangofanfan/fanwindow/client/function/VersionedRenderImpl.java similarity index 79% rename from src/versioned/1.21.5/client/java/cn/mangofanfan/fanwindow/client/function/RenderBackgroundImpl.java rename to src/versioned/1.21.4/client/java/cn/mangofanfan/fanwindow/client/function/VersionedRenderImpl.java index caee449..a2340ae 100644 --- a/src/versioned/1.21.5/client/java/cn/mangofanfan/fanwindow/client/function/RenderBackgroundImpl.java +++ b/src/versioned/1.21.4/client/java/cn/mangofanfan/fanwindow/client/function/VersionedRenderImpl.java @@ -5,7 +5,7 @@ import net.minecraft.client.render.RenderLayer; import net.minecraft.util.Identifier; -public class RenderBackgroundImpl extends RenderBackground { +public class VersionedRenderImpl extends VersionedRender { public void renderBackground(DrawContext context, int[] textureSize, int width, int height, Identifier bgTexture) { // 确保渲染背景图片正确,即图片比例不变,且在裁切时总是保持中心位置。 int[] regionSize = {0, 0}; @@ -35,4 +35,17 @@ public void renderBackground(DrawContext context, int width, int height) { ConfigManager configManager = ConfigManager.getInstance(); renderBackground(context, configManager.getBackgroundTextureSize(), width, height, configManager.getBackgroundTexture()); } + + @Override + public void fillBackgroundColor(DrawContext context, int x1, int y1, int x2, int y2, int color) { + context.fill(x1, y1, x2, y2, color); + } + + /** + * @param context DrawContext + */ + @Override + public void fillBackgroundColor(DrawContext context, int x1, int y1, int x2, int y2) { + fillBackgroundColor(context, x1, y1, x2, y2, 0x88000000); + } } diff --git a/src/versioned/1.21.4/client/java/cn/mangofanfan/fanwindow/mixin/client/GameMenuScreenMixin.java b/src/versioned/1.21.4/client/java/cn/mangofanfan/fanwindow/mixin/client/GameMenuScreenMixin.java new file mode 100644 index 0000000..af69e64 --- /dev/null +++ b/src/versioned/1.21.4/client/java/cn/mangofanfan/fanwindow/mixin/client/GameMenuScreenMixin.java @@ -0,0 +1,74 @@ +package cn.mangofanfan.fanwindow.mixin.client; + +import cn.mangofanfan.fanwindow.client.config.ConfigManager; +import cn.mangofanfan.fanwindow.client.function.SimpleToastBuilder; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.GameMenuScreen; +import net.minecraft.client.gui.screen.MessageScreen; +import net.minecraft.client.gui.screen.TitleScreen; +import net.minecraft.client.gui.screen.multiplayer.MultiplayerScreen; +import net.minecraft.client.network.ServerInfo; +import net.minecraft.client.realms.gui.screen.RealmsMainScreen; +import net.minecraft.text.Text; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(GameMenuScreen.class) +public abstract class GameMenuScreenMixin { + @Unique + private static long lastDisconnectTime = 0; + + @Inject(method = "disconnect", at = @At("HEAD"), cancellable = true) + private static void onDisconnect(CallbackInfo ci) { + // 如果没有启用退出世界确认,则无事发生 + if (!ConfigManager.getInstance().config.isExitWorldConfirm()) return; + + // 否则,需要连续尝试退出两次才允许退出 + if (System.currentTimeMillis() - lastDisconnectTime > 30000) { + lastDisconnectTime = System.currentTimeMillis(); + new SimpleToastBuilder().show( + Text.translatable("gui.fanwindow.exit_world_confirm.title"), + Text.translatable("gui.fanwindow.exit_world_confirm.description") + ); + if (MinecraftClient.getInstance().currentScreen != null) { + GameMenuScreen screen = (GameMenuScreen) MinecraftClient.getInstance().currentScreen; + if (screen.exitButton != null) { + screen.exitButton.active = true; + } + } + } + else { + normalDisconnect(); + } + ci.cancel(); + } + + @Unique + private static void normalDisconnect() { + // 首先获取 client 对象 + MinecraftClient client = MinecraftClient.getInstance(); + + boolean bl = client.isInSingleplayer(); + ServerInfo serverInfo = client.getCurrentServerEntry(); + if (client.world != null) { + client.world.disconnect(); + } + if (bl) { + client.disconnect(new MessageScreen(Text.translatable("menu.savingLevel"))); // SAVING_LEVEL_TEXT + } else { + client.disconnect(); + } + + TitleScreen titleScreen = new TitleScreen(); + if (bl) { + client.setScreen(titleScreen); + } else if (serverInfo != null && serverInfo.isRealm()) { + client.setScreen(new RealmsMainScreen(titleScreen)); + } else { + client.setScreen(new MultiplayerScreen(titleScreen)); + } + } +} diff --git a/src/versioned/1.21.4/client/java/cn/mangofanfan/fanwindow/mixin/client/MinecraftClientMixin.java b/src/versioned/1.21.4/client/java/cn/mangofanfan/fanwindow/mixin/client/MinecraftClientMixin.java new file mode 100644 index 0000000..bdc605f --- /dev/null +++ b/src/versioned/1.21.4/client/java/cn/mangofanfan/fanwindow/mixin/client/MinecraftClientMixin.java @@ -0,0 +1,9 @@ +package cn.mangofanfan.fanwindow.mixin.client; + +import net.minecraft.client.MinecraftClient; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(MinecraftClient.class) +public class MinecraftClientMixin { + // nothing to do +} diff --git a/src/versioned/1.21.2/client/java/cn/mangofanfan/fanwindow/client/function/RenderBackgroundImpl.java b/src/versioned/1.21.5/client/java/cn/mangofanfan/fanwindow/client/function/VersionedRenderImpl.java similarity index 77% rename from src/versioned/1.21.2/client/java/cn/mangofanfan/fanwindow/client/function/RenderBackgroundImpl.java rename to src/versioned/1.21.5/client/java/cn/mangofanfan/fanwindow/client/function/VersionedRenderImpl.java index 91f0761..a2340ae 100644 --- a/src/versioned/1.21.2/client/java/cn/mangofanfan/fanwindow/client/function/RenderBackgroundImpl.java +++ b/src/versioned/1.21.5/client/java/cn/mangofanfan/fanwindow/client/function/VersionedRenderImpl.java @@ -5,7 +5,7 @@ import net.minecraft.client.render.RenderLayer; import net.minecraft.util.Identifier; -public class RenderBackgroundImpl extends RenderBackground { +public class VersionedRenderImpl extends VersionedRender { public void renderBackground(DrawContext context, int[] textureSize, int width, int height, Identifier bgTexture) { // 确保渲染背景图片正确,即图片比例不变,且在裁切时总是保持中心位置。 int[] regionSize = {0, 0}; @@ -13,7 +13,8 @@ public void renderBackground(DrawContext context, int[] textureSize, int width, double var2 = (double) width / height; if (var1 == var2) { regionSize = textureSize; - } else if (var1 < var2) { + } + else if (var1 < var2) { regionSize[0] = textureSize[0]; regionSize[1] = (int) (textureSize[1] / var2 * var1); } @@ -34,4 +35,17 @@ public void renderBackground(DrawContext context, int width, int height) { ConfigManager configManager = ConfigManager.getInstance(); renderBackground(context, configManager.getBackgroundTextureSize(), width, height, configManager.getBackgroundTexture()); } + + @Override + public void fillBackgroundColor(DrawContext context, int x1, int y1, int x2, int y2, int color) { + context.fill(x1, y1, x2, y2, color); + } + + /** + * @param context DrawContext + */ + @Override + public void fillBackgroundColor(DrawContext context, int x1, int y1, int x2, int y2) { + fillBackgroundColor(context, x1, y1, x2, y2, 0x88000000); + } } diff --git a/src/versioned/1.21.5/client/java/cn/mangofanfan/fanwindow/mixin/client/GameMenuScreenMixin.java b/src/versioned/1.21.5/client/java/cn/mangofanfan/fanwindow/mixin/client/GameMenuScreenMixin.java new file mode 100644 index 0000000..af69e64 --- /dev/null +++ b/src/versioned/1.21.5/client/java/cn/mangofanfan/fanwindow/mixin/client/GameMenuScreenMixin.java @@ -0,0 +1,74 @@ +package cn.mangofanfan.fanwindow.mixin.client; + +import cn.mangofanfan.fanwindow.client.config.ConfigManager; +import cn.mangofanfan.fanwindow.client.function.SimpleToastBuilder; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.GameMenuScreen; +import net.minecraft.client.gui.screen.MessageScreen; +import net.minecraft.client.gui.screen.TitleScreen; +import net.minecraft.client.gui.screen.multiplayer.MultiplayerScreen; +import net.minecraft.client.network.ServerInfo; +import net.minecraft.client.realms.gui.screen.RealmsMainScreen; +import net.minecraft.text.Text; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(GameMenuScreen.class) +public abstract class GameMenuScreenMixin { + @Unique + private static long lastDisconnectTime = 0; + + @Inject(method = "disconnect", at = @At("HEAD"), cancellable = true) + private static void onDisconnect(CallbackInfo ci) { + // 如果没有启用退出世界确认,则无事发生 + if (!ConfigManager.getInstance().config.isExitWorldConfirm()) return; + + // 否则,需要连续尝试退出两次才允许退出 + if (System.currentTimeMillis() - lastDisconnectTime > 30000) { + lastDisconnectTime = System.currentTimeMillis(); + new SimpleToastBuilder().show( + Text.translatable("gui.fanwindow.exit_world_confirm.title"), + Text.translatable("gui.fanwindow.exit_world_confirm.description") + ); + if (MinecraftClient.getInstance().currentScreen != null) { + GameMenuScreen screen = (GameMenuScreen) MinecraftClient.getInstance().currentScreen; + if (screen.exitButton != null) { + screen.exitButton.active = true; + } + } + } + else { + normalDisconnect(); + } + ci.cancel(); + } + + @Unique + private static void normalDisconnect() { + // 首先获取 client 对象 + MinecraftClient client = MinecraftClient.getInstance(); + + boolean bl = client.isInSingleplayer(); + ServerInfo serverInfo = client.getCurrentServerEntry(); + if (client.world != null) { + client.world.disconnect(); + } + if (bl) { + client.disconnect(new MessageScreen(Text.translatable("menu.savingLevel"))); // SAVING_LEVEL_TEXT + } else { + client.disconnect(); + } + + TitleScreen titleScreen = new TitleScreen(); + if (bl) { + client.setScreen(titleScreen); + } else if (serverInfo != null && serverInfo.isRealm()) { + client.setScreen(new RealmsMainScreen(titleScreen)); + } else { + client.setScreen(new MultiplayerScreen(titleScreen)); + } + } +} diff --git a/src/versioned/1.21.5/client/java/cn/mangofanfan/fanwindow/mixin/client/MinecraftClientMixin.java b/src/versioned/1.21.5/client/java/cn/mangofanfan/fanwindow/mixin/client/MinecraftClientMixin.java new file mode 100644 index 0000000..bdc605f --- /dev/null +++ b/src/versioned/1.21.5/client/java/cn/mangofanfan/fanwindow/mixin/client/MinecraftClientMixin.java @@ -0,0 +1,9 @@ +package cn.mangofanfan.fanwindow.mixin.client; + +import net.minecraft.client.MinecraftClient; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(MinecraftClient.class) +public class MinecraftClientMixin { + // nothing to do +} diff --git a/src/versioned/1.21.6/client/java/cn/mangofanfan/fanwindow/client/function/NativeResourceLoaderImpl.java b/src/versioned/1.21.6/client/java/cn/mangofanfan/fanwindow/client/function/NativeResourceLoaderImpl.java new file mode 100644 index 0000000..fdc939b --- /dev/null +++ b/src/versioned/1.21.6/client/java/cn/mangofanfan/fanwindow/client/function/NativeResourceLoaderImpl.java @@ -0,0 +1,11 @@ +package cn.mangofanfan.fanwindow.client.function; + +import net.minecraft.client.texture.NativeImage; +import net.minecraft.client.texture.NativeImageBackedTexture; + +public class NativeResourceLoaderImpl extends NativeResourceLoader { + @Override + NativeImageBackedTexture getNativeImageBackedTexture(String pathString, NativeImage nativeImage) { + return new NativeImageBackedTexture(() -> pathString, nativeImage); + } +} diff --git a/src/versioned/1.21.6/client/java/cn/mangofanfan/fanwindow/client/function/VersionedRenderImpl.java b/src/versioned/1.21.6/client/java/cn/mangofanfan/fanwindow/client/function/VersionedRenderImpl.java new file mode 100644 index 0000000..d26ae8c --- /dev/null +++ b/src/versioned/1.21.6/client/java/cn/mangofanfan/fanwindow/client/function/VersionedRenderImpl.java @@ -0,0 +1,51 @@ +package cn.mangofanfan.fanwindow.client.function; + +import cn.mangofanfan.fanwindow.client.config.ConfigManager; +import net.minecraft.client.gl.RenderPipelines; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.util.Identifier; + +public class VersionedRenderImpl extends VersionedRender { + public void renderBackground(DrawContext context, int[] textureSize, int width, int height, Identifier bgTexture) { + // 确保渲染背景图片正确,即图片比例不变,且在裁切时总是保持中心位置。 + int[] regionSize = {0, 0}; + double var1 = (double) textureSize[0] / textureSize[1]; + double var2 = (double) width / height; + if (var1 == var2) { + regionSize = textureSize; + } + else if (var1 < var2) { + regionSize[0] = textureSize[0]; + regionSize[1] = (int) (textureSize[1] / var2 * var1); + } + else { + regionSize[0] = (int) (textureSize[0] / var1 * var2); + regionSize[1] = textureSize[1]; + } + context.drawTexture(RenderPipelines.GUI_TEXTURED, bgTexture, 0, 0, (float) (textureSize[0] - regionSize[0]) / 2, (float) (textureSize[1] - regionSize[1]) / 2, width, height, regionSize[0], regionSize[1], textureSize[0], textureSize[1]); + } + + /** + * 简化的公开方法,从 ConfigManager 获取背景图片和尺寸 + * @param context DrawContext + * @param width 游戏屏幕宽度 + * @param height 游戏屏幕高度 + */ + public void renderBackground(DrawContext context, int width, int height) { + ConfigManager configManager = ConfigManager.getInstance(); + renderBackground(context, configManager.getBackgroundTextureSize(), width, height, configManager.getBackgroundTexture()); + } + + @Override + public void fillBackgroundColor(DrawContext context, int x1, int y1, int x2, int y2, int color) { + context.fill(x1, y1, x2, y2, color); + } + + /** + * @param context DrawContext + */ + @Override + public void fillBackgroundColor(DrawContext context, int x1, int y1, int x2, int y2) { + fillBackgroundColor(context, x1, y1, x2, y2, 0x88000000); + } +} diff --git a/src/versioned/1.21.6/client/java/cn/mangofanfan/fanwindow/client/screen/NewCreateWorldScreenGameTab.java b/src/versioned/1.21.6/client/java/cn/mangofanfan/fanwindow/client/screen/NewCreateWorldScreenGameTab.java new file mode 100644 index 0000000..2265587 --- /dev/null +++ b/src/versioned/1.21.6/client/java/cn/mangofanfan/fanwindow/client/screen/NewCreateWorldScreenGameTab.java @@ -0,0 +1,110 @@ +package cn.mangofanfan.fanwindow.client.screen; + +import cn.mangofanfan.fanwindow.client.screen.widget.GameModeCardWidget; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.SharedConstants; +import net.minecraft.client.gui.screen.world.CreateWorldScreen; +import net.minecraft.client.gui.screen.world.WorldCreator; +import net.minecraft.client.gui.tab.GridScreenTab; +import net.minecraft.client.gui.tooltip.Tooltip; +import net.minecraft.client.gui.widget.*; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.minecraft.world.Difficulty; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +@Environment(EnvType.CLIENT) +public class NewCreateWorldScreenGameTab extends GridScreenTab { + private static final Text GAME_TAB_TITLE_TEXT = Text.translatable("createWorld.tab.game.title"); + private static final Text ALLOW_COMMANDS_TEXT = Text.translatable("selectWorld.allowCommands"); + private final TextFieldWidget worldNameField; + static final Text ENTER_NAME_TEXT = Text.translatable("selectWorld.enterName"); + static final Text EXPERIMENTS_TEXT = Text.translatable("selectWorld.experiments"); + static final Text ALLOW_COMMANDS_INFO_TEXT = Text.translatable("selectWorld.allowCommands.info"); + + Logger logger = LoggerFactory.getLogger(NewCreateWorldScreenGameTab.class); + + public NewCreateWorldScreenGameTab(CreateWorldScreen self) { + super(GAME_TAB_TITLE_TEXT); + GridWidget.Adder adder = this.grid.setRowSpacing(8).createAdder(1); + Positioner positioner = adder.copyPositioner(); + WorldCreator worldCreator = self.getWorldCreator(); + this.worldNameField = new TextFieldWidget(self.getTextRenderer(), 208, 20, Text.translatable("selectWorld.enterName")); + this.worldNameField.setText(worldCreator.getWorldName()); + this.worldNameField.setChangedListener(worldCreator::setWorldName); + worldCreator + .addListener( + creator -> this.worldNameField + .setTooltip( + Tooltip.of(Text.translatable("selectWorld.targetFolder", Text.literal(creator.getWorldDirectoryName()).formatted(Formatting.ITALIC))) + ) + ); + // CreateWorldScreen.this.setInitialFocus(this.worldNameField); + adder.add( + LayoutWidgets.createLabeledWidget(self.getTextRenderer(), this.worldNameField, ENTER_NAME_TEXT), + adder.copyPositioner().alignHorizontalCenter() + ); + + // 新版游戏模式选择器 + DirectionalLayoutWidget directionalLayoutWidget_GameMod = DirectionalLayoutWidget.horizontal().spacing(16); + GameModeCardWidget tab_GameModeSurvival = directionalLayoutWidget_GameMod.add(new GameModeCardWidget(worldCreator, self)); + tab_GameModeSurvival.setMode(WorldCreator.Mode.SURVIVAL); + GameModeCardWidget tab_GameModeHardcore = directionalLayoutWidget_GameMod.add(new GameModeCardWidget(worldCreator, self)); + tab_GameModeHardcore.setMode(WorldCreator.Mode.HARDCORE); + GameModeCardWidget tab_GameModeCreative = directionalLayoutWidget_GameMod.add(new GameModeCardWidget(worldCreator, self)); + tab_GameModeCreative.setMode(WorldCreator.Mode.CREATIVE); + adder.add(directionalLayoutWidget_GameMod, positioner.alignHorizontalCenter()); + + GameModeCardWidget[] tab_GameModes = {tab_GameModeSurvival, tab_GameModeHardcore, tab_GameModeCreative}; + worldCreator.addListener(creator -> { + for(GameModeCardWidget tab_GameMode : tab_GameModes) { + tab_GameMode.setChosen(false); + if (tab_GameMode.getMode().equals(worldCreator.getGameMode())) { + tab_GameMode.setChosen(true); + } + } + }); + + // 游戏难度和允许作弊按钮调整在一行内 + DirectionalLayoutWidget directionalLayoutWidget_Settings = DirectionalLayoutWidget.horizontal().spacing(8); + + CyclingButtonWidget cyclingButtonWidget_Difficulty = directionalLayoutWidget_Settings.add( + CyclingButtonWidget.builder(Difficulty::getTranslatableName) + .values(Difficulty.values()) + .build(0, 0, 150, 20, Text.translatable("options.difficulty"), (button, value) -> worldCreator.setDifficulty(value)), + positioner + ); + worldCreator.addListener(creator -> { + cyclingButtonWidget_Difficulty.setValue(creator.getDifficulty()); + cyclingButtonWidget_Difficulty.active = !creator.isHardcore(); + cyclingButtonWidget_Difficulty.setTooltip(Tooltip.of(creator.getDifficulty().getInfo())); + }); + CyclingButtonWidget cyclingButtonWidget_AllowCheat = directionalLayoutWidget_Settings.add( + CyclingButtonWidget.onOffBuilder() + .tooltip(value -> Tooltip.of(ALLOW_COMMANDS_INFO_TEXT)) + .build(0, 0, 150, 20, ALLOW_COMMANDS_TEXT, (button, value) -> worldCreator.setCheatsEnabled(value)) + ); + worldCreator.addListener(creator -> { + cyclingButtonWidget_AllowCheat.setValue(worldCreator.areCheatsEnabled()); + cyclingButtonWidget_AllowCheat.active = !worldCreator.isDebug() && !worldCreator.isHardcore(); + }); + + adder.add(directionalLayoutWidget_Settings, positioner.alignHorizontalCenter()); + + if (!SharedConstants.getGameVersion().stable()) { + adder.add( + ButtonWidget.builder( + EXPERIMENTS_TEXT, + button -> self.openExperimentsScreen(worldCreator.getGeneratorOptionsHolder().dataConfiguration()) + ) + .width(210) + .build() + ); + } + + logger.info("New Create World Screen Game Tab initialized."); + } +} diff --git a/src/versioned/1.21.6/client/java/cn/mangofanfan/fanwindow/client/screen/widget/GameModeCardWidget.java b/src/versioned/1.21.6/client/java/cn/mangofanfan/fanwindow/client/screen/widget/GameModeCardWidget.java new file mode 100644 index 0000000..07e25da --- /dev/null +++ b/src/versioned/1.21.6/client/java/cn/mangofanfan/fanwindow/client/screen/widget/GameModeCardWidget.java @@ -0,0 +1,82 @@ +package cn.mangofanfan.fanwindow.client.screen.widget; + +import net.minecraft.client.gl.RenderPipelines; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; +import net.minecraft.client.gui.screen.world.WorldCreator; +import net.minecraft.client.gui.tooltip.Tooltip; +import net.minecraft.client.gui.widget.ClickableWidget; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; + +public class GameModeCardWidget extends ClickableWidget { + WorldCreator worldCreator; + WorldCreator.Mode mode; + Identifier modePicture; + Screen screen; + Text strongText; + int borderColor; + boolean chosen; + + public GameModeCardWidget(WorldCreator worldCreator, Screen screen) { + super(0, 0, 60, 90, Text.empty()); + this.worldCreator = worldCreator; + this.screen = screen; + } + + public void setMode(WorldCreator.Mode mode) { + this.mode = mode; + this.setTooltip(Tooltip.of(mode.getInfo())); + switch (mode) { + case CREATIVE: + borderColor = 0xFF2F4F4F; + modePicture = Identifier.of("fanwindow", "textures/gamemode/creative.png"); + strongText = Text.translatable("selectWorld.gameMode.creative"); + break; + case HARDCORE: + borderColor = 0xFFFFFAFA; + modePicture = Identifier.of("fanwindow", "textures/gamemode/hardcore.png"); + strongText = Text.translatable("selectWorld.gameMode.hardcore"); + break; + case SURVIVAL: + borderColor = 0xFF00BFFF; + modePicture = Identifier.of("fanwindow", "textures/gamemode/survival.png"); + strongText = Text.translatable("selectWorld.gameMode.survival"); + break; + } + } + + public WorldCreator.Mode getMode() { + return mode; + } + + public void setChosen(boolean chosen) { + this.chosen = chosen; + } + + public boolean isChosen() { + return this.chosen; + } + + @Override + protected void renderWidget(DrawContext context, int mouseX, int mouseY, float deltaTicks) { + int bgColor = isHovered() ? 0xFF222222 : 0xFF000000; + int strongColor = isChosen() ? 0xFFFFFFFF : 0xFF000000; + int strongBgColor = isChosen() ? 0xDD000000 : 0xDDFFFFFF; + context.fill(getX() + 1, getY() + 1, getX() + width - 1, getY() + height - 1, bgColor); + context.drawTexture(RenderPipelines.GUI_TEXTURED, modePicture, getX() + 1, getY() + 1, 0, 0, width - 2, height - 2, width - 2, height - 2); + context.fill(getX() + 1, getY() + 71, getX() + width - 1, getY() + height - 1, strongBgColor); + context.drawWrappedText(screen.getTextRenderer(), strongText, getX() + 5, getY() + 76, width - 12, strongColor, false); + context.drawBorder(getX(), getY(), width, height, borderColor); + } + + @Override + public void onClick(double mouseX, double mouseY) { + super.onClick(mouseX, mouseY); + worldCreator.setGameMode(mode); + } + + @Override + protected void appendClickableNarrations(NarrationMessageBuilder builder) {} +} diff --git a/src/versioned/1.21.6/client/java/cn/mangofanfan/fanwindow/mixin/client/CreateWorldScreenMixin.java b/src/versioned/1.21.6/client/java/cn/mangofanfan/fanwindow/mixin/client/CreateWorldScreenMixin.java new file mode 100644 index 0000000..be63caa --- /dev/null +++ b/src/versioned/1.21.6/client/java/cn/mangofanfan/fanwindow/mixin/client/CreateWorldScreenMixin.java @@ -0,0 +1,77 @@ +package cn.mangofanfan.fanwindow.mixin.client; + +import cn.mangofanfan.fanwindow.client.config.ConfigManager; +import cn.mangofanfan.fanwindow.client.screen.NewCreateWorldScreenGameTab; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.world.CreateWorldScreen; +import net.minecraft.client.gui.screen.world.WorldCreator; +import net.minecraft.client.gui.tab.TabManager; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.client.gui.widget.DirectionalLayoutWidget; +import net.minecraft.client.gui.widget.TabNavigationWidget; +import net.minecraft.client.gui.widget.ThreePartsLayoutWidget; +import net.minecraft.screen.ScreenTexts; +import net.minecraft.text.Text; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + + +@Mixin(CreateWorldScreen.class) +public class CreateWorldScreenMixin extends Screen { + @Shadow + private TabNavigationWidget tabNavigation; + @Final + @Shadow + private TabManager tabManager; + @Final + @Shadow + private ThreePartsLayoutWidget layout; + @Final + @Shadow + WorldCreator worldCreator; + + @Unique + ConfigManager configManager = ConfigManager.getInstance(); + + protected CreateWorldScreenMixin(Text title) { + super(title); + } + + @Inject(method = "init", at = @At("HEAD"), cancellable = true) + protected void onInit(CallbackInfo ci) { + // 检查配置 + if (!configManager.config.isUseNewCreateWorldScreen()) { + return; + } + // 获取 CreateWorldScreen 实例 + CreateWorldScreen self = (CreateWorldScreen)(Object)this; + TabNavigationWidget.Builder builder = TabNavigationWidget.builder(this.tabManager, this.width); + builder.tabs(new NewCreateWorldScreenGameTab(self), self.new WorldTab(), self.new MoreTab()); + this.tabNavigation = builder + .build(); + this.addDrawableChild(this.tabNavigation); + DirectionalLayoutWidget directionalLayoutWidget = this.layout.addFooter(DirectionalLayoutWidget.horizontal().spacing(8)); + directionalLayoutWidget.add(ButtonWidget.builder(Text.translatable("selectWorld.create"), button -> this.createLevel()).build()); + directionalLayoutWidget.add(ButtonWidget.builder(ScreenTexts.CANCEL, button -> this.onCloseScreen()).build()); + this.layout.forEachChild(child -> { + child.setNavigationOrder(1); + this.addDrawableChild(child); + }); + this.tabNavigation.selectTab(0, false); + this.worldCreator.update(); + this.refreshWidgetPositions(); + ci.cancel(); + } + + @Shadow + public void onCloseScreen() {} + + @Shadow + private void createLevel() {} +} + diff --git a/src/versioned/1.21.6/client/java/cn/mangofanfan/fanwindow/mixin/client/GameMenuScreenMixin.java b/src/versioned/1.21.6/client/java/cn/mangofanfan/fanwindow/mixin/client/GameMenuScreenMixin.java new file mode 100644 index 0000000..8ad0a4c --- /dev/null +++ b/src/versioned/1.21.6/client/java/cn/mangofanfan/fanwindow/mixin/client/GameMenuScreenMixin.java @@ -0,0 +1,74 @@ +package cn.mangofanfan.fanwindow.mixin.client; + +import cn.mangofanfan.fanwindow.client.config.ConfigManager; +import cn.mangofanfan.fanwindow.client.function.SimpleToastBuilder; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.GameMenuScreen; +import net.minecraft.client.gui.screen.TitleScreen; +import net.minecraft.client.gui.screen.multiplayer.MultiplayerScreen; +import net.minecraft.client.network.ServerInfo; +import net.minecraft.client.realms.gui.screen.RealmsMainScreen; +import net.minecraft.text.Text; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(GameMenuScreen.class) +public abstract class GameMenuScreenMixin { + @Unique + private static long lastDisconnectTime = 0; + + @Inject(method = "disconnect(Lnet/minecraft/client/MinecraftClient;Lnet/minecraft/text/Text;)V", at = @At("HEAD"), cancellable = true) + private static void onDisconnect(MinecraftClient client, Text reasonText, CallbackInfo ci) { + // 如果没有启用退出世界确认,则无事发生 + if (!ConfigManager.getInstance().config.isExitWorldConfirm()) return; + + // 否则,需要连续尝试退出两次才允许退出 + if (System.currentTimeMillis() - lastDisconnectTime > 30000) { + lastDisconnectTime = System.currentTimeMillis(); + new SimpleToastBuilder().show( + Text.translatable("gui.fanwindow.exit_world_confirm.title"), + Text.translatable("gui.fanwindow.exit_world_confirm.description") + ); + if (MinecraftClient.getInstance().currentScreen != null) { + GameMenuScreen screen = (GameMenuScreen) MinecraftClient.getInstance().currentScreen; + if (screen.exitButton != null) { + screen.exitButton.active = true; + } + } + } + else { + normalDisconnect(reasonText); + } + ci.cancel(); + } + + @Unique + private static void normalDisconnect(Text reasonText) { + // 首先获取 client 对象 + MinecraftClient client = MinecraftClient.getInstance(); + + boolean bl = client.isInSingleplayer(); + ServerInfo serverInfo = client.getCurrentServerEntry(); + if (client.world != null) { + client.world.disconnect(reasonText); + } + + if (bl) { + client.disconnectWithSavingScreen(); + } else { + client.disconnectWithProgressScreen(); + } + + TitleScreen titleScreen = new TitleScreen(); + if (bl) { + client.setScreen(titleScreen); + } else if (serverInfo != null && serverInfo.isRealm()) { + client.setScreen(new RealmsMainScreen(titleScreen)); + } else { + client.setScreen(new MultiplayerScreen(titleScreen)); + } + } +} diff --git a/src/versioned/1.21.6/client/java/cn/mangofanfan/fanwindow/mixin/client/MinecraftClientMixin.java b/src/versioned/1.21.6/client/java/cn/mangofanfan/fanwindow/mixin/client/MinecraftClientMixin.java new file mode 100644 index 0000000..bdc605f --- /dev/null +++ b/src/versioned/1.21.6/client/java/cn/mangofanfan/fanwindow/mixin/client/MinecraftClientMixin.java @@ -0,0 +1,9 @@ +package cn.mangofanfan.fanwindow.mixin.client; + +import net.minecraft.client.MinecraftClient; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(MinecraftClient.class) +public class MinecraftClientMixin { + // nothing to do +} diff --git a/src/versioned/1.21.9/client/java/cn/mangofanfan/fanwindow/client/function/NativeResourceLoaderImpl.java b/src/versioned/1.21.9/client/java/cn/mangofanfan/fanwindow/client/function/NativeResourceLoaderImpl.java new file mode 100644 index 0000000..fdc939b --- /dev/null +++ b/src/versioned/1.21.9/client/java/cn/mangofanfan/fanwindow/client/function/NativeResourceLoaderImpl.java @@ -0,0 +1,11 @@ +package cn.mangofanfan.fanwindow.client.function; + +import net.minecraft.client.texture.NativeImage; +import net.minecraft.client.texture.NativeImageBackedTexture; + +public class NativeResourceLoaderImpl extends NativeResourceLoader { + @Override + NativeImageBackedTexture getNativeImageBackedTexture(String pathString, NativeImage nativeImage) { + return new NativeImageBackedTexture(() -> pathString, nativeImage); + } +} diff --git a/src/versioned/1.21.9/client/java/cn/mangofanfan/fanwindow/client/function/VersionedRenderImpl.java b/src/versioned/1.21.9/client/java/cn/mangofanfan/fanwindow/client/function/VersionedRenderImpl.java new file mode 100644 index 0000000..d26ae8c --- /dev/null +++ b/src/versioned/1.21.9/client/java/cn/mangofanfan/fanwindow/client/function/VersionedRenderImpl.java @@ -0,0 +1,51 @@ +package cn.mangofanfan.fanwindow.client.function; + +import cn.mangofanfan.fanwindow.client.config.ConfigManager; +import net.minecraft.client.gl.RenderPipelines; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.util.Identifier; + +public class VersionedRenderImpl extends VersionedRender { + public void renderBackground(DrawContext context, int[] textureSize, int width, int height, Identifier bgTexture) { + // 确保渲染背景图片正确,即图片比例不变,且在裁切时总是保持中心位置。 + int[] regionSize = {0, 0}; + double var1 = (double) textureSize[0] / textureSize[1]; + double var2 = (double) width / height; + if (var1 == var2) { + regionSize = textureSize; + } + else if (var1 < var2) { + regionSize[0] = textureSize[0]; + regionSize[1] = (int) (textureSize[1] / var2 * var1); + } + else { + regionSize[0] = (int) (textureSize[0] / var1 * var2); + regionSize[1] = textureSize[1]; + } + context.drawTexture(RenderPipelines.GUI_TEXTURED, bgTexture, 0, 0, (float) (textureSize[0] - regionSize[0]) / 2, (float) (textureSize[1] - regionSize[1]) / 2, width, height, regionSize[0], regionSize[1], textureSize[0], textureSize[1]); + } + + /** + * 简化的公开方法,从 ConfigManager 获取背景图片和尺寸 + * @param context DrawContext + * @param width 游戏屏幕宽度 + * @param height 游戏屏幕高度 + */ + public void renderBackground(DrawContext context, int width, int height) { + ConfigManager configManager = ConfigManager.getInstance(); + renderBackground(context, configManager.getBackgroundTextureSize(), width, height, configManager.getBackgroundTexture()); + } + + @Override + public void fillBackgroundColor(DrawContext context, int x1, int y1, int x2, int y2, int color) { + context.fill(x1, y1, x2, y2, color); + } + + /** + * @param context DrawContext + */ + @Override + public void fillBackgroundColor(DrawContext context, int x1, int y1, int x2, int y2) { + fillBackgroundColor(context, x1, y1, x2, y2, 0x88000000); + } +} diff --git a/src/versioned/1.21.9/client/java/cn/mangofanfan/fanwindow/client/screen/NewCreateWorldScreenGameTab.java b/src/versioned/1.21.9/client/java/cn/mangofanfan/fanwindow/client/screen/NewCreateWorldScreenGameTab.java new file mode 100644 index 0000000..2265587 --- /dev/null +++ b/src/versioned/1.21.9/client/java/cn/mangofanfan/fanwindow/client/screen/NewCreateWorldScreenGameTab.java @@ -0,0 +1,110 @@ +package cn.mangofanfan.fanwindow.client.screen; + +import cn.mangofanfan.fanwindow.client.screen.widget.GameModeCardWidget; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.SharedConstants; +import net.minecraft.client.gui.screen.world.CreateWorldScreen; +import net.minecraft.client.gui.screen.world.WorldCreator; +import net.minecraft.client.gui.tab.GridScreenTab; +import net.minecraft.client.gui.tooltip.Tooltip; +import net.minecraft.client.gui.widget.*; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.minecraft.world.Difficulty; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +@Environment(EnvType.CLIENT) +public class NewCreateWorldScreenGameTab extends GridScreenTab { + private static final Text GAME_TAB_TITLE_TEXT = Text.translatable("createWorld.tab.game.title"); + private static final Text ALLOW_COMMANDS_TEXT = Text.translatable("selectWorld.allowCommands"); + private final TextFieldWidget worldNameField; + static final Text ENTER_NAME_TEXT = Text.translatable("selectWorld.enterName"); + static final Text EXPERIMENTS_TEXT = Text.translatable("selectWorld.experiments"); + static final Text ALLOW_COMMANDS_INFO_TEXT = Text.translatable("selectWorld.allowCommands.info"); + + Logger logger = LoggerFactory.getLogger(NewCreateWorldScreenGameTab.class); + + public NewCreateWorldScreenGameTab(CreateWorldScreen self) { + super(GAME_TAB_TITLE_TEXT); + GridWidget.Adder adder = this.grid.setRowSpacing(8).createAdder(1); + Positioner positioner = adder.copyPositioner(); + WorldCreator worldCreator = self.getWorldCreator(); + this.worldNameField = new TextFieldWidget(self.getTextRenderer(), 208, 20, Text.translatable("selectWorld.enterName")); + this.worldNameField.setText(worldCreator.getWorldName()); + this.worldNameField.setChangedListener(worldCreator::setWorldName); + worldCreator + .addListener( + creator -> this.worldNameField + .setTooltip( + Tooltip.of(Text.translatable("selectWorld.targetFolder", Text.literal(creator.getWorldDirectoryName()).formatted(Formatting.ITALIC))) + ) + ); + // CreateWorldScreen.this.setInitialFocus(this.worldNameField); + adder.add( + LayoutWidgets.createLabeledWidget(self.getTextRenderer(), this.worldNameField, ENTER_NAME_TEXT), + adder.copyPositioner().alignHorizontalCenter() + ); + + // 新版游戏模式选择器 + DirectionalLayoutWidget directionalLayoutWidget_GameMod = DirectionalLayoutWidget.horizontal().spacing(16); + GameModeCardWidget tab_GameModeSurvival = directionalLayoutWidget_GameMod.add(new GameModeCardWidget(worldCreator, self)); + tab_GameModeSurvival.setMode(WorldCreator.Mode.SURVIVAL); + GameModeCardWidget tab_GameModeHardcore = directionalLayoutWidget_GameMod.add(new GameModeCardWidget(worldCreator, self)); + tab_GameModeHardcore.setMode(WorldCreator.Mode.HARDCORE); + GameModeCardWidget tab_GameModeCreative = directionalLayoutWidget_GameMod.add(new GameModeCardWidget(worldCreator, self)); + tab_GameModeCreative.setMode(WorldCreator.Mode.CREATIVE); + adder.add(directionalLayoutWidget_GameMod, positioner.alignHorizontalCenter()); + + GameModeCardWidget[] tab_GameModes = {tab_GameModeSurvival, tab_GameModeHardcore, tab_GameModeCreative}; + worldCreator.addListener(creator -> { + for(GameModeCardWidget tab_GameMode : tab_GameModes) { + tab_GameMode.setChosen(false); + if (tab_GameMode.getMode().equals(worldCreator.getGameMode())) { + tab_GameMode.setChosen(true); + } + } + }); + + // 游戏难度和允许作弊按钮调整在一行内 + DirectionalLayoutWidget directionalLayoutWidget_Settings = DirectionalLayoutWidget.horizontal().spacing(8); + + CyclingButtonWidget cyclingButtonWidget_Difficulty = directionalLayoutWidget_Settings.add( + CyclingButtonWidget.builder(Difficulty::getTranslatableName) + .values(Difficulty.values()) + .build(0, 0, 150, 20, Text.translatable("options.difficulty"), (button, value) -> worldCreator.setDifficulty(value)), + positioner + ); + worldCreator.addListener(creator -> { + cyclingButtonWidget_Difficulty.setValue(creator.getDifficulty()); + cyclingButtonWidget_Difficulty.active = !creator.isHardcore(); + cyclingButtonWidget_Difficulty.setTooltip(Tooltip.of(creator.getDifficulty().getInfo())); + }); + CyclingButtonWidget cyclingButtonWidget_AllowCheat = directionalLayoutWidget_Settings.add( + CyclingButtonWidget.onOffBuilder() + .tooltip(value -> Tooltip.of(ALLOW_COMMANDS_INFO_TEXT)) + .build(0, 0, 150, 20, ALLOW_COMMANDS_TEXT, (button, value) -> worldCreator.setCheatsEnabled(value)) + ); + worldCreator.addListener(creator -> { + cyclingButtonWidget_AllowCheat.setValue(worldCreator.areCheatsEnabled()); + cyclingButtonWidget_AllowCheat.active = !worldCreator.isDebug() && !worldCreator.isHardcore(); + }); + + adder.add(directionalLayoutWidget_Settings, positioner.alignHorizontalCenter()); + + if (!SharedConstants.getGameVersion().stable()) { + adder.add( + ButtonWidget.builder( + EXPERIMENTS_TEXT, + button -> self.openExperimentsScreen(worldCreator.getGeneratorOptionsHolder().dataConfiguration()) + ) + .width(210) + .build() + ); + } + + logger.info("New Create World Screen Game Tab initialized."); + } +} diff --git a/src/versioned/1.21.9/client/java/cn/mangofanfan/fanwindow/client/screen/widget/GameModeCardWidget.java b/src/versioned/1.21.9/client/java/cn/mangofanfan/fanwindow/client/screen/widget/GameModeCardWidget.java new file mode 100644 index 0000000..3563285 --- /dev/null +++ b/src/versioned/1.21.9/client/java/cn/mangofanfan/fanwindow/client/screen/widget/GameModeCardWidget.java @@ -0,0 +1,88 @@ +package cn.mangofanfan.fanwindow.client.screen.widget; + +import net.minecraft.client.gl.RenderPipelines; +import net.minecraft.client.gui.Click; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; +import net.minecraft.client.gui.screen.world.WorldCreator; +import net.minecraft.client.gui.tooltip.Tooltip; +import net.minecraft.client.gui.widget.ClickableWidget; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; + +public class GameModeCardWidget extends ClickableWidget { + WorldCreator worldCreator; + WorldCreator.Mode mode; + Identifier modePicture; + Screen screen; + Text strongText; + int borderColor; + boolean chosen; + + public GameModeCardWidget(WorldCreator worldCreator, Screen screen) { + super(0, 0, 60, 90, Text.empty()); + this.worldCreator = worldCreator; + this.screen = screen; + } + + public void setMode(WorldCreator.Mode mode) { + this.mode = mode; + this.setTooltip(Tooltip.of(mode.getInfo())); + switch (mode) { + case CREATIVE: + borderColor = 0xFF2F4F4F; + modePicture = Identifier.of("fanwindow", "textures/gamemode/creative.png"); + strongText = Text.translatable("selectWorld.gameMode.creative"); + break; + case HARDCORE: + borderColor = 0xFFFFFAFA; + modePicture = Identifier.of("fanwindow", "textures/gamemode/hardcore.png"); + strongText = Text.translatable("selectWorld.gameMode.hardcore"); + break; + case SURVIVAL: + borderColor = 0xFF00BFFF; + modePicture = Identifier.of("fanwindow", "textures/gamemode/survival.png"); + strongText = Text.translatable("selectWorld.gameMode.survival"); + break; + } + } + + public WorldCreator.Mode getMode() { + return mode; + } + + public void setChosen(boolean chosen) { + this.chosen = chosen; + } + + public boolean isChosen() { + return this.chosen; + } + + @Override + protected void renderWidget(DrawContext context, int mouseX, int mouseY, float deltaTicks) { + int bgColor = isHovered() ? 0xFF222222 : 0xFF000000; + int strongColor = isChosen() ? 0xFFFFFFFF : 0xFF000000; + int strongBgColor = isChosen() ? 0xDD000000 : 0xDDFFFFFF; + context.fill(getX() + 1, getY() + 1, getX() + width - 1, getY() + height - 1, bgColor); + context.drawTexture(RenderPipelines.GUI_TEXTURED, modePicture, getX() + 1, getY() + 1, 0, 0, width - 2, height - 2, width - 2, height - 2); + context.fill(getX() + 1, getY() + 71, getX() + width - 1, getY() + height - 1, strongBgColor); + context.drawWrappedText(screen.getTextRenderer(), strongText, getX() + 5, getY() + 76, width - 12, strongColor, false); + // context.drawBorder(getX(), getY(), width, height, borderColor); + // 绘制边框的方法不知道到哪里去了,所以先手绘一个差不多的边框叭(委屈巴巴) + context.drawHorizontalLine(getX(), getX() + width - 1, getY(), borderColor); + context.drawHorizontalLine(getX(), getX() + width - 1, getY() + height - 1, borderColor); + context.drawVerticalLine(getX(), getY(), getY() + height, borderColor); + context.drawVerticalLine(getX() + width - 1, getY(), getY() + height, borderColor); + } + + @Override + public void onClick(Click click, boolean doubled) { + super.onClick(click, doubled); + worldCreator.setGameMode(mode); + } + + @Override + protected void appendClickableNarrations(NarrationMessageBuilder builder) {} +} diff --git a/src/versioned/1.21.9/client/java/cn/mangofanfan/fanwindow/mixin/client/CreateWorldScreenMixin.java b/src/versioned/1.21.9/client/java/cn/mangofanfan/fanwindow/mixin/client/CreateWorldScreenMixin.java new file mode 100644 index 0000000..be63caa --- /dev/null +++ b/src/versioned/1.21.9/client/java/cn/mangofanfan/fanwindow/mixin/client/CreateWorldScreenMixin.java @@ -0,0 +1,77 @@ +package cn.mangofanfan.fanwindow.mixin.client; + +import cn.mangofanfan.fanwindow.client.config.ConfigManager; +import cn.mangofanfan.fanwindow.client.screen.NewCreateWorldScreenGameTab; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.world.CreateWorldScreen; +import net.minecraft.client.gui.screen.world.WorldCreator; +import net.minecraft.client.gui.tab.TabManager; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.client.gui.widget.DirectionalLayoutWidget; +import net.minecraft.client.gui.widget.TabNavigationWidget; +import net.minecraft.client.gui.widget.ThreePartsLayoutWidget; +import net.minecraft.screen.ScreenTexts; +import net.minecraft.text.Text; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + + +@Mixin(CreateWorldScreen.class) +public class CreateWorldScreenMixin extends Screen { + @Shadow + private TabNavigationWidget tabNavigation; + @Final + @Shadow + private TabManager tabManager; + @Final + @Shadow + private ThreePartsLayoutWidget layout; + @Final + @Shadow + WorldCreator worldCreator; + + @Unique + ConfigManager configManager = ConfigManager.getInstance(); + + protected CreateWorldScreenMixin(Text title) { + super(title); + } + + @Inject(method = "init", at = @At("HEAD"), cancellable = true) + protected void onInit(CallbackInfo ci) { + // 检查配置 + if (!configManager.config.isUseNewCreateWorldScreen()) { + return; + } + // 获取 CreateWorldScreen 实例 + CreateWorldScreen self = (CreateWorldScreen)(Object)this; + TabNavigationWidget.Builder builder = TabNavigationWidget.builder(this.tabManager, this.width); + builder.tabs(new NewCreateWorldScreenGameTab(self), self.new WorldTab(), self.new MoreTab()); + this.tabNavigation = builder + .build(); + this.addDrawableChild(this.tabNavigation); + DirectionalLayoutWidget directionalLayoutWidget = this.layout.addFooter(DirectionalLayoutWidget.horizontal().spacing(8)); + directionalLayoutWidget.add(ButtonWidget.builder(Text.translatable("selectWorld.create"), button -> this.createLevel()).build()); + directionalLayoutWidget.add(ButtonWidget.builder(ScreenTexts.CANCEL, button -> this.onCloseScreen()).build()); + this.layout.forEachChild(child -> { + child.setNavigationOrder(1); + this.addDrawableChild(child); + }); + this.tabNavigation.selectTab(0, false); + this.worldCreator.update(); + this.refreshWidgetPositions(); + ci.cancel(); + } + + @Shadow + public void onCloseScreen() {} + + @Shadow + private void createLevel() {} +} + diff --git a/src/versioned/1.21.9/client/java/cn/mangofanfan/fanwindow/mixin/client/GameMenuScreenMixin.java b/src/versioned/1.21.9/client/java/cn/mangofanfan/fanwindow/mixin/client/GameMenuScreenMixin.java new file mode 100644 index 0000000..f3610bb --- /dev/null +++ b/src/versioned/1.21.9/client/java/cn/mangofanfan/fanwindow/mixin/client/GameMenuScreenMixin.java @@ -0,0 +1,9 @@ +package cn.mangofanfan.fanwindow.mixin.client; + +import net.minecraft.client.gui.screen.GameMenuScreen; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(GameMenuScreen.class) +public class GameMenuScreenMixin { + // nothing to do +} \ No newline at end of file diff --git a/src/versioned/1.21.9/client/java/cn/mangofanfan/fanwindow/mixin/client/MinecraftClientMixin.java b/src/versioned/1.21.9/client/java/cn/mangofanfan/fanwindow/mixin/client/MinecraftClientMixin.java new file mode 100644 index 0000000..ac2dd78 --- /dev/null +++ b/src/versioned/1.21.9/client/java/cn/mangofanfan/fanwindow/mixin/client/MinecraftClientMixin.java @@ -0,0 +1,74 @@ +package cn.mangofanfan.fanwindow.mixin.client; + +import cn.mangofanfan.fanwindow.client.config.ConfigManager; +import cn.mangofanfan.fanwindow.client.function.SimpleToastBuilder; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.GameMenuScreen; +import net.minecraft.client.gui.screen.TitleScreen; +import net.minecraft.client.gui.screen.multiplayer.MultiplayerScreen; +import net.minecraft.client.network.ServerInfo; +import net.minecraft.client.realms.gui.screen.RealmsMainScreen; +import net.minecraft.text.Text; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(MinecraftClient.class) +public abstract class MinecraftClientMixin { + @Unique + private long lastDisconnectTime = 0; + + @Inject(method = "disconnect(Lnet/minecraft/text/Text;)V", at = @At("HEAD"), cancellable = true) + public void onDisconnect(Text reasonText, CallbackInfo ci) { + // 如果没有启用退出世界确认,则无事发生 + if (!ConfigManager.getInstance().config.isExitWorldConfirm()) return; + + // 否则,需要连续尝试退出两次才允许退出 + if (System.currentTimeMillis() - lastDisconnectTime > 30000) { + lastDisconnectTime = System.currentTimeMillis(); + new SimpleToastBuilder().show( + Text.translatable("gui.fanwindow.exit_world_confirm.title"), + Text.translatable("gui.fanwindow.exit_world_confirm.description") + ); + if (MinecraftClient.getInstance().currentScreen != null) { + GameMenuScreen screen = (GameMenuScreen) MinecraftClient.getInstance().currentScreen; + if (screen.exitButton != null) { + screen.exitButton.active = true; + } + } + } + else { + normalDisconnect(reasonText); + } + ci.cancel(); + } + + @Unique + public void normalDisconnect(Text reasonText) { + // 首先获取 self 对象 + MinecraftClient self = (MinecraftClient)(Object)this; + + boolean bl = self.isInSingleplayer(); + ServerInfo serverInfo = self.getCurrentServerEntry(); + if (self.world != null) { + self.world.disconnect(reasonText); + } + + if (bl) { + self.disconnectWithSavingScreen(); + } else { + self.disconnectWithProgressScreen(); + } + + TitleScreen titleScreen = new TitleScreen(); + if (bl) { + self.setScreen(titleScreen); + } else if (serverInfo != null && serverInfo.isRealm()) { + self.setScreen(new RealmsMainScreen(titleScreen)); + } else { + self.setScreen(new MultiplayerScreen(titleScreen)); + } + } +} diff --git a/src/versioned/1.21/client/java/cn/mangofanfan/fanwindow/client/function/RenderBackgroundImpl.java b/src/versioned/1.21/client/java/cn/mangofanfan/fanwindow/client/function/VersionedRenderImpl.java similarity index 76% rename from src/versioned/1.21/client/java/cn/mangofanfan/fanwindow/client/function/RenderBackgroundImpl.java rename to src/versioned/1.21/client/java/cn/mangofanfan/fanwindow/client/function/VersionedRenderImpl.java index 609c6fc..0cd9107 100644 --- a/src/versioned/1.21/client/java/cn/mangofanfan/fanwindow/client/function/RenderBackgroundImpl.java +++ b/src/versioned/1.21/client/java/cn/mangofanfan/fanwindow/client/function/VersionedRenderImpl.java @@ -4,7 +4,7 @@ import net.minecraft.client.gui.DrawContext; import net.minecraft.util.Identifier; -public class RenderBackgroundImpl extends RenderBackground { +public class VersionedRenderImpl extends VersionedRender { public void renderBackground(DrawContext context, int[] textureSize, int width, int height, Identifier bgTexture) { // 确保渲染背景图片正确,即图片比例不变,且在裁切时总是保持中心位置。 int[] regionSize = {0, 0}; @@ -12,7 +12,8 @@ public void renderBackground(DrawContext context, int[] textureSize, int width, double var2 = (double) width / height; if (var1 == var2) { regionSize = textureSize; - } else if (var1 < var2) { + } + else if (var1 < var2) { regionSize[0] = textureSize[0]; regionSize[1] = (int) (textureSize[1] / var2 * var1); } @@ -33,4 +34,17 @@ public void renderBackground(DrawContext context, int width, int height) { ConfigManager configManager = ConfigManager.getInstance(); renderBackground(context, configManager.getBackgroundTextureSize(), width, height, configManager.getBackgroundTexture()); } + + @Override + public void fillBackgroundColor(DrawContext context, int x1, int y1, int x2, int y2, int color) { + context.fill(x1, y1, x2, y2, color); + } + + /** + * @param context DrawContext + */ + @Override + public void fillBackgroundColor(DrawContext context, int x1, int y1, int x2, int y2) { + fillBackgroundColor(context, x1, y1, x2, y2, 0x88000000); + } } diff --git a/src/versioned/1.21/client/java/cn/mangofanfan/fanwindow/mixin/client/GameMenuScreenMixin.java b/src/versioned/1.21/client/java/cn/mangofanfan/fanwindow/mixin/client/GameMenuScreenMixin.java new file mode 100644 index 0000000..af69e64 --- /dev/null +++ b/src/versioned/1.21/client/java/cn/mangofanfan/fanwindow/mixin/client/GameMenuScreenMixin.java @@ -0,0 +1,74 @@ +package cn.mangofanfan.fanwindow.mixin.client; + +import cn.mangofanfan.fanwindow.client.config.ConfigManager; +import cn.mangofanfan.fanwindow.client.function.SimpleToastBuilder; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.GameMenuScreen; +import net.minecraft.client.gui.screen.MessageScreen; +import net.minecraft.client.gui.screen.TitleScreen; +import net.minecraft.client.gui.screen.multiplayer.MultiplayerScreen; +import net.minecraft.client.network.ServerInfo; +import net.minecraft.client.realms.gui.screen.RealmsMainScreen; +import net.minecraft.text.Text; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(GameMenuScreen.class) +public abstract class GameMenuScreenMixin { + @Unique + private static long lastDisconnectTime = 0; + + @Inject(method = "disconnect", at = @At("HEAD"), cancellable = true) + private static void onDisconnect(CallbackInfo ci) { + // 如果没有启用退出世界确认,则无事发生 + if (!ConfigManager.getInstance().config.isExitWorldConfirm()) return; + + // 否则,需要连续尝试退出两次才允许退出 + if (System.currentTimeMillis() - lastDisconnectTime > 30000) { + lastDisconnectTime = System.currentTimeMillis(); + new SimpleToastBuilder().show( + Text.translatable("gui.fanwindow.exit_world_confirm.title"), + Text.translatable("gui.fanwindow.exit_world_confirm.description") + ); + if (MinecraftClient.getInstance().currentScreen != null) { + GameMenuScreen screen = (GameMenuScreen) MinecraftClient.getInstance().currentScreen; + if (screen.exitButton != null) { + screen.exitButton.active = true; + } + } + } + else { + normalDisconnect(); + } + ci.cancel(); + } + + @Unique + private static void normalDisconnect() { + // 首先获取 client 对象 + MinecraftClient client = MinecraftClient.getInstance(); + + boolean bl = client.isInSingleplayer(); + ServerInfo serverInfo = client.getCurrentServerEntry(); + if (client.world != null) { + client.world.disconnect(); + } + if (bl) { + client.disconnect(new MessageScreen(Text.translatable("menu.savingLevel"))); // SAVING_LEVEL_TEXT + } else { + client.disconnect(); + } + + TitleScreen titleScreen = new TitleScreen(); + if (bl) { + client.setScreen(titleScreen); + } else if (serverInfo != null && serverInfo.isRealm()) { + client.setScreen(new RealmsMainScreen(titleScreen)); + } else { + client.setScreen(new MultiplayerScreen(titleScreen)); + } + } +} diff --git a/src/versioned/1.21/client/java/cn/mangofanfan/fanwindow/mixin/client/MinecraftClientMixin.java b/src/versioned/1.21/client/java/cn/mangofanfan/fanwindow/mixin/client/MinecraftClientMixin.java new file mode 100644 index 0000000..bdc605f --- /dev/null +++ b/src/versioned/1.21/client/java/cn/mangofanfan/fanwindow/mixin/client/MinecraftClientMixin.java @@ -0,0 +1,9 @@ +package cn.mangofanfan.fanwindow.mixin.client; + +import net.minecraft.client.MinecraftClient; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(MinecraftClient.class) +public class MinecraftClientMixin { + // nothing to do +} diff --git a/versions.gradle b/versions.gradle index ede381c..8302983 100644 --- a/versions.gradle +++ b/versions.gradle @@ -47,6 +47,46 @@ ext { versioned_scr: v1215_versioned_scr, publish_minecraft_version: "1.21.5", mod_sign: "mc1.21.5" + ], + "1.21.6": [ + yarn_mappings: v1216_yarn_mappings, + cloth_config_api_version: v1216_cloth_config_api_version, + modmenu_version: v1216_modmenu_version, + versioned_scr: v1216_versioned_scr, + publish_minecraft_version: ">=1.21.6 <1.21.9", + mod_sign: "mc1.21.6+mc1.21.7+mc1.21.8" + ], + "1.21.7": [ + yarn_mappings: v1217_yarn_mappings, + cloth_config_api_version: v1217_cloth_config_api_version, + modmenu_version: v1217_modmenu_version, + versioned_scr: v1217_versioned_scr, + publish_minecraft_version: ">=1.21.6 <1.21.9", + mod_sign: "mc1.21.6+mc1.21.7+mc1.21.8" + ], + "1.21.8": [ + yarn_mappings: v1218_yarn_mappings, + cloth_config_api_version: v1218_cloth_config_api_version, + modmenu_version: v1218_modmenu_version, + versioned_scr: v1218_versioned_scr, + publish_minecraft_version: ">=1.21.6 <1.21.9", + mod_sign: "mc1.21.6+mc1.21.7+mc1.21.8" + ], + "1.21.9": [ + yarn_mappings: v1219_yarn_mappings, + cloth_config_api_version: v1219_cloth_config_api_version, + modmenu_version: v1219_modmenu_version, + versioned_scr: v1219_versioned_scr, + publish_minecraft_version: ">=1.21.9 <1.21.11", + mod_sign: "mc1.21.9+mc1.21.10" + ], + "1.21.10": [ + yarn_mappings: v12110_yarn_mappings, + cloth_config_api_version: v12110_cloth_config_api_version, + modmenu_version: v12110_modmenu_version, + versioned_scr: v12110_versioned_scr, + publish_minecraft_version: ">=1.21.9 <1.21.11", + mod_sign: "mc1.21.9+mc1.21.10" ] ] } \ No newline at end of file