From e20474f71ac2cbddf9fc15f4ded82e9c4d53089c Mon Sep 17 00:00:00 2001 From: mine_ Date: Sun, 16 Nov 2025 19:50:30 +0800 Subject: [PATCH 01/69] =?UTF-8?q?feat:=E6=B7=BB=E5=8A=A0GameRulePage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/GameRulePage.java | 200 ++++++++++++++++++ .../hmcl/ui/versions/WorldManagePage.java | 10 +- 2 files changed, 208 insertions(+), 2 deletions(-) create mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java new file mode 100644 index 0000000000..260126158f --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java @@ -0,0 +1,200 @@ +package org.jackhuang.hmcl.ui.versions; + +import com.jfoenix.controls.JFXButton; +import com.jfoenix.controls.JFXListView; +import com.jfoenix.controls.JFXTextField; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.control.Label; +import javafx.scene.control.Skin; +import javafx.scene.control.SkinBase; +import javafx.scene.layout.*; +import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.ui.ListPageBase; +import org.jackhuang.hmcl.ui.SVG; +import org.jackhuang.hmcl.ui.construct.ComponentList; +import org.jackhuang.hmcl.ui.construct.MDListCell; +import org.jackhuang.hmcl.ui.construct.OptionToggleButton; +import org.jackhuang.hmcl.ui.construct.SpinnerPane; +import org.jackhuang.hmcl.util.Holder; + +import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton2; +import static org.jackhuang.hmcl.util.i18n.I18n.i18n; + +public class GameRulePage extends ListPageBase { + + WorldManagePage worldManagePage; + + public GameRulePage(WorldManagePage worldManagePage) { + this.worldManagePage = worldManagePage; + ObservableList gameRuleList = FXCollections.observableArrayList(); + //用于测试,目前还在写UI。 + gameRuleList.add(new GameRuleInfo("sneak_speed", "潜行前进速度", 2)); + gameRuleList.add(new GameRuleInfo("spawn_immediately", "死亡立即重生", false)); + gameRuleList.add(new GameRuleInfo("time_advance", "时间自然流逝", true)); + gameRuleList.add(new GameRuleInfo("max_health", "最大生命值", 30)); + gameRuleList.add(new GameRuleInfo("pvp_enabled", "启用玩家间对抗", false)); + gameRuleList.add(new GameRuleInfo("keep_inventory_on_death", "死亡后保留物品栏", true)); + gameRuleList.add(new GameRuleInfo("mob_spawning_enabled", "启用怪物生成", true)); + gameRuleList.add(new GameRuleInfo("day_night_cycle_speed", "昼夜交替速度", 1)); + gameRuleList.add(new GameRuleInfo("fall_damage_multiplier", "掉落伤害倍率", 1)); + gameRuleList.add(new GameRuleInfo("friendly_fire_enabled", "启用友军伤害", false)); + gameRuleList.add(new GameRuleInfo("health_regeneration", "启用生命值自然恢复", true)); + gameRuleList.add(new GameRuleInfo("explosions_destroy_blocks", "爆炸破坏方块", true)); + gameRuleList.add(new GameRuleInfo("show_coordinates", "显示玩家坐标", true)); + gameRuleList.add(new GameRuleInfo("crafting_enabled", "启用制作系统", true)); + gameRuleList.add(new GameRuleInfo("hunger_system_enabled", "启用饥饿系统", true)); + gameRuleList.add(new GameRuleInfo("xp_multiplier", "经验值获取倍率", 1)); + gameRuleList.add(new GameRuleInfo("max_players", "服务器最大玩家数", 10)); + gameRuleList.add(new GameRuleInfo("gravity_level", "重力等级", 10)); + gameRuleList.add(new GameRuleInfo("keep_xp_on_death", "死亡后保留经验值", false)); + gameRuleList.add(new GameRuleInfo("weather_cycle_enabled", "启用天气变化", true)); + gameRuleList.add(new GameRuleInfo("loot_drop_multiplier", "战利品掉落倍率", 1)); + gameRuleList.add(new GameRuleInfo("build_height_limit", "建筑高度限制", 256)); + gameRuleList.add(new GameRuleInfo("enable_flight", "允许玩家飞行", false)); + gameRuleList.add(new GameRuleInfo("mob_griefing", "怪物破坏环境", true)); + gameRuleList.add(new GameRuleInfo("resource_respawn_rate", "资源刷新速率", 100)); + gameRuleList.add(new GameRuleInfo("chat_enabled", "启用游戏内聊天", true)); + gameRuleList.add(new GameRuleInfo("max_mana", "最大法力值", 100)); + gameRuleList.add(new GameRuleInfo("mana_regeneration_rate", "法力恢复速度", 5)); + gameRuleList.add(new GameRuleInfo("stamina_consumption_rate", "耐力消耗速率", 1)); + gameRuleList.add(new GameRuleInfo("enable_portals", "启用传送门", true)); + gameRuleList.add(new GameRuleInfo("difficulty_level", "游戏难度等级", 2)); + gameRuleList.add(new GameRuleInfo("command_blocks_enabled", "启用命令方块", false)); + gameRuleList.add(new GameRuleInfo("structure_generation", "生成世界结构", true)); + gameRuleList.add(new GameRuleInfo("item_durability", "启用物品耐久度", true)); + + setItems(gameRuleList); + } + + @Override + protected Skin createDefaultSkin() { + return new GameRulePageSkin(this); + } + + static class GameRuleInfo { + + String ruleKey; + String displayName; + BooleanProperty onValue; + IntegerProperty currentValue; + GameRuleType gameRuleType; + + BorderPane container = new BorderPane(); + + public GameRuleInfo(String ruleKey, String displayName, Boolean onValue) { + this.ruleKey = ruleKey; + this.displayName = displayName; + this.onValue = new SimpleBooleanProperty(onValue); + gameRuleType = GameRuleType.BOOLEAN; + + OptionToggleButton toggleButton = new OptionToggleButton(); + toggleButton.setTitle(displayName); + toggleButton.setSubtitle(ruleKey); + toggleButton.setSelected(onValue); + + HBox.setHgrow(container, Priority.ALWAYS); + container.setCenter(toggleButton); + } + + public GameRuleInfo(String ruleKey, String displayName, Integer currentValue) { + this.ruleKey = ruleKey; + this.displayName = displayName; + this.currentValue = new SimpleIntegerProperty(currentValue); + gameRuleType = GameRuleType.INT; + + VBox vbox = new VBox(); + vbox.getChildren().addAll(new Label(displayName), new Label(ruleKey)); + vbox.setAlignment(Pos.CENTER_LEFT); + HBox.setHgrow(vbox, Priority.ALWAYS); + + container.setPadding(new Insets(8, 8, 8, 16)); + JFXTextField textField = new JFXTextField(); + textField.maxWidth(10); + textField.minWidth(10); + textField.textProperty().set(currentValue.toString()); + + HBox.setHgrow(container, Priority.ALWAYS); + container.setCenter(vbox); + container.setRight(textField); + } + + } + + static class GameRulePageSkin extends SkinBase { + + private final HBox searchBar; + private final JFXTextField searchField; + JFXListView listView = new JFXListView<>(); + + protected GameRulePageSkin(GameRulePage control) { + super(control); + StackPane pane = new StackPane(); + pane.setPadding(new Insets(10)); + pane.getStyleClass().addAll("notice-pane"); + + ComponentList root = new ComponentList(); + root.getStyleClass().add("no-padding"); + + { + searchBar = new HBox(); + searchBar.setAlignment(Pos.CENTER); + searchBar.setPadding(new Insets(0, 5, 0, 5)); + searchField = new JFXTextField(); + searchField.setPromptText(i18n("search")); + HBox.setHgrow(searchField, Priority.ALWAYS); + JFXButton closeSearchBar = createToolbarButton2(null, SVG.CLOSE, + searchField::clear); + FXUtils.onEscPressed(searchField, closeSearchBar::fire); + searchBar.getChildren().addAll(searchField, closeSearchBar); + root.getContent().add(searchBar); + } + + SpinnerPane center = new SpinnerPane(); + ComponentList.setVgrow(center, Priority.ALWAYS); + center.getStyleClass().add("large-spinner-pane"); + center.setContent(listView); + Holder lastCell = new Holder<>(); + listView.setItems(getSkinnable().getItems()); + listView.setCellFactory(x -> new GameRuleListCell(listView, lastCell)); + root.getContent().add(center); + + pane.getChildren().add(root); + getChildren().add(pane); + + } + } + + static class GameRuleListCell extends MDListCell { + + HBox hBox; + + public GameRuleListCell(JFXListView listView, Holder lastCell) { + super(listView, lastCell); + + hBox = new HBox(8); + hBox.setPickOnBounds(false); + hBox.setAlignment(Pos.CENTER_LEFT); + //setSelectable(); + + getContainer().getChildren().setAll(hBox); + } + + @Override + protected void updateControl(GameRuleInfo item, boolean empty) { + if (empty) return; + + hBox.getChildren().setAll(item.container); + } + } + + enum GameRuleType { + INT, BOOLEAN + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldManagePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldManagePage.java index 40c77fc96e..d4516b04ed 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldManagePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldManagePage.java @@ -29,7 +29,10 @@ import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; import org.jackhuang.hmcl.ui.animation.TransitionPane; -import org.jackhuang.hmcl.ui.construct.*; +import org.jackhuang.hmcl.ui.construct.AdvancedListBox; +import org.jackhuang.hmcl.ui.construct.IconedMenuItem; +import org.jackhuang.hmcl.ui.construct.PopupMenu; +import org.jackhuang.hmcl.ui.construct.TabHeader; import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage; import org.jackhuang.hmcl.ui.decorator.DecoratorPage; import org.jackhuang.hmcl.util.ChunkBaseApp; @@ -53,6 +56,7 @@ public final class WorldManagePage extends DecoratorAnimatedPage implements Deco private final TabHeader header; private final TabHeader.Tab worldInfoTab = new TabHeader.Tab<>("worldInfoPage"); + private final TabHeader.Tab gameRuleTab = new TabHeader.Tab<>("gameRulePage"); private final TabHeader.Tab worldBackupsTab = new TabHeader.Tab<>("worldBackupsPage"); private final TabHeader.Tab datapackTab = new TabHeader.Tab<>("datapackListPage"); @@ -65,9 +69,10 @@ public WorldManagePage(World world, Path backupsDir) { this.backupsDir = backupsDir; this.state = new SimpleObjectProperty<>(State.fromTitle(i18n("world.manage.title", world.getWorldName()))); - this.header = new TabHeader(worldInfoTab, worldBackupsTab); + this.header = new TabHeader(worldInfoTab, gameRuleTab, worldBackupsTab); worldInfoTab.setNodeSupplier(() -> new WorldInfoPage(this)); + gameRuleTab.setNodeSupplier(() -> new GameRulePage(this)); worldBackupsTab.setNodeSupplier(() -> new WorldBackupsPage(this)); datapackTab.setNodeSupplier(() -> new DatapackListPage(this)); @@ -83,6 +88,7 @@ public WorldManagePage(World world, Path backupsDir) { AdvancedListBox sideBar = new AdvancedListBox() .addNavigationDrawerTab(header, worldInfoTab, i18n("world.info"), SVG.INFO) + .addNavigationDrawerTab(header, gameRuleTab, "游戏规则", SVG.INFO) .addNavigationDrawerTab(header, worldBackupsTab, i18n("world.backup"), SVG.ARCHIVE); if (world.getGameVersion() != null && // old game will not write game version to level.dat From 73a5dff2d95535d7abf0aeb2b6a17ea35c3ce549 Mon Sep 17 00:00:00 2001 From: mine_ Date: Mon, 17 Nov 2025 09:34:49 +0800 Subject: [PATCH 02/69] =?UTF-8?q?feat:=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/jackhuang/hmcl/ui/versions/GameRulePage.java | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java index 260126158f..592c334fd5 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java @@ -173,24 +173,18 @@ protected GameRulePageSkin(GameRulePage control) { static class GameRuleListCell extends MDListCell { - HBox hBox; - public GameRuleListCell(JFXListView listView, Holder lastCell) { super(listView, lastCell); - - hBox = new HBox(8); - hBox.setPickOnBounds(false); - hBox.setAlignment(Pos.CENTER_LEFT); //setSelectable(); - getContainer().getChildren().setAll(hBox); + //getContainer().getChildren().setAll(hBox); } @Override protected void updateControl(GameRuleInfo item, boolean empty) { if (empty) return; - hBox.getChildren().setAll(item.container); + getContainer().getChildren().setAll(item.container); } } From 89b9bebbab78fd9de3f5b58c2458ed467287ce6d Mon Sep 17 00:00:00 2001 From: mine_ Date: Mon, 17 Nov 2025 16:41:11 +0800 Subject: [PATCH 03/69] =?UTF-8?q?feat:=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java index 592c334fd5..e65aa1b767 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java @@ -175,9 +175,6 @@ static class GameRuleListCell extends MDListCell { public GameRuleListCell(JFXListView listView, Holder lastCell) { super(listView, lastCell); - //setSelectable(); - - //getContainer().getChildren().setAll(hBox); } @Override From 6aeb60c0e658c6232245e8b0d0d1d7d8385090f7 Mon Sep 17 00:00:00 2001 From: mine_ Date: Tue, 25 Nov 2025 21:40:16 +0800 Subject: [PATCH 04/69] feat: update --- .../main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java index e65aa1b767..55cd2bee55 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java @@ -14,6 +14,8 @@ import javafx.scene.control.Label; import javafx.scene.control.Skin; import javafx.scene.control.SkinBase; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; import javafx.scene.layout.*; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.ListPageBase; @@ -163,6 +165,7 @@ protected GameRulePageSkin(GameRulePage control) { Holder lastCell = new Holder<>(); listView.setItems(getSkinnable().getItems()); listView.setCellFactory(x -> new GameRuleListCell(listView, lastCell)); + FXUtils.ignoreEvent(listView, KeyEvent.KEY_PRESSED, e -> e.getCode() == KeyCode.ESCAPE); root.getContent().add(center); pane.getChildren().add(root); From 58bb70386240c9f3c9c120c94b7cb5c3709ef003 Mon Sep 17 00:00:00 2001 From: mine_ Date: Sun, 7 Dec 2025 13:28:54 +0800 Subject: [PATCH 05/69] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/jackhuang/hmcl/gamerule/GameRule.java | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java new file mode 100644 index 0000000000..568a79e2ce --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java @@ -0,0 +1,43 @@ +package org.jackhuang.hmcl.gamerule; + +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleIntegerProperty; + +import java.util.ArrayList; + +public sealed class GameRule permits GameRule.BooleanGameRule, GameRule.IntGameRule { + ArrayList ruleKey; + String displayName; + + public static GameRule createGameRule(ArrayList ruleKey, String displayName, boolean defaultValue) { + return new BooleanGameRule(ruleKey, displayName, defaultValue); + } + + public static GameRule createGameRule(ArrayList ruleKey, String displayName, int defaultValue) { + return new IntGameRule(ruleKey, displayName, defaultValue); + } + + static final class BooleanGameRule extends GameRule { + BooleanProperty value; + BooleanProperty defaultValue; + + private BooleanGameRule(ArrayList ruleKey, String displayName, boolean defaultValue) { + this.ruleKey = ruleKey; + this.displayName = displayName; + this.defaultValue = new SimpleBooleanProperty(defaultValue); + } + } + + static final class IntGameRule extends GameRule { + IntegerProperty value; + IntegerProperty defaultValue; + + IntGameRule(ArrayList ruleKey, String displayName, int defaultValue) { + this.ruleKey = ruleKey; + this.displayName = displayName; + this.defaultValue = new SimpleIntegerProperty(defaultValue); + } + } +} From b509f29c520fcc682a857dec3e8392eeb2d84434 Mon Sep 17 00:00:00 2001 From: mine_ Date: Sun, 7 Dec 2025 16:29:41 +0800 Subject: [PATCH 06/69] =?UTF-8?q?feat:=20=E7=8E=B0=E5=9C=A8GameRule?= =?UTF-8?q?=E7=B1=BB=E5=BA=94=E8=AF=A5=E5=88=9D=E6=AD=A5=E5=8F=AF=E7=94=A8?= =?UTF-8?q?=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/GameRulePage.java | 5 + .../org/jackhuang/hmcl/gamerule/GameRule.java | 126 +++++++++++++++--- .../resources/assets/gamerule/gamerule.json | 17 +++ 3 files changed, 133 insertions(+), 15 deletions(-) create mode 100644 HMCLCore/src/main/resources/assets/gamerule/gamerule.json diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java index 55cd2bee55..4c43053264 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java @@ -17,6 +17,7 @@ import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; import javafx.scene.layout.*; +import org.jackhuang.hmcl.gamerule.GameRule; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.ListPageBase; import org.jackhuang.hmcl.ui.SVG; @@ -26,6 +27,8 @@ import org.jackhuang.hmcl.ui.construct.SpinnerPane; import org.jackhuang.hmcl.util.Holder; +import java.util.Map; + import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton2; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; @@ -33,6 +36,8 @@ public class GameRulePage extends ListPageBase { WorldManagePage worldManagePage; + Map gameRuleMap = GameRule.GameRuleHolder.cloneGameRuleMap(); + public GameRulePage(WorldManagePage worldManagePage) { this.worldManagePage = worldManagePage; ObservableList gameRuleList = FXCollections.observableArrayList(); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java index 568a79e2ce..3e2681e271 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java @@ -1,43 +1,139 @@ package org.jackhuang.hmcl.gamerule; +import com.google.gson.*; +import com.google.gson.annotations.JsonAdapter; import javafx.beans.property.BooleanProperty; import javafx.beans.property.IntegerProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleIntegerProperty; +import org.jackhuang.hmcl.util.gson.JsonSerializable; +import org.jackhuang.hmcl.util.gson.JsonUtils; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Type; import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; -public sealed class GameRule permits GameRule.BooleanGameRule, GameRule.IntGameRule { - ArrayList ruleKey; - String displayName; +@JsonSerializable +@JsonAdapter(GameRule.GameRuleDeserializer.class) +public sealed abstract class GameRule permits GameRule.BooleanGameRule, GameRule.IntGameRule { - public static GameRule createGameRule(ArrayList ruleKey, String displayName, boolean defaultValue) { - return new BooleanGameRule(ruleKey, displayName, defaultValue); + List ruleKey; + String displayName = ""; + + public static GameRule createGameRule(ArrayList ruleKey, String displayName, boolean value) { + return new BooleanGameRule(ruleKey, displayName, value); } - public static GameRule createGameRule(ArrayList ruleKey, String displayName, int defaultValue) { - return new IntGameRule(ruleKey, displayName, defaultValue); + public static GameRule createGameRule(ArrayList ruleKey, String displayName, int value) { + return new IntGameRule(ruleKey, displayName, value); } + public abstract GameRule clone() ; + static final class BooleanGameRule extends GameRule { - BooleanProperty value; - BooleanProperty defaultValue; + BooleanProperty value = new SimpleBooleanProperty(false); + BooleanProperty defaultValue = new SimpleBooleanProperty(false); - private BooleanGameRule(ArrayList ruleKey, String displayName, boolean defaultValue) { + private BooleanGameRule(List ruleKey, String displayName, boolean value) { this.ruleKey = ruleKey; this.displayName = displayName; - this.defaultValue = new SimpleBooleanProperty(defaultValue); + this.value.set(value); + } + + private BooleanGameRule() { + + } + + @Override + public GameRule clone() { + BooleanGameRule booleanGameRule = new BooleanGameRule(ruleKey, displayName, value.getValue()); + booleanGameRule.defaultValue.setValue(defaultValue.getValue()); + return booleanGameRule; } } static final class IntGameRule extends GameRule { - IntegerProperty value; - IntegerProperty defaultValue; + IntegerProperty value = new SimpleIntegerProperty(0); + IntegerProperty defaultValue = new SimpleIntegerProperty(0); - IntGameRule(ArrayList ruleKey, String displayName, int defaultValue) { + private IntGameRule(List ruleKey, String displayName, int value) { this.ruleKey = ruleKey; this.displayName = displayName; - this.defaultValue = new SimpleIntegerProperty(defaultValue); + this.value.set(value); } + + private IntGameRule() { + + } + + @Override + public GameRule clone() { + IntGameRule intGameRule = new IntGameRule(ruleKey, displayName, value.getValue()); + intGameRule.defaultValue.setValue(defaultValue.getValue()); + return intGameRule; + } + } + + static class GameRuleDeserializer implements JsonDeserializer { + + @Override + public GameRule deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { + JsonObject jsonObject = jsonElement.getAsJsonObject(); + GameRule gameRule; + if (jsonObject.get("defaultValue") instanceof JsonPrimitive jsonPrimitive && jsonPrimitive.isNumber()) { + gameRule = new IntGameRule(); + } else { + gameRule = new BooleanGameRule(); + } + + if (gameRule instanceof IntGameRule intGameRule) { + intGameRule.defaultValue.setValue(jsonObject.get("defaultValue").getAsInt()); + } else { + BooleanGameRule booleanGameRule = (BooleanGameRule) gameRule; + booleanGameRule.defaultValue.setValue(jsonObject.get("defaultValue").getAsBoolean()); + } + + gameRule.displayName = jsonObject.get("displayName").getAsString(); + + JsonElement ruleKeyElement = jsonObject.get("ruleKey"); + Type listType = JsonUtils.listTypeOf(String.class).getType(); + gameRule.ruleKey = jsonDeserializationContext.deserialize(ruleKeyElement, listType); + return gameRule; + } + } + + public static class GameRuleHolder { + public static Map gameRuleMap = new HashMap<>(); + + static { + List gameRules; + try (InputStream is = GameRule.class.getResourceAsStream("/assets/gamerule/gamerule.json")) { + if (is == null) { + throw new IOException("Resource not found: /assets/gamerule/gamerule.json"); + } + String jsonContent = new String(is.readAllBytes(), java.nio.charset.StandardCharsets.UTF_8); + gameRules = JsonUtils.fromNonNullJson(jsonContent, JsonUtils.listTypeOf(GameRule.class)); + } catch (IOException e) { + throw new RuntimeException(e); + } + for (GameRule gameRule : gameRules) { + for (String s : gameRule.ruleKey) { + gameRuleMap.put(s, gameRule); + } + } + + System.out.println("GameRuleMap: " + gameRuleMap); + } + + public static Map cloneGameRuleMap() { + Map newGameRuleMap = new HashMap<>(); + gameRuleMap.forEach((key, gameRule) -> newGameRuleMap.put(key, gameRule.clone())); + return newGameRuleMap; + } + } } diff --git a/HMCLCore/src/main/resources/assets/gamerule/gamerule.json b/HMCLCore/src/main/resources/assets/gamerule/gamerule.json new file mode 100644 index 0000000000..f62b2eaf47 --- /dev/null +++ b/HMCLCore/src/main/resources/assets/gamerule/gamerule.json @@ -0,0 +1,17 @@ +[ + { + "ruleKey" : [ + "doDaylightCycle", + "hello" + ], + "displayName" : "是否有昼夜交替", + "defaultValue": true + }, + { + "ruleKey" : [ + "randomTickSpeed" + ], + "displayName" : "随机刻速率", + "defaultValue": 3 + } +] \ No newline at end of file From 691599e8ca644b2ec8969a97b34bf5fb77704946 Mon Sep 17 00:00:00 2001 From: mine_ Date: Sun, 7 Dec 2025 16:52:25 +0800 Subject: [PATCH 07/69] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/GameRulePage.java | 2 +- .../org/jackhuang/hmcl/gamerule/GameRule.java | 122 +++++++++++++++--- 2 files changed, 106 insertions(+), 18 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java index 4c43053264..75595643e9 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java @@ -36,7 +36,7 @@ public class GameRulePage extends ListPageBase { WorldManagePage worldManagePage; - Map gameRuleMap = GameRule.GameRuleHolder.cloneGameRuleMap(); + Map gameRuleMap = GameRule.getCloneGameRuleMap(); public GameRulePage(WorldManagePage worldManagePage) { this.worldManagePage = worldManagePage; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java index 3e2681e271..40575ee655 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java @@ -21,8 +21,8 @@ @JsonAdapter(GameRule.GameRuleDeserializer.class) public sealed abstract class GameRule permits GameRule.BooleanGameRule, GameRule.IntGameRule { - List ruleKey; - String displayName = ""; + private List ruleKey; + private String displayName = ""; public static GameRule createGameRule(ArrayList ruleKey, String displayName, boolean value) { return new BooleanGameRule(ruleKey, displayName, value); @@ -32,15 +32,57 @@ public static GameRule createGameRule(ArrayList ruleKey, String displayN return new IntGameRule(ruleKey, displayName, value); } - public abstract GameRule clone() ; + public static Map addSimpleGameRule(Map gameRuleMap, String ruleKey, String displayName, boolean value) { + ArrayList ruleKeyList = new ArrayList<>(); + ruleKeyList.add(ruleKey); + gameRuleMap.put(ruleKey, new BooleanGameRule(ruleKeyList, displayName, value)); + return gameRuleMap; + } + + public static Map addSimpleGameRule(Map gameRuleMap, String ruleKey, String displayName, int value) { + ArrayList ruleKeyList = new ArrayList<>(); + ruleKeyList.add(ruleKey); + gameRuleMap.put(ruleKey, new IntGameRule(ruleKeyList, displayName, value)); + return gameRuleMap; + } + + public static Map addSimpleGameRule(Map gameRuleMap, String ruleKey, boolean value) { + return addSimpleGameRule(gameRuleMap, ruleKey, "", value); + } + + public static Map addSimpleGameRule(Map gameRuleMap, String ruleKey, int value) { + return addSimpleGameRule(gameRuleMap, ruleKey, "", value); + } + + public static Map getCloneGameRuleMap() { + return GameRule.GameRuleHolder.cloneGameRuleMap(); + } + + public abstract GameRule clone(); + + public void setRuleKey(List ruleKey) { + this.ruleKey = ruleKey; + } + + public List getRuleKey() { + return ruleKey; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public String getDisplayName() { + return displayName; + } static final class BooleanGameRule extends GameRule { - BooleanProperty value = new SimpleBooleanProperty(false); - BooleanProperty defaultValue = new SimpleBooleanProperty(false); + private final BooleanProperty value = new SimpleBooleanProperty(false); + private final BooleanProperty defaultValue = new SimpleBooleanProperty(false); private BooleanGameRule(List ruleKey, String displayName, boolean value) { - this.ruleKey = ruleKey; - this.displayName = displayName; + this.setRuleKey(ruleKey); + this.setDisplayName(displayName); this.value.set(value); } @@ -50,19 +92,43 @@ private BooleanGameRule() { @Override public GameRule clone() { - BooleanGameRule booleanGameRule = new BooleanGameRule(ruleKey, displayName, value.getValue()); + BooleanGameRule booleanGameRule = new BooleanGameRule(getRuleKey(), getDisplayName(), value.getValue()); booleanGameRule.defaultValue.setValue(defaultValue.getValue()); return booleanGameRule; } + + public boolean getDefaultValue() { + return defaultValue.get(); + } + + public BooleanProperty defaultValueProperty() { + return defaultValue; + } + + private void setDefaultValue(boolean value) { + this.defaultValue.setValue(value); + } + + public boolean getValue() { + return value.get(); + } + + public BooleanProperty valueProperty() { + return value; + } + + public void setValue(boolean value) { + this.value.setValue(value); + } } static final class IntGameRule extends GameRule { - IntegerProperty value = new SimpleIntegerProperty(0); - IntegerProperty defaultValue = new SimpleIntegerProperty(0); + private final IntegerProperty value = new SimpleIntegerProperty(0); + private final IntegerProperty defaultValue = new SimpleIntegerProperty(0); private IntGameRule(List ruleKey, String displayName, int value) { - this.ruleKey = ruleKey; - this.displayName = displayName; + this.setRuleKey(ruleKey); + this.setDisplayName(displayName); this.value.set(value); } @@ -72,10 +138,34 @@ private IntGameRule() { @Override public GameRule clone() { - IntGameRule intGameRule = new IntGameRule(ruleKey, displayName, value.getValue()); + IntGameRule intGameRule = new IntGameRule(getRuleKey(), getDisplayName(), value.getValue()); intGameRule.defaultValue.setValue(defaultValue.getValue()); return intGameRule; } + + public int getDefaultValue() { + return defaultValue.get(); + } + + public IntegerProperty defaultValueProperty() { + return defaultValue; + } + + private void setDefaultValue(int value) { + this.defaultValue.setValue(value); + } + + public int getValue() { + return value.get(); + } + + public IntegerProperty valueProperty() { + return value; + } + + public void setValue(int value) { + this.value.setValue(value); + } } static class GameRuleDeserializer implements JsonDeserializer { @@ -107,7 +197,7 @@ public GameRule deserialize(JsonElement jsonElement, Type type, JsonDeserializat } public static class GameRuleHolder { - public static Map gameRuleMap = new HashMap<>(); + private static final Map gameRuleMap = new HashMap<>(); static { List gameRules; @@ -125,11 +215,9 @@ public static class GameRuleHolder { gameRuleMap.put(s, gameRule); } } - - System.out.println("GameRuleMap: " + gameRuleMap); } - public static Map cloneGameRuleMap() { + private static Map cloneGameRuleMap() { Map newGameRuleMap = new HashMap<>(); gameRuleMap.forEach((key, gameRule) -> newGameRuleMap.put(key, gameRule.clone())); return newGameRuleMap; From 2d7c54ad8cc56cdea45fc7cd0a07e4b21ada82a9 Mon Sep 17 00:00:00 2001 From: mine_ Date: Sun, 7 Dec 2025 17:05:40 +0800 Subject: [PATCH 08/69] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/GameRulePage.java | 41 ++++--------------- .../org/jackhuang/hmcl/gamerule/GameRule.java | 4 +- .../resources/assets/gamerule/gamerule.json | 3 +- 3 files changed, 10 insertions(+), 38 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java index 75595643e9..a0fdee7a83 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java @@ -42,40 +42,13 @@ public GameRulePage(WorldManagePage worldManagePage) { this.worldManagePage = worldManagePage; ObservableList gameRuleList = FXCollections.observableArrayList(); //用于测试,目前还在写UI。 - gameRuleList.add(new GameRuleInfo("sneak_speed", "潜行前进速度", 2)); - gameRuleList.add(new GameRuleInfo("spawn_immediately", "死亡立即重生", false)); - gameRuleList.add(new GameRuleInfo("time_advance", "时间自然流逝", true)); - gameRuleList.add(new GameRuleInfo("max_health", "最大生命值", 30)); - gameRuleList.add(new GameRuleInfo("pvp_enabled", "启用玩家间对抗", false)); - gameRuleList.add(new GameRuleInfo("keep_inventory_on_death", "死亡后保留物品栏", true)); - gameRuleList.add(new GameRuleInfo("mob_spawning_enabled", "启用怪物生成", true)); - gameRuleList.add(new GameRuleInfo("day_night_cycle_speed", "昼夜交替速度", 1)); - gameRuleList.add(new GameRuleInfo("fall_damage_multiplier", "掉落伤害倍率", 1)); - gameRuleList.add(new GameRuleInfo("friendly_fire_enabled", "启用友军伤害", false)); - gameRuleList.add(new GameRuleInfo("health_regeneration", "启用生命值自然恢复", true)); - gameRuleList.add(new GameRuleInfo("explosions_destroy_blocks", "爆炸破坏方块", true)); - gameRuleList.add(new GameRuleInfo("show_coordinates", "显示玩家坐标", true)); - gameRuleList.add(new GameRuleInfo("crafting_enabled", "启用制作系统", true)); - gameRuleList.add(new GameRuleInfo("hunger_system_enabled", "启用饥饿系统", true)); - gameRuleList.add(new GameRuleInfo("xp_multiplier", "经验值获取倍率", 1)); - gameRuleList.add(new GameRuleInfo("max_players", "服务器最大玩家数", 10)); - gameRuleList.add(new GameRuleInfo("gravity_level", "重力等级", 10)); - gameRuleList.add(new GameRuleInfo("keep_xp_on_death", "死亡后保留经验值", false)); - gameRuleList.add(new GameRuleInfo("weather_cycle_enabled", "启用天气变化", true)); - gameRuleList.add(new GameRuleInfo("loot_drop_multiplier", "战利品掉落倍率", 1)); - gameRuleList.add(new GameRuleInfo("build_height_limit", "建筑高度限制", 256)); - gameRuleList.add(new GameRuleInfo("enable_flight", "允许玩家飞行", false)); - gameRuleList.add(new GameRuleInfo("mob_griefing", "怪物破坏环境", true)); - gameRuleList.add(new GameRuleInfo("resource_respawn_rate", "资源刷新速率", 100)); - gameRuleList.add(new GameRuleInfo("chat_enabled", "启用游戏内聊天", true)); - gameRuleList.add(new GameRuleInfo("max_mana", "最大法力值", 100)); - gameRuleList.add(new GameRuleInfo("mana_regeneration_rate", "法力恢复速度", 5)); - gameRuleList.add(new GameRuleInfo("stamina_consumption_rate", "耐力消耗速率", 1)); - gameRuleList.add(new GameRuleInfo("enable_portals", "启用传送门", true)); - gameRuleList.add(new GameRuleInfo("difficulty_level", "游戏难度等级", 2)); - gameRuleList.add(new GameRuleInfo("command_blocks_enabled", "启用命令方块", false)); - gameRuleList.add(new GameRuleInfo("structure_generation", "生成世界结构", true)); - gameRuleList.add(new GameRuleInfo("item_durability", "启用物品耐久度", true)); + gameRuleMap.forEach((s, gameRule) -> { + if (gameRule instanceof GameRule.BooleanGameRule booleanGameRule) { + gameRuleList.add(new GameRuleInfo(s, booleanGameRule.getDisplayName(), booleanGameRule.getValue())); + } else if (gameRule instanceof GameRule.IntGameRule intGameRule) { + gameRuleList.add(new GameRuleInfo(s, intGameRule.getDisplayName(), intGameRule.getValue())); + } + }); setItems(gameRuleList); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java index 40575ee655..df51002c6b 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java @@ -76,7 +76,7 @@ public String getDisplayName() { return displayName; } - static final class BooleanGameRule extends GameRule { + public static final class BooleanGameRule extends GameRule { private final BooleanProperty value = new SimpleBooleanProperty(false); private final BooleanProperty defaultValue = new SimpleBooleanProperty(false); @@ -122,7 +122,7 @@ public void setValue(boolean value) { } } - static final class IntGameRule extends GameRule { + public static final class IntGameRule extends GameRule { private final IntegerProperty value = new SimpleIntegerProperty(0); private final IntegerProperty defaultValue = new SimpleIntegerProperty(0); diff --git a/HMCLCore/src/main/resources/assets/gamerule/gamerule.json b/HMCLCore/src/main/resources/assets/gamerule/gamerule.json index f62b2eaf47..eda71bf89a 100644 --- a/HMCLCore/src/main/resources/assets/gamerule/gamerule.json +++ b/HMCLCore/src/main/resources/assets/gamerule/gamerule.json @@ -1,8 +1,7 @@ [ { "ruleKey" : [ - "doDaylightCycle", - "hello" + "doDaylightCycle" ], "displayName" : "是否有昼夜交替", "defaultValue": true From 990205fb344dd69a52c3d256009f80b563f3d146 Mon Sep 17 00:00:00 2001 From: mine_ Date: Sun, 7 Dec 2025 17:22:23 +0800 Subject: [PATCH 09/69] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/GameRulePage.java | 5 ++-- .../org/jackhuang/hmcl/gamerule/GameRule.java | 24 +++++++++---------- .../resources/assets/gamerule/gamerule.json | 6 +++-- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java index a0fdee7a83..16dba9ba75 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java @@ -41,12 +41,11 @@ public class GameRulePage extends ListPageBase { public GameRulePage(WorldManagePage worldManagePage) { this.worldManagePage = worldManagePage; ObservableList gameRuleList = FXCollections.observableArrayList(); - //用于测试,目前还在写UI。 gameRuleMap.forEach((s, gameRule) -> { if (gameRule instanceof GameRule.BooleanGameRule booleanGameRule) { - gameRuleList.add(new GameRuleInfo(s, booleanGameRule.getDisplayName(), booleanGameRule.getValue())); + gameRuleList.add(new GameRuleInfo(s, i18n(booleanGameRule.getDisplayI18nKey()), booleanGameRule.getValue())); } else if (gameRule instanceof GameRule.IntGameRule intGameRule) { - gameRuleList.add(new GameRuleInfo(s, intGameRule.getDisplayName(), intGameRule.getValue())); + gameRuleList.add(new GameRuleInfo(s, i18n(intGameRule.getDisplayI18nKey()), intGameRule.getValue())); } }); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java index df51002c6b..22fcb79233 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java @@ -22,7 +22,7 @@ public sealed abstract class GameRule permits GameRule.BooleanGameRule, GameRule.IntGameRule { private List ruleKey; - private String displayName = ""; + private String displayI18nKey = ""; public static GameRule createGameRule(ArrayList ruleKey, String displayName, boolean value) { return new BooleanGameRule(ruleKey, displayName, value); @@ -68,21 +68,21 @@ public List getRuleKey() { return ruleKey; } - public void setDisplayName(String displayName) { - this.displayName = displayName; + public void setDisplayI18nKey(String displayI18nKey) { + this.displayI18nKey = displayI18nKey; } - public String getDisplayName() { - return displayName; + public String getDisplayI18nKey() { + return displayI18nKey; } public static final class BooleanGameRule extends GameRule { private final BooleanProperty value = new SimpleBooleanProperty(false); private final BooleanProperty defaultValue = new SimpleBooleanProperty(false); - private BooleanGameRule(List ruleKey, String displayName, boolean value) { + private BooleanGameRule(List ruleKey, String displayI18nKey, boolean value) { this.setRuleKey(ruleKey); - this.setDisplayName(displayName); + this.setDisplayI18nKey(displayI18nKey); this.value.set(value); } @@ -92,7 +92,7 @@ private BooleanGameRule() { @Override public GameRule clone() { - BooleanGameRule booleanGameRule = new BooleanGameRule(getRuleKey(), getDisplayName(), value.getValue()); + BooleanGameRule booleanGameRule = new BooleanGameRule(getRuleKey(), getDisplayI18nKey(), value.getValue()); booleanGameRule.defaultValue.setValue(defaultValue.getValue()); return booleanGameRule; } @@ -126,9 +126,9 @@ public static final class IntGameRule extends GameRule { private final IntegerProperty value = new SimpleIntegerProperty(0); private final IntegerProperty defaultValue = new SimpleIntegerProperty(0); - private IntGameRule(List ruleKey, String displayName, int value) { + private IntGameRule(List ruleKey, String displayI18nKey, int value) { this.setRuleKey(ruleKey); - this.setDisplayName(displayName); + this.setDisplayI18nKey(displayI18nKey); this.value.set(value); } @@ -138,7 +138,7 @@ private IntGameRule() { @Override public GameRule clone() { - IntGameRule intGameRule = new IntGameRule(getRuleKey(), getDisplayName(), value.getValue()); + IntGameRule intGameRule = new IntGameRule(getRuleKey(), getDisplayI18nKey(), value.getValue()); intGameRule.defaultValue.setValue(defaultValue.getValue()); return intGameRule; } @@ -187,7 +187,7 @@ public GameRule deserialize(JsonElement jsonElement, Type type, JsonDeserializat booleanGameRule.defaultValue.setValue(jsonObject.get("defaultValue").getAsBoolean()); } - gameRule.displayName = jsonObject.get("displayName").getAsString(); + gameRule.displayI18nKey = jsonObject.get("displayI18nKey").getAsString(); JsonElement ruleKeyElement = jsonObject.get("ruleKey"); Type listType = JsonUtils.listTypeOf(String.class).getType(); diff --git a/HMCLCore/src/main/resources/assets/gamerule/gamerule.json b/HMCLCore/src/main/resources/assets/gamerule/gamerule.json index eda71bf89a..76171ecf34 100644 --- a/HMCLCore/src/main/resources/assets/gamerule/gamerule.json +++ b/HMCLCore/src/main/resources/assets/gamerule/gamerule.json @@ -1,16 +1,18 @@ [ { "ruleKey" : [ + "advance_time", "doDaylightCycle" ], - "displayName" : "是否有昼夜交替", + "displayI18nKey" : "gamerule.advance_time", "defaultValue": true }, { "ruleKey" : [ + "random_tick_speed", "randomTickSpeed" ], - "displayName" : "随机刻速率", + "displayI18nKey" : "gamerule.random_tick_speed", "defaultValue": 3 } ] \ No newline at end of file From d521d55cf34b73357a23e07ba02beb8e3ec0d0ff Mon Sep 17 00:00:00 2001 From: mine_ Date: Sun, 7 Dec 2025 21:18:38 +0800 Subject: [PATCH 10/69] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E6=B7=B7?= =?UTF-8?q?=E5=90=88=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/GameRulePage.java | 93 +++- .../org/jackhuang/hmcl/gamerule/GameRule.java | 27 +- .../resources/assets/gamerule/gamerule.json | 465 +++++++++++++++++- 3 files changed, 566 insertions(+), 19 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java index 16dba9ba75..d3ba81270a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java @@ -1,5 +1,8 @@ package org.jackhuang.hmcl.ui.versions; +import com.github.steveice10.opennbt.tag.builtin.ByteTag; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.IntTag; import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXListView; import com.jfoenix.controls.JFXTextField; @@ -17,7 +20,10 @@ import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; import javafx.scene.layout.*; +import org.jackhuang.hmcl.game.World; import org.jackhuang.hmcl.gamerule.GameRule; +import org.jackhuang.hmcl.task.Schedulers; +import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.ListPageBase; import org.jackhuang.hmcl.ui.SVG; @@ -27,29 +33,96 @@ import org.jackhuang.hmcl.ui.construct.SpinnerPane; import org.jackhuang.hmcl.util.Holder; +import java.io.IOException; +import java.nio.file.Files; +import java.util.HashMap; import java.util.Map; import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton2; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; +import static org.jackhuang.hmcl.util.logging.Logger.LOG; public class GameRulePage extends ListPageBase { - WorldManagePage worldManagePage; + private WorldManagePage worldManagePage; + private World world; + private CompoundTag levelDat; Map gameRuleMap = GameRule.getCloneGameRuleMap(); + Map gameRuleFinalMap = new HashMap<>(); + + ObservableList gameRuleList; public GameRulePage(WorldManagePage worldManagePage) { this.worldManagePage = worldManagePage; - ObservableList gameRuleList = FXCollections.observableArrayList(); - gameRuleMap.forEach((s, gameRule) -> { + this.world = worldManagePage.getWorld(); + + gameRuleList = FXCollections.observableArrayList(); + setItems(gameRuleList); + + this.setLoading(true); + Task.supplyAsync(this::loadWorldInfo) + .whenComplete(Schedulers.javafx(), ((result, exception) -> { + if (exception == null) { + this.levelDat = result; + updateControls(); + setLoading(false); + } else { + LOG.warning("Failed to load level.dat", exception); + setFailedReason(i18n("world.info.failed")); + } + })).start(); + } + + public void updateControls() { + boolean isNewGameRuleFormat; + + CompoundTag dataTag = levelDat.get("Data"); + CompoundTag gameRuleCompoundTag = dataTag.get("game_rules"); + if (gameRuleCompoundTag == null) { + gameRuleCompoundTag = dataTag.get("GameRules"); + isNewGameRuleFormat = false; + } else { + isNewGameRuleFormat = true; + } + if (isNewGameRuleFormat) { + gameRuleCompoundTag.iterator().forEachRemaining(gameRuleTag -> { + LOG.trace(gameRuleTag.toString()); + if (gameRuleTag instanceof IntTag intTag) { + GameRule finalGameRule = GameRule.createSimpleGameRule(intTag.getName(), intTag.getValue()); + gameRuleMap.forEach((key, itGameRule) -> { + if (gameRuleTag.getName().equals(key) && itGameRule instanceof GameRule.IntGameRule intGameRule) { + gameRuleFinalMap.put(key, GameRule.mixGameRule(finalGameRule, intGameRule)); + LOG.trace("find one: " + finalGameRule.getRuleKey() + ", intTag is " + intTag.getValue()); + } + }); + } else if (gameRuleTag instanceof ByteTag byteTag) { + GameRule finalGameRule = GameRule.createSimpleGameRule(byteTag.getName(), byteTag.getValue() == 1); + gameRuleMap.forEach((key, itGameRule) -> { + if (gameRuleTag.getName().equals(key) && itGameRule instanceof GameRule.BooleanGameRule booleanGameRule) { + gameRuleFinalMap.put(key, GameRule.mixGameRule(finalGameRule, booleanGameRule)); + LOG.trace("find one: " + finalGameRule.getRuleKey() + ", byteTag is " + byteTag.getValue()); + } + }); + } + }); + } + + LOG.trace("gameRuleFinalMap: size" + gameRuleFinalMap.size() + ", detail: " + gameRuleFinalMap.toString()); + gameRuleFinalMap.forEach((s, gameRule) -> { + String displayText; + try { + displayText = i18n(gameRule.getDisplayI18nKey()); + } catch (Exception e) { + displayText = gameRule.getDisplayI18nKey(); + } if (gameRule instanceof GameRule.BooleanGameRule booleanGameRule) { - gameRuleList.add(new GameRuleInfo(s, i18n(booleanGameRule.getDisplayI18nKey()), booleanGameRule.getValue())); + gameRuleList.add(new GameRuleInfo(s, displayText, booleanGameRule.getValue())); } else if (gameRule instanceof GameRule.IntGameRule intGameRule) { - gameRuleList.add(new GameRuleInfo(s, i18n(intGameRule.getDisplayI18nKey()), intGameRule.getValue())); + gameRuleList.add(new GameRuleInfo(s, displayText, intGameRule.getValue())); } }); - setItems(gameRuleList); } @Override @@ -168,4 +241,12 @@ protected void updateControl(GameRuleInfo item, boolean empty) { enum GameRuleType { INT, BOOLEAN } + + + private CompoundTag loadWorldInfo() throws IOException { + if (!Files.isDirectory(world.getFile())) + throw new IOException("Not a valid world directory"); + + return world.readLevelDat(); + } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java index 22fcb79233..3a7cd349a2 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java @@ -12,10 +12,7 @@ import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; @JsonSerializable @JsonAdapter(GameRule.GameRuleDeserializer.class) @@ -32,6 +29,28 @@ public static GameRule createGameRule(ArrayList ruleKey, String displayN return new IntGameRule(ruleKey, displayName, value); } + public static GameRule createSimpleGameRule(String ruleKey, boolean value) { + return new BooleanGameRule(Collections.singletonList(ruleKey), "", value); + } + + public static GameRule createSimpleGameRule(String ruleKey, int value) { + return new IntGameRule(Collections.singletonList(ruleKey), "", value); + } + + public static GameRule mixGameRule(GameRule simpleGameRule, GameRule mapGameRule) { + if (simpleGameRule instanceof BooleanGameRule simplpBooleanGameRule && mapGameRule instanceof BooleanGameRule mixBooleanGameRule) { + simplpBooleanGameRule.setDisplayI18nKey(mixBooleanGameRule.getDisplayI18nKey()); + simplpBooleanGameRule.setDefaultValue(mixBooleanGameRule.getDefaultValue()); + return simplpBooleanGameRule; + } else if (simpleGameRule instanceof IntGameRule simplpIntGameRule && simpleGameRule instanceof IntGameRule mixIntGameRule) { + simplpIntGameRule.setDisplayI18nKey(mixIntGameRule.getDisplayI18nKey()); + simplpIntGameRule.setDefaultValue(mixIntGameRule.getDefaultValue()); + return simplpIntGameRule; + } else { + return simpleGameRule; + } + } + public static Map addSimpleGameRule(Map gameRuleMap, String ruleKey, String displayName, boolean value) { ArrayList ruleKeyList = new ArrayList<>(); ruleKeyList.add(ruleKey); diff --git a/HMCLCore/src/main/resources/assets/gamerule/gamerule.json b/HMCLCore/src/main/resources/assets/gamerule/gamerule.json index 76171ecf34..4ff6d23c53 100644 --- a/HMCLCore/src/main/resources/assets/gamerule/gamerule.json +++ b/HMCLCore/src/main/resources/assets/gamerule/gamerule.json @@ -1,18 +1,465 @@ [ { - "ruleKey" : [ - "advance_time", - "doDaylightCycle" + "ruleKey": [ + "minecraft:advance_time", + "minecraft:doDaylightCycle" ], - "displayI18nKey" : "gamerule.advance_time", + "displayI18nKey": "gamerule.advance_time", "defaultValue": true }, { - "ruleKey" : [ - "random_tick_speed", - "randomTickSpeed" + "ruleKey": [ + "minecraft:advance_weather", + "minecraft:doWeatherCycle" ], - "displayI18nKey" : "gamerule.random_tick_speed", + "displayI18nKey": "gamerule.advance_weather", + "defaultValue": true + }, + { + "ruleKey": [ + "minecraft:allowFireTicksAwayFromPlayer" + ], + "displayI18nKey": "gamerule.allow_fire_ticks_away_from_player", + "defaultValue": false + }, + { + "ruleKey": [ + "minecraft:allow_entering_nether_using_portals" + ], + "displayI18nKey": "gamerule.allow_entering_nether_using_portals", + "defaultValue": true + }, + { + "ruleKey": [ + "minecraft:block_drops", + "minecraft:doTileDrops" + ], + "displayI18nKey": "gamerule.block_drops", + "defaultValue": true + }, + { + "ruleKey": [ + "minecraft:block_explosion_drop_decay" + ], + "displayI18nKey": "gamerule.block_explosion_drop_decay", + "defaultValue": true + }, + { + "ruleKey": [ + "minecraft:command_block_output", + "minecraft:commandBlockOutput" + ], + "displayI18nKey": "gamerule.command_block_output", + "defaultValue": true + }, + { + "ruleKey": [ + "minecraft:command_blocks_work", + "minecraft:commandBlocksEnabled" + ], + "displayI18nKey": "gamerule.command_blocks_work", + "defaultValue": true + }, + { + "ruleKey": [ + "minecraft:doFireTick" + ], + "displayI18nKey": "gamerule.do_fire_tick", + "defaultValue": true + }, + { + "ruleKey": [ + "minecraft:drowning_damage" + ], + "displayI18nKey": "gamerule.drowning_damage", + "defaultValue": true + }, + { + "ruleKey": [ + "minecraft:elytra_movement_check", + "minecraft:disableElytraMovementCheck" + ], + "displayI18nKey": "gamerule.elytra_movement_check", + "defaultValue": true + }, + { + "ruleKey": [ + "minecraft:ender_pearls_vanish_on_death" + ], + "displayI18nKey": "gamerule.ender_pearls_vanish_on_death", + "defaultValue": true + }, + { + "ruleKey": [ + "minecraft:entity_drops", + "minecraft:doEntityDrops" + ], + "displayI18nKey": "gamerule.entity_drops", + "defaultValue": true + }, + { + "ruleKey": [ + "minecraft:fall_damage" + ], + "displayI18nKey": "gamerule.fall_damage", + "defaultValue": true + }, + { + "ruleKey": [ + "minecraft:fire_damage" + ], + "displayI18nKey": "gamerule.fire_damage", + "defaultValue": true + }, + { + "ruleKey": [ + "minecraft:fire_spread_radius_around_player" + ], + "displayI18nKey": "gamerule.fire_spread_radius_around_player", + "defaultValue": 128 + }, + { + "ruleKey": [ + "minecraft:forgive_dead_players" + ], + "displayI18nKey": "gamerule.forgive_dead_players", + "defaultValue": true + }, + { + "ruleKey": [ + "minecraft:freeze_damage" + ], + "displayI18nKey": "gamerule.freeze_damage", + "defaultValue": true + }, + { + "ruleKey": [ + "minecraft:global_sound_events" + ], + "displayI18nKey": "gamerule.global_sound_events", + "defaultValue": true + }, + { + "ruleKey": [ + "minecraft:immediate_respawn", + "minecraft:doImmediateRespawn" + ], + "displayI18nKey": "gamerule.immediate_respawn", + "defaultValue": false + }, + { + "ruleKey": [ + "minecraft:keep_inventory", + "minecraft:keepInventory" + ], + "displayI18nKey": "gamerule.keep_inventory", + "defaultValue": false + }, + { + "ruleKey": [ + "minecraft:lava_source_conversion" + ], + "displayI18nKey": "gamerule.lava_source_conversion", + "defaultValue": false + }, + { + "ruleKey": [ + "minecraft:limited_crafting", + "minecraft:doLimitedCrafting" + ], + "displayI18nKey": "gamerule.limited_crafting", + "defaultValue": false + }, + { + "ruleKey": [ + "minecraft:locator_bar" + ], + "displayI18nKey": "gamerule.locator_bar", + "defaultValue": true + }, + { + "ruleKey": [ + "minecraft:log_admin_commands", + "minecraft:logAdminCommands" + ], + "displayI18nKey": "gamerule.log_admin_commands", + "defaultValue": true + }, + { + "ruleKey": [ + "minecraft:max_block_modifications", + "minecraft:commandModificationBlockLimit" + ], + "displayI18nKey": "gamerule.max_block_modifications", + "defaultValue": 32768 + }, + { + "ruleKey": [ + "minecraft:max_command_forks", + "minecraft:maxCommandForkCount" + ], + "displayI18nKey": "gamerule.max_command_forks", + "defaultValue": 65536 + }, + { + "ruleKey": [ + "minecraft:max_command_sequence_length", + "minecraft:maxCommandChainLength" + ], + "displayI18nKey": "gamerule.max_command_sequence_length", + "defaultValue": 65536 + }, + { + "ruleKey": [ + "minecraft:max_entity_cramming", + "minecraft:maxEntityCramming" + ], + "displayI18nKey": "gamerule.max_entity_cramming", + "defaultValue": 24 + }, + { + "ruleKey": [ + "minecraft:max_minecart_speed", + "minecraft:minecartMaxSpeed" + ], + "displayI18nKey": "gamerule.max_minecart_speed", + "defaultValue": 8 + }, + { + "ruleKey": [ + "minecraft:max_snow_accumulation_height", + "minecraft:snowAccumulationHeight" + ], + "displayI18nKey": "gamerule.max_snow_accumulation_height", + "defaultValue": 1 + }, + { + "ruleKey": [ + "minecraft:mob_drops", + "minecraft:doMobLoot" + ], + "displayI18nKey": "gamerule.mob_drops", + "defaultValue": true + }, + { + "ruleKey": [ + "minecraft:mob_explosion_drop_decay" + ], + "displayI18nKey": "gamerule.mob_explosion_drop_decay", + "defaultValue": true + }, + { + "ruleKey": [ + "minecraft:mob_griefing", + "minecraft:mobGriefing" + ], + "displayI18nKey": "gamerule.mob_griefing", + "defaultValue": true + }, + { + "ruleKey": [ + "minecraft:natural_health_regeneration", + "minecraft:naturalRegeneration" + ], + "displayI18nKey": "gamerule.natural_health_regeneration", + "defaultValue": true + }, + { + "ruleKey": [ + "minecraft:player_movement_check", + "minecraft:disablePlayerMovementCheck" + ], + "displayI18nKey": "gamerule.player_movement_check", + "defaultValue": true + }, + { + "ruleKey": [ + "minecraft:players_nether_portal_creative_delay" + ], + "displayI18nKey": "gamerule.players_nether_portal_creative_delay", + "defaultValue": 0 + }, + { + "ruleKey": [ + "minecraft:players_nether_portal_default_delay" + ], + "displayI18nKey": "gamerule.players_nether_portal_default_delay", + "defaultValue": 80 + }, + { + "ruleKey": [ + "minecraft:players_sleeping_percentage" + ], + "displayI18nKey": "gamerule.players_sleeping_percentage", + "defaultValue": 100 + }, + { + "ruleKey": [ + "minecraft:projectiles_can_break_blocks" + ], + "displayI18nKey": "gamerule.projectiles_can_break_blocks", + "defaultValue": true + }, + { + "ruleKey": [ + "minecraft:pvp" + ], + "displayI18nKey": "gamerule.pvp", + "defaultValue": true + }, + { + "ruleKey": [ + "minecraft:raids", + "minecraft:disableRaids" + ], + "displayI18nKey": "gamerule.raids", + "defaultValue": true + }, + { + "ruleKey": [ + "minecraft:random_tick_speed", + "minecraft:randomTickSpeed" + ], + "displayI18nKey": "gamerule.random_tick_speed", "defaultValue": 3 + }, + { + "ruleKey": [ + "minecraft:reduced_debug_info", + "minecraft:reducedDebugInfo" + ], + "displayI18nKey": "gamerule.reduced_debug_info", + "defaultValue": false + }, + { + "ruleKey": [ + "minecraft:respawn_radius", + "minecraft:spawnRadius" + ], + "displayI18nKey": "gamerule.respawn_radius", + "defaultValue": 10 + }, + { + "ruleKey": [ + "minecraft:send_command_feedback", + "minecraft:sendCommandFeedback" + ], + "displayI18nKey": "gamerule.send_command_feedback", + "defaultValue": true + }, + { + "ruleKey": [ + "minecraft:show_advancement_messages", + "minecraft:announceAdvancements" + ], + "displayI18nKey": "gamerule.show_advancement_messages", + "defaultValue": true + }, + { + "ruleKey": [ + "minecraft:show_death_messages", + "minecraft:showDeathMessages" + ], + "displayI18nKey": "gamerule.show_death_messages", + "defaultValue": true + }, + { + "ruleKey": [ + "minecraft:spawn_mobs", + "minecraft:doMobSpawning" + ], + "displayI18nKey": "gamerule.spawn_mobs", + "defaultValue": true + }, + { + "ruleKey": [ + "minecraft:spawn_monsters" + ], + "displayI18nKey": "gamerule.spawn_monsters", + "defaultValue": true + }, + { + "ruleKey": [ + "minecraft:spawn_patrols", + "minecraft:doPatrolSpawning" + ], + "displayI18nKey": "gamerule.spawn_patrols", + "defaultValue": true + }, + { + "ruleKey": [ + "minecraft:spawn_phantoms", + "minecraft:doInsomnia" + ], + "displayI18nKey": "gamerule.spawn_phantoms", + "defaultValue": true + }, + { + "ruleKey": [ + "minecraft:spawn_wandering_traders", + "minecraft:doTraderSpawning" + ], + "displayI18nKey": "gamerule.spawn_wandering_traders", + "defaultValue": true + }, + { + "ruleKey": [ + "minecraft:spawn_wardens", + "minecraft:doWardenSpawning" + ], + "displayI18nKey": "gamerule.spawn_wardens", + "defaultValue": true + }, + { + "ruleKey": [ + "minecraft:spawner_blocks_work", + "minecraft:spawnerBlocksEnabled" + ], + "displayI18nKey": "gamerule.spawner_blocks_work", + "defaultValue": true + }, + { + "ruleKey": [ + "minecraft:spectators_generate_chunks" + ], + "displayI18nKey": "gamerule.spectators_generate_chunks", + "defaultValue": true + }, + { + "ruleKey": [ + "minecraft:spread_vines", + "minecraft:doVinesSpread" + ], + "displayI18nKey": "gamerule.spread_vines", + "defaultValue": true + }, + { + "ruleKey": [ + "minecraft:tnt_explodes" + ], + "displayI18nKey": "gamerule.tnt_explodes", + "defaultValue": true + }, + { + "ruleKey": [ + "minecraft:tnt_explosion_drop_decay" + ], + "displayI18nKey": "gamerule.tnt_explosion_drop_decay", + "defaultValue": false + }, + { + "ruleKey": [ + "minecraft:universal_anger", + "minecraft:universalAnger" + ], + "displayI18nKey": "gamerule.universal_anger", + "defaultValue": false + }, + { + "ruleKey": [ + "minecraft:water_source_conversion" + ], + "displayI18nKey": "gamerule.water_source_conversion", + "defaultValue": true } -] \ No newline at end of file +] From 96f3b06c79a35dd9043b3e0e6e9c276f4ad9a27b Mon Sep 17 00:00:00 2001 From: mine_ Date: Sun, 7 Dec 2025 21:34:07 +0800 Subject: [PATCH 11/69] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/jackhuang/hmcl/gamerule/GameRule.java | 65 +++++++------------ 1 file changed, 22 insertions(+), 43 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java index 3a7cd349a2..c5aac838b1 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java @@ -21,14 +21,6 @@ public sealed abstract class GameRule permits GameRule.BooleanGameRule, GameRule private List ruleKey; private String displayI18nKey = ""; - public static GameRule createGameRule(ArrayList ruleKey, String displayName, boolean value) { - return new BooleanGameRule(ruleKey, displayName, value); - } - - public static GameRule createGameRule(ArrayList ruleKey, String displayName, int value) { - return new IntGameRule(ruleKey, displayName, value); - } - public static GameRule createSimpleGameRule(String ruleKey, boolean value) { return new BooleanGameRule(Collections.singletonList(ruleKey), "", value); } @@ -51,27 +43,14 @@ public static GameRule mixGameRule(GameRule simpleGameRule, GameRule mapGameRule } } - public static Map addSimpleGameRule(Map gameRuleMap, String ruleKey, String displayName, boolean value) { - ArrayList ruleKeyList = new ArrayList<>(); - ruleKeyList.add(ruleKey); - gameRuleMap.put(ruleKey, new BooleanGameRule(ruleKeyList, displayName, value)); - return gameRuleMap; + public static void addSimpleGameRule(Map gameRuleMap, String ruleKey, String displayName, boolean value) { + gameRuleMap.put(ruleKey, new BooleanGameRule(Collections.singletonList(ruleKey), displayName, value)); } - public static Map addSimpleGameRule(Map gameRuleMap, String ruleKey, String displayName, int value) { - ArrayList ruleKeyList = new ArrayList<>(); - ruleKeyList.add(ruleKey); - gameRuleMap.put(ruleKey, new IntGameRule(ruleKeyList, displayName, value)); - return gameRuleMap; + public static void addSimpleGameRule(Map gameRuleMap, String ruleKey, String displayName, int value) { + gameRuleMap.put(ruleKey, new IntGameRule(Collections.singletonList(ruleKey), displayName, value)); } - public static Map addSimpleGameRule(Map gameRuleMap, String ruleKey, boolean value) { - return addSimpleGameRule(gameRuleMap, ruleKey, "", value); - } - - public static Map addSimpleGameRule(Map gameRuleMap, String ruleKey, int value) { - return addSimpleGameRule(gameRuleMap, ruleKey, "", value); - } public static Map getCloneGameRuleMap() { return GameRule.GameRuleHolder.cloneGameRuleMap(); @@ -190,32 +169,32 @@ public void setValue(int value) { static class GameRuleDeserializer implements JsonDeserializer { @Override - public GameRule deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { + public GameRule deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext context) throws JsonParseException { JsonObject jsonObject = jsonElement.getAsJsonObject(); - GameRule gameRule; - if (jsonObject.get("defaultValue") instanceof JsonPrimitive jsonPrimitive && jsonPrimitive.isNumber()) { - gameRule = new IntGameRule(); - } else { - gameRule = new BooleanGameRule(); - } - - if (gameRule instanceof IntGameRule intGameRule) { - intGameRule.defaultValue.setValue(jsonObject.get("defaultValue").getAsInt()); - } else { - BooleanGameRule booleanGameRule = (BooleanGameRule) gameRule; - booleanGameRule.defaultValue.setValue(jsonObject.get("defaultValue").getAsBoolean()); - } + GameRule gameRule = createRuleFromDefaultValue(jsonObject.get("defaultValue")); gameRule.displayI18nKey = jsonObject.get("displayI18nKey").getAsString(); - JsonElement ruleKeyElement = jsonObject.get("ruleKey"); Type listType = JsonUtils.listTypeOf(String.class).getType(); - gameRule.ruleKey = jsonDeserializationContext.deserialize(ruleKeyElement, listType); + gameRule.ruleKey = context.deserialize(jsonObject.get("ruleKey"), listType); + return gameRule; } + + private GameRule createRuleFromDefaultValue(JsonElement defaultValueElement) { + if (defaultValueElement instanceof JsonPrimitive p && p.isNumber()) { + IntGameRule rule = new IntGameRule(); + rule.setDefaultValue(defaultValueElement.getAsInt()); + return rule; + } else { + BooleanGameRule rule = new BooleanGameRule(); + rule.setDefaultValue(defaultValueElement.getAsBoolean()); + return rule; + } + } } - public static class GameRuleHolder { + static final class GameRuleHolder { private static final Map gameRuleMap = new HashMap<>(); static { @@ -227,7 +206,7 @@ public static class GameRuleHolder { String jsonContent = new String(is.readAllBytes(), java.nio.charset.StandardCharsets.UTF_8); gameRules = JsonUtils.fromNonNullJson(jsonContent, JsonUtils.listTypeOf(GameRule.class)); } catch (IOException e) { - throw new RuntimeException(e); + throw new RuntimeException("Failed to initialize GameRuleHolder", e); } for (GameRule gameRule : gameRules) { for (String s : gameRule.ruleKey) { From 352d142f10964f9e0bfe00f4c47aea69cfc355f3 Mon Sep 17 00:00:00 2001 From: mine_ Date: Sun, 7 Dec 2025 21:41:13 +0800 Subject: [PATCH 12/69] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/jackhuang/hmcl/gamerule/GameRule.java | 45 ++++++++++++------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java index c5aac838b1..4a7b8da108 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java @@ -21,6 +21,14 @@ public sealed abstract class GameRule permits GameRule.BooleanGameRule, GameRule private List ruleKey; private String displayI18nKey = ""; + protected GameRule() { + } + + protected GameRule(List ruleKey, String displayI18nKey) { + this.ruleKey = ruleKey; + this.displayI18nKey = displayI18nKey; + } + public static GameRule createSimpleGameRule(String ruleKey, boolean value) { return new BooleanGameRule(Collections.singletonList(ruleKey), "", value); } @@ -30,17 +38,8 @@ public static GameRule createSimpleGameRule(String ruleKey, int value) { } public static GameRule mixGameRule(GameRule simpleGameRule, GameRule mapGameRule) { - if (simpleGameRule instanceof BooleanGameRule simplpBooleanGameRule && mapGameRule instanceof BooleanGameRule mixBooleanGameRule) { - simplpBooleanGameRule.setDisplayI18nKey(mixBooleanGameRule.getDisplayI18nKey()); - simplpBooleanGameRule.setDefaultValue(mixBooleanGameRule.getDefaultValue()); - return simplpBooleanGameRule; - } else if (simpleGameRule instanceof IntGameRule simplpIntGameRule && simpleGameRule instanceof IntGameRule mixIntGameRule) { - simplpIntGameRule.setDisplayI18nKey(mixIntGameRule.getDisplayI18nKey()); - simplpIntGameRule.setDefaultValue(mixIntGameRule.getDefaultValue()); - return simplpIntGameRule; - } else { - return simpleGameRule; - } + simpleGameRule.applyMetadata(mapGameRule); + return simpleGameRule; } public static void addSimpleGameRule(Map gameRuleMap, String ruleKey, String displayName, boolean value) { @@ -58,6 +57,8 @@ public static Map getCloneGameRuleMap() { public abstract GameRule clone(); + public abstract void applyMetadata(GameRule metadataSource); + public void setRuleKey(List ruleKey) { this.ruleKey = ruleKey; } @@ -79,8 +80,7 @@ public static final class BooleanGameRule extends GameRule { private final BooleanProperty defaultValue = new SimpleBooleanProperty(false); private BooleanGameRule(List ruleKey, String displayI18nKey, boolean value) { - this.setRuleKey(ruleKey); - this.setDisplayI18nKey(displayI18nKey); + super(ruleKey, displayI18nKey); this.value.set(value); } @@ -95,6 +95,14 @@ public GameRule clone() { return booleanGameRule; } + @Override + public void applyMetadata(GameRule metadataSource) { + if (metadataSource instanceof BooleanGameRule source) { + this.setDisplayI18nKey(source.getDisplayI18nKey()); + this.setDefaultValue(source.getDefaultValue()); + } + } + public boolean getDefaultValue() { return defaultValue.get(); } @@ -125,8 +133,7 @@ public static final class IntGameRule extends GameRule { private final IntegerProperty defaultValue = new SimpleIntegerProperty(0); private IntGameRule(List ruleKey, String displayI18nKey, int value) { - this.setRuleKey(ruleKey); - this.setDisplayI18nKey(displayI18nKey); + super(ruleKey, displayI18nKey); this.value.set(value); } @@ -141,6 +148,14 @@ public GameRule clone() { return intGameRule; } + @Override + public void applyMetadata(GameRule metadataSource) { + if (metadataSource instanceof IntGameRule source) { + this.setDisplayI18nKey(source.getDisplayI18nKey()); + this.setDefaultValue(source.getDefaultValue()); + } + } + public int getDefaultValue() { return defaultValue.get(); } From 7ab690f1addbb518d757236921a11ec05bc8ac51 Mon Sep 17 00:00:00 2001 From: mine_ Date: Sun, 7 Dec 2025 21:59:43 +0800 Subject: [PATCH 13/69] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0i18n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/GameRulePage.java | 31 +++++----- .../assets/lang/I18N_zh_CN.properties | 62 +++++++++++++++++++ .../org/jackhuang/hmcl/gamerule/GameRule.java | 7 ++- 3 files changed, 82 insertions(+), 18 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java index d3ba81270a..e4114a8669 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java @@ -78,32 +78,31 @@ public void updateControls() { boolean isNewGameRuleFormat; CompoundTag dataTag = levelDat.get("Data"); - CompoundTag gameRuleCompoundTag = dataTag.get("game_rules"); - if (gameRuleCompoundTag == null) { + CompoundTag gameRuleCompoundTag; + gameRuleCompoundTag = dataTag.get("game_rules"); + if (gameRuleCompoundTag != null) { + isNewGameRuleFormat = true; + } else { gameRuleCompoundTag = dataTag.get("GameRules"); isNewGameRuleFormat = false; - } else { - isNewGameRuleFormat = true; } if (isNewGameRuleFormat) { gameRuleCompoundTag.iterator().forEachRemaining(gameRuleTag -> { LOG.trace(gameRuleTag.toString()); if (gameRuleTag instanceof IntTag intTag) { GameRule finalGameRule = GameRule.createSimpleGameRule(intTag.getName(), intTag.getValue()); - gameRuleMap.forEach((key, itGameRule) -> { - if (gameRuleTag.getName().equals(key) && itGameRule instanceof GameRule.IntGameRule intGameRule) { - gameRuleFinalMap.put(key, GameRule.mixGameRule(finalGameRule, intGameRule)); - LOG.trace("find one: " + finalGameRule.getRuleKey() + ", intTag is " + intTag.getValue()); - } - }); + GameRule gr = gameRuleMap.getOrDefault(intTag.getName(), null); + if (gr instanceof GameRule.IntGameRule intGR) { + gameRuleFinalMap.put(intTag.getName(), GameRule.mixGameRule(finalGameRule, intGR)); + LOG.trace("find one: " + finalGameRule.getRuleKey() + ", intTag is " + intTag.getValue()); + } } else if (gameRuleTag instanceof ByteTag byteTag) { GameRule finalGameRule = GameRule.createSimpleGameRule(byteTag.getName(), byteTag.getValue() == 1); - gameRuleMap.forEach((key, itGameRule) -> { - if (gameRuleTag.getName().equals(key) && itGameRule instanceof GameRule.BooleanGameRule booleanGameRule) { - gameRuleFinalMap.put(key, GameRule.mixGameRule(finalGameRule, booleanGameRule)); - LOG.trace("find one: " + finalGameRule.getRuleKey() + ", byteTag is " + byteTag.getValue()); - } - }); + GameRule gr = gameRuleMap.getOrDefault(byteTag.getName(), null); + if (gr instanceof GameRule.BooleanGameRule booleanGR) { + gameRuleFinalMap.put(byteTag.getName(), GameRule.mixGameRule(finalGameRule, booleanGR)); + LOG.trace("find one: " + finalGameRule.getRuleKey() + ", byteTag is " + byteTag.getValue()); + } } }); } diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 0e2e57588f..509b208ff0 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -502,6 +502,68 @@ game.crash.title=游戏意外退出 game.directory=游戏文件夹路径 game.version=游戏实例 +gamerule.advance_time=游戏内时间流逝 +gamerule.advance_weather=天气更替 +gamerule.allow_fire_ticks_away_from_player=允许火在远离玩家处蔓延 +gamerule.allow_entering_nether_using_portals=允许进入下界 +gamerule.block_drops=方块掉落 +gamerule.block_explosion_drop_decay=在方块交互爆炸中,一些方块不会掉落战利品 +gamerule.command_block_output=广播命令方块输出 +gamerule.command_blocks_work=启用命令方块 +gamerule.do_fire_tick=火焰蔓延 +gamerule.drowning_damage=溺水伤害 +gamerule.elytra_movement_check=启用鞘翅移动检测 +gamerule.ender_pearls_vanish_on_death=掷出的末影珍珠在死亡时消失 +gamerule.entity_drops=非生物实体掉落 +gamerule.fall_damage=摔落伤害 +gamerule.fire_damage=火焰伤害 +gamerule.fire_spread_radius_around_player=火焰蔓延半径 +gamerule.forgive_dead_players=宽恕死亡玩家 +gamerule.freeze_damage=冰冻伤害 +gamerule.global_sound_events=全局声音事件 +gamerule.immediate_respawn=立即重生 +gamerule.keep_inventory=死亡后保留物品栏 +gamerule.lava_source_conversion=允许流动熔岩转化为熔岩源 +gamerule.limited_crafting=合成需要配方 +gamerule.locator_bar=启用玩家定位栏 +gamerule.log_admin_commands=通告管理员命令 +gamerule.max_block_modifications=命令修改方块数量限制 +gamerule.max_command_forks=命令上下文数量限制 +gamerule.max_command_sequence_length=命令连锁执行数量限制 +gamerule.max_entity_cramming=实体挤压上限 +gamerule.max_minecart_speed=矿车最大速度 +gamerule.max_snow_accumulation_height=积雪厚度 +gamerule.mob_drops=生物战利品掉落 +gamerule.mob_explosion_drop_decay=在生物爆炸中,一些方块不会掉落战利品 +gamerule.mob_griefing=允许破坏性生物行为 +gamerule.natural_health_regeneration=生命值自然恢复 +gamerule.player_movement_check=启用玩家移动检测 +gamerule.players_nether_portal_creative_delay=创造模式下玩家在下界传送门中等待的时间 +gamerule.players_nether_portal_default_delay=非创造模式下玩家在下界传送门中等待的时间 +gamerule.players_sleeping_percentage=入睡占比 +gamerule.projectiles_can_break_blocks=弹射物能否破坏方块 +gamerule.pvp=启用PvP +gamerule.raids=启用袭击 +gamerule.random_tick_speed=随机刻速率 +gamerule.reduced_debug_info=简化调试信息 +gamerule.respawn_radius=重生点半径 +gamerule.send_command_feedback=发送命令反馈 +gamerule.show_advancement_messages=进度通知 +gamerule.show_death_messages=显示死亡消息 +gamerule.spawn_mobs=生成生物 +gamerule.spawn_monsters=生成怪物 +gamerule.spawn_patrols=生成灾厄巡逻队 +gamerule.spawn_phantoms=生成幻翼 +gamerule.spawn_wandering_traders=生成流浪商人 +gamerule.spawn_wardens=生成监守者 +gamerule.spawner_blocks_work=启用刷怪笼方块 +gamerule.spectators_generate_chunks=允许旁观者生成地形 +gamerule.spread_vines=藤蔓蔓延 +gamerule.tnt_explodes=允许TNT被点燃并爆炸 +gamerule.tnt_explosion_drop_decay=在TNT爆炸中,一些方块不会掉落战利品 +gamerule.universal_anger=无差别愤怒 +gamerule.water_source_conversion=允许流动水转化为水源 + help=帮助 help.doc=Hello Minecraft! Launcher 帮助文档 help.detail=可查阅数据包、整合包制作指南等内容 diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java index 4a7b8da108..cbbc355ccf 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java @@ -12,7 +12,10 @@ import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Type; -import java.util.*; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; @JsonSerializable @JsonAdapter(GameRule.GameRuleDeserializer.class) @@ -209,7 +212,7 @@ private GameRule createRuleFromDefaultValue(JsonElement defaultValueElement) { } } - static final class GameRuleHolder { + static final class GameRuleHolder { private static final Map gameRuleMap = new HashMap<>(); static { From 40bf910ef4bc0b47ae5226063ecf4d99774434fe Mon Sep 17 00:00:00 2001 From: mine_ Date: Sun, 7 Dec 2025 22:11:52 +0800 Subject: [PATCH 14/69] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0i18n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/assets/lang/I18N.properties | 62 +++++++++++++++++++ .../resources/assets/lang/I18N_zh.properties | 62 +++++++++++++++++++ 2 files changed, 124 insertions(+) diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 5fabd687ec..f15a9a0bfb 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -685,6 +685,68 @@ game.crash.title=Game Crashed game.directory=Game Path game.version=Game Instance +gamerule.advance_time=Advance Time +gamerule.advance_weather=Advance Weather +gamerule.allow_fire_ticks_away_from_player=Allow Fire Ticks Away From Player +gamerule.allow_entering_nether_using_portals=Allow Entering Nether Using Portals +gamerule.block_drops=Block Drops +gamerule.block_explosion_drop_decay=Block Explosion Drop Decay +gamerule.command_block_output=Command Block Output +gamerule.command_blocks_work=Enable Command Blocks +gamerule.do_fire_tick=Fire Tick +gamerule.drowning_damage=Drowning Damage +gamerule.elytra_movement_check=Elytra Movement Check +gamerule.ender_pearls_vanish_on_death=Ender Pearls Vanish on Death +gamerule.entity_drops=Entity Drops +gamerule.fall_damage=Fall Damage +gamerule.fire_damage=Fire Damage +gamerule.fire_spread_radius_around_player=Fire Spread Radius Around Player +gamerule.forgive_dead_players=Forgive Dead Players +gamerule.freeze_damage=Freeze Damage +gamerule.global_sound_events=Global Sound Events +gamerule.immediate_respawn=Immediate Respawn +gamerule.keep_inventory=Keep Inventory +gamerule.lava_source_conversion=Lava Source Conversion +gamerule.limited_crafting=Limited Crafting +gamerule.locator_bar=Enable Locator Bar +gamerule.log_admin_commands=Log Admin Commands +gamerule.max_block_modifications=Command Modification Block Limit +gamerule.max_command_forks=Max Command Fork Count +gamerule.max_command_sequence_length=Max Command Chain Length +gamerule.max_entity_cramming=Max Entity Cramming +gamerule.max_minecart_speed=Minecart Max Speed +gamerule.max_snow_accumulation_height=Snow Accumulation Height +gamerule.mob_drops=Mob Drops +gamerule.mob_explosion_drop_decay=Mob Explosion Drop Decay +gamerule.mob_griefing=Mob Griefing +gamerule.natural_health_regeneration=Natural Health Regeneration +gamerule.player_movement_check=Player Movement Check +gamerule.players_nether_portal_creative_delay=Players Nether Portal Creative Delay +gamerule.players_nether_portal_default_delay=Players Nether Portal Default Delay +gamerule.players_sleeping_percentage=Players Sleeping Percentage +gamerule.projectiles_can_break_blocks=Projectiles Can Break Blocks +gamerule.pvp=Enable PvP +gamerule.raids=Enable Raids +gamerule.random_tick_speed=Random Tick Speed +gamerule.reduced_debug_info=Reduced Debug Info +gamerule.respawn_radius=Respawn Radius +gamerule.send_command_feedback=Send Command Feedback +gamerule.show_advancement_messages=Show Advancement Messages +gamerule.show_death_messages=Show Death Messages +gamerule.spawn_mobs=Spawn Mobs +gamerule.spawn_monsters=Spawn Monsters +gamerule.spawn_patrols=Spawn Patrols +gamerule.spawn_phantoms=Spawn Phantoms +gamerule.spawn_wandering_traders=Spawn Wandering Traders +gamerule.spawn_wardens=Spawn Wardens +gamerule.spawner_blocks_work=Enable Spawner Blocks +gamerule.spectators_generate_chunks=Spectators Generate Chunks +gamerule.spread_vines=Spread Vines +gamerule.tnt_explodes=TNT Explodes +gamerule.tnt_explosion_drop_decay=TNT Explosion Drop Decay +gamerule.universal_anger=Universal Anger +gamerule.water_source_conversion=Water Source Conversion + help=Help help.doc=Hello Minecraft! Launcher Documentation help.detail=For datapack and modpack makers. diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 6ccc3dd27b..8d19207ff0 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -492,6 +492,68 @@ game.crash.title=遊戲意外退出 game.directory=遊戲目錄路徑 game.version=遊戲實例 +gamerule.advance_time=日夜交替 +gamerule.advance_weather=更新天氣 +gamerule.allow_fire_ticks_away_from_player=允許火在遠離玩家處蔓延 +gamerule.allow_entering_nether_using_portals=允許進入地獄 +gamerule.block_drops=掉落方塊 +gamerule.block_explosion_drop_decay=在與方塊互動的爆炸中,部分方塊不會掉落成戰利品 +gamerule.command_block_output=記錄指令方塊輸出 +gamerule.command_blocks_work=啟用指令方塊 +gamerule.do_fire_tick=更新火焰 +gamerule.drowning_damage=造成溺水傷害 +gamerule.elytra_movement_check=啟用鞘翅移動檢測 +gamerule.ender_pearls_vanish_on_death=拋出的終界珍珠在死亡時消失 +gamerule.entity_drops=掉落實體裝備 +gamerule.fall_damage=造成摔落傷害 +gamerule.fire_damage=造成火焰傷害 +gamerule.fire_spread_radius_around_player=火焰蔓延半徑 +gamerule.forgive_dead_players=原諒死者 +gamerule.freeze_damage=造成冰凍傷害 +gamerule.global_sound_events=全域聲音事件 +gamerule.immediate_respawn=立即重生 +gamerule.keep_inventory=死亡後保留物品欄 +gamerule.lava_source_conversion=流動熔岩轉化成熔岩源 +gamerule.limited_crafting=需要配方才能合成 +gamerule.locator_bar=啟用玩家定位條 +gamerule.log_admin_commands=記錄管理員指令 +gamerule.max_block_modifications=指令修改方塊數量限制 +gamerule.max_command_forks=指令的上下文上限 +gamerule.max_command_sequence_length=指令連鎖大小限制 +gamerule.max_entity_cramming=實體擠壓上限 +gamerule.max_minecart_speed=礦車最大速度 +gamerule.max_snow_accumulation_height=積雪厚度 +gamerule.mob_drops=掉落生物戰利品 +gamerule.mob_explosion_drop_decay=在生物的爆炸中,部分方塊不會掉落成戰利品 +gamerule.mob_griefing=允許生物的破壞行為 +gamerule.natural_health_regeneration=自然回血 +gamerule.player_movement_check=啟用玩家移動檢測 +gamerule.players_nether_portal_creative_delay=創造模式玩家使用地獄傳送門的等待時間 +gamerule.players_nether_portal_default_delay=非創造模式玩家使用地獄傳送門的等待時間 +gamerule.players_sleeping_percentage=睡眠比例 +gamerule.projectiles_can_break_blocks=投射物是否能破壞方塊 +gamerule.pvp=啟用 PvP +gamerule.raids=啟用突襲 +gamerule.random_tick_speed=隨機刻速率 +gamerule.reduced_debug_info=簡化除錯資訊 +gamerule.respawn_radius=重生點半徑 +gamerule.send_command_feedback=回傳指令回饋 +gamerule.show_advancement_messages=進度通知 +gamerule.show_death_messages=顯示死亡訊息 +gamerule.spawn_mobs=生成生物 +gamerule.spawn_monsters=生成怪物 +gamerule.spawn_patrols=生成掠奪者巡邏隊 +gamerule.spawn_phantoms=生成夜魅 +gamerule.spawn_wandering_traders=生成流浪商人 +gamerule.spawn_wardens=生成伏守者 +gamerule.spawner_blocks_work=啟用生怪磚 +gamerule.spectators_generate_chunks=允許旁觀者生成地形 +gamerule.spread_vines=藤蔓蔓延 +gamerule.tnt_explodes=允許 TNT 被點燃並爆炸 +gamerule.tnt_explosion_drop_decay=在 TNT 的爆炸中,部分方塊不會掉落成戰利品 +gamerule.universal_anger=無差別憤怒 +gamerule.water_source_conversion=流動水轉化成水源 + help=說明 help.doc=Hello Minecraft! Launcher 說明文件 help.detail=可查閱資料包、模組包製作教學等內容 From 4a7519c8ea37169ded3558e821d253bc44baa420 Mon Sep 17 00:00:00 2001 From: mine_ Date: Sun, 7 Dec 2025 22:16:49 +0800 Subject: [PATCH 15/69] fix style --- .../main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java | 1 - HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java | 1 - 2 files changed, 2 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java index e4114a8669..631d123cbd 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java @@ -241,7 +241,6 @@ enum GameRuleType { INT, BOOLEAN } - private CompoundTag loadWorldInfo() throws IOException { if (!Files.isDirectory(world.getFile())) throw new IOException("Not a valid world directory"); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java index cbbc355ccf..8adb054650 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java @@ -53,7 +53,6 @@ public static void addSimpleGameRule(Map gameRuleMap, String r gameRuleMap.put(ruleKey, new IntGameRule(Collections.singletonList(ruleKey), displayName, value)); } - public static Map getCloneGameRuleMap() { return GameRule.GameRuleHolder.cloneGameRuleMap(); } From 612855ce599afb912b3d89c7f54ba46b005c0435 Mon Sep 17 00:00:00 2001 From: mine_ Date: Mon, 8 Dec 2025 16:01:29 +0800 Subject: [PATCH 16/69] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0maxValue?= =?UTF-8?q?=E5=92=8CminValue=E7=9A=84=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/construct/NumberRangeValidator.java | 67 +++++++++++ .../hmcl/ui/versions/GameRulePage.java | 107 +++++++++++++----- .../resources/assets/lang/I18N.properties | 1 + .../resources/assets/lang/I18N_zh.properties | 1 + .../assets/lang/I18N_zh_CN.properties | 1 + .../org/jackhuang/hmcl/gamerule/GameRule.java | 58 +++++++++- .../resources/assets/gamerule/gamerule.json | 38 +++++-- 7 files changed, 228 insertions(+), 45 deletions(-) create mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/NumberRangeValidator.java diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/NumberRangeValidator.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/NumberRangeValidator.java new file mode 100644 index 0000000000..aa69f7e1ed --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/NumberRangeValidator.java @@ -0,0 +1,67 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2025 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.ui.construct; + +import com.jfoenix.validation.base.ValidatorBase; +import javafx.beans.NamedArg; +import javafx.scene.control.TextInputControl; +import org.jackhuang.hmcl.util.Lang; +import org.jackhuang.hmcl.util.StringUtils; + +public class NumberRangeValidator extends ValidatorBase { + private final String notNumberMessage; + private final String outOfLimitMessage; + private final boolean nullable; + private final int minValue; + private final int maxValue; + + public NumberRangeValidator(@NamedArg("notNumberMessage") String notNumberMessage, @NamedArg("outOfLimitMessage") String outOfLimitMessage, @NamedArg("minValue") int minValue, @NamedArg("maxValue") int maxValue, @NamedArg("nullable") boolean nullable) { + super(notNumberMessage); + this.notNumberMessage = notNumberMessage; + this.outOfLimitMessage = outOfLimitMessage; + this.nullable = nullable; + this.minValue = minValue; + this.maxValue = maxValue; + } + + @Override + protected void eval() { + if (srcControl.get() instanceof TextInputControl) { + evalTextInputField(); + } + } + + private void evalTextInputField() { + TextInputControl textField = ((TextInputControl) srcControl.get()); + + if (StringUtils.isBlank(textField.getText())) + hasErrors.set(!nullable); + else { + Integer intOrNull = Lang.toIntOrNull(textField.getText()); + if (intOrNull == null) { + setMessage(notNumberMessage); + hasErrors.set(true); + } else if (intOrNull > maxValue || intOrNull < minValue) { + setMessage(outOfLimitMessage); + hasErrors.set(true); + } else { + hasErrors.set(false); + } + } + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java index 631d123cbd..3155d2e8af 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java @@ -1,8 +1,26 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2025 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package org.jackhuang.hmcl.ui.versions; import com.github.steveice10.opennbt.tag.builtin.ByteTag; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.IntTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXListView; import com.jfoenix.controls.JFXTextField; @@ -27,15 +45,12 @@ import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.ListPageBase; import org.jackhuang.hmcl.ui.SVG; -import org.jackhuang.hmcl.ui.construct.ComponentList; -import org.jackhuang.hmcl.ui.construct.MDListCell; -import org.jackhuang.hmcl.ui.construct.OptionToggleButton; -import org.jackhuang.hmcl.ui.construct.SpinnerPane; +import org.jackhuang.hmcl.ui.construct.*; import org.jackhuang.hmcl.util.Holder; +import org.jackhuang.hmcl.util.Lang; import java.io.IOException; import java.nio.file.Files; -import java.util.HashMap; import java.util.Map; import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton2; @@ -49,7 +64,6 @@ public class GameRulePage extends ListPageBase { private CompoundTag levelDat; Map gameRuleMap = GameRule.getCloneGameRuleMap(); - Map gameRuleFinalMap = new HashMap<>(); ObservableList gameRuleList; @@ -88,40 +102,50 @@ public void updateControls() { } if (isNewGameRuleFormat) { gameRuleCompoundTag.iterator().forEachRemaining(gameRuleTag -> { - LOG.trace(gameRuleTag.toString()); + //LOG.trace(gameRuleTag.toString()); + GameRule finalGameRule; if (gameRuleTag instanceof IntTag intTag) { - GameRule finalGameRule = GameRule.createSimpleGameRule(intTag.getName(), intTag.getValue()); + finalGameRule = GameRule.createSimpleGameRule(intTag.getName(), intTag.getValue()); GameRule gr = gameRuleMap.getOrDefault(intTag.getName(), null); if (gr instanceof GameRule.IntGameRule intGR) { - gameRuleFinalMap.put(intTag.getName(), GameRule.mixGameRule(finalGameRule, intGR)); - LOG.trace("find one: " + finalGameRule.getRuleKey() + ", intTag is " + intTag.getValue()); + GameRule.mixGameRule(finalGameRule, intGR); + } + String displayText; + try { + displayText = i18n(finalGameRule.getDisplayI18nKey()); + } catch (Exception e) { + displayText = finalGameRule.getDisplayI18nKey(); + } + if (finalGameRule instanceof GameRule.IntGameRule intGameRule) { + LOG.trace("find one: " + finalGameRule.getRuleKey() + intGameRule.getValue() + "minValue: " + intGameRule.getMinValue() + ", maxValue" + intGameRule.getMaxValue() + ", intTag is " + intTag.getValue()); + gameRuleList.add(new GameRuleInfo(finalGameRule.getRuleKey().get(0), displayText, intGameRule.getValue(), intGameRule.getMinValue(), intGameRule.getMaxValue(), intTag)); } } else if (gameRuleTag instanceof ByteTag byteTag) { - GameRule finalGameRule = GameRule.createSimpleGameRule(byteTag.getName(), byteTag.getValue() == 1); + finalGameRule = GameRule.createSimpleGameRule(byteTag.getName(), byteTag.getValue() == 1); GameRule gr = gameRuleMap.getOrDefault(byteTag.getName(), null); if (gr instanceof GameRule.BooleanGameRule booleanGR) { - gameRuleFinalMap.put(byteTag.getName(), GameRule.mixGameRule(finalGameRule, booleanGR)); - LOG.trace("find one: " + finalGameRule.getRuleKey() + ", byteTag is " + byteTag.getValue()); + GameRule.mixGameRule(finalGameRule, booleanGR); + //LOG.trace("find one: " + finalGameRule.getRuleKey() + ", byteTag is " + byteTag.getValue()); + } + String displayText; + try { + displayText = i18n(finalGameRule.getDisplayI18nKey()); + } catch (Exception e) { + displayText = finalGameRule.getDisplayI18nKey(); } + if (finalGameRule instanceof GameRule.BooleanGameRule booleanGameRule) { + gameRuleList.add(new GameRuleInfo(finalGameRule.getRuleKey().get(0), displayText, booleanGameRule.getValue(), byteTag)); + } + } else { + return; } }); + } else { + gameRuleCompoundTag.iterator().forEachRemaining(gameRuleTag -> { + LOG.trace(gameRuleTag.toString()); + }); } - LOG.trace("gameRuleFinalMap: size" + gameRuleFinalMap.size() + ", detail: " + gameRuleFinalMap.toString()); - gameRuleFinalMap.forEach((s, gameRule) -> { - String displayText; - try { - displayText = i18n(gameRule.getDisplayI18nKey()); - } catch (Exception e) { - displayText = gameRule.getDisplayI18nKey(); - } - if (gameRule instanceof GameRule.BooleanGameRule booleanGameRule) { - gameRuleList.add(new GameRuleInfo(s, displayText, booleanGameRule.getValue())); - } else if (gameRule instanceof GameRule.IntGameRule intGameRule) { - gameRuleList.add(new GameRuleInfo(s, displayText, intGameRule.getValue())); - } - }); - } @Override @@ -136,29 +160,38 @@ static class GameRuleInfo { BooleanProperty onValue; IntegerProperty currentValue; GameRuleType gameRuleType; + Tag tag; BorderPane container = new BorderPane(); - public GameRuleInfo(String ruleKey, String displayName, Boolean onValue) { + public GameRuleInfo(String ruleKey, String displayName, Boolean onValue, ByteTag byteTag) { this.ruleKey = ruleKey; this.displayName = displayName; this.onValue = new SimpleBooleanProperty(onValue); gameRuleType = GameRuleType.BOOLEAN; + this.tag = byteTag; OptionToggleButton toggleButton = new OptionToggleButton(); toggleButton.setTitle(displayName); toggleButton.setSubtitle(ruleKey); toggleButton.setSelected(onValue); + toggleButton.selectedProperty().addListener((observable, oldValue, newValue) -> { + ByteTag theByteTag = (ByteTag) tag; + theByteTag.setValue((byte) (newValue ? 1 : 0)); + LOG.trace(theByteTag.toString()); + }); + HBox.setHgrow(container, Priority.ALWAYS); container.setCenter(toggleButton); } - public GameRuleInfo(String ruleKey, String displayName, Integer currentValue) { + public GameRuleInfo(String ruleKey, String displayName, Integer currentValue, int minValue, int maxValue, IntTag intTag) { this.ruleKey = ruleKey; this.displayName = displayName; this.currentValue = new SimpleIntegerProperty(currentValue); gameRuleType = GameRuleType.INT; + this.tag = intTag; VBox vbox = new VBox(); vbox.getChildren().addAll(new Label(displayName), new Label(ruleKey)); @@ -170,6 +203,20 @@ public GameRuleInfo(String ruleKey, String displayName, Integer currentValue) { textField.maxWidth(10); textField.minWidth(10); textField.textProperty().set(currentValue.toString()); + FXUtils.setValidateWhileTextChanged(textField, true); + textField.setValidators(new NumberRangeValidator(i18n("input.number"), i18n("input.number_range", minValue, maxValue), minValue, maxValue, false)); + textField.textProperty().addListener((observable, oldValue, newValue) -> { + IntTag theIntTag = (IntTag) tag; + Integer value = Lang.toIntOrNull(newValue); + if (value == null) { + return; + } else if (value > maxValue || value < minValue) { + return; + } else { + theIntTag.setValue(value); + } + LOG.trace(theIntTag.toString()); + }); HBox.setHgrow(container, Priority.ALWAYS); container.setCenter(vbox); diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index f15a9a0bfb..54cac91f3c 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -753,6 +753,7 @@ help.detail=For datapack and modpack makers. input.email=The username must be an email address. input.number=The input must be numbers. +input.number_range=The input number is outside the range of %d to %d input.not_empty=This is a required field. input.url=The input must be a valid URL. diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 8d19207ff0..cc34fd5e3f 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -560,6 +560,7 @@ help.detail=可查閱資料包、模組包製作教學等內容 input.email=[使用者名稱] 必須是電子信箱格式 input.number=必須是數字 +input.number_range=輸入的數字在範圍 %d~%d 之外 input.not_empty=必填 input.url=必須是有效連結 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 509b208ff0..65b0b53470 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -570,6 +570,7 @@ help.detail=可查阅数据包、整合包制作指南等内容 input.email=用户名必须是邮箱 input.number=必须是数字 +input.number_range=输入的数字在范围 %d~%d 之外 input.not_empty=必填项 input.url=必须是合法的链接 diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java index 8adb054650..544d905eaa 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java @@ -1,3 +1,20 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2025 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package org.jackhuang.hmcl.gamerule; import com.google.gson.*; @@ -40,9 +57,8 @@ public static GameRule createSimpleGameRule(String ruleKey, int value) { return new IntGameRule(Collections.singletonList(ruleKey), "", value); } - public static GameRule mixGameRule(GameRule simpleGameRule, GameRule mapGameRule) { - simpleGameRule.applyMetadata(mapGameRule); - return simpleGameRule; + public static void mixGameRule(GameRule simpleGameRule, GameRule gameRule) { + simpleGameRule.applyMetadata(gameRule); } public static void addSimpleGameRule(Map gameRuleMap, String ruleKey, String displayName, boolean value) { @@ -133,6 +149,8 @@ public void setValue(boolean value) { public static final class IntGameRule extends GameRule { private final IntegerProperty value = new SimpleIntegerProperty(0); private final IntegerProperty defaultValue = new SimpleIntegerProperty(0); + private int maxValue = 0; + private int minValue = 0; private IntGameRule(List ruleKey, String displayI18nKey, int value) { super(ruleKey, displayI18nKey); @@ -147,6 +165,8 @@ private IntGameRule() { public GameRule clone() { IntGameRule intGameRule = new IntGameRule(getRuleKey(), getDisplayI18nKey(), value.getValue()); intGameRule.defaultValue.setValue(defaultValue.getValue()); + intGameRule.minValue = minValue; + intGameRule.maxValue = maxValue; return intGameRule; } @@ -155,6 +175,8 @@ public void applyMetadata(GameRule metadataSource) { if (metadataSource instanceof IntGameRule source) { this.setDisplayI18nKey(source.getDisplayI18nKey()); this.setDefaultValue(source.getDefaultValue()); + this.maxValue = source.getMaxValue(); + this.minValue = source.getMinValue(); } } @@ -181,6 +203,22 @@ public IntegerProperty valueProperty() { public void setValue(int value) { this.value.setValue(value); } + + public int getMaxValue() { + return maxValue; + } + + public void setMaxValue(int maxValue) { + this.maxValue = maxValue; + } + + public int getMinValue() { + return minValue; + } + + public void setMinValue(int minValue) { + this.minValue = minValue; + } } static class GameRuleDeserializer implements JsonDeserializer { @@ -195,6 +233,20 @@ public GameRule deserialize(JsonElement jsonElement, Type type, JsonDeserializat Type listType = JsonUtils.listTypeOf(String.class).getType(); gameRule.ruleKey = context.deserialize(jsonObject.get("ruleKey"), listType); + if (gameRule instanceof IntGameRule intGameRule) { + if (jsonObject.has("maxValue") && jsonObject.get("maxValue") instanceof JsonPrimitive jsonPrimitive) { + intGameRule.maxValue = jsonPrimitive.getAsInt(); + } else { + intGameRule.maxValue = Integer.MAX_VALUE; + } + + if (jsonObject.has("minValue") && jsonObject.get("minValue") instanceof JsonPrimitive jsonPrimitive) { + intGameRule.minValue = jsonPrimitive.getAsInt(); + } else { + intGameRule.minValue = Integer.MIN_VALUE; + } + } + return gameRule; } diff --git a/HMCLCore/src/main/resources/assets/gamerule/gamerule.json b/HMCLCore/src/main/resources/assets/gamerule/gamerule.json index 4ff6d23c53..5dc838d228 100644 --- a/HMCLCore/src/main/resources/assets/gamerule/gamerule.json +++ b/HMCLCore/src/main/resources/assets/gamerule/gamerule.json @@ -116,7 +116,8 @@ "minecraft:fire_spread_radius_around_player" ], "displayI18nKey": "gamerule.fire_spread_radius_around_player", - "defaultValue": 128 + "defaultValue": 128, + "minValue": -1 }, { "ruleKey": [ @@ -191,7 +192,8 @@ "minecraft:commandModificationBlockLimit" ], "displayI18nKey": "gamerule.max_block_modifications", - "defaultValue": 32768 + "defaultValue": 32768, + "minValue": -1 }, { "ruleKey": [ @@ -199,7 +201,8 @@ "minecraft:maxCommandForkCount" ], "displayI18nKey": "gamerule.max_command_forks", - "defaultValue": 65536 + "defaultValue": 65536, + "minValue": 0 }, { "ruleKey": [ @@ -207,7 +210,8 @@ "minecraft:maxCommandChainLength" ], "displayI18nKey": "gamerule.max_command_sequence_length", - "defaultValue": 65536 + "defaultValue": 65536, + "minValue": 0 }, { "ruleKey": [ @@ -215,7 +219,8 @@ "minecraft:maxEntityCramming" ], "displayI18nKey": "gamerule.max_entity_cramming", - "defaultValue": 24 + "defaultValue": 24, + "minValue": 0 }, { "ruleKey": [ @@ -223,7 +228,9 @@ "minecraft:minecartMaxSpeed" ], "displayI18nKey": "gamerule.max_minecart_speed", - "defaultValue": 8 + "defaultValue": 8, + "minValue": 1, + "maxValue": 1000 }, { "ruleKey": [ @@ -231,7 +238,9 @@ "minecraft:snowAccumulationHeight" ], "displayI18nKey": "gamerule.max_snow_accumulation_height", - "defaultValue": 1 + "defaultValue": 1, + "minValue": 0, + "maxValue": 8 }, { "ruleKey": [ @@ -277,21 +286,24 @@ "minecraft:players_nether_portal_creative_delay" ], "displayI18nKey": "gamerule.players_nether_portal_creative_delay", - "defaultValue": 0 + "defaultValue": 0, + "minValue": 0 }, { "ruleKey": [ "minecraft:players_nether_portal_default_delay" ], "displayI18nKey": "gamerule.players_nether_portal_default_delay", - "defaultValue": 80 + "defaultValue": 80, + "minValue": 0 }, { "ruleKey": [ "minecraft:players_sleeping_percentage" ], "displayI18nKey": "gamerule.players_sleeping_percentage", - "defaultValue": 100 + "defaultValue": 100, + "minValue": 0 }, { "ruleKey": [ @@ -321,7 +333,8 @@ "minecraft:randomTickSpeed" ], "displayI18nKey": "gamerule.random_tick_speed", - "defaultValue": 3 + "defaultValue": 3, + "minValue": 0 }, { "ruleKey": [ @@ -337,7 +350,8 @@ "minecraft:spawnRadius" ], "displayI18nKey": "gamerule.respawn_radius", - "defaultValue": 10 + "defaultValue": 10, + "minValue": 0 }, { "ruleKey": [ From f000020e5d83c5f8706c2f4989db61cf171cd6ce Mon Sep 17 00:00:00 2001 From: mine_ Date: Mon, 8 Dec 2025 16:19:47 +0800 Subject: [PATCH 17/69] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96i18n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java | 2 +- HMCL/src/main/resources/assets/lang/I18N.properties | 3 ++- HMCL/src/main/resources/assets/lang/I18N_zh.properties | 3 ++- HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties | 3 ++- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java index 3155d2e8af..e1002edabc 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java @@ -204,7 +204,7 @@ public GameRuleInfo(String ruleKey, String displayName, Integer currentValue, in textField.minWidth(10); textField.textProperty().set(currentValue.toString()); FXUtils.setValidateWhileTextChanged(textField, true); - textField.setValidators(new NumberRangeValidator(i18n("input.number"), i18n("input.number_range", minValue, maxValue), minValue, maxValue, false)); + textField.setValidators(new NumberRangeValidator(i18n("input.integer"), i18n("input.number_range", minValue, maxValue), minValue, maxValue, false)); textField.textProperty().addListener((observable, oldValue, newValue) -> { IntTag theIntTag = (IntTag) tag; Integer value = Lang.toIntOrNull(newValue); diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 54cac91f3c..7d3d34a936 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -753,7 +753,8 @@ help.detail=For datapack and modpack makers. input.email=The username must be an email address. input.number=The input must be numbers. -input.number_range=The input number is outside the range of %d to %d +input.integer=The input must be an integer. +input.number_range=The input number is outside the valid range of %d to %d. input.not_empty=This is a required field. input.url=The input must be a valid URL. diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index cc34fd5e3f..55e01b8cd6 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -560,7 +560,8 @@ help.detail=可查閱資料包、模組包製作教學等內容 input.email=[使用者名稱] 必須是電子信箱格式 input.number=必須是數字 -input.number_range=輸入的數字在範圍 %d~%d 之外 +input.integer=必须是整數 +input.number_range=輸入的數字在有效範圍 %d~%d 之外 input.not_empty=必填 input.url=必須是有效連結 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 65b0b53470..a96fe9788c 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -570,7 +570,8 @@ help.detail=可查阅数据包、整合包制作指南等内容 input.email=用户名必须是邮箱 input.number=必须是数字 -input.number_range=输入的数字在范围 %d~%d 之外 +input.integer=必须是整数. +input.number_range=输入的数字在有效范围 %d~%d 之外 input.not_empty=必填项 input.url=必须是合法的链接 From 20c814c54ac4fd3c37223bb89c9bbefbb3e79bff Mon Sep 17 00:00:00 2001 From: mine_ Date: Mon, 8 Dec 2025 16:36:13 +0800 Subject: [PATCH 18/69] =?UTF-8?q?feat:=20=E5=88=86=E7=A6=BB=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/GameRulePage.java | 139 +-------------- .../hmcl/ui/versions/GameRulePageSkin.java | 159 ++++++++++++++++++ 2 files changed, 163 insertions(+), 135 deletions(-) create mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java index e1002edabc..534497c562 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java @@ -57,7 +57,7 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.logging.Logger.LOG; -public class GameRulePage extends ListPageBase { +public class GameRulePage extends ListPageBase { private WorldManagePage worldManagePage; private World world; @@ -65,7 +65,7 @@ public class GameRulePage extends ListPageBase { Map gameRuleMap = GameRule.getCloneGameRuleMap(); - ObservableList gameRuleList; + ObservableList gameRuleList; public GameRulePage(WorldManagePage worldManagePage) { this.worldManagePage = worldManagePage; @@ -118,7 +118,7 @@ public void updateControls() { } if (finalGameRule instanceof GameRule.IntGameRule intGameRule) { LOG.trace("find one: " + finalGameRule.getRuleKey() + intGameRule.getValue() + "minValue: " + intGameRule.getMinValue() + ", maxValue" + intGameRule.getMaxValue() + ", intTag is " + intTag.getValue()); - gameRuleList.add(new GameRuleInfo(finalGameRule.getRuleKey().get(0), displayText, intGameRule.getValue(), intGameRule.getMinValue(), intGameRule.getMaxValue(), intTag)); + gameRuleList.add(new GameRulePageSkin.GameRuleInfo(finalGameRule.getRuleKey().get(0), displayText, intGameRule.getValue(), intGameRule.getMinValue(), intGameRule.getMaxValue(), intTag)); } } else if (gameRuleTag instanceof ByteTag byteTag) { finalGameRule = GameRule.createSimpleGameRule(byteTag.getName(), byteTag.getValue() == 1); @@ -134,7 +134,7 @@ public void updateControls() { displayText = finalGameRule.getDisplayI18nKey(); } if (finalGameRule instanceof GameRule.BooleanGameRule booleanGameRule) { - gameRuleList.add(new GameRuleInfo(finalGameRule.getRuleKey().get(0), displayText, booleanGameRule.getValue(), byteTag)); + gameRuleList.add(new GameRulePageSkin.GameRuleInfo(finalGameRule.getRuleKey().get(0), displayText, booleanGameRule.getValue(), byteTag)); } } else { return; @@ -153,137 +153,6 @@ protected Skin createDefaultSkin() { return new GameRulePageSkin(this); } - static class GameRuleInfo { - - String ruleKey; - String displayName; - BooleanProperty onValue; - IntegerProperty currentValue; - GameRuleType gameRuleType; - Tag tag; - - BorderPane container = new BorderPane(); - - public GameRuleInfo(String ruleKey, String displayName, Boolean onValue, ByteTag byteTag) { - this.ruleKey = ruleKey; - this.displayName = displayName; - this.onValue = new SimpleBooleanProperty(onValue); - gameRuleType = GameRuleType.BOOLEAN; - this.tag = byteTag; - - OptionToggleButton toggleButton = new OptionToggleButton(); - toggleButton.setTitle(displayName); - toggleButton.setSubtitle(ruleKey); - toggleButton.setSelected(onValue); - - toggleButton.selectedProperty().addListener((observable, oldValue, newValue) -> { - ByteTag theByteTag = (ByteTag) tag; - theByteTag.setValue((byte) (newValue ? 1 : 0)); - LOG.trace(theByteTag.toString()); - }); - - HBox.setHgrow(container, Priority.ALWAYS); - container.setCenter(toggleButton); - } - - public GameRuleInfo(String ruleKey, String displayName, Integer currentValue, int minValue, int maxValue, IntTag intTag) { - this.ruleKey = ruleKey; - this.displayName = displayName; - this.currentValue = new SimpleIntegerProperty(currentValue); - gameRuleType = GameRuleType.INT; - this.tag = intTag; - - VBox vbox = new VBox(); - vbox.getChildren().addAll(new Label(displayName), new Label(ruleKey)); - vbox.setAlignment(Pos.CENTER_LEFT); - HBox.setHgrow(vbox, Priority.ALWAYS); - - container.setPadding(new Insets(8, 8, 8, 16)); - JFXTextField textField = new JFXTextField(); - textField.maxWidth(10); - textField.minWidth(10); - textField.textProperty().set(currentValue.toString()); - FXUtils.setValidateWhileTextChanged(textField, true); - textField.setValidators(new NumberRangeValidator(i18n("input.integer"), i18n("input.number_range", minValue, maxValue), minValue, maxValue, false)); - textField.textProperty().addListener((observable, oldValue, newValue) -> { - IntTag theIntTag = (IntTag) tag; - Integer value = Lang.toIntOrNull(newValue); - if (value == null) { - return; - } else if (value > maxValue || value < minValue) { - return; - } else { - theIntTag.setValue(value); - } - LOG.trace(theIntTag.toString()); - }); - - HBox.setHgrow(container, Priority.ALWAYS); - container.setCenter(vbox); - container.setRight(textField); - } - - } - - static class GameRulePageSkin extends SkinBase { - - private final HBox searchBar; - private final JFXTextField searchField; - JFXListView listView = new JFXListView<>(); - - protected GameRulePageSkin(GameRulePage control) { - super(control); - StackPane pane = new StackPane(); - pane.setPadding(new Insets(10)); - pane.getStyleClass().addAll("notice-pane"); - - ComponentList root = new ComponentList(); - root.getStyleClass().add("no-padding"); - - { - searchBar = new HBox(); - searchBar.setAlignment(Pos.CENTER); - searchBar.setPadding(new Insets(0, 5, 0, 5)); - searchField = new JFXTextField(); - searchField.setPromptText(i18n("search")); - HBox.setHgrow(searchField, Priority.ALWAYS); - JFXButton closeSearchBar = createToolbarButton2(null, SVG.CLOSE, - searchField::clear); - FXUtils.onEscPressed(searchField, closeSearchBar::fire); - searchBar.getChildren().addAll(searchField, closeSearchBar); - root.getContent().add(searchBar); - } - - SpinnerPane center = new SpinnerPane(); - ComponentList.setVgrow(center, Priority.ALWAYS); - center.getStyleClass().add("large-spinner-pane"); - center.setContent(listView); - Holder lastCell = new Holder<>(); - listView.setItems(getSkinnable().getItems()); - listView.setCellFactory(x -> new GameRuleListCell(listView, lastCell)); - FXUtils.ignoreEvent(listView, KeyEvent.KEY_PRESSED, e -> e.getCode() == KeyCode.ESCAPE); - root.getContent().add(center); - - pane.getChildren().add(root); - getChildren().add(pane); - - } - } - - static class GameRuleListCell extends MDListCell { - - public GameRuleListCell(JFXListView listView, Holder lastCell) { - super(listView, lastCell); - } - - @Override - protected void updateControl(GameRuleInfo item, boolean empty) { - if (empty) return; - - getContainer().getChildren().setAll(item.container); - } - } - enum GameRuleType { INT, BOOLEAN } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java new file mode 100644 index 0000000000..5c99101a14 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java @@ -0,0 +1,159 @@ +package org.jackhuang.hmcl.ui.versions; + +import com.github.steveice10.opennbt.tag.builtin.ByteTag; +import com.github.steveice10.opennbt.tag.builtin.IntTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; +import com.jfoenix.controls.JFXButton; +import com.jfoenix.controls.JFXListView; +import com.jfoenix.controls.JFXTextField; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.control.Label; +import javafx.scene.control.SkinBase; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; +import javafx.scene.layout.*; +import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.ui.SVG; +import org.jackhuang.hmcl.ui.construct.*; +import org.jackhuang.hmcl.util.Holder; +import org.jackhuang.hmcl.util.Lang; + +import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton2; +import static org.jackhuang.hmcl.util.i18n.I18n.i18n; +import static org.jackhuang.hmcl.util.logging.Logger.LOG; + +class GameRulePageSkin extends SkinBase { + + private final HBox searchBar; + private final JFXTextField searchField; + JFXListView listView = new JFXListView<>(); + + protected GameRulePageSkin(GameRulePage control) { + super(control); + StackPane pane = new StackPane(); + pane.setPadding(new Insets(10)); + pane.getStyleClass().addAll("notice-pane"); + + ComponentList root = new ComponentList(); + root.getStyleClass().add("no-padding"); + + { + searchBar = new HBox(); + searchBar.setAlignment(Pos.CENTER); + searchBar.setPadding(new Insets(0, 5, 0, 5)); + searchField = new JFXTextField(); + searchField.setPromptText(i18n("search")); + HBox.setHgrow(searchField, Priority.ALWAYS); + JFXButton closeSearchBar = createToolbarButton2(null, SVG.CLOSE, + searchField::clear); + FXUtils.onEscPressed(searchField, closeSearchBar::fire); + searchBar.getChildren().addAll(searchField, closeSearchBar); + root.getContent().add(searchBar); + } + + SpinnerPane center = new SpinnerPane(); + ComponentList.setVgrow(center, Priority.ALWAYS); + center.getStyleClass().add("large-spinner-pane"); + center.setContent(listView); + Holder lastCell = new Holder<>(); + listView.setItems(getSkinnable().getItems()); + listView.setCellFactory(x -> new GameRuleListCell(listView, lastCell)); + FXUtils.ignoreEvent(listView, KeyEvent.KEY_PRESSED, e -> e.getCode() == KeyCode.ESCAPE); + root.getContent().add(center); + + pane.getChildren().add(root); + getChildren().add(pane); + + } + + static class GameRuleInfo { + + String ruleKey; + String displayName; + BooleanProperty onValue; + IntegerProperty currentValue; + GameRulePage.GameRuleType gameRuleType; + Tag tag; + + BorderPane container = new BorderPane(); + + public GameRuleInfo(String ruleKey, String displayName, Boolean onValue, ByteTag byteTag) { + this.ruleKey = ruleKey; + this.displayName = displayName; + this.onValue = new SimpleBooleanProperty(onValue); + gameRuleType = GameRulePage.GameRuleType.BOOLEAN; + this.tag = byteTag; + + OptionToggleButton toggleButton = new OptionToggleButton(); + toggleButton.setTitle(displayName); + toggleButton.setSubtitle(ruleKey); + toggleButton.setSelected(onValue); + + toggleButton.selectedProperty().addListener((observable, oldValue, newValue) -> { + ByteTag theByteTag = (ByteTag) tag; + theByteTag.setValue((byte) (newValue ? 1 : 0)); + LOG.trace(theByteTag.toString()); + }); + + HBox.setHgrow(container, Priority.ALWAYS); + container.setCenter(toggleButton); + } + + public GameRuleInfo(String ruleKey, String displayName, Integer currentValue, int minValue, int maxValue, IntTag intTag) { + this.ruleKey = ruleKey; + this.displayName = displayName; + this.currentValue = new SimpleIntegerProperty(currentValue); + gameRuleType = GameRulePage.GameRuleType.INT; + this.tag = intTag; + + VBox vbox = new VBox(); + vbox.getChildren().addAll(new Label(displayName), new Label(ruleKey)); + vbox.setAlignment(Pos.CENTER_LEFT); + HBox.setHgrow(vbox, Priority.ALWAYS); + + container.setPadding(new Insets(8, 8, 8, 16)); + JFXTextField textField = new JFXTextField(); + textField.maxWidth(10); + textField.minWidth(10); + textField.textProperty().set(currentValue.toString()); + FXUtils.setValidateWhileTextChanged(textField, true); + textField.setValidators(new NumberRangeValidator(i18n("input.integer"), i18n("input.number_range", minValue, maxValue), minValue, maxValue, false)); + textField.textProperty().addListener((observable, oldValue, newValue) -> { + IntTag theIntTag = (IntTag) tag; + Integer value = Lang.toIntOrNull(newValue); + if (value == null) { + return; + } else if (value > maxValue || value < minValue) { + return; + } else { + theIntTag.setValue(value); + } + LOG.trace(theIntTag.toString()); + }); + + HBox.setHgrow(container, Priority.ALWAYS); + container.setCenter(vbox); + container.setRight(textField); + } + + } + + static class GameRuleListCell extends MDListCell { + + public GameRuleListCell(JFXListView listView, Holder lastCell) { + super(listView, lastCell); + } + + @Override + protected void updateControl(GameRuleInfo item, boolean empty) { + if (empty) return; + + getContainer().getChildren().setAll(item.container); + } + } +} From d7c5a77ca5b5608021dfe70a1247afdf1ba71b7f Mon Sep 17 00:00:00 2001 From: mine_ Date: Mon, 8 Dec 2025 16:38:53 +0800 Subject: [PATCH 19/69] =?UTF-8?q?feat:=20=E5=88=86=E7=A6=BB=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/GameRulePage.java | 25 ------------------- .../hmcl/ui/versions/GameRulePageSkin.java | 11 -------- 2 files changed, 36 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java index 534497c562..2628a4ece7 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java @@ -20,40 +20,19 @@ import com.github.steveice10.opennbt.tag.builtin.ByteTag; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.IntTag; -import com.github.steveice10.opennbt.tag.builtin.Tag; -import com.jfoenix.controls.JFXButton; -import com.jfoenix.controls.JFXListView; -import com.jfoenix.controls.JFXTextField; -import javafx.beans.property.BooleanProperty; -import javafx.beans.property.IntegerProperty; -import javafx.beans.property.SimpleBooleanProperty; -import javafx.beans.property.SimpleIntegerProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; -import javafx.geometry.Insets; -import javafx.geometry.Pos; -import javafx.scene.control.Label; import javafx.scene.control.Skin; -import javafx.scene.control.SkinBase; -import javafx.scene.input.KeyCode; -import javafx.scene.input.KeyEvent; -import javafx.scene.layout.*; import org.jackhuang.hmcl.game.World; import org.jackhuang.hmcl.gamerule.GameRule; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; -import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.ListPageBase; -import org.jackhuang.hmcl.ui.SVG; -import org.jackhuang.hmcl.ui.construct.*; -import org.jackhuang.hmcl.util.Holder; -import org.jackhuang.hmcl.util.Lang; import java.io.IOException; import java.nio.file.Files; import java.util.Map; -import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton2; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.logging.Logger.LOG; @@ -153,10 +132,6 @@ protected Skin createDefaultSkin() { return new GameRulePageSkin(this); } - enum GameRuleType { - INT, BOOLEAN - } - private CompoundTag loadWorldInfo() throws IOException { if (!Files.isDirectory(world.getFile())) throw new IOException("Not a valid world directory"); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java index 5c99101a14..9063b6e7f2 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java @@ -6,10 +6,6 @@ import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXListView; import com.jfoenix.controls.JFXTextField; -import javafx.beans.property.BooleanProperty; -import javafx.beans.property.IntegerProperty; -import javafx.beans.property.SimpleBooleanProperty; -import javafx.beans.property.SimpleIntegerProperty; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.Label; @@ -75,9 +71,6 @@ static class GameRuleInfo { String ruleKey; String displayName; - BooleanProperty onValue; - IntegerProperty currentValue; - GameRulePage.GameRuleType gameRuleType; Tag tag; BorderPane container = new BorderPane(); @@ -85,8 +78,6 @@ static class GameRuleInfo { public GameRuleInfo(String ruleKey, String displayName, Boolean onValue, ByteTag byteTag) { this.ruleKey = ruleKey; this.displayName = displayName; - this.onValue = new SimpleBooleanProperty(onValue); - gameRuleType = GameRulePage.GameRuleType.BOOLEAN; this.tag = byteTag; OptionToggleButton toggleButton = new OptionToggleButton(); @@ -107,8 +98,6 @@ public GameRuleInfo(String ruleKey, String displayName, Boolean onValue, ByteTag public GameRuleInfo(String ruleKey, String displayName, Integer currentValue, int minValue, int maxValue, IntTag intTag) { this.ruleKey = ruleKey; this.displayName = displayName; - this.currentValue = new SimpleIntegerProperty(currentValue); - gameRuleType = GameRulePage.GameRuleType.INT; this.tag = intTag; VBox vbox = new VBox(); From ee41ed2c09e9dbb8f0f0371f7b3e2cf603fd5c5c Mon Sep 17 00:00:00 2001 From: mine_ Date: Mon, 8 Dec 2025 20:08:21 +0800 Subject: [PATCH 20/69] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E4=BF=9D?= =?UTF-8?q?=E5=AD=98=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jackhuang/hmcl/ui/versions/GameRulePage.java | 13 +++++++++++-- .../hmcl/ui/versions/GameRulePageSkin.java | 7 ++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java index 2628a4ece7..b4a301404a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java @@ -97,7 +97,7 @@ public void updateControls() { } if (finalGameRule instanceof GameRule.IntGameRule intGameRule) { LOG.trace("find one: " + finalGameRule.getRuleKey() + intGameRule.getValue() + "minValue: " + intGameRule.getMinValue() + ", maxValue" + intGameRule.getMaxValue() + ", intTag is " + intTag.getValue()); - gameRuleList.add(new GameRulePageSkin.GameRuleInfo(finalGameRule.getRuleKey().get(0), displayText, intGameRule.getValue(), intGameRule.getMinValue(), intGameRule.getMaxValue(), intTag)); + gameRuleList.add(new GameRulePageSkin.GameRuleInfo(finalGameRule.getRuleKey().get(0), displayText, intGameRule.getValue(), intGameRule.getMinValue(), intGameRule.getMaxValue(), intTag, this::saveLevelDat)); } } else if (gameRuleTag instanceof ByteTag byteTag) { finalGameRule = GameRule.createSimpleGameRule(byteTag.getName(), byteTag.getValue() == 1); @@ -113,7 +113,7 @@ public void updateControls() { displayText = finalGameRule.getDisplayI18nKey(); } if (finalGameRule instanceof GameRule.BooleanGameRule booleanGameRule) { - gameRuleList.add(new GameRulePageSkin.GameRuleInfo(finalGameRule.getRuleKey().get(0), displayText, booleanGameRule.getValue(), byteTag)); + gameRuleList.add(new GameRulePageSkin.GameRuleInfo(finalGameRule.getRuleKey().get(0), displayText, booleanGameRule.getValue(), byteTag, this::saveLevelDat)); } } else { return; @@ -138,4 +138,13 @@ private CompoundTag loadWorldInfo() throws IOException { return world.readLevelDat(); } + + void saveLevelDat() { + LOG.info("Saving level.dat of world " + world.getWorldName()); + try { + this.world.writeLevelDat(levelDat); + } catch (IOException e) { + LOG.warning("Failed to save level.dat of world " + world.getWorldName(), e); + } + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java index 9063b6e7f2..407ce1f150 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java @@ -75,7 +75,7 @@ static class GameRuleInfo { BorderPane container = new BorderPane(); - public GameRuleInfo(String ruleKey, String displayName, Boolean onValue, ByteTag byteTag) { + public GameRuleInfo(String ruleKey, String displayName, Boolean onValue, ByteTag byteTag, Runnable onSave) { this.ruleKey = ruleKey; this.displayName = displayName; this.tag = byteTag; @@ -88,6 +88,7 @@ public GameRuleInfo(String ruleKey, String displayName, Boolean onValue, ByteTag toggleButton.selectedProperty().addListener((observable, oldValue, newValue) -> { ByteTag theByteTag = (ByteTag) tag; theByteTag.setValue((byte) (newValue ? 1 : 0)); + onSave.run(); LOG.trace(theByteTag.toString()); }); @@ -95,7 +96,7 @@ public GameRuleInfo(String ruleKey, String displayName, Boolean onValue, ByteTag container.setCenter(toggleButton); } - public GameRuleInfo(String ruleKey, String displayName, Integer currentValue, int minValue, int maxValue, IntTag intTag) { + public GameRuleInfo(String ruleKey, String displayName, Integer currentValue, int minValue, int maxValue, IntTag intTag, Runnable onSave) { this.ruleKey = ruleKey; this.displayName = displayName; this.tag = intTag; @@ -121,6 +122,7 @@ public GameRuleInfo(String ruleKey, String displayName, Integer currentValue, in return; } else { theIntTag.setValue(value); + onSave.run(); } LOG.trace(theIntTag.toString()); }); @@ -141,7 +143,6 @@ public GameRuleListCell(JFXListView listView, Holder lastC @Override protected void updateControl(GameRuleInfo item, boolean empty) { if (empty) return; - getContainer().getChildren().setAll(item.container); } } From 190322d373c4fa6479a11f5e6ac105d81bc4ded5 Mon Sep 17 00:00:00 2001 From: mine_ Date: Mon, 8 Dec 2025 22:11:26 +0800 Subject: [PATCH 21/69] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=AF=B9?= =?UTF-8?q?=E6=97=A7=E7=89=88=E6=A0=BC=E5=BC=8F=E7=9A=84=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/GameRulePage.java | 78 ++++++---------- .../hmcl/ui/versions/GameRulePageSkin.java | 23 ++--- .../org/jackhuang/hmcl/gamerule/GameRule.java | 57 ++++++++++-- .../jackhuang/hmcl/gamerule/GameRuleNbt.java | 92 +++++++++++++++++++ 4 files changed, 178 insertions(+), 72 deletions(-) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRuleNbt.java diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java index b4a301404a..3468889c82 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java @@ -17,14 +17,13 @@ */ package org.jackhuang.hmcl.ui.versions; -import com.github.steveice10.opennbt.tag.builtin.ByteTag; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.github.steveice10.opennbt.tag.builtin.IntTag; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.scene.control.Skin; import org.jackhuang.hmcl.game.World; import org.jackhuang.hmcl.gamerule.GameRule; +import org.jackhuang.hmcl.gamerule.GameRuleNbt; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.ListPageBase; @@ -68,62 +67,39 @@ public GameRulePage(WorldManagePage worldManagePage) { } public void updateControls() { - boolean isNewGameRuleFormat; - CompoundTag dataTag = levelDat.get("Data"); CompoundTag gameRuleCompoundTag; gameRuleCompoundTag = dataTag.get("game_rules"); if (gameRuleCompoundTag != null) { - isNewGameRuleFormat = true; } else { gameRuleCompoundTag = dataTag.get("GameRules"); - isNewGameRuleFormat = false; - } - if (isNewGameRuleFormat) { - gameRuleCompoundTag.iterator().forEachRemaining(gameRuleTag -> { - //LOG.trace(gameRuleTag.toString()); - GameRule finalGameRule; - if (gameRuleTag instanceof IntTag intTag) { - finalGameRule = GameRule.createSimpleGameRule(intTag.getName(), intTag.getValue()); - GameRule gr = gameRuleMap.getOrDefault(intTag.getName(), null); - if (gr instanceof GameRule.IntGameRule intGR) { - GameRule.mixGameRule(finalGameRule, intGR); - } - String displayText; - try { - displayText = i18n(finalGameRule.getDisplayI18nKey()); - } catch (Exception e) { - displayText = finalGameRule.getDisplayI18nKey(); - } - if (finalGameRule instanceof GameRule.IntGameRule intGameRule) { - LOG.trace("find one: " + finalGameRule.getRuleKey() + intGameRule.getValue() + "minValue: " + intGameRule.getMinValue() + ", maxValue" + intGameRule.getMaxValue() + ", intTag is " + intTag.getValue()); - gameRuleList.add(new GameRulePageSkin.GameRuleInfo(finalGameRule.getRuleKey().get(0), displayText, intGameRule.getValue(), intGameRule.getMinValue(), intGameRule.getMaxValue(), intTag, this::saveLevelDat)); - } - } else if (gameRuleTag instanceof ByteTag byteTag) { - finalGameRule = GameRule.createSimpleGameRule(byteTag.getName(), byteTag.getValue() == 1); - GameRule gr = gameRuleMap.getOrDefault(byteTag.getName(), null); - if (gr instanceof GameRule.BooleanGameRule booleanGR) { - GameRule.mixGameRule(finalGameRule, booleanGR); - //LOG.trace("find one: " + finalGameRule.getRuleKey() + ", byteTag is " + byteTag.getValue()); - } - String displayText; - try { - displayText = i18n(finalGameRule.getDisplayI18nKey()); - } catch (Exception e) { - displayText = finalGameRule.getDisplayI18nKey(); - } - if (finalGameRule instanceof GameRule.BooleanGameRule booleanGameRule) { - gameRuleList.add(new GameRulePageSkin.GameRuleInfo(finalGameRule.getRuleKey().get(0), displayText, booleanGameRule.getValue(), byteTag, this::saveLevelDat)); - } - } else { - return; - } - }); - } else { - gameRuleCompoundTag.iterator().forEachRemaining(gameRuleTag -> { - LOG.trace(gameRuleTag.toString()); - }); } + gameRuleCompoundTag.iterator().forEachRemaining(gameRuleTag -> { + //LOG.trace(gameRuleTag.toString()); + GameRule finalGameRule; + + GameRuleNbt gameRuleNbt = GameRule.createGameRuleNbt(gameRuleTag).orElse(null); + finalGameRule = GameRule.getFullGameRule(gameRuleTag, gameRuleMap).orElse(null); + if (gameRuleNbt == null || finalGameRule == null) { + return; + } + + //LOG.trace(finalGameRule.getRuleKey().toString()); + + String displayText; + try { + displayText = i18n(finalGameRule.getDisplayI18nKey()); + } catch (Exception e) { + displayText = finalGameRule.getDisplayI18nKey(); + } + + if (finalGameRule instanceof GameRule.IntGameRule intGameRule) { + gameRuleList.add(new GameRulePageSkin.GameRuleInfo(intGameRule.getRuleKey().get(0), displayText, intGameRule.getValue(), intGameRule.getMinValue(), intGameRule.getMaxValue(), gameRuleNbt, this::saveLevelDat)); + } else if (finalGameRule instanceof GameRule.BooleanGameRule booleanGameRule) { + gameRuleList.add(new GameRulePageSkin.GameRuleInfo(booleanGameRule.getRuleKey().get(0), displayText, booleanGameRule.getValue(), gameRuleNbt, this::saveLevelDat)); + } + }); + } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java index 407ce1f150..8946538046 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java @@ -1,8 +1,5 @@ package org.jackhuang.hmcl.ui.versions; -import com.github.steveice10.opennbt.tag.builtin.ByteTag; -import com.github.steveice10.opennbt.tag.builtin.IntTag; -import com.github.steveice10.opennbt.tag.builtin.Tag; import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXListView; import com.jfoenix.controls.JFXTextField; @@ -13,6 +10,7 @@ import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; import javafx.scene.layout.*; +import org.jackhuang.hmcl.gamerule.GameRuleNbt; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; import org.jackhuang.hmcl.ui.construct.*; @@ -21,7 +19,6 @@ import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton2; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; -import static org.jackhuang.hmcl.util.logging.Logger.LOG; class GameRulePageSkin extends SkinBase { @@ -71,14 +68,14 @@ static class GameRuleInfo { String ruleKey; String displayName; - Tag tag; + GameRuleNbt gameRuleNbt; BorderPane container = new BorderPane(); - public GameRuleInfo(String ruleKey, String displayName, Boolean onValue, ByteTag byteTag, Runnable onSave) { + public GameRuleInfo(String ruleKey, String displayName, Boolean onValue, GameRuleNbt gameRuleNbt, Runnable onSave) { this.ruleKey = ruleKey; this.displayName = displayName; - this.tag = byteTag; + this.gameRuleNbt = gameRuleNbt; OptionToggleButton toggleButton = new OptionToggleButton(); toggleButton.setTitle(displayName); @@ -86,20 +83,18 @@ public GameRuleInfo(String ruleKey, String displayName, Boolean onValue, ByteTag toggleButton.setSelected(onValue); toggleButton.selectedProperty().addListener((observable, oldValue, newValue) -> { - ByteTag theByteTag = (ByteTag) tag; - theByteTag.setValue((byte) (newValue ? 1 : 0)); + gameRuleNbt.changeValue(newValue); onSave.run(); - LOG.trace(theByteTag.toString()); }); HBox.setHgrow(container, Priority.ALWAYS); container.setCenter(toggleButton); } - public GameRuleInfo(String ruleKey, String displayName, Integer currentValue, int minValue, int maxValue, IntTag intTag, Runnable onSave) { + public GameRuleInfo(String ruleKey, String displayName, Integer currentValue, int minValue, int maxValue, GameRuleNbt gameRuleNbt, Runnable onSave) { this.ruleKey = ruleKey; this.displayName = displayName; - this.tag = intTag; + this.gameRuleNbt = gameRuleNbt; VBox vbox = new VBox(); vbox.getChildren().addAll(new Label(displayName), new Label(ruleKey)); @@ -114,17 +109,15 @@ public GameRuleInfo(String ruleKey, String displayName, Integer currentValue, in FXUtils.setValidateWhileTextChanged(textField, true); textField.setValidators(new NumberRangeValidator(i18n("input.integer"), i18n("input.number_range", minValue, maxValue), minValue, maxValue, false)); textField.textProperty().addListener((observable, oldValue, newValue) -> { - IntTag theIntTag = (IntTag) tag; Integer value = Lang.toIntOrNull(newValue); if (value == null) { return; } else if (value > maxValue || value < minValue) { return; } else { - theIntTag.setValue(value); + gameRuleNbt.changeValue(newValue); onSave.run(); } - LOG.trace(theIntTag.toString()); }); HBox.setHgrow(container, Priority.ALWAYS); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java index 544d905eaa..ade6df0cb8 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java @@ -17,22 +17,24 @@ */ package org.jackhuang.hmcl.gamerule; +import com.github.steveice10.opennbt.tag.builtin.ByteTag; +import com.github.steveice10.opennbt.tag.builtin.IntTag; +import com.github.steveice10.opennbt.tag.builtin.StringTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; import com.google.gson.*; import com.google.gson.annotations.JsonAdapter; import javafx.beans.property.BooleanProperty; import javafx.beans.property.IntegerProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleIntegerProperty; +import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.gson.JsonSerializable; import org.jackhuang.hmcl.util.gson.JsonUtils; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Type; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; @JsonSerializable @JsonAdapter(GameRule.GameRuleDeserializer.class) @@ -54,11 +56,15 @@ public static GameRule createSimpleGameRule(String ruleKey, boolean value) { } public static GameRule createSimpleGameRule(String ruleKey, int value) { - return new IntGameRule(Collections.singletonList(ruleKey), "", value); + IntGameRule intGameRule = new IntGameRule(Collections.singletonList(ruleKey), "", value); + intGameRule.maxValue = Integer.MAX_VALUE; + intGameRule.minValue = Integer.MIN_VALUE; + return intGameRule; } - public static void mixGameRule(GameRule simpleGameRule, GameRule gameRule) { + public static GameRule mixGameRule(GameRule simpleGameRule, GameRule gameRule) { simpleGameRule.applyMetadata(gameRule); + return simpleGameRule; } public static void addSimpleGameRule(Map gameRuleMap, String ruleKey, String displayName, boolean value) { @@ -69,6 +75,45 @@ public static void addSimpleGameRule(Map gameRuleMap, String r gameRuleMap.put(ruleKey, new IntGameRule(Collections.singletonList(ruleKey), displayName, value)); } + public static Optional createGameRuleNbt(Tag tag) { + if (tag instanceof StringTag stringTag && (tag.getValue().equals("true") || tag.getValue().equals("false"))) { + return Optional.of(new GameRuleNbt.StringByteGameRuleNBT(stringTag)); + } else if (tag instanceof StringTag stringTag && Lang.toIntOrNull(stringTag.getValue()) != null) { + return Optional.of(new GameRuleNbt.StringIntGameRuleNBT(stringTag)); + } else if (tag instanceof IntTag intTag) { + return Optional.of(new GameRuleNbt.IntGameRuleNBT(intTag)); + } else if (tag instanceof ByteTag byteTag) { + return Optional.of(new GameRuleNbt.ByteRuleNBT(byteTag)); + } + return Optional.empty(); + } + + public static Optional getFullGameRule(Tag tag, Map gameRuleMap) { + GameRule dataGameRule = gameRuleMap.getOrDefault(tag.getName(), null); + if (dataGameRule != null && tag instanceof IntTag intTag) { + return Optional.of(mixGameRule(GameRule.createSimpleGameRule(intTag.getName(), intTag.getValue()), dataGameRule)); + } else if (dataGameRule != null && tag instanceof ByteTag byteTag) { + return Optional.of(mixGameRule(GameRule.createSimpleGameRule(byteTag.getName(), byteTag.getValue() == 1), dataGameRule)); + } else if (dataGameRule == null && tag instanceof IntTag intTag) { + return Optional.of(createSimpleGameRule(tag.getName(), intTag.getValue())); + } else if (dataGameRule == null && tag instanceof ByteTag byteTag) { + return Optional.of(createSimpleGameRule(tag.getName(), byteTag.getValue() == 1)); + } else if (dataGameRule != null && tag instanceof StringTag stringTag) { + if (stringTag.getValue().equals("true") || stringTag.getValue().equals("false")) { + return Optional.of(mixGameRule(GameRule.createSimpleGameRule(stringTag.getName(), Boolean.parseBoolean(stringTag.getValue())), dataGameRule)); + } else if (Lang.toIntOrNull(stringTag.getValue()) != null) { + return Optional.of(mixGameRule(GameRule.createSimpleGameRule(stringTag.getName(), Lang.toIntOrNull(stringTag.getValue())), dataGameRule)); + } + } else if (dataGameRule == null && tag instanceof StringTag stringTag) { + if (stringTag.getValue().equals("true") || stringTag.getValue().equals("false")) { + return Optional.of(createSimpleGameRule(stringTag.getName(), Boolean.parseBoolean(stringTag.getValue()))); + } else if (Lang.toIntOrNull(stringTag.getValue()) != null) { + return Optional.of(createSimpleGameRule(stringTag.getName(), Lang.toIntOrNull(stringTag.getValue()))); + } + } + return Optional.empty(); + } + public static Map getCloneGameRuleMap() { return GameRule.GameRuleHolder.cloneGameRuleMap(); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRuleNbt.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRuleNbt.java new file mode 100644 index 0000000000..ad6bcd8e41 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRuleNbt.java @@ -0,0 +1,92 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2025 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.gamerule; + +import com.github.steveice10.opennbt.tag.builtin.ByteTag; +import com.github.steveice10.opennbt.tag.builtin.IntTag; +import com.github.steveice10.opennbt.tag.builtin.StringTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; +import org.jackhuang.hmcl.util.Lang; + +public abstract class GameRuleNbt { + + private Tag gameRuleTag; + + public abstract void changeValue(T newValue); + + public Tag getGameRuleTag() { + return gameRuleTag; + } + + public void setGameRuleTag(Tag gameRuleTag) { + this.gameRuleTag = gameRuleTag; + } + + static class IntGameRuleNBT extends GameRuleNbt { + + public IntGameRuleNBT(IntTag gameRuleTag) { + setGameRuleTag(gameRuleTag); + } + + @Override + public void changeValue(String newValue) { + IntTag intTag = (IntTag) getGameRuleTag(); + Integer value = Lang.toIntOrNull(newValue); + intTag.setValue(value); + } + } + + static class ByteRuleNBT extends GameRuleNbt { + + public ByteRuleNBT(ByteTag gameRuleTag) { + setGameRuleTag(gameRuleTag); + } + + @Override + public void changeValue(Boolean newValue) { + ByteTag byteTag = (ByteTag) getGameRuleTag(); + byteTag.setValue((byte) (newValue ? 1 : 0)); + } + } + + static class StringIntGameRuleNBT extends GameRuleNbt { + + public StringIntGameRuleNBT(StringTag gameRuleTag) { + setGameRuleTag(gameRuleTag); + } + + @Override + public void changeValue(String newValue) { + StringTag stringTag = (StringTag) getGameRuleTag(); + stringTag.setValue(newValue); + } + } + + static class StringByteGameRuleNBT extends GameRuleNbt { + + public StringByteGameRuleNBT(StringTag gameRuleTag) { + setGameRuleTag(gameRuleTag); + } + + @Override + public void changeValue(Boolean newValue) { + StringTag stringTag = (StringTag) getGameRuleTag(); + stringTag.setValue(newValue ? "true" : "false"); + } + } +} From 1099b347e35e5fe00c0932ddcc32393492f101f8 Mon Sep 17 00:00:00 2001 From: mine_ Date: Mon, 8 Dec 2025 22:16:22 +0800 Subject: [PATCH 22/69] =?UTF-8?q?feat:=20=E9=87=8D=E5=91=BD=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/jackhuang/hmcl/ui/versions/GameRulePage.java | 4 ++-- .../jackhuang/hmcl/ui/versions/GameRulePageSkin.java | 8 ++++---- .../java/org/jackhuang/hmcl/gamerule/GameRule.java | 10 +++++----- .../gamerule/{GameRuleNbt.java => GameRuleNBT.java} | 10 +++++----- 4 files changed, 16 insertions(+), 16 deletions(-) rename HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/{GameRuleNbt.java => GameRuleNBT.java} (90%) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java index 3468889c82..df8f3883c5 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java @@ -23,7 +23,7 @@ import javafx.scene.control.Skin; import org.jackhuang.hmcl.game.World; import org.jackhuang.hmcl.gamerule.GameRule; -import org.jackhuang.hmcl.gamerule.GameRuleNbt; +import org.jackhuang.hmcl.gamerule.GameRuleNBT; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.ListPageBase; @@ -78,7 +78,7 @@ public void updateControls() { //LOG.trace(gameRuleTag.toString()); GameRule finalGameRule; - GameRuleNbt gameRuleNbt = GameRule.createGameRuleNbt(gameRuleTag).orElse(null); + GameRuleNBT gameRuleNbt = GameRule.createGameRuleNbt(gameRuleTag).orElse(null); finalGameRule = GameRule.getFullGameRule(gameRuleTag, gameRuleMap).orElse(null); if (gameRuleNbt == null || finalGameRule == null) { return; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java index 8946538046..54f570a9b4 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java @@ -10,7 +10,7 @@ import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; import javafx.scene.layout.*; -import org.jackhuang.hmcl.gamerule.GameRuleNbt; +import org.jackhuang.hmcl.gamerule.GameRuleNBT; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; import org.jackhuang.hmcl.ui.construct.*; @@ -68,11 +68,11 @@ static class GameRuleInfo { String ruleKey; String displayName; - GameRuleNbt gameRuleNbt; + GameRuleNBT gameRuleNbt; BorderPane container = new BorderPane(); - public GameRuleInfo(String ruleKey, String displayName, Boolean onValue, GameRuleNbt gameRuleNbt, Runnable onSave) { + public GameRuleInfo(String ruleKey, String displayName, Boolean onValue, GameRuleNBT gameRuleNbt, Runnable onSave) { this.ruleKey = ruleKey; this.displayName = displayName; this.gameRuleNbt = gameRuleNbt; @@ -91,7 +91,7 @@ public GameRuleInfo(String ruleKey, String displayName, Boolean onValue, GameRul container.setCenter(toggleButton); } - public GameRuleInfo(String ruleKey, String displayName, Integer currentValue, int minValue, int maxValue, GameRuleNbt gameRuleNbt, Runnable onSave) { + public GameRuleInfo(String ruleKey, String displayName, Integer currentValue, int minValue, int maxValue, GameRuleNBT gameRuleNbt, Runnable onSave) { this.ruleKey = ruleKey; this.displayName = displayName; this.gameRuleNbt = gameRuleNbt; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java index ade6df0cb8..ea1d8b7f24 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java @@ -75,15 +75,15 @@ public static void addSimpleGameRule(Map gameRuleMap, String r gameRuleMap.put(ruleKey, new IntGameRule(Collections.singletonList(ruleKey), displayName, value)); } - public static Optional createGameRuleNbt(Tag tag) { + public static Optional createGameRuleNbt(Tag tag) { if (tag instanceof StringTag stringTag && (tag.getValue().equals("true") || tag.getValue().equals("false"))) { - return Optional.of(new GameRuleNbt.StringByteGameRuleNBT(stringTag)); + return Optional.of(new GameRuleNBT.StringByteGameRuleNBT(stringTag)); } else if (tag instanceof StringTag stringTag && Lang.toIntOrNull(stringTag.getValue()) != null) { - return Optional.of(new GameRuleNbt.StringIntGameRuleNBT(stringTag)); + return Optional.of(new GameRuleNBT.StringIntGameRuleNBT(stringTag)); } else if (tag instanceof IntTag intTag) { - return Optional.of(new GameRuleNbt.IntGameRuleNBT(intTag)); + return Optional.of(new GameRuleNBT.IntGameRuleNBT(intTag)); } else if (tag instanceof ByteTag byteTag) { - return Optional.of(new GameRuleNbt.ByteRuleNBT(byteTag)); + return Optional.of(new GameRuleNBT.ByteRuleNBT(byteTag)); } return Optional.empty(); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRuleNbt.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRuleNBT.java similarity index 90% rename from HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRuleNbt.java rename to HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRuleNBT.java index ad6bcd8e41..829418dd3d 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRuleNbt.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRuleNBT.java @@ -23,7 +23,7 @@ import com.github.steveice10.opennbt.tag.builtin.Tag; import org.jackhuang.hmcl.util.Lang; -public abstract class GameRuleNbt { +public abstract class GameRuleNBT { private Tag gameRuleTag; @@ -37,7 +37,7 @@ public void setGameRuleTag(Tag gameRuleTag) { this.gameRuleTag = gameRuleTag; } - static class IntGameRuleNBT extends GameRuleNbt { + static class IntGameRuleNBT extends GameRuleNBT { public IntGameRuleNBT(IntTag gameRuleTag) { setGameRuleTag(gameRuleTag); @@ -51,7 +51,7 @@ public void changeValue(String newValue) { } } - static class ByteRuleNBT extends GameRuleNbt { + static class ByteRuleNBT extends GameRuleNBT { public ByteRuleNBT(ByteTag gameRuleTag) { setGameRuleTag(gameRuleTag); @@ -64,7 +64,7 @@ public void changeValue(Boolean newValue) { } } - static class StringIntGameRuleNBT extends GameRuleNbt { + static class StringIntGameRuleNBT extends GameRuleNBT { public StringIntGameRuleNBT(StringTag gameRuleTag) { setGameRuleTag(gameRuleTag); @@ -77,7 +77,7 @@ public void changeValue(String newValue) { } } - static class StringByteGameRuleNBT extends GameRuleNbt { + static class StringByteGameRuleNBT extends GameRuleNBT { public StringByteGameRuleNBT(StringTag gameRuleTag) { setGameRuleTag(gameRuleTag); From 7c8bb5cbd8b4479c8fc796e8d9d7ce8d26922e3f Mon Sep 17 00:00:00 2001 From: mine_ Date: Mon, 8 Dec 2025 22:37:08 +0800 Subject: [PATCH 23/69] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=90=9C?= =?UTF-8?q?=E7=B4=A2=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/GameRulePage.java | 55 ++++++++++++++----- .../hmcl/ui/versions/GameRulePageSkin.java | 18 +++++- 2 files changed, 56 insertions(+), 17 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java index df8f3883c5..52cafc9e95 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java @@ -27,10 +27,15 @@ import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.ListPageBase; +import org.jackhuang.hmcl.util.StringUtils; +import org.jetbrains.annotations.NotNull; import java.io.IOException; import java.nio.file.Files; +import java.util.Locale; import java.util.Map; +import java.util.function.Predicate; +import java.util.regex.Pattern; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.logging.Logger.LOG; @@ -75,32 +80,29 @@ public void updateControls() { gameRuleCompoundTag = dataTag.get("GameRules"); } gameRuleCompoundTag.iterator().forEachRemaining(gameRuleTag -> { - //LOG.trace(gameRuleTag.toString()); - GameRule finalGameRule; - - GameRuleNBT gameRuleNbt = GameRule.createGameRuleNbt(gameRuleTag).orElse(null); - finalGameRule = GameRule.getFullGameRule(gameRuleTag, gameRuleMap).orElse(null); - if (gameRuleNbt == null || finalGameRule == null) { + GameRuleNBT gameRuleNBT = GameRule.createGameRuleNbt(gameRuleTag).orElse(null); + GameRule finalGameRule = GameRule.getFullGameRule(gameRuleTag, gameRuleMap).orElse(null); + if (gameRuleNBT == null || finalGameRule == null) { return; } - //LOG.trace(finalGameRule.getRuleKey().toString()); - String displayText; try { - displayText = i18n(finalGameRule.getDisplayI18nKey()); + if (StringUtils.isNotBlank(finalGameRule.getDisplayI18nKey())) { + displayText = i18n(finalGameRule.getDisplayI18nKey()); + } else { + displayText = ""; + } } catch (Exception e) { - displayText = finalGameRule.getDisplayI18nKey(); + displayText = ""; } if (finalGameRule instanceof GameRule.IntGameRule intGameRule) { - gameRuleList.add(new GameRulePageSkin.GameRuleInfo(intGameRule.getRuleKey().get(0), displayText, intGameRule.getValue(), intGameRule.getMinValue(), intGameRule.getMaxValue(), gameRuleNbt, this::saveLevelDat)); + gameRuleList.add(new GameRulePageSkin.GameRuleInfo(intGameRule.getRuleKey().get(0), displayText, intGameRule.getValue(), intGameRule.getMinValue(), intGameRule.getMaxValue(), gameRuleNBT, this::saveLevelDat)); } else if (finalGameRule instanceof GameRule.BooleanGameRule booleanGameRule) { - gameRuleList.add(new GameRulePageSkin.GameRuleInfo(booleanGameRule.getRuleKey().get(0), displayText, booleanGameRule.getValue(), gameRuleNbt, this::saveLevelDat)); + gameRuleList.add(new GameRulePageSkin.GameRuleInfo(booleanGameRule.getRuleKey().get(0), displayText, booleanGameRule.getValue(), gameRuleNBT, this::saveLevelDat)); } }); - - } @Override @@ -123,4 +125,29 @@ void saveLevelDat() { LOG.warning("Failed to save level.dat of world " + world.getWorldName(), e); } } + + @NotNull Predicate updateSearchPredicate(String queryString) { + if (queryString.isBlank()) { + return gameRuleInfo -> true; + } + + final Predicate stringPredicate; + if (queryString.startsWith("regex:")) { + try { + Pattern pattern = Pattern.compile(StringUtils.substringAfter(queryString, "regex:")); + stringPredicate = s -> s != null && pattern.matcher(s).find(); + } catch (Exception e) { + return dataPack -> false; + } + } else { + String lowerCaseFilter = queryString.toLowerCase(Locale.ROOT); + stringPredicate = s -> s != null && s.toLowerCase(Locale.ROOT).contains(lowerCaseFilter); + } + + return gameRuleInfo -> { + String displayName = gameRuleInfo.displayName; + String ruleKey = gameRuleInfo.ruleKey; + return stringPredicate.test(displayName) || stringPredicate.test(ruleKey); + }; + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java index 54f570a9b4..4d12b92394 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java @@ -3,6 +3,8 @@ import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXListView; import com.jfoenix.controls.JFXTextField; +import javafx.animation.PauseTransition; +import javafx.collections.transformation.FilteredList; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.Label; @@ -10,6 +12,7 @@ import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; import javafx.scene.layout.*; +import javafx.util.Duration; import org.jackhuang.hmcl.gamerule.GameRuleNBT; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; @@ -25,9 +28,10 @@ class GameRulePageSkin extends SkinBase { private final HBox searchBar; private final JFXTextField searchField; JFXListView listView = new JFXListView<>(); + private final FilteredList filteredList; - protected GameRulePageSkin(GameRulePage control) { - super(control); + GameRulePageSkin(GameRulePage skinnable) { + super(skinnable); StackPane pane = new StackPane(); pane.setPadding(new Insets(10)); pane.getStyleClass().addAll("notice-pane"); @@ -35,12 +39,20 @@ protected GameRulePageSkin(GameRulePage control) { ComponentList root = new ComponentList(); root.getStyleClass().add("no-padding"); + filteredList = new FilteredList<>(skinnable.getItems()); + { searchBar = new HBox(); searchBar.setAlignment(Pos.CENTER); searchBar.setPadding(new Insets(0, 5, 0, 5)); searchField = new JFXTextField(); searchField.setPromptText(i18n("search")); + PauseTransition pause = new PauseTransition(Duration.millis(100)); + pause.setOnFinished(event -> filteredList.setPredicate(skinnable.updateSearchPredicate(searchField.getText()))); + searchField.textProperty().addListener((observable) -> { + pause.setRate(1); + pause.playFromStart(); + }); HBox.setHgrow(searchField, Priority.ALWAYS); JFXButton closeSearchBar = createToolbarButton2(null, SVG.CLOSE, searchField::clear); @@ -54,7 +66,7 @@ protected GameRulePageSkin(GameRulePage control) { center.getStyleClass().add("large-spinner-pane"); center.setContent(listView); Holder lastCell = new Holder<>(); - listView.setItems(getSkinnable().getItems()); + listView.setItems(filteredList); listView.setCellFactory(x -> new GameRuleListCell(listView, lastCell)); FXUtils.ignoreEvent(listView, KeyEvent.KEY_PRESSED, e -> e.getCode() == KeyCode.ESCAPE); root.getContent().add(center); From 4f7311e1da5052a024f5e15e7faf886fabe21e3b Mon Sep 17 00:00:00 2001 From: mine_ Date: Mon, 8 Dec 2025 23:07:05 +0800 Subject: [PATCH 24/69] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96gamerule.json?= =?UTF-8?q?=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/assets/gamerule/gamerule.json | 142 ++++++++++-------- 1 file changed, 82 insertions(+), 60 deletions(-) diff --git a/HMCLCore/src/main/resources/assets/gamerule/gamerule.json b/HMCLCore/src/main/resources/assets/gamerule/gamerule.json index 5dc838d228..6219a02329 100644 --- a/HMCLCore/src/main/resources/assets/gamerule/gamerule.json +++ b/HMCLCore/src/main/resources/assets/gamerule/gamerule.json @@ -2,7 +2,7 @@ { "ruleKey": [ "minecraft:advance_time", - "minecraft:doDaylightCycle" + "doDaylightCycle" ], "displayI18nKey": "gamerule.advance_time", "defaultValue": true @@ -10,21 +10,22 @@ { "ruleKey": [ "minecraft:advance_weather", - "minecraft:doWeatherCycle" + "doWeatherCycle" ], "displayI18nKey": "gamerule.advance_weather", "defaultValue": true }, { "ruleKey": [ - "minecraft:allowFireTicksAwayFromPlayer" + "allowFireTicksAwayFromPlayer" ], "displayI18nKey": "gamerule.allow_fire_ticks_away_from_player", "defaultValue": false }, { "ruleKey": [ - "minecraft:allow_entering_nether_using_portals" + "minecraft:allow_entering_nether_using_portals", + "allowEnteringNetherUsingPortals" ], "displayI18nKey": "gamerule.allow_entering_nether_using_portals", "defaultValue": true @@ -32,14 +33,15 @@ { "ruleKey": [ "minecraft:block_drops", - "minecraft:doTileDrops" + "doTileDrops" ], "displayI18nKey": "gamerule.block_drops", "defaultValue": true }, { "ruleKey": [ - "minecraft:block_explosion_drop_decay" + "minecraft:block_explosion_drop_decay", + "blockExplosionDropDecay" ], "displayI18nKey": "gamerule.block_explosion_drop_decay", "defaultValue": true @@ -47,7 +49,7 @@ { "ruleKey": [ "minecraft:command_block_output", - "minecraft:commandBlockOutput" + "commandBlockOutput" ], "displayI18nKey": "gamerule.command_block_output", "defaultValue": true @@ -55,21 +57,22 @@ { "ruleKey": [ "minecraft:command_blocks_work", - "minecraft:commandBlocksEnabled" + "commandBlocksEnabled" ], "displayI18nKey": "gamerule.command_blocks_work", "defaultValue": true }, { "ruleKey": [ - "minecraft:doFireTick" + "doFireTick" ], "displayI18nKey": "gamerule.do_fire_tick", "defaultValue": true }, { "ruleKey": [ - "minecraft:drowning_damage" + "minecraft:drowning_damage", + "drowningDamage" ], "displayI18nKey": "gamerule.drowning_damage", "defaultValue": true @@ -77,14 +80,15 @@ { "ruleKey": [ "minecraft:elytra_movement_check", - "minecraft:disableElytraMovementCheck" + "disableElytraMovementCheck" ], "displayI18nKey": "gamerule.elytra_movement_check", "defaultValue": true }, { "ruleKey": [ - "minecraft:ender_pearls_vanish_on_death" + "minecraft:ender_pearls_vanish_on_death", + "enderPearlsVanishOnDeath" ], "displayI18nKey": "gamerule.ender_pearls_vanish_on_death", "defaultValue": true @@ -92,21 +96,23 @@ { "ruleKey": [ "minecraft:entity_drops", - "minecraft:doEntityDrops" + "doEntityDrops" ], "displayI18nKey": "gamerule.entity_drops", "defaultValue": true }, { "ruleKey": [ - "minecraft:fall_damage" + "minecraft:fall_damage", + "fallDamage" ], "displayI18nKey": "gamerule.fall_damage", "defaultValue": true }, { "ruleKey": [ - "minecraft:fire_damage" + "minecraft:fire_damage", + "fireDamage" ], "displayI18nKey": "gamerule.fire_damage", "defaultValue": true @@ -121,21 +127,24 @@ }, { "ruleKey": [ - "minecraft:forgive_dead_players" + "minecraft:forgive_dead_players", + "forgiveDeadPlayers" ], "displayI18nKey": "gamerule.forgive_dead_players", "defaultValue": true }, { "ruleKey": [ - "minecraft:freeze_damage" + "minecraft:freeze_damage", + "freezeDamage" ], "displayI18nKey": "gamerule.freeze_damage", "defaultValue": true }, { "ruleKey": [ - "minecraft:global_sound_events" + "minecraft:global_sound_events", + "globalSoundEvents" ], "displayI18nKey": "gamerule.global_sound_events", "defaultValue": true @@ -143,7 +152,7 @@ { "ruleKey": [ "minecraft:immediate_respawn", - "minecraft:doImmediateRespawn" + "doImmediateRespawn" ], "displayI18nKey": "gamerule.immediate_respawn", "defaultValue": false @@ -151,14 +160,15 @@ { "ruleKey": [ "minecraft:keep_inventory", - "minecraft:keepInventory" + "keepInventory" ], "displayI18nKey": "gamerule.keep_inventory", "defaultValue": false }, { "ruleKey": [ - "minecraft:lava_source_conversion" + "minecraft:lava_source_conversion", + "lavaSourceConversion" ], "displayI18nKey": "gamerule.lava_source_conversion", "defaultValue": false @@ -166,14 +176,15 @@ { "ruleKey": [ "minecraft:limited_crafting", - "minecraft:doLimitedCrafting" + "doLimitedCrafting" ], "displayI18nKey": "gamerule.limited_crafting", "defaultValue": false }, { "ruleKey": [ - "minecraft:locator_bar" + "minecraft:locator_bar", + "locatorBar" ], "displayI18nKey": "gamerule.locator_bar", "defaultValue": true @@ -181,7 +192,7 @@ { "ruleKey": [ "minecraft:log_admin_commands", - "minecraft:logAdminCommands" + "logAdminCommands" ], "displayI18nKey": "gamerule.log_admin_commands", "defaultValue": true @@ -189,7 +200,7 @@ { "ruleKey": [ "minecraft:max_block_modifications", - "minecraft:commandModificationBlockLimit" + "commandModificationBlockLimit" ], "displayI18nKey": "gamerule.max_block_modifications", "defaultValue": 32768, @@ -198,7 +209,7 @@ { "ruleKey": [ "minecraft:max_command_forks", - "minecraft:maxCommandForkCount" + "maxCommandForkCount" ], "displayI18nKey": "gamerule.max_command_forks", "defaultValue": 65536, @@ -207,7 +218,7 @@ { "ruleKey": [ "minecraft:max_command_sequence_length", - "minecraft:maxCommandChainLength" + "maxCommandChainLength" ], "displayI18nKey": "gamerule.max_command_sequence_length", "defaultValue": 65536, @@ -216,7 +227,7 @@ { "ruleKey": [ "minecraft:max_entity_cramming", - "minecraft:maxEntityCramming" + "maxEntityCramming" ], "displayI18nKey": "gamerule.max_entity_cramming", "defaultValue": 24, @@ -225,7 +236,7 @@ { "ruleKey": [ "minecraft:max_minecart_speed", - "minecraft:minecartMaxSpeed" + "minecartMaxSpeed" ], "displayI18nKey": "gamerule.max_minecart_speed", "defaultValue": 8, @@ -235,7 +246,7 @@ { "ruleKey": [ "minecraft:max_snow_accumulation_height", - "minecraft:snowAccumulationHeight" + "snowAccumulationHeight" ], "displayI18nKey": "gamerule.max_snow_accumulation_height", "defaultValue": 1, @@ -245,14 +256,15 @@ { "ruleKey": [ "minecraft:mob_drops", - "minecraft:doMobLoot" + "doMobLoot" ], "displayI18nKey": "gamerule.mob_drops", "defaultValue": true }, { "ruleKey": [ - "minecraft:mob_explosion_drop_decay" + "minecraft:mob_explosion_drop_decay", + "mobExplosionDropDecay" ], "displayI18nKey": "gamerule.mob_explosion_drop_decay", "defaultValue": true @@ -260,7 +272,7 @@ { "ruleKey": [ "minecraft:mob_griefing", - "minecraft:mobGriefing" + "mobGriefing" ], "displayI18nKey": "gamerule.mob_griefing", "defaultValue": true @@ -268,7 +280,7 @@ { "ruleKey": [ "minecraft:natural_health_regeneration", - "minecraft:naturalRegeneration" + "naturalRegeneration" ], "displayI18nKey": "gamerule.natural_health_regeneration", "defaultValue": true @@ -276,14 +288,15 @@ { "ruleKey": [ "minecraft:player_movement_check", - "minecraft:disablePlayerMovementCheck" + "disablePlayerMovementCheck" ], "displayI18nKey": "gamerule.player_movement_check", "defaultValue": true }, { "ruleKey": [ - "minecraft:players_nether_portal_creative_delay" + "minecraft:players_nether_portal_creative_delay", + "playersNetherPortalCreativeDelay" ], "displayI18nKey": "gamerule.players_nether_portal_creative_delay", "defaultValue": 0, @@ -291,7 +304,8 @@ }, { "ruleKey": [ - "minecraft:players_nether_portal_default_delay" + "minecraft:players_nether_portal_default_delay", + "playersNetherPortalDefaultDelay" ], "displayI18nKey": "gamerule.players_nether_portal_default_delay", "defaultValue": 80, @@ -299,7 +313,8 @@ }, { "ruleKey": [ - "minecraft:players_sleeping_percentage" + "minecraft:players_sleeping_percentage", + "playersSleepingPercentage" ], "displayI18nKey": "gamerule.players_sleeping_percentage", "defaultValue": 100, @@ -307,14 +322,16 @@ }, { "ruleKey": [ - "minecraft:projectiles_can_break_blocks" + "minecraft:projectiles_can_break_blocks", + "projectilesCanBreakBlocks" ], "displayI18nKey": "gamerule.projectiles_can_break_blocks", "defaultValue": true }, { "ruleKey": [ - "minecraft:pvp" + "minecraft:pvp", + "pvp" ], "displayI18nKey": "gamerule.pvp", "defaultValue": true @@ -322,7 +339,7 @@ { "ruleKey": [ "minecraft:raids", - "minecraft:disableRaids" + "disableRaids" ], "displayI18nKey": "gamerule.raids", "defaultValue": true @@ -330,7 +347,7 @@ { "ruleKey": [ "minecraft:random_tick_speed", - "minecraft:randomTickSpeed" + "randomTickSpeed" ], "displayI18nKey": "gamerule.random_tick_speed", "defaultValue": 3, @@ -339,7 +356,7 @@ { "ruleKey": [ "minecraft:reduced_debug_info", - "minecraft:reducedDebugInfo" + "reducedDebugInfo" ], "displayI18nKey": "gamerule.reduced_debug_info", "defaultValue": false @@ -347,7 +364,7 @@ { "ruleKey": [ "minecraft:respawn_radius", - "minecraft:spawnRadius" + "spawnRadius" ], "displayI18nKey": "gamerule.respawn_radius", "defaultValue": 10, @@ -356,7 +373,7 @@ { "ruleKey": [ "minecraft:send_command_feedback", - "minecraft:sendCommandFeedback" + "sendCommandFeedback" ], "displayI18nKey": "gamerule.send_command_feedback", "defaultValue": true @@ -364,7 +381,7 @@ { "ruleKey": [ "minecraft:show_advancement_messages", - "minecraft:announceAdvancements" + "announceAdvancements" ], "displayI18nKey": "gamerule.show_advancement_messages", "defaultValue": true @@ -372,7 +389,7 @@ { "ruleKey": [ "minecraft:show_death_messages", - "minecraft:showDeathMessages" + "showDeathMessages" ], "displayI18nKey": "gamerule.show_death_messages", "defaultValue": true @@ -380,14 +397,15 @@ { "ruleKey": [ "minecraft:spawn_mobs", - "minecraft:doMobSpawning" + "doMobSpawning" ], "displayI18nKey": "gamerule.spawn_mobs", "defaultValue": true }, { "ruleKey": [ - "minecraft:spawn_monsters" + "minecraft:spawn_monsters", + "spawnMonsters" ], "displayI18nKey": "gamerule.spawn_monsters", "defaultValue": true @@ -395,7 +413,7 @@ { "ruleKey": [ "minecraft:spawn_patrols", - "minecraft:doPatrolSpawning" + "doPatrolSpawning" ], "displayI18nKey": "gamerule.spawn_patrols", "defaultValue": true @@ -403,7 +421,7 @@ { "ruleKey": [ "minecraft:spawn_phantoms", - "minecraft:doInsomnia" + "doInsomnia" ], "displayI18nKey": "gamerule.spawn_phantoms", "defaultValue": true @@ -411,7 +429,7 @@ { "ruleKey": [ "minecraft:spawn_wandering_traders", - "minecraft:doTraderSpawning" + "doTraderSpawning" ], "displayI18nKey": "gamerule.spawn_wandering_traders", "defaultValue": true @@ -419,7 +437,7 @@ { "ruleKey": [ "minecraft:spawn_wardens", - "minecraft:doWardenSpawning" + "doWardenSpawning" ], "displayI18nKey": "gamerule.spawn_wardens", "defaultValue": true @@ -427,14 +445,15 @@ { "ruleKey": [ "minecraft:spawner_blocks_work", - "minecraft:spawnerBlocksEnabled" + "spawnerBlocksEnabled" ], "displayI18nKey": "gamerule.spawner_blocks_work", "defaultValue": true }, { "ruleKey": [ - "minecraft:spectators_generate_chunks" + "minecraft:spectators_generate_chunks", + "spectatorsGenerateChunks" ], "displayI18nKey": "gamerule.spectators_generate_chunks", "defaultValue": true @@ -442,21 +461,23 @@ { "ruleKey": [ "minecraft:spread_vines", - "minecraft:doVinesSpread" + "doVinesSpread" ], "displayI18nKey": "gamerule.spread_vines", "defaultValue": true }, { "ruleKey": [ - "minecraft:tnt_explodes" + "minecraft:tnt_explodes", + "tntExplodes" ], "displayI18nKey": "gamerule.tnt_explodes", "defaultValue": true }, { "ruleKey": [ - "minecraft:tnt_explosion_drop_decay" + "minecraft:tnt_explosion_drop_decay", + "tntExplosionDropDecay" ], "displayI18nKey": "gamerule.tnt_explosion_drop_decay", "defaultValue": false @@ -464,14 +485,15 @@ { "ruleKey": [ "minecraft:universal_anger", - "minecraft:universalAnger" + "universalAnger" ], "displayI18nKey": "gamerule.universal_anger", "defaultValue": false }, { "ruleKey": [ - "minecraft:water_source_conversion" + "minecraft:water_source_conversion", + "waterSourceConversion" ], "displayI18nKey": "gamerule.water_source_conversion", "defaultValue": true From 557b820f396b23bff1be2db4c3896be13af5f664 Mon Sep 17 00:00:00 2001 From: mine_ Date: Tue, 9 Dec 2025 11:35:38 +0800 Subject: [PATCH 25/69] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/GameRulePage.java | 53 +++++----- .../hmcl/ui/versions/GameRulePageSkin.java | 22 ++++- .../org/jackhuang/hmcl/gamerule/GameRule.java | 99 +++++++++++++------ .../jackhuang/hmcl/gamerule/GameRuleNBT.java | 59 ++++++----- 4 files changed, 149 insertions(+), 84 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java index 52cafc9e95..b21806f99c 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java @@ -23,7 +23,6 @@ import javafx.scene.control.Skin; import org.jackhuang.hmcl.game.World; import org.jackhuang.hmcl.gamerule.GameRule; -import org.jackhuang.hmcl.gamerule.GameRuleNBT; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.ListPageBase; @@ -75,33 +74,33 @@ public void updateControls() { CompoundTag dataTag = levelDat.get("Data"); CompoundTag gameRuleCompoundTag; gameRuleCompoundTag = dataTag.get("game_rules"); - if (gameRuleCompoundTag != null) { - } else { + if (gameRuleCompoundTag == null) { gameRuleCompoundTag = dataTag.get("GameRules"); } - gameRuleCompoundTag.iterator().forEachRemaining(gameRuleTag -> { - GameRuleNBT gameRuleNBT = GameRule.createGameRuleNbt(gameRuleTag).orElse(null); - GameRule finalGameRule = GameRule.getFullGameRule(gameRuleTag, gameRuleMap).orElse(null); - if (gameRuleNBT == null || finalGameRule == null) { - return; - } - String displayText; - try { - if (StringUtils.isNotBlank(finalGameRule.getDisplayI18nKey())) { - displayText = i18n(finalGameRule.getDisplayI18nKey()); - } else { - displayText = ""; - } - } catch (Exception e) { - displayText = ""; - } + if (gameRuleCompoundTag == null) { + LOG.warning("Neither 'game_rules' nor 'GameRules' tag found in level.dat"); + return; + } - if (finalGameRule instanceof GameRule.IntGameRule intGameRule) { - gameRuleList.add(new GameRulePageSkin.GameRuleInfo(intGameRule.getRuleKey().get(0), displayText, intGameRule.getValue(), intGameRule.getMinValue(), intGameRule.getMaxValue(), gameRuleNBT, this::saveLevelDat)); - } else if (finalGameRule instanceof GameRule.BooleanGameRule booleanGameRule) { - gameRuleList.add(new GameRulePageSkin.GameRuleInfo(booleanGameRule.getRuleKey().get(0), displayText, booleanGameRule.getValue(), gameRuleNBT, this::saveLevelDat)); - } + gameRuleCompoundTag.iterator().forEachRemaining(gameRuleTag -> { + GameRule.createGameRuleNbt(gameRuleTag).ifPresent(gameRuleNBT -> { + GameRule.getFullGameRule(gameRuleTag, gameRuleMap).ifPresent(gameRule -> { + String displayText = ""; + try { + if (StringUtils.isNotBlank(gameRule.getDisplayI18nKey())) { + displayText = i18n(gameRule.getDisplayI18nKey()); + } + } catch (Exception e) { + LOG.warning("Failed to get i18n text for key: " + gameRule.getDisplayI18nKey(), e); + } + if (gameRule instanceof GameRule.IntGameRule intGameRule) { + gameRuleList.add(new GameRulePageSkin.GameRuleInfo(intGameRule.getRuleKey().get(0), displayText, intGameRule.getValue(), intGameRule.getMinValue(), intGameRule.getMaxValue(), gameRuleNBT, this::saveLevelDat)); + } else if (gameRule instanceof GameRule.BooleanGameRule booleanGameRule) { + gameRuleList.add(new GameRulePageSkin.GameRuleInfo(booleanGameRule.getRuleKey().get(0), displayText, booleanGameRule.getValue(), gameRuleNBT, this::saveLevelDat)); + } + }); + }); }); } @@ -144,10 +143,6 @@ void saveLevelDat() { stringPredicate = s -> s != null && s.toLowerCase(Locale.ROOT).contains(lowerCaseFilter); } - return gameRuleInfo -> { - String displayName = gameRuleInfo.displayName; - String ruleKey = gameRuleInfo.ruleKey; - return stringPredicate.test(displayName) || stringPredicate.test(ruleKey); - }; + return gameRuleInfo -> stringPredicate.test(gameRuleInfo.displayName) || stringPredicate.test(gameRuleInfo.ruleKey); } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java index 4d12b92394..fad4d68397 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java @@ -1,5 +1,23 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2025 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package org.jackhuang.hmcl.ui.versions; +import com.github.steveice10.opennbt.tag.builtin.Tag; import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXListView; import com.jfoenix.controls.JFXTextField; @@ -84,7 +102,7 @@ static class GameRuleInfo { BorderPane container = new BorderPane(); - public GameRuleInfo(String ruleKey, String displayName, Boolean onValue, GameRuleNBT gameRuleNbt, Runnable onSave) { + public GameRuleInfo(String ruleKey, String displayName, Boolean onValue, GameRuleNBT gameRuleNbt, Runnable onSave) { this.ruleKey = ruleKey; this.displayName = displayName; this.gameRuleNbt = gameRuleNbt; @@ -103,7 +121,7 @@ public GameRuleInfo(String ruleKey, String displayName, Boolean onValue, GameRul container.setCenter(toggleButton); } - public GameRuleInfo(String ruleKey, String displayName, Integer currentValue, int minValue, int maxValue, GameRuleNBT gameRuleNbt, Runnable onSave) { + public GameRuleInfo(String ruleKey, String displayName, Integer currentValue, int minValue, int maxValue, GameRuleNBT gameRuleNbt, Runnable onSave) { this.ruleKey = ruleKey; this.displayName = displayName; this.gameRuleNbt = gameRuleNbt; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java index ea1d8b7f24..bdd585a8b3 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java @@ -35,7 +35,17 @@ import java.io.InputStream; import java.lang.reflect.Type; import java.util.*; - +import java.util.stream.Collectors; + +/// Represents an abstract game rule in Minecraft (e.g., `doDaylightCycle`, `randomTickSpeed`). +/// +/// This class handles the logic for: +/// * Defining rule types (Boolean or Integer). +/// * Parsing rules from NBT tags (read from `level.dat`). +/// * Serializing/Deserializing rules via GSON. +/// * Binding values to JavaFX properties for UI integration. +/// +/// It is a sealed class permitting only [BooleanGameRule] and [IntGameRule]. @JsonSerializable @JsonAdapter(GameRule.GameRuleDeserializer.class) public sealed abstract class GameRule permits GameRule.BooleanGameRule, GameRule.IntGameRule { @@ -62,6 +72,42 @@ public static GameRule createSimpleGameRule(String ruleKey, int value) { return intGameRule; } + /// Parses an NBT Tag to create a corresponding [GameRule]. + /// + /// This method handles type coercion: + /// * [IntTag] -> [IntGameRule] + /// * [ByteTag] -> [BooleanGameRule] + /// * [StringTag] -> Tries to parse as [BooleanGameRule] ("true"/"false") or [IntGameRule]. + /// + /// @param tag The NBT tag to parse. + /// @return An Optional containing the GameRule if parsing was successful. + private static Optional createSimpleRuleFromTag(Tag tag) { + String name = tag.getName(); + + if (tag instanceof IntTag intTag) { + return Optional.of(createSimpleGameRule(name, intTag.getValue())); + } + if (tag instanceof ByteTag byteTag) { + return Optional.of(createSimpleGameRule(name, byteTag.getValue() == 1)); + } + if (tag instanceof StringTag stringTag) { + String value = stringTag.getValue(); + if ("true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value)) { + return Optional.of(createSimpleGameRule(name, Boolean.parseBoolean(value))); + } + Integer intValue = Lang.toIntOrNull(value); + if (intValue != null) { + return Optional.of(createSimpleGameRule(name, intValue)); + } + } + + return Optional.empty(); + } + + /// Applies metadata from a definition rule to a simple value rule. + /// + /// This is used to hydrate a raw rule read from NBT (which only has a ruleKey and value) + /// with static definition data (translation keys, min/max values) loaded from JSON. public static GameRule mixGameRule(GameRule simpleGameRule, GameRule gameRule) { simpleGameRule.applyMetadata(gameRule); return simpleGameRule; @@ -75,6 +121,10 @@ public static void addSimpleGameRule(Map gameRuleMap, String r gameRuleMap.put(ruleKey, new IntGameRule(Collections.singletonList(ruleKey), displayName, value)); } + /// Creates a [GameRuleNBT] wrapper around an NBT Tag. + /// Used for unified changing operations back to NBT format. + /// + /// @see GameRuleNBT public static Optional createGameRuleNbt(Tag tag) { if (tag instanceof StringTag stringTag && (tag.getValue().equals("true") || tag.getValue().equals("false"))) { return Optional.of(new GameRuleNBT.StringByteGameRuleNBT(stringTag)); @@ -83,35 +133,21 @@ public static Optional createGameRuleNbt(Tag tag) { } else if (tag instanceof IntTag intTag) { return Optional.of(new GameRuleNBT.IntGameRuleNBT(intTag)); } else if (tag instanceof ByteTag byteTag) { - return Optional.of(new GameRuleNBT.ByteRuleNBT(byteTag)); + return Optional.of(new GameRuleNBT.ByteGameRuleNBT(byteTag)); } return Optional.empty(); } + /// Retrieves a fully populated GameRule based on an NBT tag. + /// + /// This combines parsing the tag [#createGameRuleNbt(Tag)] and applying known metadata + /// from the provided `gameRuleMap`. public static Optional getFullGameRule(Tag tag, Map gameRuleMap) { - GameRule dataGameRule = gameRuleMap.getOrDefault(tag.getName(), null); - if (dataGameRule != null && tag instanceof IntTag intTag) { - return Optional.of(mixGameRule(GameRule.createSimpleGameRule(intTag.getName(), intTag.getValue()), dataGameRule)); - } else if (dataGameRule != null && tag instanceof ByteTag byteTag) { - return Optional.of(mixGameRule(GameRule.createSimpleGameRule(byteTag.getName(), byteTag.getValue() == 1), dataGameRule)); - } else if (dataGameRule == null && tag instanceof IntTag intTag) { - return Optional.of(createSimpleGameRule(tag.getName(), intTag.getValue())); - } else if (dataGameRule == null && tag instanceof ByteTag byteTag) { - return Optional.of(createSimpleGameRule(tag.getName(), byteTag.getValue() == 1)); - } else if (dataGameRule != null && tag instanceof StringTag stringTag) { - if (stringTag.getValue().equals("true") || stringTag.getValue().equals("false")) { - return Optional.of(mixGameRule(GameRule.createSimpleGameRule(stringTag.getName(), Boolean.parseBoolean(stringTag.getValue())), dataGameRule)); - } else if (Lang.toIntOrNull(stringTag.getValue()) != null) { - return Optional.of(mixGameRule(GameRule.createSimpleGameRule(stringTag.getName(), Lang.toIntOrNull(stringTag.getValue())), dataGameRule)); - } - } else if (dataGameRule == null && tag instanceof StringTag stringTag) { - if (stringTag.getValue().equals("true") || stringTag.getValue().equals("false")) { - return Optional.of(createSimpleGameRule(stringTag.getName(), Boolean.parseBoolean(stringTag.getValue()))); - } else if (Lang.toIntOrNull(stringTag.getValue()) != null) { - return Optional.of(createSimpleGameRule(stringTag.getName(), Lang.toIntOrNull(stringTag.getValue()))); - } - } - return Optional.empty(); + return createSimpleRuleFromTag(tag).map(simpleGameRule -> { + Optional.ofNullable(gameRuleMap.get(tag.getName())) + .ifPresent(simpleGameRule::applyMetadata); + return simpleGameRule; + }); } public static Map getCloneGameRuleMap() { @@ -120,6 +156,7 @@ public static Map getCloneGameRuleMap() { public abstract GameRule clone(); + /// Copies metadata (e.g., descriptions, ranges) from the source rule to this instance. public abstract void applyMetadata(GameRule metadataSource); public void setRuleKey(List ruleKey) { @@ -138,6 +175,8 @@ public String getDisplayI18nKey() { return displayI18nKey; } + /// Implementation of a boolean-based GameRule. + /// Wraps values in [BooleanProperty] for UI binding. public static final class BooleanGameRule extends GameRule { private final BooleanProperty value = new SimpleBooleanProperty(false); private final BooleanProperty defaultValue = new SimpleBooleanProperty(false); @@ -191,6 +230,8 @@ public void setValue(boolean value) { } } + /// Implementation of an integer-based GameRule. + /// Wraps values in [IntegerProperty] and supports min/max value validation. public static final class IntGameRule extends GameRule { private final IntegerProperty value = new SimpleIntegerProperty(0); private final IntegerProperty defaultValue = new SimpleIntegerProperty(0); @@ -266,6 +307,8 @@ public void setMinValue(int minValue) { } } + /// Custom GSON deserializer for [GameRule]. + /// Determines whether to create an [IntGameRule] or [BooleanGameRule] based on the JSON content. static class GameRuleDeserializer implements JsonDeserializer { @Override @@ -330,10 +373,8 @@ static final class GameRuleHolder { } private static Map cloneGameRuleMap() { - Map newGameRuleMap = new HashMap<>(); - gameRuleMap.forEach((key, gameRule) -> newGameRuleMap.put(key, gameRule.clone())); - return newGameRuleMap; + return gameRuleMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().clone())); } } -} +} \ No newline at end of file diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRuleNBT.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRuleNBT.java index 829418dd3d..ad0391ce2f 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRuleNBT.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRuleNBT.java @@ -23,70 +23,81 @@ import com.github.steveice10.opennbt.tag.builtin.Tag; import org.jackhuang.hmcl.util.Lang; -public abstract class GameRuleNBT { +/** + * An abstract representation of a Minecraft game rule stored as an NBT tag. + *

+ * This class acts as a generic wrapper for a specific game rule's NBT tag, + * providing a unified interface to modify its value. It abstracts the underlying + * NBT tag implementation, allowing different types of game rule values (like integers, + * booleans, or strings) to be handled consistently. + *

+ * Subclasses must implement the {@link #changeValue(Object)} method to define + * how an input value of type {@code T} is converted and applied to the wrapped + * NBT tag of type {@code V}. + * + * @param The type of the value used to update the game rule (e.g., {@link String}, {@link Boolean}). + * @param The specific {@link Tag} subtype being wrapped (e.g., {@link IntTag}, {@link ByteTag}). + */ +public sealed abstract class GameRuleNBT permits GameRuleNBT.IntGameRuleNBT, GameRuleNBT.ByteGameRuleNBT, GameRuleNBT.StringIntGameRuleNBT, GameRuleNBT.StringByteGameRuleNBT { - private Tag gameRuleTag; + private final V gameRuleTag; + + protected GameRuleNBT(V gameRuleTag) { + this.gameRuleTag = gameRuleTag; + } public abstract void changeValue(T newValue); - public Tag getGameRuleTag() { + public V getGameRuleTag() { return gameRuleTag; } - public void setGameRuleTag(Tag gameRuleTag) { - this.gameRuleTag = gameRuleTag; - } - - static class IntGameRuleNBT extends GameRuleNBT { + static final class IntGameRuleNBT extends GameRuleNBT { public IntGameRuleNBT(IntTag gameRuleTag) { - setGameRuleTag(gameRuleTag); + super(gameRuleTag); } @Override public void changeValue(String newValue) { - IntTag intTag = (IntTag) getGameRuleTag(); Integer value = Lang.toIntOrNull(newValue); - intTag.setValue(value); + getGameRuleTag().setValue(value); } } - static class ByteRuleNBT extends GameRuleNBT { + static final class ByteGameRuleNBT extends GameRuleNBT { - public ByteRuleNBT(ByteTag gameRuleTag) { - setGameRuleTag(gameRuleTag); + public ByteGameRuleNBT(ByteTag gameRuleTag) { + super(gameRuleTag); } @Override public void changeValue(Boolean newValue) { - ByteTag byteTag = (ByteTag) getGameRuleTag(); - byteTag.setValue((byte) (newValue ? 1 : 0)); + getGameRuleTag().setValue((byte) (newValue ? 1 : 0)); } } - static class StringIntGameRuleNBT extends GameRuleNBT { + static final class StringIntGameRuleNBT extends GameRuleNBT { public StringIntGameRuleNBT(StringTag gameRuleTag) { - setGameRuleTag(gameRuleTag); + super(gameRuleTag); } @Override public void changeValue(String newValue) { - StringTag stringTag = (StringTag) getGameRuleTag(); - stringTag.setValue(newValue); + getGameRuleTag().setValue(newValue); } } - static class StringByteGameRuleNBT extends GameRuleNBT { + static final class StringByteGameRuleNBT extends GameRuleNBT { public StringByteGameRuleNBT(StringTag gameRuleTag) { - setGameRuleTag(gameRuleTag); + super(gameRuleTag); } @Override public void changeValue(Boolean newValue) { - StringTag stringTag = (StringTag) getGameRuleTag(); - stringTag.setValue(newValue ? "true" : "false"); + getGameRuleTag().setValue(newValue ? "true" : "false"); } } } From 10446c99424bef892ba68266ecb86c137df2c70b Mon Sep 17 00:00:00 2001 From: mine_ Date: Tue, 9 Dec 2025 12:09:09 +0800 Subject: [PATCH 26/69] fix style --- .../src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java index bdd585a8b3..276c145c7b 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java @@ -377,4 +377,4 @@ private static Map cloneGameRuleMap() { } } -} \ No newline at end of file +} From 536bef3f3c1b8eae15be3e7c2c052d9650ce8a21 Mon Sep 17 00:00:00 2001 From: mine_ Date: Tue, 9 Dec 2025 15:11:41 +0800 Subject: [PATCH 27/69] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=EF=BC=8C=E6=B7=BB=E5=8A=A0=E8=A7=84=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jackhuang/hmcl/ui/versions/GameRulePage.java | 13 ++++++++----- .../src/main/resources/assets/lang/I18N.properties | 2 ++ .../main/resources/assets/lang/I18N_zh.properties | 2 ++ .../resources/assets/lang/I18N_zh_CN.properties | 2 ++ .../main/resources/assets/gamerule/gamerule.json | 14 ++++++++++++++ 5 files changed, 28 insertions(+), 5 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java index b21806f99c..902c5af5a1 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java @@ -118,11 +118,14 @@ private CompoundTag loadWorldInfo() throws IOException { void saveLevelDat() { LOG.info("Saving level.dat of world " + world.getWorldName()); - try { - this.world.writeLevelDat(levelDat); - } catch (IOException e) { - LOG.warning("Failed to save level.dat of world " + world.getWorldName(), e); - } + Task.runAsync(Schedulers.io(), () -> { + this.world.writeLevelDat(levelDat); + }) + .whenComplete(Schedulers.defaultScheduler(), ((result, exception) -> { + if (exception != null) { + LOG.warning("Failed to save level.dat of world " + world.getWorldName(), exception); + } + })).start(); } @NotNull Predicate updateSearchPredicate(String queryString) { diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 7d3d34a936..013cbdc423 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -698,6 +698,7 @@ gamerule.drowning_damage=Drowning Damage gamerule.elytra_movement_check=Elytra Movement Check gamerule.ender_pearls_vanish_on_death=Ender Pearls Vanish on Death gamerule.entity_drops=Entity Drops +gamerule.entities_with_passengers_can_use_portals=Allow ridden entities to use portals gamerule.fall_damage=Fall Damage gamerule.fire_damage=Fire Damage gamerule.fire_spread_radius_around_player=Fire Spread Radius Around Player @@ -733,6 +734,7 @@ gamerule.respawn_radius=Respawn Radius gamerule.send_command_feedback=Send Command Feedback gamerule.show_advancement_messages=Show Advancement Messages gamerule.show_death_messages=Show Death Messages +gamerule.spawn_chunk_radius=Spawn Chunk Radius gamerule.spawn_mobs=Spawn Mobs gamerule.spawn_monsters=Spawn Monsters gamerule.spawn_patrols=Spawn Patrols diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 55e01b8cd6..230ed0d303 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -505,6 +505,7 @@ gamerule.drowning_damage=造成溺水傷害 gamerule.elytra_movement_check=啟用鞘翅移動檢測 gamerule.ender_pearls_vanish_on_death=拋出的終界珍珠在死亡時消失 gamerule.entity_drops=掉落實體裝備 +gamerule.entities_with_passengers_can_use_portals=被騎乘的實體能否使用傳送門 gamerule.fall_damage=造成摔落傷害 gamerule.fire_damage=造成火焰傷害 gamerule.fire_spread_radius_around_player=火焰蔓延半徑 @@ -540,6 +541,7 @@ gamerule.respawn_radius=重生點半徑 gamerule.send_command_feedback=回傳指令回饋 gamerule.show_advancement_messages=進度通知 gamerule.show_death_messages=顯示死亡訊息 +gamerule.spawn_chunk_radius=出生區塊半徑 gamerule.spawn_mobs=生成生物 gamerule.spawn_monsters=生成怪物 gamerule.spawn_patrols=生成掠奪者巡邏隊 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index a96fe9788c..2811b3f66d 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -515,6 +515,7 @@ gamerule.drowning_damage=溺水伤害 gamerule.elytra_movement_check=启用鞘翅移动检测 gamerule.ender_pearls_vanish_on_death=掷出的末影珍珠在死亡时消失 gamerule.entity_drops=非生物实体掉落 +gamerule.entities_with_passengers_can_use_portals=被骑乘的实体能否使用传送门 gamerule.fall_damage=摔落伤害 gamerule.fire_damage=火焰伤害 gamerule.fire_spread_radius_around_player=火焰蔓延半径 @@ -550,6 +551,7 @@ gamerule.respawn_radius=重生点半径 gamerule.send_command_feedback=发送命令反馈 gamerule.show_advancement_messages=进度通知 gamerule.show_death_messages=显示死亡消息 +gamerule.spawn_chunk_radius=出生区块半径 gamerule.spawn_mobs=生成生物 gamerule.spawn_monsters=生成怪物 gamerule.spawn_patrols=生成灾厄巡逻队 diff --git a/HMCLCore/src/main/resources/assets/gamerule/gamerule.json b/HMCLCore/src/main/resources/assets/gamerule/gamerule.json index 6219a02329..08219de778 100644 --- a/HMCLCore/src/main/resources/assets/gamerule/gamerule.json +++ b/HMCLCore/src/main/resources/assets/gamerule/gamerule.json @@ -101,6 +101,13 @@ "displayI18nKey": "gamerule.entity_drops", "defaultValue": true }, + { + "ruleKey": [ + "entitiesWithPassengersCanUsePortals" + ], + "displayI18nKey": "gamerule.entities_with_passengers_can_use_portals", + "defaultValue": false + }, { "ruleKey": [ "minecraft:fall_damage", @@ -394,6 +401,13 @@ "displayI18nKey": "gamerule.show_death_messages", "defaultValue": true }, + { + "ruleKey": [ + "spawnChunkRadius" + ], + "displayI18nKey": "gamerule.spawn_chunk_radius", + "defaultValue": 2 + }, { "ruleKey": [ "minecraft:spawn_mobs", From 117578c8ce519f044409e24d221e4b5a58b5844a Mon Sep 17 00:00:00 2001 From: mine_ Date: Tue, 9 Dec 2025 16:28:07 +0800 Subject: [PATCH 28/69] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=92=A4?= =?UTF-8?q?=E5=9B=9E=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/GameRulePage.java | 4 +-- .../hmcl/ui/versions/GameRulePageSkin.java | 30 +++++++++++++++++-- .../org/jackhuang/hmcl/gamerule/GameRule.java | 29 +++++++++--------- 3 files changed, 44 insertions(+), 19 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java index 902c5af5a1..30b9e53998 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java @@ -95,9 +95,9 @@ public void updateControls() { LOG.warning("Failed to get i18n text for key: " + gameRule.getDisplayI18nKey(), e); } if (gameRule instanceof GameRule.IntGameRule intGameRule) { - gameRuleList.add(new GameRulePageSkin.GameRuleInfo(intGameRule.getRuleKey().get(0), displayText, intGameRule.getValue(), intGameRule.getMinValue(), intGameRule.getMaxValue(), gameRuleNBT, this::saveLevelDat)); + gameRuleList.add(new GameRulePageSkin.GameRuleInfo(intGameRule.getRuleKey().get(0), displayText, intGameRule.getValue(), intGameRule.getMinValue(), intGameRule.getMaxValue(), intGameRule.getDefaultValue(), gameRuleNBT, this::saveLevelDat)); } else if (gameRule instanceof GameRule.BooleanGameRule booleanGameRule) { - gameRuleList.add(new GameRulePageSkin.GameRuleInfo(booleanGameRule.getRuleKey().get(0), displayText, booleanGameRule.getValue(), gameRuleNBT, this::saveLevelDat)); + gameRuleList.add(new GameRulePageSkin.GameRuleInfo(booleanGameRule.getRuleKey().get(0), displayText, booleanGameRule.getValue(), booleanGameRule.getDefaultValue(), gameRuleNBT, this::saveLevelDat)); } }); }); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java index fad4d68397..15a8c1fd84 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java @@ -38,6 +38,8 @@ import org.jackhuang.hmcl.util.Holder; import org.jackhuang.hmcl.util.Lang; +import java.util.Optional; + import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton2; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; @@ -102,7 +104,7 @@ static class GameRuleInfo { BorderPane container = new BorderPane(); - public GameRuleInfo(String ruleKey, String displayName, Boolean onValue, GameRuleNBT gameRuleNbt, Runnable onSave) { + public GameRuleInfo(String ruleKey, String displayName, Boolean onValue, Optional defaultValue, GameRuleNBT gameRuleNbt, Runnable onSave) { this.ruleKey = ruleKey; this.displayName = displayName; this.gameRuleNbt = gameRuleNbt; @@ -119,9 +121,19 @@ public GameRuleInfo(String ruleKey, String displayName, Boolean onValue, GameRul HBox.setHgrow(container, Priority.ALWAYS); container.setCenter(toggleButton); + + JFXButton resetButton = new JFXButton(); + resetButton.setGraphic(SVG.ARROW_BACK.createIcon(24)); + defaultValue.ifPresentOrElse(value -> { + resetButton.setOnAction(event -> { + toggleButton.selectedProperty().set(value); + }); + }, () -> resetButton.setDisable(true)); + resetButton.setAlignment(Pos.BOTTOM_CENTER); + container.setRight(resetButton); } - public GameRuleInfo(String ruleKey, String displayName, Integer currentValue, int minValue, int maxValue, GameRuleNBT gameRuleNbt, Runnable onSave) { + public GameRuleInfo(String ruleKey, String displayName, Integer currentValue, int minValue, int maxValue, Optional defaultValue, GameRuleNBT gameRuleNbt, Runnable onSave) { this.ruleKey = ruleKey; this.displayName = displayName; this.gameRuleNbt = gameRuleNbt; @@ -132,6 +144,8 @@ public GameRuleInfo(String ruleKey, String displayName, Integer currentValue, in HBox.setHgrow(vbox, Priority.ALWAYS); container.setPadding(new Insets(8, 8, 8, 16)); + HBox hBox = new HBox(); + JFXTextField textField = new JFXTextField(); textField.maxWidth(10); textField.minWidth(10); @@ -149,10 +163,20 @@ public GameRuleInfo(String ruleKey, String displayName, Integer currentValue, in onSave.run(); } }); + hBox.getChildren().add(textField); + + JFXButton resetButton = new JFXButton(); + resetButton.setGraphic(SVG.ARROW_BACK.createIcon(24)); + defaultValue.ifPresentOrElse(value -> { + resetButton.setOnAction(event -> textField.textProperty().set(String.valueOf(value))); + }, () -> resetButton.setDisable(true)); + resetButton.setAlignment(Pos.BOTTOM_CENTER); + hBox.setSpacing(4); + hBox.getChildren().add(resetButton); HBox.setHgrow(container, Priority.ALWAYS); container.setCenter(vbox); - container.setRight(textField); + container.setRight(hBox); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java index 276c145c7b..c1e07de21d 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java @@ -27,6 +27,7 @@ import javafx.beans.property.IntegerProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.value.ObservableBooleanValue; import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.gson.JsonSerializable; import org.jackhuang.hmcl.util.gson.JsonUtils; @@ -179,7 +180,7 @@ public String getDisplayI18nKey() { /// Wraps values in [BooleanProperty] for UI binding. public static final class BooleanGameRule extends GameRule { private final BooleanProperty value = new SimpleBooleanProperty(false); - private final BooleanProperty defaultValue = new SimpleBooleanProperty(false); + private Optional defaultValue = Optional.empty(); private BooleanGameRule(List ruleKey, String displayI18nKey, boolean value) { super(ruleKey, displayI18nKey); @@ -193,7 +194,7 @@ private BooleanGameRule() { @Override public GameRule clone() { BooleanGameRule booleanGameRule = new BooleanGameRule(getRuleKey(), getDisplayI18nKey(), value.getValue()); - booleanGameRule.defaultValue.setValue(defaultValue.getValue()); + this.getDefaultValue().ifPresent(booleanGameRule::setDefaultValue); return booleanGameRule; } @@ -201,20 +202,20 @@ public GameRule clone() { public void applyMetadata(GameRule metadataSource) { if (metadataSource instanceof BooleanGameRule source) { this.setDisplayI18nKey(source.getDisplayI18nKey()); - this.setDefaultValue(source.getDefaultValue()); + source.getDefaultValue().ifPresent(this::setDefaultValue); } } - public boolean getDefaultValue() { - return defaultValue.get(); + public Optional getDefaultValue() { + return defaultValue.map(ObservableBooleanValue::get); } - public BooleanProperty defaultValueProperty() { + public Optional defaultValueProperty() { return defaultValue; } private void setDefaultValue(boolean value) { - this.defaultValue.setValue(value); + this.defaultValue.ifPresentOrElse(defaultValue -> defaultValue.setValue(value), () -> defaultValue = Optional.of(new SimpleBooleanProperty(value))); } public boolean getValue() { @@ -234,7 +235,7 @@ public void setValue(boolean value) { /// Wraps values in [IntegerProperty] and supports min/max value validation. public static final class IntGameRule extends GameRule { private final IntegerProperty value = new SimpleIntegerProperty(0); - private final IntegerProperty defaultValue = new SimpleIntegerProperty(0); + private Optional defaultValue = Optional.empty(); private int maxValue = 0; private int minValue = 0; @@ -250,7 +251,7 @@ private IntGameRule() { @Override public GameRule clone() { IntGameRule intGameRule = new IntGameRule(getRuleKey(), getDisplayI18nKey(), value.getValue()); - intGameRule.defaultValue.setValue(defaultValue.getValue()); + getDefaultValue().ifPresent(intGameRule::setDefaultValue); intGameRule.minValue = minValue; intGameRule.maxValue = maxValue; return intGameRule; @@ -260,22 +261,22 @@ public GameRule clone() { public void applyMetadata(GameRule metadataSource) { if (metadataSource instanceof IntGameRule source) { this.setDisplayI18nKey(source.getDisplayI18nKey()); - this.setDefaultValue(source.getDefaultValue()); + source.getDefaultValue().ifPresent(this::setDefaultValue); this.maxValue = source.getMaxValue(); this.minValue = source.getMinValue(); } } - public int getDefaultValue() { - return defaultValue.get(); + public Optional getDefaultValue() { + return defaultValue.map(IntegerProperty::getValue); } - public IntegerProperty defaultValueProperty() { + public Optional defaultValueProperty() { return defaultValue; } private void setDefaultValue(int value) { - this.defaultValue.setValue(value); + defaultValue.ifPresentOrElse(defaultValue -> defaultValue.set(value), () -> defaultValue = Optional.of(new SimpleIntegerProperty(value))); } public int getValue() { From 1fc786fb7aad90cd3ec0a40fb1a6909d29ad924f Mon Sep 17 00:00:00 2001 From: mine_ Date: Tue, 9 Dec 2025 17:05:51 +0800 Subject: [PATCH 29/69] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F=EF=BC=8C=E6=B7=BB=E5=8A=A0i18n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/GameRulePageSkin.java | 44 ++++++++++++------- .../resources/assets/lang/I18N.properties | 1 + .../resources/assets/lang/I18N_zh.properties | 1 + .../assets/lang/I18N_zh_CN.properties | 1 + 4 files changed, 32 insertions(+), 15 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java index 15a8c1fd84..c49df6a475 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java @@ -102,7 +102,7 @@ static class GameRuleInfo { String displayName; GameRuleNBT gameRuleNbt; - BorderPane container = new BorderPane(); + HBox container = new HBox(); public GameRuleInfo(String ruleKey, String displayName, Boolean onValue, Optional defaultValue, GameRuleNBT gameRuleNbt, Runnable onSave) { this.ruleKey = ruleKey; @@ -113,14 +113,14 @@ public GameRuleInfo(String ruleKey, String displayName, Boolean onValue, Optiona toggleButton.setTitle(displayName); toggleButton.setSubtitle(ruleKey); toggleButton.setSelected(onValue); - toggleButton.selectedProperty().addListener((observable, oldValue, newValue) -> { gameRuleNbt.changeValue(newValue); onSave.run(); }); + HBox.setHgrow(toggleButton, Priority.ALWAYS); HBox.setHgrow(container, Priority.ALWAYS); - container.setCenter(toggleButton); + container.getChildren().add(toggleButton); JFXButton resetButton = new JFXButton(); resetButton.setGraphic(SVG.ARROW_BACK.createIcon(24)); @@ -128,9 +128,14 @@ public GameRuleInfo(String ruleKey, String displayName, Boolean onValue, Optiona resetButton.setOnAction(event -> { toggleButton.selectedProperty().set(value); }); - }, () -> resetButton.setDisable(true)); - resetButton.setAlignment(Pos.BOTTOM_CENTER); - container.setRight(resetButton); + FXUtils.installSlowTooltip(resetButton, i18n("gamerule.restore_default_values.tooltip", value)); + }, () -> { + resetButton.setDisable(true); + }); + + container.setAlignment(Pos.CENTER_LEFT); + container.setPadding(new Insets(0, 8, 0, 0)); + container.getChildren().add(resetButton); } public GameRuleInfo(String ruleKey, String displayName, Integer currentValue, int minValue, int maxValue, Optional defaultValue, GameRuleNBT gameRuleNbt, Runnable onSave) { @@ -140,15 +145,13 @@ public GameRuleInfo(String ruleKey, String displayName, Integer currentValue, in VBox vbox = new VBox(); vbox.getChildren().addAll(new Label(displayName), new Label(ruleKey)); + vbox.setAlignment(Pos.CENTER_LEFT); HBox.setHgrow(vbox, Priority.ALWAYS); - container.setPadding(new Insets(8, 8, 8, 16)); - HBox hBox = new HBox(); + HBox hBox = new HBox(); JFXTextField textField = new JFXTextField(); - textField.maxWidth(10); - textField.minWidth(10); textField.textProperty().set(currentValue.toString()); FXUtils.setValidateWhileTextChanged(textField, true); textField.setValidators(new NumberRangeValidator(i18n("input.integer"), i18n("input.number_range", minValue, maxValue), minValue, maxValue, false)); @@ -163,20 +166,31 @@ public GameRuleInfo(String ruleKey, String displayName, Integer currentValue, in onSave.run(); } }); + + textField.maxWidth(10); + textField.minWidth(10); hBox.getChildren().add(textField); JFXButton resetButton = new JFXButton(); resetButton.setGraphic(SVG.ARROW_BACK.createIcon(24)); defaultValue.ifPresentOrElse(value -> { - resetButton.setOnAction(event -> textField.textProperty().set(String.valueOf(value))); - }, () -> resetButton.setDisable(true)); + resetButton.setOnAction(event -> { + textField.textProperty().set(String.valueOf(value)); + }); + FXUtils.installSlowTooltip(resetButton, i18n("gamerule.restore_default_values.tooltip", value)); + }, () -> { + resetButton.setDisable(true); + }); + resetButton.setAlignment(Pos.BOTTOM_CENTER); - hBox.setSpacing(4); + hBox.setSpacing(12); hBox.getChildren().add(resetButton); + hBox.setAlignment(Pos.CENTER_LEFT); HBox.setHgrow(container, Priority.ALWAYS); - container.setCenter(vbox); - container.setRight(hBox); + container.getChildren().add(vbox); + container.getChildren().add(hBox); + container.setAlignment(Pos.CENTER_LEFT); } } diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 013cbdc423..44a9f0d441 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -685,6 +685,7 @@ game.crash.title=Game Crashed game.directory=Game Path game.version=Game Instance +gamerule.restore_default_values.tooltip=Click to reset to default\nDefault: %s gamerule.advance_time=Advance Time gamerule.advance_weather=Advance Weather gamerule.allow_fire_ticks_away_from_player=Allow Fire Ticks Away From Player diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 230ed0d303..3b224ecf26 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -492,6 +492,7 @@ game.crash.title=遊戲意外退出 game.directory=遊戲目錄路徑 game.version=遊戲實例 +gamerule.restore_default_values.tooltip=點擊以還原為預設值\n預設值: %s gamerule.advance_time=日夜交替 gamerule.advance_weather=更新天氣 gamerule.allow_fire_ticks_away_from_player=允許火在遠離玩家處蔓延 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 2811b3f66d..2692e5283b 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -502,6 +502,7 @@ game.crash.title=游戏意外退出 game.directory=游戏文件夹路径 game.version=游戏实例 +gamerule.restore_default_values.tooltip=点击恢复默认值\n默认值: %s gamerule.advance_time=游戏内时间流逝 gamerule.advance_weather=天气更替 gamerule.allow_fire_ticks_away_from_player=允许火在远离玩家处蔓延 From 589450007bad7cb0cd3feaabdbbfb27af7fa2aaa Mon Sep 17 00:00:00 2001 From: mine_ Date: Tue, 9 Dec 2025 19:45:32 +0800 Subject: [PATCH 30/69] fix style --- .../java/org/jackhuang/hmcl/ui/versions/GameRulePage.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java index 30b9e53998..5c1d35a4f3 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java @@ -118,9 +118,7 @@ private CompoundTag loadWorldInfo() throws IOException { void saveLevelDat() { LOG.info("Saving level.dat of world " + world.getWorldName()); - Task.runAsync(Schedulers.io(), () -> { - this.world.writeLevelDat(levelDat); - }) + Task.runAsync(Schedulers.io(), () -> this.world.writeLevelDat(levelDat)) .whenComplete(Schedulers.defaultScheduler(), ((result, exception) -> { if (exception != null) { LOG.warning("Failed to save level.dat of world " + world.getWorldName(), exception); From 0e6126d47258c6b429ee5e3503e3afd8e9a74f0f Mon Sep 17 00:00:00 2001 From: mine_ Date: Wed, 10 Dec 2025 20:19:40 +0800 Subject: [PATCH 31/69] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E9=81=97?= =?UTF-8?q?=E6=BC=8F=E7=9A=84=E8=A7=84=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HMCL/src/main/resources/assets/lang/I18N.properties | 1 + HMCL/src/main/resources/assets/lang/I18N_zh.properties | 1 + .../main/resources/assets/lang/I18N_zh_CN.properties | 1 + .../src/main/resources/assets/gamerule/gamerule.json | 10 ++++++++-- 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 44a9f0d441..536d1b41c5 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -694,6 +694,7 @@ gamerule.block_drops=Block Drops gamerule.block_explosion_drop_decay=Block Explosion Drop Decay gamerule.command_block_output=Command Block Output gamerule.command_blocks_work=Enable Command Blocks +gamerule.disable_raids=Disable Raids gamerule.do_fire_tick=Fire Tick gamerule.drowning_damage=Drowning Damage gamerule.elytra_movement_check=Elytra Movement Check diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 3b224ecf26..e6d1ff7815 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -501,6 +501,7 @@ gamerule.block_drops=掉落方塊 gamerule.block_explosion_drop_decay=在與方塊互動的爆炸中,部分方塊不會掉落成戰利品 gamerule.command_block_output=記錄指令方塊輸出 gamerule.command_blocks_work=啟用指令方塊 +gamerule.disable_raids=停用突襲 gamerule.do_fire_tick=更新火焰 gamerule.drowning_damage=造成溺水傷害 gamerule.elytra_movement_check=啟用鞘翅移動檢測 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 2692e5283b..36fecc464b 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -511,6 +511,7 @@ gamerule.block_drops=方块掉落 gamerule.block_explosion_drop_decay=在方块交互爆炸中,一些方块不会掉落战利品 gamerule.command_block_output=广播命令方块输出 gamerule.command_blocks_work=启用命令方块 +gamerule.disable_raids=禁用袭击 gamerule.do_fire_tick=火焰蔓延 gamerule.drowning_damage=溺水伤害 gamerule.elytra_movement_check=启用鞘翅移动检测 diff --git a/HMCLCore/src/main/resources/assets/gamerule/gamerule.json b/HMCLCore/src/main/resources/assets/gamerule/gamerule.json index 08219de778..2d5bbe2969 100644 --- a/HMCLCore/src/main/resources/assets/gamerule/gamerule.json +++ b/HMCLCore/src/main/resources/assets/gamerule/gamerule.json @@ -62,6 +62,13 @@ "displayI18nKey": "gamerule.command_blocks_work", "defaultValue": true }, + { + "ruleKey": [ + "disableRaids" + ], + "displayI18nKey": "gamerule.disable_raids", + "defaultValue": false + }, { "ruleKey": [ "doFireTick" @@ -345,8 +352,7 @@ }, { "ruleKey": [ - "minecraft:raids", - "disableRaids" + "minecraft:raids" ], "displayI18nKey": "gamerule.raids", "defaultValue": true From d5eec2286b017320d48ee04457a7b2857714a278 Mon Sep 17 00:00:00 2001 From: mine_ Date: Wed, 10 Dec 2025 20:23:32 +0800 Subject: [PATCH 32/69] =?UTF-8?q?feat:=20=E6=9B=B4=E6=94=B9gamerule=20i18n?= =?UTF-8?q?=E8=A7=84=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/assets/lang/I18N.properties | 128 +++++++++--------- .../resources/assets/lang/I18N_zh.properties | 128 +++++++++--------- .../assets/lang/I18N_zh_CN.properties | 128 +++++++++--------- .../resources/assets/gamerule/gamerule.json | 128 +++++++++--------- 4 files changed, 256 insertions(+), 256 deletions(-) diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 536d1b41c5..ed3fb0f04e 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -686,70 +686,70 @@ game.directory=Game Path game.version=Game Instance gamerule.restore_default_values.tooltip=Click to reset to default\nDefault: %s -gamerule.advance_time=Advance Time -gamerule.advance_weather=Advance Weather -gamerule.allow_fire_ticks_away_from_player=Allow Fire Ticks Away From Player -gamerule.allow_entering_nether_using_portals=Allow Entering Nether Using Portals -gamerule.block_drops=Block Drops -gamerule.block_explosion_drop_decay=Block Explosion Drop Decay -gamerule.command_block_output=Command Block Output -gamerule.command_blocks_work=Enable Command Blocks -gamerule.disable_raids=Disable Raids -gamerule.do_fire_tick=Fire Tick -gamerule.drowning_damage=Drowning Damage -gamerule.elytra_movement_check=Elytra Movement Check -gamerule.ender_pearls_vanish_on_death=Ender Pearls Vanish on Death -gamerule.entity_drops=Entity Drops -gamerule.entities_with_passengers_can_use_portals=Allow ridden entities to use portals -gamerule.fall_damage=Fall Damage -gamerule.fire_damage=Fire Damage -gamerule.fire_spread_radius_around_player=Fire Spread Radius Around Player -gamerule.forgive_dead_players=Forgive Dead Players -gamerule.freeze_damage=Freeze Damage -gamerule.global_sound_events=Global Sound Events -gamerule.immediate_respawn=Immediate Respawn -gamerule.keep_inventory=Keep Inventory -gamerule.lava_source_conversion=Lava Source Conversion -gamerule.limited_crafting=Limited Crafting -gamerule.locator_bar=Enable Locator Bar -gamerule.log_admin_commands=Log Admin Commands -gamerule.max_block_modifications=Command Modification Block Limit -gamerule.max_command_forks=Max Command Fork Count -gamerule.max_command_sequence_length=Max Command Chain Length -gamerule.max_entity_cramming=Max Entity Cramming -gamerule.max_minecart_speed=Minecart Max Speed -gamerule.max_snow_accumulation_height=Snow Accumulation Height -gamerule.mob_drops=Mob Drops -gamerule.mob_explosion_drop_decay=Mob Explosion Drop Decay -gamerule.mob_griefing=Mob Griefing -gamerule.natural_health_regeneration=Natural Health Regeneration -gamerule.player_movement_check=Player Movement Check -gamerule.players_nether_portal_creative_delay=Players Nether Portal Creative Delay -gamerule.players_nether_portal_default_delay=Players Nether Portal Default Delay -gamerule.players_sleeping_percentage=Players Sleeping Percentage -gamerule.projectiles_can_break_blocks=Projectiles Can Break Blocks -gamerule.pvp=Enable PvP -gamerule.raids=Enable Raids -gamerule.random_tick_speed=Random Tick Speed -gamerule.reduced_debug_info=Reduced Debug Info -gamerule.respawn_radius=Respawn Radius -gamerule.send_command_feedback=Send Command Feedback -gamerule.show_advancement_messages=Show Advancement Messages -gamerule.show_death_messages=Show Death Messages -gamerule.spawn_chunk_radius=Spawn Chunk Radius -gamerule.spawn_mobs=Spawn Mobs -gamerule.spawn_monsters=Spawn Monsters -gamerule.spawn_patrols=Spawn Patrols -gamerule.spawn_phantoms=Spawn Phantoms -gamerule.spawn_wandering_traders=Spawn Wandering Traders -gamerule.spawn_wardens=Spawn Wardens -gamerule.spawner_blocks_work=Enable Spawner Blocks -gamerule.spectators_generate_chunks=Spectators Generate Chunks -gamerule.spread_vines=Spread Vines -gamerule.tnt_explodes=TNT Explodes -gamerule.tnt_explosion_drop_decay=TNT Explosion Drop Decay -gamerule.universal_anger=Universal Anger -gamerule.water_source_conversion=Water Source Conversion +gamerule.rule.advance_time=Advance Time +gamerule.rule.advance_weather=Advance Weather +gamerule.rule.allow_fire_ticks_away_from_player=Allow Fire Ticks Away From Player +gamerule.rule.allow_entering_nether_using_portals=Allow Entering Nether Using Portals +gamerule.rule.block_drops=Block Drops +gamerule.rule.block_explosion_drop_decay=Block Explosion Drop Decay +gamerule.rule.command_block_output=Command Block Output +gamerule.rule.command_blocks_work=Enable Command Blocks +gamerule.rule.disable_raids=Disable Raids +gamerule.rule.do_fire_tick=Fire Tick +gamerule.rule.drowning_damage=Drowning Damage +gamerule.rule.elytra_movement_check=Elytra Movement Check +gamerule.rule.ender_pearls_vanish_on_death=Ender Pearls Vanish on Death +gamerule.rule.entity_drops=Entity Drops +gamerule.rule.entities_with_passengers_can_use_portals=Allow ridden entities to use portals +gamerule.rule.fall_damage=Fall Damage +gamerule.rule.fire_damage=Fire Damage +gamerule.rule.fire_spread_radius_around_player=Fire Spread Radius Around Player +gamerule.rule.forgive_dead_players=Forgive Dead Players +gamerule.rule.freeze_damage=Freeze Damage +gamerule.rule.global_sound_events=Global Sound Events +gamerule.rule.immediate_respawn=Immediate Respawn +gamerule.rule.keep_inventory=Keep Inventory +gamerule.rule.lava_source_conversion=Lava Source Conversion +gamerule.rule.limited_crafting=Limited Crafting +gamerule.rule.locator_bar=Enable Locator Bar +gamerule.rule.log_admin_commands=Log Admin Commands +gamerule.rule.max_block_modifications=Command Modification Block Limit +gamerule.rule.max_command_forks=Max Command Fork Count +gamerule.rule.max_command_sequence_length=Max Command Chain Length +gamerule.rule.max_entity_cramming=Max Entity Cramming +gamerule.rule.max_minecart_speed=Minecart Max Speed +gamerule.rule.max_snow_accumulation_height=Snow Accumulation Height +gamerule.rule.mob_drops=Mob Drops +gamerule.rule.mob_explosion_drop_decay=Mob Explosion Drop Decay +gamerule.rule.mob_griefing=Mob Griefing +gamerule.rule.natural_health_regeneration=Natural Health Regeneration +gamerule.rule.player_movement_check=Player Movement Check +gamerule.rule.players_nether_portal_creative_delay=Players Nether Portal Creative Delay +gamerule.rule.players_nether_portal_default_delay=Players Nether Portal Default Delay +gamerule.rule.players_sleeping_percentage=Players Sleeping Percentage +gamerule.rule.projectiles_can_break_blocks=Projectiles Can Break Blocks +gamerule.rule.pvp=Enable PvP +gamerule.rule.raids=Enable Raids +gamerule.rule.random_tick_speed=Random Tick Speed +gamerule.rule.reduced_debug_info=Reduced Debug Info +gamerule.rule.respawn_radius=Respawn Radius +gamerule.rule.send_command_feedback=Send Command Feedback +gamerule.rule.show_advancement_messages=Show Advancement Messages +gamerule.rule.show_death_messages=Show Death Messages +gamerule.rule.spawn_chunk_radius=Spawn Chunk Radius +gamerule.rule.spawn_mobs=Spawn Mobs +gamerule.rule.spawn_monsters=Spawn Monsters +gamerule.rule.spawn_patrols=Spawn Patrols +gamerule.rule.spawn_phantoms=Spawn Phantoms +gamerule.rule.spawn_wandering_traders=Spawn Wandering Traders +gamerule.rule.spawn_wardens=Spawn Wardens +gamerule.rule.spawner_blocks_work=Enable Spawner Blocks +gamerule.rule.spectators_generate_chunks=Spectators Generate Chunks +gamerule.rule.spread_vines=Spread Vines +gamerule.rule.tnt_explodes=TNT Explodes +gamerule.rule.tnt_explosion_drop_decay=TNT Explosion Drop Decay +gamerule.rule.universal_anger=Universal Anger +gamerule.rule.water_source_conversion=Water Source Conversion help=Help help.doc=Hello Minecraft! Launcher Documentation diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index e6d1ff7815..a018134039 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -493,70 +493,70 @@ game.directory=遊戲目錄路徑 game.version=遊戲實例 gamerule.restore_default_values.tooltip=點擊以還原為預設值\n預設值: %s -gamerule.advance_time=日夜交替 -gamerule.advance_weather=更新天氣 -gamerule.allow_fire_ticks_away_from_player=允許火在遠離玩家處蔓延 -gamerule.allow_entering_nether_using_portals=允許進入地獄 -gamerule.block_drops=掉落方塊 -gamerule.block_explosion_drop_decay=在與方塊互動的爆炸中,部分方塊不會掉落成戰利品 -gamerule.command_block_output=記錄指令方塊輸出 -gamerule.command_blocks_work=啟用指令方塊 -gamerule.disable_raids=停用突襲 -gamerule.do_fire_tick=更新火焰 -gamerule.drowning_damage=造成溺水傷害 -gamerule.elytra_movement_check=啟用鞘翅移動檢測 -gamerule.ender_pearls_vanish_on_death=拋出的終界珍珠在死亡時消失 -gamerule.entity_drops=掉落實體裝備 -gamerule.entities_with_passengers_can_use_portals=被騎乘的實體能否使用傳送門 -gamerule.fall_damage=造成摔落傷害 -gamerule.fire_damage=造成火焰傷害 -gamerule.fire_spread_radius_around_player=火焰蔓延半徑 -gamerule.forgive_dead_players=原諒死者 -gamerule.freeze_damage=造成冰凍傷害 -gamerule.global_sound_events=全域聲音事件 -gamerule.immediate_respawn=立即重生 -gamerule.keep_inventory=死亡後保留物品欄 -gamerule.lava_source_conversion=流動熔岩轉化成熔岩源 -gamerule.limited_crafting=需要配方才能合成 -gamerule.locator_bar=啟用玩家定位條 -gamerule.log_admin_commands=記錄管理員指令 -gamerule.max_block_modifications=指令修改方塊數量限制 -gamerule.max_command_forks=指令的上下文上限 -gamerule.max_command_sequence_length=指令連鎖大小限制 -gamerule.max_entity_cramming=實體擠壓上限 -gamerule.max_minecart_speed=礦車最大速度 -gamerule.max_snow_accumulation_height=積雪厚度 -gamerule.mob_drops=掉落生物戰利品 -gamerule.mob_explosion_drop_decay=在生物的爆炸中,部分方塊不會掉落成戰利品 -gamerule.mob_griefing=允許生物的破壞行為 -gamerule.natural_health_regeneration=自然回血 -gamerule.player_movement_check=啟用玩家移動檢測 -gamerule.players_nether_portal_creative_delay=創造模式玩家使用地獄傳送門的等待時間 -gamerule.players_nether_portal_default_delay=非創造模式玩家使用地獄傳送門的等待時間 -gamerule.players_sleeping_percentage=睡眠比例 -gamerule.projectiles_can_break_blocks=投射物是否能破壞方塊 -gamerule.pvp=啟用 PvP -gamerule.raids=啟用突襲 -gamerule.random_tick_speed=隨機刻速率 -gamerule.reduced_debug_info=簡化除錯資訊 -gamerule.respawn_radius=重生點半徑 -gamerule.send_command_feedback=回傳指令回饋 -gamerule.show_advancement_messages=進度通知 -gamerule.show_death_messages=顯示死亡訊息 -gamerule.spawn_chunk_radius=出生區塊半徑 -gamerule.spawn_mobs=生成生物 -gamerule.spawn_monsters=生成怪物 -gamerule.spawn_patrols=生成掠奪者巡邏隊 -gamerule.spawn_phantoms=生成夜魅 -gamerule.spawn_wandering_traders=生成流浪商人 -gamerule.spawn_wardens=生成伏守者 -gamerule.spawner_blocks_work=啟用生怪磚 -gamerule.spectators_generate_chunks=允許旁觀者生成地形 -gamerule.spread_vines=藤蔓蔓延 -gamerule.tnt_explodes=允許 TNT 被點燃並爆炸 -gamerule.tnt_explosion_drop_decay=在 TNT 的爆炸中,部分方塊不會掉落成戰利品 -gamerule.universal_anger=無差別憤怒 -gamerule.water_source_conversion=流動水轉化成水源 +gamerule.rule.advance_time=日夜交替 +gamerule.rule.advance_weather=更新天氣 +gamerule.rule.allow_fire_ticks_away_from_player=允許火在遠離玩家處蔓延 +gamerule.rule.allow_entering_nether_using_portals=允許進入地獄 +gamerule.rule.block_drops=掉落方塊 +gamerule.rule.block_explosion_drop_decay=在與方塊互動的爆炸中,部分方塊不會掉落成戰利品 +gamerule.rule.command_block_output=記錄指令方塊輸出 +gamerule.rule.command_blocks_work=啟用指令方塊 +gamerule.rule.disable_raids=停用突襲 +gamerule.rule.do_fire_tick=更新火焰 +gamerule.rule.drowning_damage=造成溺水傷害 +gamerule.rule.elytra_movement_check=啟用鞘翅移動檢測 +gamerule.rule.ender_pearls_vanish_on_death=拋出的終界珍珠在死亡時消失 +gamerule.rule.entity_drops=掉落實體裝備 +gamerule.rule.entities_with_passengers_can_use_portals=被騎乘的實體能否使用傳送門 +gamerule.rule.fall_damage=造成摔落傷害 +gamerule.rule.fire_damage=造成火焰傷害 +gamerule.rule.fire_spread_radius_around_player=火焰蔓延半徑 +gamerule.rule.forgive_dead_players=原諒死者 +gamerule.rule.freeze_damage=造成冰凍傷害 +gamerule.rule.global_sound_events=全域聲音事件 +gamerule.rule.immediate_respawn=立即重生 +gamerule.rule.keep_inventory=死亡後保留物品欄 +gamerule.rule.lava_source_conversion=流動熔岩轉化成熔岩源 +gamerule.rule.limited_crafting=需要配方才能合成 +gamerule.rule.locator_bar=啟用玩家定位條 +gamerule.rule.log_admin_commands=記錄管理員指令 +gamerule.rule.max_block_modifications=指令修改方塊數量限制 +gamerule.rule.max_command_forks=指令的上下文上限 +gamerule.rule.max_command_sequence_length=指令連鎖大小限制 +gamerule.rule.max_entity_cramming=實體擠壓上限 +gamerule.rule.max_minecart_speed=礦車最大速度 +gamerule.rule.max_snow_accumulation_height=積雪厚度 +gamerule.rule.mob_drops=掉落生物戰利品 +gamerule.rule.mob_explosion_drop_decay=在生物的爆炸中,部分方塊不會掉落成戰利品 +gamerule.rule.mob_griefing=允許生物的破壞行為 +gamerule.rule.natural_health_regeneration=自然回血 +gamerule.rule.player_movement_check=啟用玩家移動檢測 +gamerule.rule.players_nether_portal_creative_delay=創造模式玩家使用地獄傳送門的等待時間 +gamerule.rule.players_nether_portal_default_delay=非創造模式玩家使用地獄傳送門的等待時間 +gamerule.rule.players_sleeping_percentage=睡眠比例 +gamerule.rule.projectiles_can_break_blocks=投射物是否能破壞方塊 +gamerule.rule.pvp=啟用 PvP +gamerule.rule.raids=啟用突襲 +gamerule.rule.random_tick_speed=隨機刻速率 +gamerule.rule.reduced_debug_info=簡化除錯資訊 +gamerule.rule.respawn_radius=重生點半徑 +gamerule.rule.send_command_feedback=回傳指令回饋 +gamerule.rule.show_advancement_messages=進度通知 +gamerule.rule.show_death_messages=顯示死亡訊息 +gamerule.rule.spawn_chunk_radius=出生區塊半徑 +gamerule.rule.spawn_mobs=生成生物 +gamerule.rule.spawn_monsters=生成怪物 +gamerule.rule.spawn_patrols=生成掠奪者巡邏隊 +gamerule.rule.spawn_phantoms=生成夜魅 +gamerule.rule.spawn_wandering_traders=生成流浪商人 +gamerule.rule.spawn_wardens=生成伏守者 +gamerule.rule.spawner_blocks_work=啟用生怪磚 +gamerule.rule.spectators_generate_chunks=允許旁觀者生成地形 +gamerule.rule.spread_vines=藤蔓蔓延 +gamerule.rule.tnt_explodes=允許 TNT 被點燃並爆炸 +gamerule.rule.tnt_explosion_drop_decay=在 TNT 的爆炸中,部分方塊不會掉落成戰利品 +gamerule.rule.universal_anger=無差別憤怒 +gamerule.rule.water_source_conversion=流動水轉化成水源 help=說明 help.doc=Hello Minecraft! Launcher 說明文件 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 36fecc464b..3b1a6f5398 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -503,70 +503,70 @@ game.directory=游戏文件夹路径 game.version=游戏实例 gamerule.restore_default_values.tooltip=点击恢复默认值\n默认值: %s -gamerule.advance_time=游戏内时间流逝 -gamerule.advance_weather=天气更替 -gamerule.allow_fire_ticks_away_from_player=允许火在远离玩家处蔓延 -gamerule.allow_entering_nether_using_portals=允许进入下界 -gamerule.block_drops=方块掉落 -gamerule.block_explosion_drop_decay=在方块交互爆炸中,一些方块不会掉落战利品 -gamerule.command_block_output=广播命令方块输出 -gamerule.command_blocks_work=启用命令方块 -gamerule.disable_raids=禁用袭击 -gamerule.do_fire_tick=火焰蔓延 -gamerule.drowning_damage=溺水伤害 -gamerule.elytra_movement_check=启用鞘翅移动检测 -gamerule.ender_pearls_vanish_on_death=掷出的末影珍珠在死亡时消失 -gamerule.entity_drops=非生物实体掉落 -gamerule.entities_with_passengers_can_use_portals=被骑乘的实体能否使用传送门 -gamerule.fall_damage=摔落伤害 -gamerule.fire_damage=火焰伤害 -gamerule.fire_spread_radius_around_player=火焰蔓延半径 -gamerule.forgive_dead_players=宽恕死亡玩家 -gamerule.freeze_damage=冰冻伤害 -gamerule.global_sound_events=全局声音事件 -gamerule.immediate_respawn=立即重生 -gamerule.keep_inventory=死亡后保留物品栏 -gamerule.lava_source_conversion=允许流动熔岩转化为熔岩源 -gamerule.limited_crafting=合成需要配方 -gamerule.locator_bar=启用玩家定位栏 -gamerule.log_admin_commands=通告管理员命令 -gamerule.max_block_modifications=命令修改方块数量限制 -gamerule.max_command_forks=命令上下文数量限制 -gamerule.max_command_sequence_length=命令连锁执行数量限制 -gamerule.max_entity_cramming=实体挤压上限 -gamerule.max_minecart_speed=矿车最大速度 -gamerule.max_snow_accumulation_height=积雪厚度 -gamerule.mob_drops=生物战利品掉落 -gamerule.mob_explosion_drop_decay=在生物爆炸中,一些方块不会掉落战利品 -gamerule.mob_griefing=允许破坏性生物行为 -gamerule.natural_health_regeneration=生命值自然恢复 -gamerule.player_movement_check=启用玩家移动检测 -gamerule.players_nether_portal_creative_delay=创造模式下玩家在下界传送门中等待的时间 -gamerule.players_nether_portal_default_delay=非创造模式下玩家在下界传送门中等待的时间 -gamerule.players_sleeping_percentage=入睡占比 -gamerule.projectiles_can_break_blocks=弹射物能否破坏方块 -gamerule.pvp=启用PvP -gamerule.raids=启用袭击 -gamerule.random_tick_speed=随机刻速率 -gamerule.reduced_debug_info=简化调试信息 -gamerule.respawn_radius=重生点半径 -gamerule.send_command_feedback=发送命令反馈 -gamerule.show_advancement_messages=进度通知 -gamerule.show_death_messages=显示死亡消息 -gamerule.spawn_chunk_radius=出生区块半径 -gamerule.spawn_mobs=生成生物 -gamerule.spawn_monsters=生成怪物 -gamerule.spawn_patrols=生成灾厄巡逻队 -gamerule.spawn_phantoms=生成幻翼 -gamerule.spawn_wandering_traders=生成流浪商人 -gamerule.spawn_wardens=生成监守者 -gamerule.spawner_blocks_work=启用刷怪笼方块 -gamerule.spectators_generate_chunks=允许旁观者生成地形 -gamerule.spread_vines=藤蔓蔓延 -gamerule.tnt_explodes=允许TNT被点燃并爆炸 -gamerule.tnt_explosion_drop_decay=在TNT爆炸中,一些方块不会掉落战利品 -gamerule.universal_anger=无差别愤怒 -gamerule.water_source_conversion=允许流动水转化为水源 +gamerule.rule.advance_time=游戏内时间流逝 +gamerule.rule.advance_weather=天气更替 +gamerule.rule.allow_fire_ticks_away_from_player=允许火在远离玩家处蔓延 +gamerule.rule.allow_entering_nether_using_portals=允许进入下界 +gamerule.rule.block_drops=方块掉落 +gamerule.rule.block_explosion_drop_decay=在方块交互爆炸中,一些方块不会掉落战利品 +gamerule.rule.command_block_output=广播命令方块输出 +gamerule.rule.command_blocks_work=启用命令方块 +gamerule.rule.disable_raids=禁用袭击 +gamerule.rule.do_fire_tick=火焰蔓延 +gamerule.rule.drowning_damage=溺水伤害 +gamerule.rule.elytra_movement_check=启用鞘翅移动检测 +gamerule.rule.ender_pearls_vanish_on_death=掷出的末影珍珠在死亡时消失 +gamerule.rule.entity_drops=非生物实体掉落 +gamerule.rule.entities_with_passengers_can_use_portals=被骑乘的实体能否使用传送门 +gamerule.rule.fall_damage=摔落伤害 +gamerule.rule.fire_damage=火焰伤害 +gamerule.rule.fire_spread_radius_around_player=火焰蔓延半径 +gamerule.rule.forgive_dead_players=宽恕死亡玩家 +gamerule.rule.freeze_damage=冰冻伤害 +gamerule.rule.global_sound_events=全局声音事件 +gamerule.rule.immediate_respawn=立即重生 +gamerule.rule.keep_inventory=死亡后保留物品栏 +gamerule.rule.lava_source_conversion=允许流动熔岩转化为熔岩源 +gamerule.rule.limited_crafting=合成需要配方 +gamerule.rule.locator_bar=启用玩家定位栏 +gamerule.rule.log_admin_commands=通告管理员命令 +gamerule.rule.max_block_modifications=命令修改方块数量限制 +gamerule.rule.max_command_forks=命令上下文数量限制 +gamerule.rule.max_command_sequence_length=命令连锁执行数量限制 +gamerule.rule.max_entity_cramming=实体挤压上限 +gamerule.rule.max_minecart_speed=矿车最大速度 +gamerule.rule.max_snow_accumulation_height=积雪厚度 +gamerule.rule.mob_drops=生物战利品掉落 +gamerule.rule.mob_explosion_drop_decay=在生物爆炸中,一些方块不会掉落战利品 +gamerule.rule.mob_griefing=允许破坏性生物行为 +gamerule.rule.natural_health_regeneration=生命值自然恢复 +gamerule.rule.player_movement_check=启用玩家移动检测 +gamerule.rule.players_nether_portal_creative_delay=创造模式下玩家在下界传送门中等待的时间 +gamerule.rule.players_nether_portal_default_delay=非创造模式下玩家在下界传送门中等待的时间 +gamerule.rule.players_sleeping_percentage=入睡占比 +gamerule.rule.projectiles_can_break_blocks=弹射物能否破坏方块 +gamerule.rule.pvp=启用PvP +gamerule.rule.raids=启用袭击 +gamerule.rule.random_tick_speed=随机刻速率 +gamerule.rule.reduced_debug_info=简化调试信息 +gamerule.rule.respawn_radius=重生点半径 +gamerule.rule.send_command_feedback=发送命令反馈 +gamerule.rule.show_advancement_messages=进度通知 +gamerule.rule.show_death_messages=显示死亡消息 +gamerule.rule.spawn_chunk_radius=出生区块半径 +gamerule.rule.spawn_mobs=生成生物 +gamerule.rule.spawn_monsters=生成怪物 +gamerule.rule.spawn_patrols=生成灾厄巡逻队 +gamerule.rule.spawn_phantoms=生成幻翼 +gamerule.rule.spawn_wandering_traders=生成流浪商人 +gamerule.rule.spawn_wardens=生成监守者 +gamerule.rule.spawner_blocks_work=启用刷怪笼方块 +gamerule.rule.spectators_generate_chunks=允许旁观者生成地形 +gamerule.rule.spread_vines=藤蔓蔓延 +gamerule.rule.tnt_explodes=允许TNT被点燃并爆炸 +gamerule.rule.tnt_explosion_drop_decay=在TNT爆炸中,一些方块不会掉落战利品 +gamerule.rule.universal_anger=无差别愤怒 +gamerule.rule.water_source_conversion=允许流动水转化为水源 help=帮助 help.doc=Hello Minecraft! Launcher 帮助文档 diff --git a/HMCLCore/src/main/resources/assets/gamerule/gamerule.json b/HMCLCore/src/main/resources/assets/gamerule/gamerule.json index 2d5bbe2969..7adf471c45 100644 --- a/HMCLCore/src/main/resources/assets/gamerule/gamerule.json +++ b/HMCLCore/src/main/resources/assets/gamerule/gamerule.json @@ -4,7 +4,7 @@ "minecraft:advance_time", "doDaylightCycle" ], - "displayI18nKey": "gamerule.advance_time", + "displayI18nKey": "gamerule.rule.advance_time", "defaultValue": true }, { @@ -12,14 +12,14 @@ "minecraft:advance_weather", "doWeatherCycle" ], - "displayI18nKey": "gamerule.advance_weather", + "displayI18nKey": "gamerule.rule.advance_weather", "defaultValue": true }, { "ruleKey": [ "allowFireTicksAwayFromPlayer" ], - "displayI18nKey": "gamerule.allow_fire_ticks_away_from_player", + "displayI18nKey": "gamerule.rule.allow_fire_ticks_away_from_player", "defaultValue": false }, { @@ -27,7 +27,7 @@ "minecraft:allow_entering_nether_using_portals", "allowEnteringNetherUsingPortals" ], - "displayI18nKey": "gamerule.allow_entering_nether_using_portals", + "displayI18nKey": "gamerule.rule.allow_entering_nether_using_portals", "defaultValue": true }, { @@ -35,7 +35,7 @@ "minecraft:block_drops", "doTileDrops" ], - "displayI18nKey": "gamerule.block_drops", + "displayI18nKey": "gamerule.rule.block_drops", "defaultValue": true }, { @@ -43,7 +43,7 @@ "minecraft:block_explosion_drop_decay", "blockExplosionDropDecay" ], - "displayI18nKey": "gamerule.block_explosion_drop_decay", + "displayI18nKey": "gamerule.rule.block_explosion_drop_decay", "defaultValue": true }, { @@ -51,7 +51,7 @@ "minecraft:command_block_output", "commandBlockOutput" ], - "displayI18nKey": "gamerule.command_block_output", + "displayI18nKey": "gamerule.rule.command_block_output", "defaultValue": true }, { @@ -59,21 +59,21 @@ "minecraft:command_blocks_work", "commandBlocksEnabled" ], - "displayI18nKey": "gamerule.command_blocks_work", + "displayI18nKey": "gamerule.rule.command_blocks_work", "defaultValue": true }, { "ruleKey": [ "disableRaids" ], - "displayI18nKey": "gamerule.disable_raids", + "displayI18nKey": "gamerule.rule.disable_raids", "defaultValue": false }, { "ruleKey": [ "doFireTick" ], - "displayI18nKey": "gamerule.do_fire_tick", + "displayI18nKey": "gamerule.rule.do_fire_tick", "defaultValue": true }, { @@ -81,7 +81,7 @@ "minecraft:drowning_damage", "drowningDamage" ], - "displayI18nKey": "gamerule.drowning_damage", + "displayI18nKey": "gamerule.rule.drowning_damage", "defaultValue": true }, { @@ -89,7 +89,7 @@ "minecraft:elytra_movement_check", "disableElytraMovementCheck" ], - "displayI18nKey": "gamerule.elytra_movement_check", + "displayI18nKey": "gamerule.rule.elytra_movement_check", "defaultValue": true }, { @@ -97,7 +97,7 @@ "minecraft:ender_pearls_vanish_on_death", "enderPearlsVanishOnDeath" ], - "displayI18nKey": "gamerule.ender_pearls_vanish_on_death", + "displayI18nKey": "gamerule.rule.ender_pearls_vanish_on_death", "defaultValue": true }, { @@ -105,14 +105,14 @@ "minecraft:entity_drops", "doEntityDrops" ], - "displayI18nKey": "gamerule.entity_drops", + "displayI18nKey": "gamerule.rule.entity_drops", "defaultValue": true }, { "ruleKey": [ "entitiesWithPassengersCanUsePortals" ], - "displayI18nKey": "gamerule.entities_with_passengers_can_use_portals", + "displayI18nKey": "gamerule.rule.entities_with_passengers_can_use_portals", "defaultValue": false }, { @@ -120,7 +120,7 @@ "minecraft:fall_damage", "fallDamage" ], - "displayI18nKey": "gamerule.fall_damage", + "displayI18nKey": "gamerule.rule.fall_damage", "defaultValue": true }, { @@ -128,14 +128,14 @@ "minecraft:fire_damage", "fireDamage" ], - "displayI18nKey": "gamerule.fire_damage", + "displayI18nKey": "gamerule.rule.fire_damage", "defaultValue": true }, { "ruleKey": [ "minecraft:fire_spread_radius_around_player" ], - "displayI18nKey": "gamerule.fire_spread_radius_around_player", + "displayI18nKey": "gamerule.rule.fire_spread_radius_around_player", "defaultValue": 128, "minValue": -1 }, @@ -144,7 +144,7 @@ "minecraft:forgive_dead_players", "forgiveDeadPlayers" ], - "displayI18nKey": "gamerule.forgive_dead_players", + "displayI18nKey": "gamerule.rule.forgive_dead_players", "defaultValue": true }, { @@ -152,7 +152,7 @@ "minecraft:freeze_damage", "freezeDamage" ], - "displayI18nKey": "gamerule.freeze_damage", + "displayI18nKey": "gamerule.rule.freeze_damage", "defaultValue": true }, { @@ -160,7 +160,7 @@ "minecraft:global_sound_events", "globalSoundEvents" ], - "displayI18nKey": "gamerule.global_sound_events", + "displayI18nKey": "gamerule.rule.global_sound_events", "defaultValue": true }, { @@ -168,7 +168,7 @@ "minecraft:immediate_respawn", "doImmediateRespawn" ], - "displayI18nKey": "gamerule.immediate_respawn", + "displayI18nKey": "gamerule.rule.immediate_respawn", "defaultValue": false }, { @@ -176,7 +176,7 @@ "minecraft:keep_inventory", "keepInventory" ], - "displayI18nKey": "gamerule.keep_inventory", + "displayI18nKey": "gamerule.rule.keep_inventory", "defaultValue": false }, { @@ -184,7 +184,7 @@ "minecraft:lava_source_conversion", "lavaSourceConversion" ], - "displayI18nKey": "gamerule.lava_source_conversion", + "displayI18nKey": "gamerule.rule.lava_source_conversion", "defaultValue": false }, { @@ -192,7 +192,7 @@ "minecraft:limited_crafting", "doLimitedCrafting" ], - "displayI18nKey": "gamerule.limited_crafting", + "displayI18nKey": "gamerule.rule.limited_crafting", "defaultValue": false }, { @@ -200,7 +200,7 @@ "minecraft:locator_bar", "locatorBar" ], - "displayI18nKey": "gamerule.locator_bar", + "displayI18nKey": "gamerule.rule.locator_bar", "defaultValue": true }, { @@ -208,7 +208,7 @@ "minecraft:log_admin_commands", "logAdminCommands" ], - "displayI18nKey": "gamerule.log_admin_commands", + "displayI18nKey": "gamerule.rule.log_admin_commands", "defaultValue": true }, { @@ -216,7 +216,7 @@ "minecraft:max_block_modifications", "commandModificationBlockLimit" ], - "displayI18nKey": "gamerule.max_block_modifications", + "displayI18nKey": "gamerule.rule.max_block_modifications", "defaultValue": 32768, "minValue": -1 }, @@ -225,7 +225,7 @@ "minecraft:max_command_forks", "maxCommandForkCount" ], - "displayI18nKey": "gamerule.max_command_forks", + "displayI18nKey": "gamerule.rule.max_command_forks", "defaultValue": 65536, "minValue": 0 }, @@ -234,7 +234,7 @@ "minecraft:max_command_sequence_length", "maxCommandChainLength" ], - "displayI18nKey": "gamerule.max_command_sequence_length", + "displayI18nKey": "gamerule.rule.max_command_sequence_length", "defaultValue": 65536, "minValue": 0 }, @@ -243,7 +243,7 @@ "minecraft:max_entity_cramming", "maxEntityCramming" ], - "displayI18nKey": "gamerule.max_entity_cramming", + "displayI18nKey": "gamerule.rule.max_entity_cramming", "defaultValue": 24, "minValue": 0 }, @@ -252,7 +252,7 @@ "minecraft:max_minecart_speed", "minecartMaxSpeed" ], - "displayI18nKey": "gamerule.max_minecart_speed", + "displayI18nKey": "gamerule.rule.max_minecart_speed", "defaultValue": 8, "minValue": 1, "maxValue": 1000 @@ -262,7 +262,7 @@ "minecraft:max_snow_accumulation_height", "snowAccumulationHeight" ], - "displayI18nKey": "gamerule.max_snow_accumulation_height", + "displayI18nKey": "gamerule.rule.max_snow_accumulation_height", "defaultValue": 1, "minValue": 0, "maxValue": 8 @@ -272,7 +272,7 @@ "minecraft:mob_drops", "doMobLoot" ], - "displayI18nKey": "gamerule.mob_drops", + "displayI18nKey": "gamerule.rule.mob_drops", "defaultValue": true }, { @@ -280,7 +280,7 @@ "minecraft:mob_explosion_drop_decay", "mobExplosionDropDecay" ], - "displayI18nKey": "gamerule.mob_explosion_drop_decay", + "displayI18nKey": "gamerule.rule.mob_explosion_drop_decay", "defaultValue": true }, { @@ -288,7 +288,7 @@ "minecraft:mob_griefing", "mobGriefing" ], - "displayI18nKey": "gamerule.mob_griefing", + "displayI18nKey": "gamerule.rule.mob_griefing", "defaultValue": true }, { @@ -296,7 +296,7 @@ "minecraft:natural_health_regeneration", "naturalRegeneration" ], - "displayI18nKey": "gamerule.natural_health_regeneration", + "displayI18nKey": "gamerule.rule.natural_health_regeneration", "defaultValue": true }, { @@ -304,7 +304,7 @@ "minecraft:player_movement_check", "disablePlayerMovementCheck" ], - "displayI18nKey": "gamerule.player_movement_check", + "displayI18nKey": "gamerule.rule.player_movement_check", "defaultValue": true }, { @@ -312,7 +312,7 @@ "minecraft:players_nether_portal_creative_delay", "playersNetherPortalCreativeDelay" ], - "displayI18nKey": "gamerule.players_nether_portal_creative_delay", + "displayI18nKey": "gamerule.rule.players_nether_portal_creative_delay", "defaultValue": 0, "minValue": 0 }, @@ -321,7 +321,7 @@ "minecraft:players_nether_portal_default_delay", "playersNetherPortalDefaultDelay" ], - "displayI18nKey": "gamerule.players_nether_portal_default_delay", + "displayI18nKey": "gamerule.rule.players_nether_portal_default_delay", "defaultValue": 80, "minValue": 0 }, @@ -330,7 +330,7 @@ "minecraft:players_sleeping_percentage", "playersSleepingPercentage" ], - "displayI18nKey": "gamerule.players_sleeping_percentage", + "displayI18nKey": "gamerule.rule.players_sleeping_percentage", "defaultValue": 100, "minValue": 0 }, @@ -339,7 +339,7 @@ "minecraft:projectiles_can_break_blocks", "projectilesCanBreakBlocks" ], - "displayI18nKey": "gamerule.projectiles_can_break_blocks", + "displayI18nKey": "gamerule.rule.projectiles_can_break_blocks", "defaultValue": true }, { @@ -347,14 +347,14 @@ "minecraft:pvp", "pvp" ], - "displayI18nKey": "gamerule.pvp", + "displayI18nKey": "gamerule.rule.pvp", "defaultValue": true }, { "ruleKey": [ "minecraft:raids" ], - "displayI18nKey": "gamerule.raids", + "displayI18nKey": "gamerule.rule.raids", "defaultValue": true }, { @@ -362,7 +362,7 @@ "minecraft:random_tick_speed", "randomTickSpeed" ], - "displayI18nKey": "gamerule.random_tick_speed", + "displayI18nKey": "gamerule.rule.random_tick_speed", "defaultValue": 3, "minValue": 0 }, @@ -371,7 +371,7 @@ "minecraft:reduced_debug_info", "reducedDebugInfo" ], - "displayI18nKey": "gamerule.reduced_debug_info", + "displayI18nKey": "gamerule.rule.reduced_debug_info", "defaultValue": false }, { @@ -379,7 +379,7 @@ "minecraft:respawn_radius", "spawnRadius" ], - "displayI18nKey": "gamerule.respawn_radius", + "displayI18nKey": "gamerule.rule.respawn_radius", "defaultValue": 10, "minValue": 0 }, @@ -388,7 +388,7 @@ "minecraft:send_command_feedback", "sendCommandFeedback" ], - "displayI18nKey": "gamerule.send_command_feedback", + "displayI18nKey": "gamerule.rule.send_command_feedback", "defaultValue": true }, { @@ -396,7 +396,7 @@ "minecraft:show_advancement_messages", "announceAdvancements" ], - "displayI18nKey": "gamerule.show_advancement_messages", + "displayI18nKey": "gamerule.rule.show_advancement_messages", "defaultValue": true }, { @@ -404,14 +404,14 @@ "minecraft:show_death_messages", "showDeathMessages" ], - "displayI18nKey": "gamerule.show_death_messages", + "displayI18nKey": "gamerule.rule.show_death_messages", "defaultValue": true }, { "ruleKey": [ "spawnChunkRadius" ], - "displayI18nKey": "gamerule.spawn_chunk_radius", + "displayI18nKey": "gamerule.rule.spawn_chunk_radius", "defaultValue": 2 }, { @@ -419,7 +419,7 @@ "minecraft:spawn_mobs", "doMobSpawning" ], - "displayI18nKey": "gamerule.spawn_mobs", + "displayI18nKey": "gamerule.rule.spawn_mobs", "defaultValue": true }, { @@ -427,7 +427,7 @@ "minecraft:spawn_monsters", "spawnMonsters" ], - "displayI18nKey": "gamerule.spawn_monsters", + "displayI18nKey": "gamerule.rule.spawn_monsters", "defaultValue": true }, { @@ -435,7 +435,7 @@ "minecraft:spawn_patrols", "doPatrolSpawning" ], - "displayI18nKey": "gamerule.spawn_patrols", + "displayI18nKey": "gamerule.rule.spawn_patrols", "defaultValue": true }, { @@ -443,7 +443,7 @@ "minecraft:spawn_phantoms", "doInsomnia" ], - "displayI18nKey": "gamerule.spawn_phantoms", + "displayI18nKey": "gamerule.rule.spawn_phantoms", "defaultValue": true }, { @@ -451,7 +451,7 @@ "minecraft:spawn_wandering_traders", "doTraderSpawning" ], - "displayI18nKey": "gamerule.spawn_wandering_traders", + "displayI18nKey": "gamerule.rule.spawn_wandering_traders", "defaultValue": true }, { @@ -459,7 +459,7 @@ "minecraft:spawn_wardens", "doWardenSpawning" ], - "displayI18nKey": "gamerule.spawn_wardens", + "displayI18nKey": "gamerule.rule.spawn_wardens", "defaultValue": true }, { @@ -467,7 +467,7 @@ "minecraft:spawner_blocks_work", "spawnerBlocksEnabled" ], - "displayI18nKey": "gamerule.spawner_blocks_work", + "displayI18nKey": "gamerule.rule.spawner_blocks_work", "defaultValue": true }, { @@ -475,7 +475,7 @@ "minecraft:spectators_generate_chunks", "spectatorsGenerateChunks" ], - "displayI18nKey": "gamerule.spectators_generate_chunks", + "displayI18nKey": "gamerule.rule.spectators_generate_chunks", "defaultValue": true }, { @@ -483,7 +483,7 @@ "minecraft:spread_vines", "doVinesSpread" ], - "displayI18nKey": "gamerule.spread_vines", + "displayI18nKey": "gamerule.rule.spread_vines", "defaultValue": true }, { @@ -491,7 +491,7 @@ "minecraft:tnt_explodes", "tntExplodes" ], - "displayI18nKey": "gamerule.tnt_explodes", + "displayI18nKey": "gamerule.rule.tnt_explodes", "defaultValue": true }, { @@ -499,7 +499,7 @@ "minecraft:tnt_explosion_drop_decay", "tntExplosionDropDecay" ], - "displayI18nKey": "gamerule.tnt_explosion_drop_decay", + "displayI18nKey": "gamerule.rule.tnt_explosion_drop_decay", "defaultValue": false }, { @@ -507,7 +507,7 @@ "minecraft:universal_anger", "universalAnger" ], - "displayI18nKey": "gamerule.universal_anger", + "displayI18nKey": "gamerule.rule.universal_anger", "defaultValue": false }, { @@ -515,7 +515,7 @@ "minecraft:water_source_conversion", "waterSourceConversion" ], - "displayI18nKey": "gamerule.water_source_conversion", + "displayI18nKey": "gamerule.rule.water_source_conversion", "defaultValue": true } ] From 83cd5c8977dcb9cda7e6b7fd02b2f3c2c986f2a4 Mon Sep 17 00:00:00 2001 From: mine_ Date: Thu, 11 Dec 2025 17:06:24 +0800 Subject: [PATCH 33/69] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/GameRuleInfo.java | 204 ++++++++++++++++++ .../hmcl/ui/versions/GameRulePage.java | 27 ++- .../hmcl/ui/versions/GameRulePageSkin.java | 129 ++--------- .../resources/assets/lang/I18N.properties | 1 + .../resources/assets/lang/I18N_zh.properties | 1 + .../assets/lang/I18N_zh_CN.properties | 1 + 6 files changed, 247 insertions(+), 116 deletions(-) create mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java new file mode 100644 index 0000000000..1bebcbb6f7 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java @@ -0,0 +1,204 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2025 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.ui.versions; + +import com.github.steveice10.opennbt.tag.builtin.Tag; +import com.jfoenix.controls.JFXButton; +import com.jfoenix.controls.JFXTextField; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.VBox; +import org.jackhuang.hmcl.gamerule.GameRuleNBT; +import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.ui.SVG; +import org.jackhuang.hmcl.ui.construct.NumberRangeValidator; +import org.jackhuang.hmcl.ui.construct.OptionToggleButton; +import org.jackhuang.hmcl.util.Lang; + +import java.util.Optional; + +import static org.jackhuang.hmcl.util.i18n.I18n.i18n; + +public abstract class GameRuleInfo { + private String ruleKey; + private String displayName; + private GameRuleNBT gameRuleNBT; + + private HBox container = new HBox(); + private Runnable setToDefault = () -> { + }; + + public void resetValue() { + setToDefault.run(); + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public String getRuleKey() { + return ruleKey; + } + + public void setRuleKey(String ruleKey) { + this.ruleKey = ruleKey; + } + + public GameRuleNBT getGameRuleNBT() { + return gameRuleNBT; + } + + public void setGameRuleNBT(GameRuleNBT gameRuleNBT) { + this.gameRuleNBT = gameRuleNBT; + } + + public HBox getContainer() { + return container; + } + + public void setContainer(HBox container) { + this.container = container; + } + + public Runnable getSetToDefault() { + return setToDefault; + } + + public void setSetToDefault(Runnable setToDefault) { + this.setToDefault = setToDefault; + } + + static class BooleanGameRuleInfo extends GameRuleInfo { + + public BooleanGameRuleInfo(String ruleKey, String displayName, Boolean onValue, Optional defaultValue, GameRuleNBT gameRuleNBT, Runnable onSave) { + this.setRuleKey(ruleKey); + this.setDisplayName(displayName); + this.setGameRuleNBT(gameRuleNBT); + + { + HBox.setHgrow(getContainer(), Priority.ALWAYS); + getContainer().setAlignment(Pos.CENTER_LEFT); + getContainer().setPadding(new Insets(0, 8, 0, 0)); + } + + OptionToggleButton toggleButton = new OptionToggleButton(); + { + toggleButton.setTitle(displayName); + toggleButton.setSubtitle(ruleKey); + toggleButton.setSelected(onValue); + toggleButton.selectedProperty().addListener((observable, oldValue, newValue) -> { + gameRuleNBT.changeValue(newValue); + onSave.run(); + }); + HBox.setHgrow(toggleButton, Priority.ALWAYS); + } + + JFXButton resetButton = new JFXButton(); + { + resetButton.setGraphic(SVG.RESTORE.createIcon(24)); + defaultValue.ifPresentOrElse(value -> { + setSetToDefault(() -> toggleButton.selectedProperty().set(value)); + resetButton.setOnAction(event -> { + getSetToDefault().run(); + }); + FXUtils.installSlowTooltip(resetButton, i18n("gamerule.restore_default_values.tooltip", value)); + }, () -> { + resetButton.setDisable(true); + }); + } + + getContainer().getChildren().addAll(toggleButton, resetButton); + } + + } + + static class IntGameRuleInfo extends GameRuleInfo { + + public IntGameRuleInfo(String ruleKey, String displayName, Integer currentValue, int minValue, int maxValue, Optional defaultValue, GameRuleNBT gameRuleNBT, Runnable onSave) { + this.setRuleKey(ruleKey); + this.setDisplayName(displayName); + this.setGameRuleNBT(gameRuleNBT); + + { + getContainer().setPadding(new Insets(8, 8, 8, 16)); + HBox.setHgrow(getContainer(), Priority.ALWAYS); + getContainer().setAlignment(Pos.CENTER_LEFT); + } + + VBox displayInfoVBox = new VBox(); + { + displayInfoVBox.getChildren().addAll(new Label(displayName), new Label(ruleKey)); + displayInfoVBox.setAlignment(Pos.CENTER_LEFT); + HBox.setHgrow(displayInfoVBox, Priority.ALWAYS); + } + + HBox hBox = new HBox(); + { + hBox.setSpacing(12); + hBox.setAlignment(Pos.CENTER_LEFT); + } + + JFXTextField textField = new JFXTextField(); + { + textField.textProperty().set(currentValue.toString()); + FXUtils.setValidateWhileTextChanged(textField, true); + textField.setValidators(new NumberRangeValidator(i18n("input.integer"), i18n("input.number_range", minValue, maxValue), minValue, maxValue, false)); + textField.textProperty().addListener((observable, oldValue, newValue) -> { + Integer value = Lang.toIntOrNull(newValue); + if (value == null) { + return; + } else if (value > maxValue || value < minValue) { + return; + } else { + gameRuleNBT.changeValue(newValue); + onSave.run(); + } + }); + + textField.maxWidth(10); + textField.minWidth(10); + } + + JFXButton resetButton = new JFXButton(); + { + resetButton.setGraphic(SVG.RESTORE.createIcon(24)); + defaultValue.ifPresentOrElse(value -> { + setSetToDefault(() -> textField.textProperty().set(String.valueOf(value))); + resetButton.setOnAction(event -> { + getSetToDefault().run(); + }); + FXUtils.installSlowTooltip(resetButton, i18n("gamerule.restore_default_values.tooltip", value)); + }, () -> { + resetButton.setDisable(true); + }); + + resetButton.setAlignment(Pos.BOTTOM_CENTER); + } + + hBox.getChildren().addAll(textField, resetButton); + getContainer().getChildren().addAll(displayInfoVBox, hBox); + } + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java index 5c1d35a4f3..7e9b4c5c7d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java @@ -39,7 +39,7 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.logging.Logger.LOG; -public class GameRulePage extends ListPageBase { +public class GameRulePage extends ListPageBase { private WorldManagePage worldManagePage; private World world; @@ -47,7 +47,8 @@ public class GameRulePage extends ListPageBase { Map gameRuleMap = GameRule.getCloneGameRuleMap(); - ObservableList gameRuleList; + ObservableList gameRuleList; + private boolean isResettingAll = false; public GameRulePage(WorldManagePage worldManagePage) { this.worldManagePage = worldManagePage; @@ -95,9 +96,9 @@ public void updateControls() { LOG.warning("Failed to get i18n text for key: " + gameRule.getDisplayI18nKey(), e); } if (gameRule instanceof GameRule.IntGameRule intGameRule) { - gameRuleList.add(new GameRulePageSkin.GameRuleInfo(intGameRule.getRuleKey().get(0), displayText, intGameRule.getValue(), intGameRule.getMinValue(), intGameRule.getMaxValue(), intGameRule.getDefaultValue(), gameRuleNBT, this::saveLevelDat)); + gameRuleList.add(new GameRuleInfo.IntGameRuleInfo(intGameRule.getRuleKey().get(0), displayText, intGameRule.getValue(), intGameRule.getMinValue(), intGameRule.getMaxValue(), intGameRule.getDefaultValue(), gameRuleNBT, this::saveLevelDatIfNotResettingAll)); } else if (gameRule instanceof GameRule.BooleanGameRule booleanGameRule) { - gameRuleList.add(new GameRulePageSkin.GameRuleInfo(booleanGameRule.getRuleKey().get(0), displayText, booleanGameRule.getValue(), booleanGameRule.getDefaultValue(), gameRuleNBT, this::saveLevelDat)); + gameRuleList.add(new GameRuleInfo.BooleanGameRuleInfo(booleanGameRule.getRuleKey().get(0), displayText, booleanGameRule.getValue(), booleanGameRule.getDefaultValue(), gameRuleNBT, this::saveLevelDatIfNotResettingAll)); } }); }); @@ -109,6 +110,14 @@ protected Skin createDefaultSkin() { return new GameRulePageSkin(this); } + public boolean isIsResettingAll() { + return isResettingAll; + } + + public void setIsResettingAll(boolean isResettingAll) { + this.isResettingAll = isResettingAll; + } + private CompoundTag loadWorldInfo() throws IOException { if (!Files.isDirectory(world.getFile())) throw new IOException("Not a valid world directory"); @@ -126,7 +135,13 @@ void saveLevelDat() { })).start(); } - @NotNull Predicate updateSearchPredicate(String queryString) { + void saveLevelDatIfNotResettingAll() { + if (!isResettingAll) { + saveLevelDat(); + } + } + + @NotNull Predicate updateSearchPredicate(String queryString) { if (queryString.isBlank()) { return gameRuleInfo -> true; } @@ -144,6 +159,6 @@ void saveLevelDat() { stringPredicate = s -> s != null && s.toLowerCase(Locale.ROOT).contains(lowerCaseFilter); } - return gameRuleInfo -> stringPredicate.test(gameRuleInfo.displayName) || stringPredicate.test(gameRuleInfo.ruleKey); + return gameRuleInfo -> stringPredicate.test(gameRuleInfo.getDisplayName()) || stringPredicate.test(gameRuleInfo.getRuleKey()); } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java index c49df6a475..cbc2e1b709 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java @@ -17,7 +17,6 @@ */ package org.jackhuang.hmcl.ui.versions; -import com.github.steveice10.opennbt.tag.builtin.Tag; import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXListView; import com.jfoenix.controls.JFXTextField; @@ -25,20 +24,19 @@ import javafx.collections.transformation.FilteredList; import javafx.geometry.Insets; import javafx.geometry.Pos; -import javafx.scene.control.Label; import javafx.scene.control.SkinBase; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; -import javafx.scene.layout.*; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.StackPane; import javafx.util.Duration; -import org.jackhuang.hmcl.gamerule.GameRuleNBT; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; -import org.jackhuang.hmcl.ui.construct.*; +import org.jackhuang.hmcl.ui.construct.ComponentList; +import org.jackhuang.hmcl.ui.construct.MDListCell; +import org.jackhuang.hmcl.ui.construct.SpinnerPane; import org.jackhuang.hmcl.util.Holder; -import org.jackhuang.hmcl.util.Lang; - -import java.util.Optional; import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton2; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; @@ -47,7 +45,7 @@ class GameRulePageSkin extends SkinBase { private final HBox searchBar; private final JFXTextField searchField; - JFXListView listView = new JFXListView<>(); + JFXListView listView = new JFXListView<>(); private final FilteredList filteredList; GameRulePageSkin(GameRulePage skinnable) { @@ -62,6 +60,15 @@ class GameRulePageSkin extends SkinBase { filteredList = new FilteredList<>(skinnable.getItems()); { + JFXButton resetAllButton = createToolbarButton2(i18n("gamerule.restore_default_values_all.button"), SVG.RESTORE, () -> { + skinnable.setIsResettingAll(true); + for (GameRuleInfo gameRuleInfo : filteredList) { + gameRuleInfo.resetValue(); + } + skinnable.saveLevelDat(); + skinnable.setIsResettingAll(false); + }); + searchBar = new HBox(); searchBar.setAlignment(Pos.CENTER); searchBar.setPadding(new Insets(0, 5, 0, 5)); @@ -74,10 +81,11 @@ class GameRulePageSkin extends SkinBase { pause.playFromStart(); }); HBox.setHgrow(searchField, Priority.ALWAYS); + JFXButton closeSearchBar = createToolbarButton2(null, SVG.CLOSE, searchField::clear); FXUtils.onEscPressed(searchField, closeSearchBar::fire); - searchBar.getChildren().addAll(searchField, closeSearchBar); + searchBar.getChildren().addAll(resetAllButton, searchField, closeSearchBar); root.getContent().add(searchBar); } @@ -96,105 +104,6 @@ class GameRulePageSkin extends SkinBase { } - static class GameRuleInfo { - - String ruleKey; - String displayName; - GameRuleNBT gameRuleNbt; - - HBox container = new HBox(); - - public GameRuleInfo(String ruleKey, String displayName, Boolean onValue, Optional defaultValue, GameRuleNBT gameRuleNbt, Runnable onSave) { - this.ruleKey = ruleKey; - this.displayName = displayName; - this.gameRuleNbt = gameRuleNbt; - - OptionToggleButton toggleButton = new OptionToggleButton(); - toggleButton.setTitle(displayName); - toggleButton.setSubtitle(ruleKey); - toggleButton.setSelected(onValue); - toggleButton.selectedProperty().addListener((observable, oldValue, newValue) -> { - gameRuleNbt.changeValue(newValue); - onSave.run(); - }); - - HBox.setHgrow(toggleButton, Priority.ALWAYS); - HBox.setHgrow(container, Priority.ALWAYS); - container.getChildren().add(toggleButton); - - JFXButton resetButton = new JFXButton(); - resetButton.setGraphic(SVG.ARROW_BACK.createIcon(24)); - defaultValue.ifPresentOrElse(value -> { - resetButton.setOnAction(event -> { - toggleButton.selectedProperty().set(value); - }); - FXUtils.installSlowTooltip(resetButton, i18n("gamerule.restore_default_values.tooltip", value)); - }, () -> { - resetButton.setDisable(true); - }); - - container.setAlignment(Pos.CENTER_LEFT); - container.setPadding(new Insets(0, 8, 0, 0)); - container.getChildren().add(resetButton); - } - - public GameRuleInfo(String ruleKey, String displayName, Integer currentValue, int minValue, int maxValue, Optional defaultValue, GameRuleNBT gameRuleNbt, Runnable onSave) { - this.ruleKey = ruleKey; - this.displayName = displayName; - this.gameRuleNbt = gameRuleNbt; - - VBox vbox = new VBox(); - vbox.getChildren().addAll(new Label(displayName), new Label(ruleKey)); - - vbox.setAlignment(Pos.CENTER_LEFT); - HBox.setHgrow(vbox, Priority.ALWAYS); - container.setPadding(new Insets(8, 8, 8, 16)); - - HBox hBox = new HBox(); - JFXTextField textField = new JFXTextField(); - textField.textProperty().set(currentValue.toString()); - FXUtils.setValidateWhileTextChanged(textField, true); - textField.setValidators(new NumberRangeValidator(i18n("input.integer"), i18n("input.number_range", minValue, maxValue), minValue, maxValue, false)); - textField.textProperty().addListener((observable, oldValue, newValue) -> { - Integer value = Lang.toIntOrNull(newValue); - if (value == null) { - return; - } else if (value > maxValue || value < minValue) { - return; - } else { - gameRuleNbt.changeValue(newValue); - onSave.run(); - } - }); - - textField.maxWidth(10); - textField.minWidth(10); - hBox.getChildren().add(textField); - - JFXButton resetButton = new JFXButton(); - resetButton.setGraphic(SVG.ARROW_BACK.createIcon(24)); - defaultValue.ifPresentOrElse(value -> { - resetButton.setOnAction(event -> { - textField.textProperty().set(String.valueOf(value)); - }); - FXUtils.installSlowTooltip(resetButton, i18n("gamerule.restore_default_values.tooltip", value)); - }, () -> { - resetButton.setDisable(true); - }); - - resetButton.setAlignment(Pos.BOTTOM_CENTER); - hBox.setSpacing(12); - hBox.getChildren().add(resetButton); - hBox.setAlignment(Pos.CENTER_LEFT); - - HBox.setHgrow(container, Priority.ALWAYS); - container.getChildren().add(vbox); - container.getChildren().add(hBox); - container.setAlignment(Pos.CENTER_LEFT); - } - - } - static class GameRuleListCell extends MDListCell { public GameRuleListCell(JFXListView listView, Holder lastCell) { @@ -204,7 +113,7 @@ public GameRuleListCell(JFXListView listView, Holder lastC @Override protected void updateControl(GameRuleInfo item, boolean empty) { if (empty) return; - getContainer().getChildren().setAll(item.container); + getContainer().getChildren().setAll(item.getContainer()); } } } diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index ed3fb0f04e..8ed704430a 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -685,6 +685,7 @@ game.crash.title=Game Crashed game.directory=Game Path game.version=Game Instance +gamerule.restore_default_values_all.button=reset all gamerule.restore_default_values.tooltip=Click to reset to default\nDefault: %s gamerule.rule.advance_time=Advance Time gamerule.rule.advance_weather=Advance Weather diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index a018134039..355bbce650 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -492,6 +492,7 @@ game.crash.title=遊戲意外退出 game.directory=遊戲目錄路徑 game.version=遊戲實例 +gamerule.restore_default_values_all.button=全部恢复默认值 gamerule.restore_default_values.tooltip=點擊以還原為預設值\n預設值: %s gamerule.rule.advance_time=日夜交替 gamerule.rule.advance_weather=更新天氣 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 3b1a6f5398..a8c2ad7c56 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -502,6 +502,7 @@ game.crash.title=游戏意外退出 game.directory=游戏文件夹路径 game.version=游戏实例 +gamerule.restore_default_values_all.button=全部恢复默认值 gamerule.restore_default_values.tooltip=点击恢复默认值\n默认值: %s gamerule.rule.advance_time=游戏内时间流逝 gamerule.rule.advance_weather=天气更替 From f116b668d48d537747477bd79b5f7aeb9856112c Mon Sep 17 00:00:00 2001 From: mine_ Date: Thu, 11 Dec 2025 17:54:03 +0800 Subject: [PATCH 34/69] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/jackhuang/hmcl/ui/versions/GameRuleInfo.java | 1 + .../org/jackhuang/hmcl/ui/versions/GameRulePage.java | 9 +++++++++ .../org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java | 9 +-------- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java index 1bebcbb6f7..c8cb9e3cdf 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java @@ -42,6 +42,7 @@ public abstract class GameRuleInfo { private String displayName; private GameRuleNBT gameRuleNBT; + //Due to the significant difference in skin between BooleanGameRuleInfo and IntGameRuleInfo, which are essentially two completely different styles, it is not suitable to update each other in Cell#updateControl. Therefore, they are directly integrated into the info. private HBox container = new HBox(); private Runnable setToDefault = () -> { }; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java index 7e9b4c5c7d..21f79ef68d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java @@ -141,6 +141,15 @@ void saveLevelDatIfNotResettingAll() { } } + void resettingAllGameRule(){ + isResettingAll = true; + for (GameRuleInfo gameRuleInfo : getItems()) { + gameRuleInfo.resetValue(); + } + saveLevelDat(); + isResettingAll = false; + } + @NotNull Predicate updateSearchPredicate(String queryString) { if (queryString.isBlank()) { return gameRuleInfo -> true; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java index cbc2e1b709..983bf3a56e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java @@ -60,14 +60,7 @@ class GameRulePageSkin extends SkinBase { filteredList = new FilteredList<>(skinnable.getItems()); { - JFXButton resetAllButton = createToolbarButton2(i18n("gamerule.restore_default_values_all.button"), SVG.RESTORE, () -> { - skinnable.setIsResettingAll(true); - for (GameRuleInfo gameRuleInfo : filteredList) { - gameRuleInfo.resetValue(); - } - skinnable.saveLevelDat(); - skinnable.setIsResettingAll(false); - }); + JFXButton resetAllButton = createToolbarButton2(i18n("gamerule.restore_default_values_all.button"), SVG.RESTORE, skinnable::resettingAllGameRule); searchBar = new HBox(); searchBar.setAlignment(Pos.CENTER); From 244dce2cb14f9214499c9a7c289a5009faf7aa1f Mon Sep 17 00:00:00 2001 From: mine_ Date: Thu, 11 Dec 2025 18:59:23 +0800 Subject: [PATCH 35/69] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java index 21f79ef68d..daf833b2da 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java @@ -141,7 +141,7 @@ void saveLevelDatIfNotResettingAll() { } } - void resettingAllGameRule(){ + void resettingAllGameRule() { isResettingAll = true; for (GameRuleInfo gameRuleInfo : getItems()) { gameRuleInfo.resetValue(); From 61ece690fe9462994f10363b0856ab23d68121e9 Mon Sep 17 00:00:00 2001 From: mine_ Date: Thu, 11 Dec 2025 21:54:40 +0800 Subject: [PATCH 36/69] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0i18n=EF=BC=8C?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=A1=AE=E8=AE=A4=E5=BC=B9=E7=AA=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java | 1 + .../org/jackhuang/hmcl/ui/versions/GameRulePage.java | 2 ++ .../org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java | 5 ++++- .../org/jackhuang/hmcl/ui/versions/WorldManagePage.java | 2 +- HMCL/src/main/resources/assets/lang/I18N.properties | 7 +++++-- HMCL/src/main/resources/assets/lang/I18N_zh.properties | 9 ++++++--- .../src/main/resources/assets/lang/I18N_zh_CN.properties | 7 +++++-- 7 files changed, 24 insertions(+), 9 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java index 2f1ea2aa13..5c6e459a6d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java @@ -104,6 +104,7 @@ public enum SVG { REFRESH("M12 20Q8.65 20 6.325 17.675T4 12Q4 8.65 6.325 6.325T12 4Q13.725 4 15.3 4.7125T18 6.75V4H20V11H13V9H17.2Q16.4 7.6 15.0125 6.8T12 6Q9.5 6 7.75 7.75T6 12Q6 14.5 7.75 16.25T12 18Q13.925 18 15.475 16.9T17.65 14H19.75Q19.05 16.65 16.9 18.325T12 20Z"), RELEASE_CIRCLE("M9,7H13A2,2 0 0,1 15,9V11C15,11.84 14.5,12.55 13.76,12.85L15,17H13L11.8,13H11V17H9V7M11,9V11H13V9H11M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12C4,16.41 7.58,20 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z"), // Not Material RESTORE("M12 21Q8.55 21 5.9875 18.7125T3.05 13H5.1Q5.45 15.6 7.4125 17.3T12 19Q14.925 19 16.9625 16.9625T19 12Q19 9.075 16.9625 7.0375T12 5Q10.275 5 8.775 5.8T6.25 8H9V10H3V4H5V6.35Q6.275 4.75 8.1125 3.875T12 3Q13.875 3 15.5125 3.7125T18.3625 5.6375Q19.575 6.85 20.2875 8.4875T21 12Q21 13.875 20.2875 15.5125T18.3625 18.3625Q17.15 19.575 15.5125 20.2875T12 21Z"), // Not Material + RULE("m14.4 20-1.4-1.4 2.6-2.6-2.6-2.6 1.4-1.4 2.6 2.6 2.6-2.6 1.4 1.4-2.6 2.6 2.6 2.6-1.4 1.4-2.6-2.6-2.6 2.6ZM16.375 11L12.825 7.45l1.4-1.4 2.125 2.125 4.25-4.25 1.4 1.425-5.625 5.65ZM2 17v-2h9v2H2ZM2 9v-2h9v2H2Z"), ROCKET_LAUNCH("M5.65 10.025 7.6 10.85Q7.95 10.15 8.325 9.5T9.15 8.2L7.75 7.925 5.65 10.025ZM9.2 12.1 12.05 14.925Q13.1 14.525 14.3 13.7T16.55 11.825Q18.3 10.075 19.2875 7.9375T20.15 4Q18.35 3.875 16.2 4.8625T12.3 7.6Q11.25 8.65 10.425 9.85T9.2 12.1ZM13.65 10.475Q13.075 9.9 13.075 9.0625T13.65 7.65Q14.225 7.075 15.075 7.075T16.5 7.65Q17.075 8.225 17.075 9.0625T16.5 10.475Q15.925 11.05 15.075 11.05T13.65 10.475ZM14.125 18.5 16.225 16.4 15.95 15Q15.3 15.45 14.65 15.8125T13.3 16.525L14.125 18.5ZM21.95 2.175Q22.425 5.2 21.3625 8.0625T17.7 13.525L18.2 16Q18.3 16.5 18.15 16.975T17.65 17.8L13.45 22 11.35 17.075 7.075 12.8 2.15 10.7 6.325 6.5Q6.675 6.15 7.1625 6T8.15 5.95L10.625 6.45Q13.225 3.85 16.075 2.775T21.95 2.175ZM3.925 15.975Q4.8 15.1 6.0625 15.0875T8.2 15.95Q9.075 16.825 9.0625 18.0875T8.175 20.225Q7.55 20.85 6.0875 21.3T2.05 22.1Q2.4 19.525 2.85 18.0625T3.925 15.975ZM5.35 17.375Q5.1 17.625 4.85 18.2875T4.5 19.625Q5.175 19.525 5.8375 19.2875T6.75 18.8Q7.05 18.5 7.075 18.075T6.8 17.35Q6.5 17.05 6.075 17.0625T5.35 17.375Z"), SCHEMA("M4 23V17H6.5V15H4V9H6.5V7H4V1h7V7H8.5V9H11v2h3V9h7v6H14V13H11v2H8.5v2H11v6H4Zm2-2H9V19H6v2Zm0-8H9V11H6v2Zm10 0h3V11H16v2ZM6 5H9V3H6V5ZM7.5 4Zm0 8Zm10 0Zm-10 8Z"), SCHEMA_FILL("M4 23V17H6.5V15H4V9H6.5V7H4V1h7V7H8.5V9H11v2h3V9h7v6H14V13H11v2H8.5v2H11v6H4Z"), diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java index daf833b2da..e7c532418a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java @@ -25,6 +25,7 @@ import org.jackhuang.hmcl.gamerule.GameRule; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.ListPageBase; import org.jackhuang.hmcl.util.StringUtils; import org.jetbrains.annotations.NotNull; @@ -148,6 +149,7 @@ void resettingAllGameRule() { } saveLevelDat(); isResettingAll = false; + Controllers.showToast(i18n("gamerule.restore_default_values_all.finish.toast")); } @NotNull Predicate updateSearchPredicate(String queryString) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java index 983bf3a56e..2be6e63a5d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java @@ -31,6 +31,7 @@ import javafx.scene.layout.Priority; import javafx.scene.layout.StackPane; import javafx.util.Duration; +import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; import org.jackhuang.hmcl.ui.construct.ComponentList; @@ -60,7 +61,9 @@ class GameRulePageSkin extends SkinBase { filteredList = new FilteredList<>(skinnable.getItems()); { - JFXButton resetAllButton = createToolbarButton2(i18n("gamerule.restore_default_values_all.button"), SVG.RESTORE, skinnable::resettingAllGameRule); + JFXButton resetAllButton = createToolbarButton2(i18n("gamerule.restore_default_values_all"), SVG.RESTORE, () -> { + Controllers.confirm(i18n("gamerule.restore_default_values_all.confirm"), null, skinnable::resettingAllGameRule, null); + }); searchBar = new HBox(); searchBar.setAlignment(Pos.CENTER); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldManagePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldManagePage.java index 4e41578521..0eebe6617d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldManagePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldManagePage.java @@ -82,7 +82,7 @@ public WorldManagePage(World world, Path backupsDir) { AdvancedListBox sideBar = new AdvancedListBox() .addNavigationDrawerTab(header, worldInfoTab, i18n("world.info"), SVG.INFO, SVG.INFO_FILL) - .addNavigationDrawerTab(header, gameRuleTab, "游戏规则", SVG.INFO, SVG.INFO_FILL) + .addNavigationDrawerTab(header, gameRuleTab, i18n("gamerule"), SVG.RULE) .addNavigationDrawerTab(header, worldBackupsTab, i18n("world.backup"), SVG.ARCHIVE, SVG.ARCHIVE_FILL); if (world.getGameVersion() != null && // old game will not write game version to level.dat diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 8ed704430a..e290608305 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -685,8 +685,11 @@ game.crash.title=Game Crashed game.directory=Game Path game.version=Game Instance -gamerule.restore_default_values_all.button=reset all -gamerule.restore_default_values.tooltip=Click to reset to default\nDefault: %s +gamerule=Game Rule +gamerule.restore_default_values_all=Reset all to game defaults +gamerule.restore_default_values_all.confirm=Confirm resetting all to game defaults? +gamerule.restore_default_values_all.finish.toast=All game rules have been reset to default values +gamerule.restore_default_values.tooltip=Reset this rule to game default.\nDefault: %s gamerule.rule.advance_time=Advance Time gamerule.rule.advance_weather=Advance Weather gamerule.rule.allow_fire_ticks_away_from_player=Allow Fire Ticks Away From Player diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 355bbce650..8524a8fdfd 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -492,8 +492,11 @@ game.crash.title=遊戲意外退出 game.directory=遊戲目錄路徑 game.version=遊戲實例 -gamerule.restore_default_values_all.button=全部恢复默认值 -gamerule.restore_default_values.tooltip=點擊以還原為預設值\n預設值: %s +gamerule=遊戲規則 +gamerule.restore_default_values_all=復原遊戲預設規則 +gamerule.restore_default_values_all.confirm=確定復原遊戲預設規則? +gamerule.restore_default_values_all.finish.toast=已將所有遊戲規則還原至預設值 +gamerule.restore_default_values.tooltip=復原此項預設規則\n預設值: %s gamerule.rule.advance_time=日夜交替 gamerule.rule.advance_weather=更新天氣 gamerule.rule.allow_fire_ticks_away_from_player=允許火在遠離玩家處蔓延 @@ -565,7 +568,7 @@ help.detail=可查閱資料包、模組包製作教學等內容 input.email=[使用者名稱] 必須是電子信箱格式 input.number=必須是數字 -input.integer=必须是整數 +input.integer=必須是整數 input.number_range=輸入的數字在有效範圍 %d~%d 之外 input.not_empty=必填 input.url=必須是有效連結 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index a8c2ad7c56..e314aeaac0 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -502,8 +502,11 @@ game.crash.title=游戏意外退出 game.directory=游戏文件夹路径 game.version=游戏实例 -gamerule.restore_default_values_all.button=全部恢复默认值 -gamerule.restore_default_values.tooltip=点击恢复默认值\n默认值: %s +gamerule=游戏规则 +gamerule.restore_default_values_all=恢复游戏默认规则 +gamerule.restore_default_values_all.confirm=确认恢复游戏默认规则? +gamerule.restore_default_values_all.finish.toast=已将所有游戏规则恢复至默认值 +gamerule.restore_default_values.tooltip=恢复此项默认规则\n默认值: %s gamerule.rule.advance_time=游戏内时间流逝 gamerule.rule.advance_weather=天气更替 gamerule.rule.allow_fire_ticks_away_from_player=允许火在远离玩家处蔓延 From bf3f8498e7291ee2fa37a8e3f6098fc1c0d131cb Mon Sep 17 00:00:00 2001 From: mine_ Date: Wed, 24 Dec 2025 22:28:12 +0800 Subject: [PATCH 37/69] =?UTF-8?q?feat:=20=E7=8E=B0=E5=9C=A8NumberRangeVali?= =?UTF-8?q?dator=E5=8F=AA=E4=BC=9A=E9=AA=8C=E8=AF=81=E6=98=AF=E5=90=A6?= =?UTF-8?q?=E5=9C=A8=E8=8C=83=E5=9B=B4=E5=86=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/construct/NumberRangeValidator.java | 38 ++++++++----------- .../hmcl/ui/versions/GameRuleInfo.java | 5 ++- .../resources/assets/lang/I18N.properties | 4 +- .../resources/assets/lang/I18N_zh.properties | 2 +- .../assets/lang/I18N_zh_CN.properties | 4 +- 5 files changed, 25 insertions(+), 28 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/NumberRangeValidator.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/NumberRangeValidator.java index aa69f7e1ed..5b5894818a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/NumberRangeValidator.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/NumberRangeValidator.java @@ -21,22 +21,24 @@ import javafx.beans.NamedArg; import javafx.scene.control.TextInputControl; import org.jackhuang.hmcl.util.Lang; -import org.jackhuang.hmcl.util.StringUtils; +/// NumberRangeValidator only check whether inputted number is in range, but not if it is a number, +/// if the input is not a number, NumberRangeValidator will not show error message public class NumberRangeValidator extends ValidatorBase { - private final String notNumberMessage; - private final String outOfLimitMessage; - private final boolean nullable; private final int minValue; private final int maxValue; - public NumberRangeValidator(@NamedArg("notNumberMessage") String notNumberMessage, @NamedArg("outOfLimitMessage") String outOfLimitMessage, @NamedArg("minValue") int minValue, @NamedArg("maxValue") int maxValue, @NamedArg("nullable") boolean nullable) { - super(notNumberMessage); - this.notNumberMessage = notNumberMessage; - this.outOfLimitMessage = outOfLimitMessage; - this.nullable = nullable; + public NumberRangeValidator(@NamedArg("outOfLimitMessage") String outOfLimitMessage, @NamedArg("minValue") int minValue, @NamedArg("maxValue") int maxValue) { + super(outOfLimitMessage); this.minValue = minValue; this.maxValue = maxValue; + if (srcControl.get() instanceof TextInputControl textInputControl) { + textInputControl.tooltipProperty().addListener((ov, t, t1) -> { + if (t1 != null) { + System.out.println("new tooltip is " + t1.getText()); + } + }); + } } @Override @@ -48,20 +50,12 @@ protected void eval() { private void evalTextInputField() { TextInputControl textField = ((TextInputControl) srcControl.get()); + Double intOrNull = Lang.toDoubleOrNull(textField.getText()); - if (StringUtils.isBlank(textField.getText())) - hasErrors.set(!nullable); - else { - Integer intOrNull = Lang.toIntOrNull(textField.getText()); - if (intOrNull == null) { - setMessage(notNumberMessage); - hasErrors.set(true); - } else if (intOrNull > maxValue || intOrNull < minValue) { - setMessage(outOfLimitMessage); - hasErrors.set(true); - } else { - hasErrors.set(false); - } + if (intOrNull == null) { + hasErrors.set(false); + } else { + hasErrors.set(intOrNull > maxValue || intOrNull < minValue); } } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java index c8cb9e3cdf..74966cf447 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java @@ -30,6 +30,7 @@ import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; import org.jackhuang.hmcl.ui.construct.NumberRangeValidator; +import org.jackhuang.hmcl.ui.construct.NumberValidator; import org.jackhuang.hmcl.ui.construct.OptionToggleButton; import org.jackhuang.hmcl.util.Lang; @@ -165,7 +166,9 @@ public IntGameRuleInfo(String ruleKey, String displayName, Integer currentValue, { textField.textProperty().set(currentValue.toString()); FXUtils.setValidateWhileTextChanged(textField, true); - textField.setValidators(new NumberRangeValidator(i18n("input.integer"), i18n("input.number_range", minValue, maxValue), minValue, maxValue, false)); + textField.setValidators( + new NumberValidator(i18n("input.integer"), false), + new NumberRangeValidator(i18n("input.number_range", minValue, maxValue), minValue, maxValue)); textField.textProperty().addListener((observable, oldValue, newValue) -> { Integer value = Lang.toIntOrNull(newValue); if (value == null) { diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index e290608305..aae6b85d65 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -761,8 +761,8 @@ help.detail=For datapack and modpack makers. input.email=The username must be an email address. input.number=The input must be numbers. -input.integer=The input must be an integer. -input.number_range=The input number is outside the valid range of %d to %d. +input.integer=Must be an integer. +input.number_range=Range: %d to %d. input.not_empty=This is a required field. input.url=The input must be a valid URL. diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 8524a8fdfd..fa1006da8c 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -569,7 +569,7 @@ help.detail=可查閱資料包、模組包製作教學等內容 input.email=[使用者名稱] 必須是電子信箱格式 input.number=必須是數字 input.integer=必須是整數 -input.number_range=輸入的數字在有效範圍 %d~%d 之外 +input.number_range=有效範圍: %d~%d input.not_empty=必填 input.url=必須是有效連結 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index e314aeaac0..ce0e326c95 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -578,8 +578,8 @@ help.detail=可查阅数据包、整合包制作指南等内容 input.email=用户名必须是邮箱 input.number=必须是数字 -input.integer=必须是整数. -input.number_range=输入的数字在有效范围 %d~%d 之外 +input.integer=必须是整数 +input.number_range=有效范围: %d~%d input.not_empty=必填项 input.url=必须是合法的链接 From 1a7ab6aec549dfcf142166c03795125e4a9e99e5 Mon Sep 17 00:00:00 2001 From: mine_ Date: Wed, 24 Dec 2025 22:43:58 +0800 Subject: [PATCH 38/69] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E6=81=A2?= =?UTF-8?q?=E5=A4=8D=E5=85=A8=E9=83=A8=E6=B8=B8=E6=88=8F=E8=A7=84=E5=88=99?= =?UTF-8?q?=E8=AD=A6=E5=91=8A=E5=BC=B9=E7=AA=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java | 3 ++- HMCL/src/main/resources/assets/lang/I18N.properties | 2 +- HMCL/src/main/resources/assets/lang/I18N_zh.properties | 4 ++-- HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java index 2be6e63a5d..4b5d82ec94 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java @@ -36,6 +36,7 @@ import org.jackhuang.hmcl.ui.SVG; import org.jackhuang.hmcl.ui.construct.ComponentList; import org.jackhuang.hmcl.ui.construct.MDListCell; +import org.jackhuang.hmcl.ui.construct.MessageDialogPane; import org.jackhuang.hmcl.ui.construct.SpinnerPane; import org.jackhuang.hmcl.util.Holder; @@ -62,7 +63,7 @@ class GameRulePageSkin extends SkinBase { { JFXButton resetAllButton = createToolbarButton2(i18n("gamerule.restore_default_values_all"), SVG.RESTORE, () -> { - Controllers.confirm(i18n("gamerule.restore_default_values_all.confirm"), null, skinnable::resettingAllGameRule, null); + Controllers.confirm(i18n("gamerule.restore_default_values_all.confirm"), i18n("message.warning"), MessageDialogPane.MessageType.WARNING, skinnable::resettingAllGameRule, null); }); searchBar = new HBox(); diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index aae6b85d65..79b6fb5b1d 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -687,7 +687,7 @@ game.version=Game Instance gamerule=Game Rule gamerule.restore_default_values_all=Reset all to game defaults -gamerule.restore_default_values_all.confirm=Confirm resetting all to game defaults? +gamerule.restore_default_values_all.confirm=Are you sure you want to reset all rules to the game's default? This action cannot be undone! gamerule.restore_default_values_all.finish.toast=All game rules have been reset to default values gamerule.restore_default_values.tooltip=Reset this rule to game default.\nDefault: %s gamerule.rule.advance_time=Advance Time diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index fa1006da8c..e6098737e2 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -494,8 +494,8 @@ game.version=遊戲實例 gamerule=遊戲規則 gamerule.restore_default_values_all=復原遊戲預設規則 -gamerule.restore_default_values_all.confirm=確定復原遊戲預設規則? -gamerule.restore_default_values_all.finish.toast=已將所有遊戲規則還原至預設值 +gamerule.restore_default_values_all.confirm=你確定要復原所有規則至遊戲預設嗎?該操作無法復原! +gamerule.restore_default_values_all.finish.toast=已將所有遊戲規則復原至預設值 gamerule.restore_default_values.tooltip=復原此項預設規則\n預設值: %s gamerule.rule.advance_time=日夜交替 gamerule.rule.advance_weather=更新天氣 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index ce0e326c95..6bed674ff4 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -504,7 +504,7 @@ game.version=游戏实例 gamerule=游戏规则 gamerule.restore_default_values_all=恢复游戏默认规则 -gamerule.restore_default_values_all.confirm=确认恢复游戏默认规则? +gamerule.restore_default_values_all.confirm=你确定要恢复所有规则为游戏默认吗?此操作无法撤销! gamerule.restore_default_values_all.finish.toast=已将所有游戏规则恢复至默认值 gamerule.restore_default_values.tooltip=恢复此项默认规则\n默认值: %s gamerule.rule.advance_time=游戏内时间流逝 From 6a6dc0f047795a91120396d986c3c2db1aaa107d Mon Sep 17 00:00:00 2001 From: mine_ Date: Wed, 24 Dec 2025 23:00:34 +0800 Subject: [PATCH 39/69] =?UTF-8?q?fix:=20=E6=97=A0=E6=B3=95=E8=BF=90?= =?UTF-8?q?=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java index 4b5d82ec94..6eb4ef960e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java @@ -38,7 +38,6 @@ import org.jackhuang.hmcl.ui.construct.MDListCell; import org.jackhuang.hmcl.ui.construct.MessageDialogPane; import org.jackhuang.hmcl.ui.construct.SpinnerPane; -import org.jackhuang.hmcl.util.Holder; import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton2; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; @@ -90,9 +89,8 @@ class GameRulePageSkin extends SkinBase { ComponentList.setVgrow(center, Priority.ALWAYS); center.getStyleClass().add("large-spinner-pane"); center.setContent(listView); - Holder lastCell = new Holder<>(); listView.setItems(filteredList); - listView.setCellFactory(x -> new GameRuleListCell(listView, lastCell)); + listView.setCellFactory(x -> new GameRuleListCell(listView)); FXUtils.ignoreEvent(listView, KeyEvent.KEY_PRESSED, e -> e.getCode() == KeyCode.ESCAPE); root.getContent().add(center); @@ -103,8 +101,8 @@ class GameRulePageSkin extends SkinBase { static class GameRuleListCell extends MDListCell { - public GameRuleListCell(JFXListView listView, Holder lastCell) { - super(listView, lastCell); + public GameRuleListCell(JFXListView listView) { + super(listView); } @Override From 9ba99bad2c996fd0562938ce05777d3029150dab Mon Sep 17 00:00:00 2001 From: mine_ Date: Wed, 24 Dec 2025 23:55:27 +0800 Subject: [PATCH 40/69] =?UTF-8?q?fix:=20=E9=87=8D=E6=9E=84=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E4=BB=A5=E9=81=BF=E5=85=8D=E8=A3=B8=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E6=B3=9B=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/GameRuleInfo.java | 28 +++++++------------ .../hmcl/ui/versions/GameRulePage.java | 20 +++++++++---- .../hmcl/ui/versions/GameRulePageSkin.java | 10 +++---- .../org/jackhuang/hmcl/gamerule/GameRule.java | 2 +- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java index 74966cf447..f9d6f917cc 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java @@ -38,10 +38,10 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n; -public abstract class GameRuleInfo { +public sealed abstract class GameRuleInfo permits GameRuleInfo.BooleanGameRuleInfo, GameRuleInfo.IntGameRuleInfo { private String ruleKey; private String displayName; - private GameRuleNBT gameRuleNBT; + private GameRuleNBT gameRuleNBT; //Due to the significant difference in skin between BooleanGameRuleInfo and IntGameRuleInfo, which are essentially two completely different styles, it is not suitable to update each other in Cell#updateControl. Therefore, they are directly integrated into the info. private HBox container = new HBox(); @@ -68,11 +68,11 @@ public void setRuleKey(String ruleKey) { this.ruleKey = ruleKey; } - public GameRuleNBT getGameRuleNBT() { + public GameRuleNBT getGameRuleNBT() { return gameRuleNBT; } - public void setGameRuleNBT(GameRuleNBT gameRuleNBT) { + public void setGameRuleNBT(GameRuleNBT gameRuleNBT) { this.gameRuleNBT = gameRuleNBT; } @@ -92,7 +92,7 @@ public void setSetToDefault(Runnable setToDefault) { this.setToDefault = setToDefault; } - static class BooleanGameRuleInfo extends GameRuleInfo { + static final class BooleanGameRuleInfo extends GameRuleInfo { public BooleanGameRuleInfo(String ruleKey, String displayName, Boolean onValue, Optional defaultValue, GameRuleNBT gameRuleNBT, Runnable onSave) { this.setRuleKey(ruleKey); @@ -122,13 +122,9 @@ public BooleanGameRuleInfo(String ruleKey, String displayName, Boolean onValue, resetButton.setGraphic(SVG.RESTORE.createIcon(24)); defaultValue.ifPresentOrElse(value -> { setSetToDefault(() -> toggleButton.selectedProperty().set(value)); - resetButton.setOnAction(event -> { - getSetToDefault().run(); - }); + resetButton.setOnAction(event -> getSetToDefault().run()); FXUtils.installSlowTooltip(resetButton, i18n("gamerule.restore_default_values.tooltip", value)); - }, () -> { - resetButton.setDisable(true); - }); + }, () -> resetButton.setDisable(true)); } getContainer().getChildren().addAll(toggleButton, resetButton); @@ -136,7 +132,7 @@ public BooleanGameRuleInfo(String ruleKey, String displayName, Boolean onValue, } - static class IntGameRuleInfo extends GameRuleInfo { + static final class IntGameRuleInfo extends GameRuleInfo { public IntGameRuleInfo(String ruleKey, String displayName, Integer currentValue, int minValue, int maxValue, Optional defaultValue, GameRuleNBT gameRuleNBT, Runnable onSave) { this.setRuleKey(ruleKey); @@ -190,13 +186,9 @@ public IntGameRuleInfo(String ruleKey, String displayName, Integer currentValue, resetButton.setGraphic(SVG.RESTORE.createIcon(24)); defaultValue.ifPresentOrElse(value -> { setSetToDefault(() -> textField.textProperty().set(String.valueOf(value))); - resetButton.setOnAction(event -> { - getSetToDefault().run(); - }); + resetButton.setOnAction(event -> getSetToDefault().run()); FXUtils.installSlowTooltip(resetButton, i18n("gamerule.restore_default_values.tooltip", value)); - }, () -> { - resetButton.setDisable(true); - }); + }, () -> resetButton.setDisable(true)); resetButton.setAlignment(Pos.BOTTOM_CENTER); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java index e7c532418a..086df91754 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java @@ -18,11 +18,13 @@ package org.jackhuang.hmcl.ui.versions; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.scene.control.Skin; import org.jackhuang.hmcl.game.World; import org.jackhuang.hmcl.gamerule.GameRule; +import org.jackhuang.hmcl.gamerule.GameRuleNBT; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.Controllers; @@ -40,7 +42,7 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.logging.Logger.LOG; -public class GameRulePage extends ListPageBase { +public class GameRulePage extends ListPageBase> { private WorldManagePage worldManagePage; private World world; @@ -48,7 +50,7 @@ public class GameRulePage extends ListPageBase { Map gameRuleMap = GameRule.getCloneGameRuleMap(); - ObservableList gameRuleList; + ObservableList> gameRuleList; private boolean isResettingAll = false; public GameRulePage(WorldManagePage worldManagePage) { @@ -97,9 +99,15 @@ public void updateControls() { LOG.warning("Failed to get i18n text for key: " + gameRule.getDisplayI18nKey(), e); } if (gameRule instanceof GameRule.IntGameRule intGameRule) { - gameRuleList.add(new GameRuleInfo.IntGameRuleInfo(intGameRule.getRuleKey().get(0), displayText, intGameRule.getValue(), intGameRule.getMinValue(), intGameRule.getMaxValue(), intGameRule.getDefaultValue(), gameRuleNBT, this::saveLevelDatIfNotResettingAll)); + @SuppressWarnings("unchecked") + GameRuleNBT typedGameRuleNBT = (GameRuleNBT) gameRuleNBT; + + gameRuleList.add(new GameRuleInfo.IntGameRuleInfo(intGameRule.getRuleKey().get(0), displayText, intGameRule.getValue(), intGameRule.getMinValue(), intGameRule.getMaxValue(), intGameRule.getDefaultValue(), typedGameRuleNBT, this::saveLevelDatIfNotResettingAll)); } else if (gameRule instanceof GameRule.BooleanGameRule booleanGameRule) { - gameRuleList.add(new GameRuleInfo.BooleanGameRuleInfo(booleanGameRule.getRuleKey().get(0), displayText, booleanGameRule.getValue(), booleanGameRule.getDefaultValue(), gameRuleNBT, this::saveLevelDatIfNotResettingAll)); + @SuppressWarnings("unchecked") + GameRuleNBT typedGameRuleNBT = (GameRuleNBT) gameRuleNBT; + + gameRuleList.add(new GameRuleInfo.BooleanGameRuleInfo(booleanGameRule.getRuleKey().get(0), displayText, booleanGameRule.getValue(), booleanGameRule.getDefaultValue(), typedGameRuleNBT, this::saveLevelDatIfNotResettingAll)); } }); }); @@ -144,7 +152,7 @@ void saveLevelDatIfNotResettingAll() { void resettingAllGameRule() { isResettingAll = true; - for (GameRuleInfo gameRuleInfo : getItems()) { + for (GameRuleInfo gameRuleInfo : getItems()) { gameRuleInfo.resetValue(); } saveLevelDat(); @@ -152,7 +160,7 @@ void resettingAllGameRule() { Controllers.showToast(i18n("gamerule.restore_default_values_all.finish.toast")); } - @NotNull Predicate updateSearchPredicate(String queryString) { + @NotNull Predicate> updateSearchPredicate(String queryString) { if (queryString.isBlank()) { return gameRuleInfo -> true; } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java index 6eb4ef960e..01596af70d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java @@ -46,8 +46,8 @@ class GameRulePageSkin extends SkinBase { private final HBox searchBar; private final JFXTextField searchField; - JFXListView listView = new JFXListView<>(); - private final FilteredList filteredList; + JFXListView> listView = new JFXListView<>(); + private final FilteredList> filteredList; GameRulePageSkin(GameRulePage skinnable) { super(skinnable); @@ -99,14 +99,14 @@ class GameRulePageSkin extends SkinBase { } - static class GameRuleListCell extends MDListCell { + static class GameRuleListCell extends MDListCell> { - public GameRuleListCell(JFXListView listView) { + public GameRuleListCell(JFXListView> listView) { super(listView); } @Override - protected void updateControl(GameRuleInfo item, boolean empty) { + protected void updateControl(GameRuleInfo item, boolean empty) { if (empty) return; getContainer().getChildren().setAll(item.getContainer()); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java index c1e07de21d..61800bf643 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java @@ -126,7 +126,7 @@ public static void addSimpleGameRule(Map gameRuleMap, String r /// Used for unified changing operations back to NBT format. /// /// @see GameRuleNBT - public static Optional createGameRuleNbt(Tag tag) { + public static Optional> createGameRuleNbt(Tag tag) { if (tag instanceof StringTag stringTag && (tag.getValue().equals("true") || tag.getValue().equals("false"))) { return Optional.of(new GameRuleNBT.StringByteGameRuleNBT(stringTag)); } else if (tag instanceof StringTag stringTag && Lang.toIntOrNull(stringTag.getValue()) != null) { From 1440a4ad0c12684e0381aa374a130372cbdacc7d Mon Sep 17 00:00:00 2001 From: mine_ Date: Thu, 25 Dec 2025 10:33:47 +0800 Subject: [PATCH 41/69] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/GameRuleInfo.java | 102 +++++++++++++----- .../hmcl/ui/versions/GameRulePage.java | 12 +-- .../org/jackhuang/hmcl/gamerule/GameRule.java | 8 +- 3 files changed, 83 insertions(+), 39 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java index f9d6f917cc..89efeabb81 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java @@ -26,6 +26,7 @@ import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; +import org.jackhuang.hmcl.gamerule.GameRule; import org.jackhuang.hmcl.gamerule.GameRuleNBT; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; @@ -33,15 +34,16 @@ import org.jackhuang.hmcl.ui.construct.NumberValidator; import org.jackhuang.hmcl.ui.construct.OptionToggleButton; import org.jackhuang.hmcl.util.Lang; - -import java.util.Optional; +import org.jackhuang.hmcl.util.StringUtils; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; +import static org.jackhuang.hmcl.util.logging.Logger.LOG; public sealed abstract class GameRuleInfo permits GameRuleInfo.BooleanGameRuleInfo, GameRuleInfo.IntGameRuleInfo { private String ruleKey; private String displayName; private GameRuleNBT gameRuleNBT; + private Runnable onSave; //Due to the significant difference in skin between BooleanGameRuleInfo and IntGameRuleInfo, which are essentially two completely different styles, it is not suitable to update each other in Cell#updateControl. Therefore, they are directly integrated into the info. private HBox container = new HBox(); @@ -76,6 +78,14 @@ public void setGameRuleNBT(GameRuleNBT gameRuleNBT) { this.gameRuleNBT = gameRuleNBT; } + public void setOnSave(Runnable onSave) { + this.onSave = onSave; + } + + public Runnable getOnSave() { + return onSave; + } + public HBox getContainer() { return container; } @@ -93,12 +103,29 @@ public void setSetToDefault(Runnable setToDefault) { } static final class BooleanGameRuleInfo extends GameRuleInfo { - - public BooleanGameRuleInfo(String ruleKey, String displayName, Boolean onValue, Optional defaultValue, GameRuleNBT gameRuleNBT, Runnable onSave) { - this.setRuleKey(ruleKey); + boolean currentValue; + Boolean defaultValue; + + public BooleanGameRuleInfo(GameRule.BooleanGameRule booleanGameRule, GameRuleNBT gameRuleNBT, Runnable onSave) { + this.setRuleKey(booleanGameRule.getRuleKey().get(0)); + String displayName = ""; + try { + if (StringUtils.isNotBlank(booleanGameRule.getDisplayI18nKey())) { + displayName = i18n(booleanGameRule.getDisplayI18nKey()); + } + } catch (Exception e) { + LOG.warning("Failed to get i18n text for key: " + booleanGameRule.getDisplayI18nKey(), e); + } this.setDisplayName(displayName); + this.currentValue = booleanGameRule.getValue(); + this.defaultValue = booleanGameRule.getDefaultValue().orElse(null); this.setGameRuleNBT(gameRuleNBT); + this.setOnSave(onSave); + buildNodes(); + } + + public void buildNodes() { { HBox.setHgrow(getContainer(), Priority.ALWAYS); getContainer().setAlignment(Pos.CENTER_LEFT); @@ -107,12 +134,12 @@ public BooleanGameRuleInfo(String ruleKey, String displayName, Boolean onValue, OptionToggleButton toggleButton = new OptionToggleButton(); { - toggleButton.setTitle(displayName); - toggleButton.setSubtitle(ruleKey); - toggleButton.setSelected(onValue); + toggleButton.setTitle(getDisplayName()); + toggleButton.setSubtitle(getRuleKey()); + toggleButton.setSelected(currentValue); toggleButton.selectedProperty().addListener((observable, oldValue, newValue) -> { - gameRuleNBT.changeValue(newValue); - onSave.run(); + getGameRuleNBT().changeValue(newValue); + getOnSave().run(); }); HBox.setHgrow(toggleButton, Priority.ALWAYS); } @@ -120,11 +147,13 @@ public BooleanGameRuleInfo(String ruleKey, String displayName, Boolean onValue, JFXButton resetButton = new JFXButton(); { resetButton.setGraphic(SVG.RESTORE.createIcon(24)); - defaultValue.ifPresentOrElse(value -> { - setSetToDefault(() -> toggleButton.selectedProperty().set(value)); + if (defaultValue != null) { + setSetToDefault(() -> toggleButton.selectedProperty().set(defaultValue)); resetButton.setOnAction(event -> getSetToDefault().run()); - FXUtils.installSlowTooltip(resetButton, i18n("gamerule.restore_default_values.tooltip", value)); - }, () -> resetButton.setDisable(true)); + FXUtils.installSlowTooltip(resetButton, i18n("gamerule.restore_default_values.tooltip", defaultValue)); + } else { + resetButton.setDisable(true); + } } getContainer().getChildren().addAll(toggleButton, resetButton); @@ -133,12 +162,33 @@ public BooleanGameRuleInfo(String ruleKey, String displayName, Boolean onValue, } static final class IntGameRuleInfo extends GameRuleInfo { - - public IntGameRuleInfo(String ruleKey, String displayName, Integer currentValue, int minValue, int maxValue, Optional defaultValue, GameRuleNBT gameRuleNBT, Runnable onSave) { - this.setRuleKey(ruleKey); + int currentValue; + int minValue; + int maxValue; + Integer defaultValue; + + public IntGameRuleInfo(GameRule.IntGameRule intGameRule, GameRuleNBT gameRuleNBT, Runnable onSave) { + this.setRuleKey(intGameRule.getRuleKey().get(0)); + String displayName = ""; + try { + if (StringUtils.isNotBlank(intGameRule.getDisplayI18nKey())) { + displayName = i18n(intGameRule.getDisplayI18nKey()); + } + } catch (Exception e) { + LOG.warning("Failed to get i18n text for key: " + intGameRule.getDisplayI18nKey(), e); + } this.setDisplayName(displayName); + currentValue = intGameRule.getValue(); + minValue = intGameRule.getMinValue(); + maxValue = intGameRule.getMaxValue(); + defaultValue = intGameRule.getDefaultValue().orElse(null); this.setGameRuleNBT(gameRuleNBT); + this.setOnSave(onSave); + + buildNodes(); + } + public void buildNodes() { { getContainer().setPadding(new Insets(8, 8, 8, 16)); HBox.setHgrow(getContainer(), Priority.ALWAYS); @@ -147,7 +197,7 @@ public IntGameRuleInfo(String ruleKey, String displayName, Integer currentValue, VBox displayInfoVBox = new VBox(); { - displayInfoVBox.getChildren().addAll(new Label(displayName), new Label(ruleKey)); + displayInfoVBox.getChildren().addAll(new Label(getDisplayName()), new Label(getRuleKey())); displayInfoVBox.setAlignment(Pos.CENTER_LEFT); HBox.setHgrow(displayInfoVBox, Priority.ALWAYS); } @@ -160,7 +210,7 @@ public IntGameRuleInfo(String ruleKey, String displayName, Integer currentValue, JFXTextField textField = new JFXTextField(); { - textField.textProperty().set(currentValue.toString()); + textField.textProperty().set(Integer.toString(currentValue)); FXUtils.setValidateWhileTextChanged(textField, true); textField.setValidators( new NumberValidator(i18n("input.integer"), false), @@ -172,8 +222,8 @@ public IntGameRuleInfo(String ruleKey, String displayName, Integer currentValue, } else if (value > maxValue || value < minValue) { return; } else { - gameRuleNBT.changeValue(newValue); - onSave.run(); + getGameRuleNBT().changeValue(newValue); + getOnSave().run(); } }); @@ -184,11 +234,13 @@ public IntGameRuleInfo(String ruleKey, String displayName, Integer currentValue, JFXButton resetButton = new JFXButton(); { resetButton.setGraphic(SVG.RESTORE.createIcon(24)); - defaultValue.ifPresentOrElse(value -> { - setSetToDefault(() -> textField.textProperty().set(String.valueOf(value))); + if (defaultValue != null) { + setSetToDefault(() -> textField.textProperty().set(String.valueOf(defaultValue))); resetButton.setOnAction(event -> getSetToDefault().run()); - FXUtils.installSlowTooltip(resetButton, i18n("gamerule.restore_default_values.tooltip", value)); - }, () -> resetButton.setDisable(true)); + FXUtils.installSlowTooltip(resetButton, i18n("gamerule.restore_default_values.tooltip", defaultValue)); + } else { + resetButton.setDisable(true); + } resetButton.setAlignment(Pos.BOTTOM_CENTER); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java index 086df91754..a4f184313f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java @@ -90,24 +90,16 @@ public void updateControls() { gameRuleCompoundTag.iterator().forEachRemaining(gameRuleTag -> { GameRule.createGameRuleNbt(gameRuleTag).ifPresent(gameRuleNBT -> { GameRule.getFullGameRule(gameRuleTag, gameRuleMap).ifPresent(gameRule -> { - String displayText = ""; - try { - if (StringUtils.isNotBlank(gameRule.getDisplayI18nKey())) { - displayText = i18n(gameRule.getDisplayI18nKey()); - } - } catch (Exception e) { - LOG.warning("Failed to get i18n text for key: " + gameRule.getDisplayI18nKey(), e); - } if (gameRule instanceof GameRule.IntGameRule intGameRule) { @SuppressWarnings("unchecked") GameRuleNBT typedGameRuleNBT = (GameRuleNBT) gameRuleNBT; - gameRuleList.add(new GameRuleInfo.IntGameRuleInfo(intGameRule.getRuleKey().get(0), displayText, intGameRule.getValue(), intGameRule.getMinValue(), intGameRule.getMaxValue(), intGameRule.getDefaultValue(), typedGameRuleNBT, this::saveLevelDatIfNotResettingAll)); + gameRuleList.add(new GameRuleInfo.IntGameRuleInfo(intGameRule, typedGameRuleNBT, this::saveLevelDatIfNotResettingAll)); } else if (gameRule instanceof GameRule.BooleanGameRule booleanGameRule) { @SuppressWarnings("unchecked") GameRuleNBT typedGameRuleNBT = (GameRuleNBT) gameRuleNBT; - gameRuleList.add(new GameRuleInfo.BooleanGameRuleInfo(booleanGameRule.getRuleKey().get(0), displayText, booleanGameRule.getValue(), booleanGameRule.getDefaultValue(), typedGameRuleNBT, this::saveLevelDatIfNotResettingAll)); + gameRuleList.add(new GameRuleInfo.BooleanGameRuleInfo(booleanGameRule, typedGameRuleNBT, this::saveLevelDatIfNotResettingAll)); } }); }); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java index 61800bf643..9c34148eb8 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java @@ -235,7 +235,7 @@ public void setValue(boolean value) { /// Wraps values in [IntegerProperty] and supports min/max value validation. public static final class IntGameRule extends GameRule { private final IntegerProperty value = new SimpleIntegerProperty(0); - private Optional defaultValue = Optional.empty(); + private IntegerProperty defaultValue; private int maxValue = 0; private int minValue = 0; @@ -268,15 +268,15 @@ public void applyMetadata(GameRule metadataSource) { } public Optional getDefaultValue() { - return defaultValue.map(IntegerProperty::getValue); + return Optional.ofNullable(defaultValue.getValue()); } public Optional defaultValueProperty() { - return defaultValue; + return Optional.ofNullable(defaultValue); } private void setDefaultValue(int value) { - defaultValue.ifPresentOrElse(defaultValue -> defaultValue.set(value), () -> defaultValue = Optional.of(new SimpleIntegerProperty(value))); + defaultValueProperty().ifPresentOrElse(defaultValue -> defaultValue.set(value), () -> defaultValue = new SimpleIntegerProperty(value)); } public int getValue() { From f6796ea2ba7d3d3e172386eed5723f6700100c25 Mon Sep 17 00:00:00 2001 From: mine_ Date: Thu, 25 Dec 2025 10:51:01 +0800 Subject: [PATCH 42/69] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/jackhuang/hmcl/ui/versions/GameRuleInfo.java | 6 +----- .../org/jackhuang/hmcl/ui/versions/GameRulePage.java | 6 +++--- .../main/java/org/jackhuang/hmcl/gamerule/GameRule.java | 9 ++++----- .../java/org/jackhuang/hmcl/gamerule/GameRuleNBT.java | 4 +++- 4 files changed, 11 insertions(+), 14 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java index 89efeabb81..e674aa8c2b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java @@ -217,11 +217,7 @@ public void buildNodes() { new NumberRangeValidator(i18n("input.number_range", minValue, maxValue), minValue, maxValue)); textField.textProperty().addListener((observable, oldValue, newValue) -> { Integer value = Lang.toIntOrNull(newValue); - if (value == null) { - return; - } else if (value > maxValue || value < minValue) { - return; - } else { + if (value != null && value >= minValue && value <= maxValue) { getGameRuleNBT().changeValue(newValue); getOnSave().run(); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java index a4f184313f..c94777f532 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java @@ -44,8 +44,8 @@ public class GameRulePage extends ListPageBase> { - private WorldManagePage worldManagePage; - private World world; + private final WorldManagePage worldManagePage; + private final World world; private CompoundTag levelDat; Map gameRuleMap = GameRule.getCloneGameRuleMap(); @@ -111,7 +111,7 @@ protected Skin createDefaultSkin() { return new GameRulePageSkin(this); } - public boolean isIsResettingAll() { + public boolean isResettingAll() { return isResettingAll; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java index 9c34148eb8..769a71f792 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java @@ -27,7 +27,6 @@ import javafx.beans.property.IntegerProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleIntegerProperty; -import javafx.beans.value.ObservableBooleanValue; import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.gson.JsonSerializable; import org.jackhuang.hmcl.util.gson.JsonUtils; @@ -180,7 +179,7 @@ public String getDisplayI18nKey() { /// Wraps values in [BooleanProperty] for UI binding. public static final class BooleanGameRule extends GameRule { private final BooleanProperty value = new SimpleBooleanProperty(false); - private Optional defaultValue = Optional.empty(); + private BooleanProperty defaultValue; private BooleanGameRule(List ruleKey, String displayI18nKey, boolean value) { super(ruleKey, displayI18nKey); @@ -207,15 +206,15 @@ public void applyMetadata(GameRule metadataSource) { } public Optional getDefaultValue() { - return defaultValue.map(ObservableBooleanValue::get); + return Optional.ofNullable(defaultValue.getValue()); } public Optional defaultValueProperty() { - return defaultValue; + return Optional.ofNullable(defaultValue); } private void setDefaultValue(boolean value) { - this.defaultValue.ifPresentOrElse(defaultValue -> defaultValue.setValue(value), () -> defaultValue = Optional.of(new SimpleBooleanProperty(value))); + defaultValueProperty().ifPresentOrElse(defaultValue -> defaultValue.setValue(value), () -> defaultValue = new SimpleBooleanProperty(value)); } public boolean getValue() { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRuleNBT.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRuleNBT.java index ad0391ce2f..904f37328b 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRuleNBT.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRuleNBT.java @@ -61,7 +61,9 @@ public IntGameRuleNBT(IntTag gameRuleTag) { @Override public void changeValue(String newValue) { Integer value = Lang.toIntOrNull(newValue); - getGameRuleTag().setValue(value); + if (value != null) { + getGameRuleTag().setValue(value); + } } } From 323e01f686b5051cd58aa47d2d60d2d3d3a9a097 Mon Sep 17 00:00:00 2001 From: mine_ Date: Thu, 25 Dec 2025 21:13:42 +0800 Subject: [PATCH 43/69] =?UTF-8?q?feat:=20=E5=BD=93=E5=80=BC=E4=B8=BA?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E5=80=BC=E6=97=B6=E7=A6=81=E7=94=A8=E9=87=8D?= =?UTF-8?q?=E7=BD=AE=E5=9B=BE=E6=A0=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/GameRuleInfo.java | 76 +++++++++++-------- .../resources/assets/lang/I18N.properties | 2 + .../resources/assets/lang/I18N_zh.properties | 2 + .../assets/lang/I18N_zh_CN.properties | 2 + .../org/jackhuang/hmcl/gamerule/GameRule.java | 2 +- 5 files changed, 52 insertions(+), 32 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java index e674aa8c2b..cb480a2e39 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java @@ -23,9 +23,7 @@ import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.Label; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Priority; -import javafx.scene.layout.VBox; +import javafx.scene.layout.*; import org.jackhuang.hmcl.gamerule.GameRule; import org.jackhuang.hmcl.gamerule.GameRuleNBT; import org.jackhuang.hmcl.ui.FXUtils; @@ -102,21 +100,26 @@ public void setSetToDefault(Runnable setToDefault) { this.setToDefault = setToDefault; } + public void setFatherValue(GameRule gameRule) { + setRuleKey(gameRule.getRuleKey().get(0)); + String displayName = ""; + try { + if (StringUtils.isNotBlank(gameRule.getDisplayI18nKey())) { + displayName = i18n(gameRule.getDisplayI18nKey()); + } + } catch (Exception e) { + LOG.warning("Failed to get i18n text for key: " + gameRule.getDisplayI18nKey(), e); + } + setDisplayName(displayName); + setDisplayName(displayName); + } + static final class BooleanGameRuleInfo extends GameRuleInfo { boolean currentValue; Boolean defaultValue; public BooleanGameRuleInfo(GameRule.BooleanGameRule booleanGameRule, GameRuleNBT gameRuleNBT, Runnable onSave) { - this.setRuleKey(booleanGameRule.getRuleKey().get(0)); - String displayName = ""; - try { - if (StringUtils.isNotBlank(booleanGameRule.getDisplayI18nKey())) { - displayName = i18n(booleanGameRule.getDisplayI18nKey()); - } - } catch (Exception e) { - LOG.warning("Failed to get i18n text for key: " + booleanGameRule.getDisplayI18nKey(), e); - } - this.setDisplayName(displayName); + setFatherValue(booleanGameRule); this.currentValue = booleanGameRule.getValue(); this.defaultValue = booleanGameRule.getDefaultValue().orElse(null); this.setGameRuleNBT(gameRuleNBT); @@ -132,6 +135,8 @@ public void buildNodes() { getContainer().setPadding(new Insets(0, 8, 0, 0)); } + JFXButton resetButton = new JFXButton(); + StackPane wrapperPane = new StackPane(resetButton); OptionToggleButton toggleButton = new OptionToggleButton(); { toggleButton.setTitle(getDisplayName()); @@ -140,23 +145,31 @@ public void buildNodes() { toggleButton.selectedProperty().addListener((observable, oldValue, newValue) -> { getGameRuleNBT().changeValue(newValue); getOnSave().run(); + resetButton.setDisable(shouldResetButtonDisabled(newValue)); }); HBox.setHgrow(toggleButton, Priority.ALWAYS); } - - JFXButton resetButton = new JFXButton(); { + wrapperPane.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE); + resetButton.setFocusTraversable(false); + resetButton.setDisable(shouldResetButtonDisabled(currentValue)); resetButton.setGraphic(SVG.RESTORE.createIcon(24)); if (defaultValue != null) { setSetToDefault(() -> toggleButton.selectedProperty().set(defaultValue)); resetButton.setOnAction(event -> getSetToDefault().run()); - FXUtils.installSlowTooltip(resetButton, i18n("gamerule.restore_default_values.tooltip", defaultValue)); + FXUtils.installFastTooltip(resetButton, i18n("gamerule.restore_default_values.tooltip", defaultValue)); + FXUtils.installFastTooltip(wrapperPane, i18n("gamerule.now_is_default_values.tooltip")); } else { resetButton.setDisable(true); + FXUtils.installFastTooltip(wrapperPane, i18n("gamerule.not_have_default_values.tooltip")); } } - getContainer().getChildren().addAll(toggleButton, resetButton); + getContainer().getChildren().addAll(toggleButton, wrapperPane); + } + + public boolean shouldResetButtonDisabled(boolean newValue) { + return defaultValue == null || newValue == defaultValue; } } @@ -168,16 +181,7 @@ static final class IntGameRuleInfo extends GameRuleInfo { Integer defaultValue; public IntGameRuleInfo(GameRule.IntGameRule intGameRule, GameRuleNBT gameRuleNBT, Runnable onSave) { - this.setRuleKey(intGameRule.getRuleKey().get(0)); - String displayName = ""; - try { - if (StringUtils.isNotBlank(intGameRule.getDisplayI18nKey())) { - displayName = i18n(intGameRule.getDisplayI18nKey()); - } - } catch (Exception e) { - LOG.warning("Failed to get i18n text for key: " + intGameRule.getDisplayI18nKey(), e); - } - this.setDisplayName(displayName); + setFatherValue(intGameRule); currentValue = intGameRule.getValue(); minValue = intGameRule.getMinValue(); maxValue = intGameRule.getMaxValue(); @@ -208,6 +212,8 @@ public void buildNodes() { hBox.setAlignment(Pos.CENTER_LEFT); } + JFXButton resetButton = new JFXButton(); + StackPane wrapperPane = new StackPane(resetButton); JFXTextField textField = new JFXTextField(); { textField.textProperty().set(Integer.toString(currentValue)); @@ -220,29 +226,37 @@ public void buildNodes() { if (value != null && value >= minValue && value <= maxValue) { getGameRuleNBT().changeValue(newValue); getOnSave().run(); + resetButton.setDisable(shouldResetButtonDisabled(value)); } }); textField.maxWidth(10); textField.minWidth(10); } - - JFXButton resetButton = new JFXButton(); { + wrapperPane.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE); + resetButton.setFocusTraversable(false); + resetButton.setDisable(shouldResetButtonDisabled(currentValue)); resetButton.setGraphic(SVG.RESTORE.createIcon(24)); if (defaultValue != null) { setSetToDefault(() -> textField.textProperty().set(String.valueOf(defaultValue))); resetButton.setOnAction(event -> getSetToDefault().run()); - FXUtils.installSlowTooltip(resetButton, i18n("gamerule.restore_default_values.tooltip", defaultValue)); + FXUtils.installFastTooltip(resetButton, i18n("gamerule.restore_default_values.tooltip", defaultValue)); + FXUtils.installFastTooltip(wrapperPane, i18n("gamerule.now_is_default_values.tooltip")); } else { resetButton.setDisable(true); + FXUtils.installFastTooltip(wrapperPane, i18n("gamerule.not_have_default_values.tooltip")); } resetButton.setAlignment(Pos.BOTTOM_CENTER); } - hBox.getChildren().addAll(textField, resetButton); + hBox.getChildren().addAll(textField, wrapperPane); getContainer().getChildren().addAll(displayInfoVBox, hBox); } + + public boolean shouldResetButtonDisabled(int newValue) { + return defaultValue == null || newValue == defaultValue; + } } } diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index e179227295..0104f9aa59 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -691,6 +691,8 @@ gamerule.restore_default_values_all=Reset all to game defaults gamerule.restore_default_values_all.confirm=Are you sure you want to reset all rules to the game's default? This action cannot be undone! gamerule.restore_default_values_all.finish.toast=All game rules have been reset to default values gamerule.restore_default_values.tooltip=Reset this rule to game default.\nDefault: %s +gamerule.now_is_default_values.tooltip=It is already the default value. +gamerule.not_have_default_values.tooltip=Default value not found. gamerule.rule.advance_time=Advance Time gamerule.rule.advance_weather=Advance Weather gamerule.rule.allow_fire_ticks_away_from_player=Allow Fire Ticks Away From Player diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 05d280f195..f4d08f7530 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -498,6 +498,8 @@ gamerule.restore_default_values_all=復原遊戲預設規則 gamerule.restore_default_values_all.confirm=你確定要復原所有規則至遊戲預設嗎?該操作無法復原! gamerule.restore_default_values_all.finish.toast=已將所有遊戲規則復原至預設值 gamerule.restore_default_values.tooltip=復原此項預設規則\n預設值: %s +gamerule.now_is_default_values.tooltip=目前已是預設值 +gamerule.not_have_default_values.tooltip=未找到預設值 gamerule.rule.advance_time=日夜交替 gamerule.rule.advance_weather=更新天氣 gamerule.rule.allow_fire_ticks_away_from_player=允許火在遠離玩家處蔓延 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index c22eafdb70..2dd5a5461d 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -508,6 +508,8 @@ gamerule.restore_default_values_all=恢复游戏默认规则 gamerule.restore_default_values_all.confirm=你确定要恢复所有规则为游戏默认吗?此操作无法撤销! gamerule.restore_default_values_all.finish.toast=已将所有游戏规则恢复至默认值 gamerule.restore_default_values.tooltip=恢复此项默认规则\n默认值: %s +gamerule.now_is_default_values.tooltip=现在已是默认值 +gamerule.not_have_default_values.tooltip=未找到默认值 gamerule.rule.advance_time=游戏内时间流逝 gamerule.rule.advance_weather=天气更替 gamerule.rule.allow_fire_ticks_away_from_player=允许火在远离玩家处蔓延 diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java index 769a71f792..e255c0fec0 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java @@ -206,7 +206,7 @@ public void applyMetadata(GameRule metadataSource) { } public Optional getDefaultValue() { - return Optional.ofNullable(defaultValue.getValue()); + return Optional.ofNullable(defaultValue).map(BooleanProperty::get); } public Optional defaultValueProperty() { From a1fb0bbd13785c69310c290f200e51bbe4e2dc3d Mon Sep 17 00:00:00 2001 From: mine_ Date: Thu, 25 Dec 2025 22:51:12 +0800 Subject: [PATCH 44/69] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/GameRuleInfo.java | 10 +- .../hmcl/ui/versions/GameRulePage.java | 7 +- .../org/jackhuang/hmcl/gamerule/GameRule.java | 134 ++++++------------ 3 files changed, 48 insertions(+), 103 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java index cb480a2e39..d36bee9b26 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java @@ -206,10 +206,10 @@ public void buildNodes() { HBox.setHgrow(displayInfoVBox, Priority.ALWAYS); } - HBox hBox = new HBox(); + HBox rightHBox = new HBox(); { - hBox.setSpacing(12); - hBox.setAlignment(Pos.CENTER_LEFT); + rightHBox.setSpacing(12); + rightHBox.setAlignment(Pos.CENTER_LEFT); } JFXButton resetButton = new JFXButton(); @@ -251,8 +251,8 @@ public void buildNodes() { resetButton.setAlignment(Pos.BOTTOM_CENTER); } - hBox.getChildren().addAll(textField, wrapperPane); - getContainer().getChildren().addAll(displayInfoVBox, hBox); + rightHBox.getChildren().addAll(textField, wrapperPane); + getContainer().getChildren().addAll(displayInfoVBox, rightHBox); } public boolean shouldResetButtonDisabled(int newValue) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java index c94777f532..82cef03068 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java @@ -35,7 +35,6 @@ import java.io.IOException; import java.nio.file.Files; import java.util.Locale; -import java.util.Map; import java.util.function.Predicate; import java.util.regex.Pattern; @@ -48,8 +47,6 @@ public class GameRulePage extends ListPageBase> { private final World world; private CompoundTag levelDat; - Map gameRuleMap = GameRule.getCloneGameRuleMap(); - ObservableList> gameRuleList; private boolean isResettingAll = false; @@ -88,8 +85,8 @@ public void updateControls() { } gameRuleCompoundTag.iterator().forEachRemaining(gameRuleTag -> { - GameRule.createGameRuleNbt(gameRuleTag).ifPresent(gameRuleNBT -> { - GameRule.getFullGameRule(gameRuleTag, gameRuleMap).ifPresent(gameRule -> { + GameRule.createGameRuleNBT(gameRuleTag).ifPresent(gameRuleNBT -> { + GameRule.getFullGameRule(gameRuleTag).ifPresent(gameRule -> { if (gameRule instanceof GameRule.IntGameRule intGameRule) { @SuppressWarnings("unchecked") GameRuleNBT typedGameRuleNBT = (GameRuleNBT) gameRuleNBT; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java index e255c0fec0..b089c33cb2 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java @@ -23,10 +23,6 @@ import com.github.steveice10.opennbt.tag.builtin.Tag; import com.google.gson.*; import com.google.gson.annotations.JsonAdapter; -import javafx.beans.property.BooleanProperty; -import javafx.beans.property.IntegerProperty; -import javafx.beans.property.SimpleBooleanProperty; -import javafx.beans.property.SimpleIntegerProperty; import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.gson.JsonSerializable; import org.jackhuang.hmcl.util.gson.JsonUtils; @@ -35,7 +31,6 @@ import java.io.InputStream; import java.lang.reflect.Type; import java.util.*; -import java.util.stream.Collectors; /// Represents an abstract game rule in Minecraft (e.g., `doDaylightCycle`, `randomTickSpeed`). /// @@ -43,7 +38,6 @@ /// * Defining rule types (Boolean or Integer). /// * Parsing rules from NBT tags (read from `level.dat`). /// * Serializing/Deserializing rules via GSON. -/// * Binding values to JavaFX properties for UI integration. /// /// It is a sealed class permitting only [BooleanGameRule] and [IntGameRule]. @JsonSerializable @@ -62,11 +56,11 @@ protected GameRule(List ruleKey, String displayI18nKey) { } public static GameRule createSimpleGameRule(String ruleKey, boolean value) { - return new BooleanGameRule(Collections.singletonList(ruleKey), "", value); + return new BooleanGameRule(Collections.singletonList(ruleKey), value); } public static GameRule createSimpleGameRule(String ruleKey, int value) { - IntGameRule intGameRule = new IntGameRule(Collections.singletonList(ruleKey), "", value); + IntGameRule intGameRule = new IntGameRule(Collections.singletonList(ruleKey), value); intGameRule.maxValue = Integer.MAX_VALUE; intGameRule.minValue = Integer.MIN_VALUE; return intGameRule; @@ -104,28 +98,23 @@ private static Optional createSimpleRuleFromTag(Tag tag) { return Optional.empty(); } - /// Applies metadata from a definition rule to a simple value rule. + /// Retrieves a fully populated GameRule based on an NBT tag. /// - /// This is used to hydrate a raw rule read from NBT (which only has a ruleKey and value) - /// with static definition data (translation keys, min/max values) loaded from JSON. - public static GameRule mixGameRule(GameRule simpleGameRule, GameRule gameRule) { - simpleGameRule.applyMetadata(gameRule); - return simpleGameRule; - } - - public static void addSimpleGameRule(Map gameRuleMap, String ruleKey, String displayName, boolean value) { - gameRuleMap.put(ruleKey, new BooleanGameRule(Collections.singletonList(ruleKey), displayName, value)); - } - - public static void addSimpleGameRule(Map gameRuleMap, String ruleKey, String displayName, int value) { - gameRuleMap.put(ruleKey, new IntGameRule(Collections.singletonList(ruleKey), displayName, value)); + /// This combines parsing the tag [#createGameRuleNBT(Tag)] and applying known metadata + /// from the provided `gameRuleMap`. + public static Optional getFullGameRule(Tag tag) { + return createSimpleRuleFromTag(tag).map(simpleGameRule -> { + Optional.ofNullable(GameRuleHolder.metaDataGameRuleMap.get(tag.getName())) + .ifPresent(simpleGameRule::applyMetadata); + return simpleGameRule; + }); } /// Creates a [GameRuleNBT] wrapper around an NBT Tag. /// Used for unified changing operations back to NBT format. /// /// @see GameRuleNBT - public static Optional> createGameRuleNbt(Tag tag) { + public static Optional> createGameRuleNBT(Tag tag) { if (tag instanceof StringTag stringTag && (tag.getValue().equals("true") || tag.getValue().equals("false"))) { return Optional.of(new GameRuleNBT.StringByteGameRuleNBT(stringTag)); } else if (tag instanceof StringTag stringTag && Lang.toIntOrNull(stringTag.getValue()) != null) { @@ -138,24 +127,6 @@ public static void addSimpleGameRule(Map gameRuleMap, String r return Optional.empty(); } - /// Retrieves a fully populated GameRule based on an NBT tag. - /// - /// This combines parsing the tag [#createGameRuleNbt(Tag)] and applying known metadata - /// from the provided `gameRuleMap`. - public static Optional getFullGameRule(Tag tag, Map gameRuleMap) { - return createSimpleRuleFromTag(tag).map(simpleGameRule -> { - Optional.ofNullable(gameRuleMap.get(tag.getName())) - .ifPresent(simpleGameRule::applyMetadata); - return simpleGameRule; - }); - } - - public static Map getCloneGameRuleMap() { - return GameRule.GameRuleHolder.cloneGameRuleMap(); - } - - public abstract GameRule clone(); - /// Copies metadata (e.g., descriptions, ranges) from the source rule to this instance. public abstract void applyMetadata(GameRule metadataSource); @@ -176,25 +147,23 @@ public String getDisplayI18nKey() { } /// Implementation of a boolean-based GameRule. - /// Wraps values in [BooleanProperty] for UI binding. public static final class BooleanGameRule extends GameRule { - private final BooleanProperty value = new SimpleBooleanProperty(false); - private BooleanProperty defaultValue; + private Boolean value = false; + private Boolean defaultValue; - private BooleanGameRule(List ruleKey, String displayI18nKey, boolean value) { + private BooleanGameRule(List ruleKey, String displayI18nKey, Boolean value, Boolean defaultValue) { super(ruleKey, displayI18nKey); - this.value.set(value); + this.value = value; + this.defaultValue = defaultValue; } - private BooleanGameRule() { - + private BooleanGameRule(List ruleKey, boolean value) { + super(ruleKey, ""); + this.value = value; } - @Override - public GameRule clone() { - BooleanGameRule booleanGameRule = new BooleanGameRule(getRuleKey(), getDisplayI18nKey(), value.getValue()); - this.getDefaultValue().ifPresent(booleanGameRule::setDefaultValue); - return booleanGameRule; + private BooleanGameRule() { + } @Override @@ -206,54 +175,45 @@ public void applyMetadata(GameRule metadataSource) { } public Optional getDefaultValue() { - return Optional.ofNullable(defaultValue).map(BooleanProperty::get); - } - - public Optional defaultValueProperty() { return Optional.ofNullable(defaultValue); } private void setDefaultValue(boolean value) { - defaultValueProperty().ifPresentOrElse(defaultValue -> defaultValue.setValue(value), () -> defaultValue = new SimpleBooleanProperty(value)); + defaultValue = value; } public boolean getValue() { - return value.get(); - } - - public BooleanProperty valueProperty() { return value; } public void setValue(boolean value) { - this.value.setValue(value); + this.value = value; } } /// Implementation of an integer-based GameRule. - /// Wraps values in [IntegerProperty] and supports min/max value validation. + /// supports min/max value validation. public static final class IntGameRule extends GameRule { - private final IntegerProperty value = new SimpleIntegerProperty(0); - private IntegerProperty defaultValue; + private Integer value = 0; + private Integer defaultValue; private int maxValue = 0; private int minValue = 0; - private IntGameRule(List ruleKey, String displayI18nKey, int value) { + private IntGameRule(List ruleKey, String displayI18nKey, Integer value, Integer defaultValue, int maxValue, int minValue) { super(ruleKey, displayI18nKey); - this.value.set(value); + this.value = value; + this.defaultValue = defaultValue; + this.maxValue = maxValue; + this.minValue = minValue; } - private IntGameRule() { - + private IntGameRule(List ruleKey, int value) { + super(ruleKey, ""); + this.value = value; } - @Override - public GameRule clone() { - IntGameRule intGameRule = new IntGameRule(getRuleKey(), getDisplayI18nKey(), value.getValue()); - getDefaultValue().ifPresent(intGameRule::setDefaultValue); - intGameRule.minValue = minValue; - intGameRule.maxValue = maxValue; - return intGameRule; + private IntGameRule() { + } @Override @@ -267,27 +227,19 @@ public void applyMetadata(GameRule metadataSource) { } public Optional getDefaultValue() { - return Optional.ofNullable(defaultValue.getValue()); - } - - public Optional defaultValueProperty() { return Optional.ofNullable(defaultValue); } private void setDefaultValue(int value) { - defaultValueProperty().ifPresentOrElse(defaultValue -> defaultValue.set(value), () -> defaultValue = new SimpleIntegerProperty(value)); + defaultValue = value; } public int getValue() { - return value.get(); - } - - public IntegerProperty valueProperty() { return value; } public void setValue(int value) { - this.value.setValue(value); + this.value = value; } public int getMaxValue() { @@ -352,7 +304,7 @@ private GameRule createRuleFromDefaultValue(JsonElement defaultValueElement) { } static final class GameRuleHolder { - private static final Map gameRuleMap = new HashMap<>(); + private static final Map metaDataGameRuleMap = new HashMap<>(); static { List gameRules; @@ -367,14 +319,10 @@ static final class GameRuleHolder { } for (GameRule gameRule : gameRules) { for (String s : gameRule.ruleKey) { - gameRuleMap.put(s, gameRule); + metaDataGameRuleMap.put(s, gameRule); } } } - private static Map cloneGameRuleMap() { - return gameRuleMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().clone())); - } - } } From 75ac9d4dd47c1b39e2073379c5ffe80c856d9edc Mon Sep 17 00:00:00 2001 From: mine_ Date: Thu, 25 Dec 2025 23:16:11 +0800 Subject: [PATCH 45/69] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/jackhuang/hmcl/gamerule/GameRule.java | 20 +++-------------- .../jackhuang/hmcl/gamerule/GameRuleNBT.java | 22 ++++++------------- 2 files changed, 10 insertions(+), 32 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java index b089c33cb2..f82c740a79 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java @@ -115,7 +115,7 @@ public static Optional getFullGameRule(Tag tag) { /// /// @see GameRuleNBT public static Optional> createGameRuleNBT(Tag tag) { - if (tag instanceof StringTag stringTag && (tag.getValue().equals("true") || tag.getValue().equals("false"))) { + if (tag instanceof StringTag stringTag && (stringTag.getValue().equals("true") || stringTag.getValue().equals("false"))) { return Optional.of(new GameRuleNBT.StringByteGameRuleNBT(stringTag)); } else if (tag instanceof StringTag stringTag && Lang.toIntOrNull(stringTag.getValue()) != null) { return Optional.of(new GameRuleNBT.StringIntGameRuleNBT(stringTag)); @@ -148,15 +148,9 @@ public String getDisplayI18nKey() { /// Implementation of a boolean-based GameRule. public static final class BooleanGameRule extends GameRule { - private Boolean value = false; + private boolean value = false; private Boolean defaultValue; - private BooleanGameRule(List ruleKey, String displayI18nKey, Boolean value, Boolean defaultValue) { - super(ruleKey, displayI18nKey); - this.value = value; - this.defaultValue = defaultValue; - } - private BooleanGameRule(List ruleKey, boolean value) { super(ruleKey, ""); this.value = value; @@ -194,19 +188,11 @@ public void setValue(boolean value) { /// Implementation of an integer-based GameRule. /// supports min/max value validation. public static final class IntGameRule extends GameRule { - private Integer value = 0; + private int value = 0; private Integer defaultValue; private int maxValue = 0; private int minValue = 0; - private IntGameRule(List ruleKey, String displayI18nKey, Integer value, Integer defaultValue, int maxValue, int minValue) { - super(ruleKey, displayI18nKey); - this.value = value; - this.defaultValue = defaultValue; - this.maxValue = maxValue; - this.minValue = minValue; - } - private IntGameRule(List ruleKey, int value) { super(ruleKey, ""); this.value = value; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRuleNBT.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRuleNBT.java index 904f37328b..e222ff6ff3 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRuleNBT.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRuleNBT.java @@ -23,21 +23,13 @@ import com.github.steveice10.opennbt.tag.builtin.Tag; import org.jackhuang.hmcl.util.Lang; -/** - * An abstract representation of a Minecraft game rule stored as an NBT tag. - *

- * This class acts as a generic wrapper for a specific game rule's NBT tag, - * providing a unified interface to modify its value. It abstracts the underlying - * NBT tag implementation, allowing different types of game rule values (like integers, - * booleans, or strings) to be handled consistently. - *

- * Subclasses must implement the {@link #changeValue(Object)} method to define - * how an input value of type {@code T} is converted and applied to the wrapped - * NBT tag of type {@code V}. - * - * @param The type of the value used to update the game rule (e.g., {@link String}, {@link Boolean}). - * @param The specific {@link Tag} subtype being wrapped (e.g., {@link IntTag}, {@link ByteTag}). - */ +/// A sealed abstract wrapper for a game rule stored in NBT. +/// +/// This class **holds a single NBT `Tag` instance** (`gameRuleTag`) and provides a unified API ([#changeValue(Object)]) +/// to update that tag’s underlying value from a higher-level Java value. +/// +/// @param The Java type used to represent the game rule's value (e.g., [String], [Boolean]). +/// @param The specific NBT [Tag] type that this object wraps and persists. public sealed abstract class GameRuleNBT permits GameRuleNBT.IntGameRuleNBT, GameRuleNBT.ByteGameRuleNBT, GameRuleNBT.StringIntGameRuleNBT, GameRuleNBT.StringByteGameRuleNBT { private final V gameRuleTag; From e3698d6f48ac52782dd53fe8b1145c163d9b4b6b Mon Sep 17 00:00:00 2001 From: mineDiamond Date: Thu, 25 Dec 2025 23:19:05 +0800 Subject: [PATCH 46/69] Update HMCL/src/main/resources/assets/lang/I18N.properties Co-authored-by: 3gf8jv4dv <3gf8jv4dv@gmail.com> --- HMCL/src/main/resources/assets/lang/I18N.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 0104f9aa59..39f49bd731 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -691,7 +691,7 @@ gamerule.restore_default_values_all=Reset all to game defaults gamerule.restore_default_values_all.confirm=Are you sure you want to reset all rules to the game's default? This action cannot be undone! gamerule.restore_default_values_all.finish.toast=All game rules have been reset to default values gamerule.restore_default_values.tooltip=Reset this rule to game default.\nDefault: %s -gamerule.now_is_default_values.tooltip=It is already the default value. +gamerule.now_is_default_values.tooltip=Already at default. gamerule.not_have_default_values.tooltip=Default value not found. gamerule.rule.advance_time=Advance Time gamerule.rule.advance_weather=Advance Weather From a667ad62ecdc32bca0f9f629303830630843495b Mon Sep 17 00:00:00 2001 From: mine_ Date: Fri, 26 Dec 2025 13:08:25 +0800 Subject: [PATCH 47/69] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96gamerule.json?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HMCLCore/src/main/resources/assets/gamerule/gamerule.json | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/HMCLCore/src/main/resources/assets/gamerule/gamerule.json b/HMCLCore/src/main/resources/assets/gamerule/gamerule.json index 7adf471c45..23b330bab2 100644 --- a/HMCLCore/src/main/resources/assets/gamerule/gamerule.json +++ b/HMCLCore/src/main/resources/assets/gamerule/gamerule.json @@ -57,7 +57,8 @@ { "ruleKey": [ "minecraft:command_blocks_work", - "commandBlocksEnabled" + "commandBlocksEnabled", + "enableCommandBlocks" ], "displayI18nKey": "gamerule.rule.command_blocks_work", "defaultValue": true @@ -198,7 +199,8 @@ { "ruleKey": [ "minecraft:locator_bar", - "locatorBar" + "locatorBar", + "useLocatorBar" ], "displayI18nKey": "gamerule.rule.locator_bar", "defaultValue": true @@ -218,7 +220,7 @@ ], "displayI18nKey": "gamerule.rule.max_block_modifications", "defaultValue": 32768, - "minValue": -1 + "minValue": 1 }, { "ruleKey": [ From 773ff0cafabeacfc4cdd1dca36f9ce7a58255511 Mon Sep 17 00:00:00 2001 From: mine_ Date: Fri, 26 Dec 2025 15:41:34 +0800 Subject: [PATCH 48/69] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E5=9C=A8?= =?UTF-8?q?=E4=B8=8D=E5=90=8C=E7=89=88=E6=9C=AC=E4=BD=BF=E7=94=A8=E4=B8=8D?= =?UTF-8?q?=E5=90=8C=E7=9A=84=E9=BB=98=E8=AE=A4=E5=80=BC/=E6=9C=80?= =?UTF-8?q?=E5=A4=A7=E5=80=BC/=E6=9C=80=E5=B0=8F=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/GameRuleInfo.java | 21 +-- .../hmcl/ui/versions/GameRulePage.java | 4 +- .../org/jackhuang/hmcl/gamerule/GameRule.java | 126 ++++++++++++------ .../resources/assets/gamerule/gamerule.json | 120 +++++++++++++++-- 4 files changed, 210 insertions(+), 61 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java index d36bee9b26..ad61512c67 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java @@ -33,6 +33,7 @@ import org.jackhuang.hmcl.ui.construct.OptionToggleButton; import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.StringUtils; +import org.jackhuang.hmcl.util.versioning.GameVersionNumber; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.logging.Logger.LOG; @@ -42,6 +43,7 @@ public sealed abstract class GameRuleInfo permits GameRuleInfo.BooleanGameRul private String displayName; private GameRuleNBT gameRuleNBT; private Runnable onSave; + private GameVersionNumber gameVersionNumber; //Due to the significant difference in skin between BooleanGameRuleInfo and IntGameRuleInfo, which are essentially two completely different styles, it is not suitable to update each other in Cell#updateControl. Therefore, they are directly integrated into the info. private HBox container = new HBox(); @@ -100,7 +102,7 @@ public void setSetToDefault(Runnable setToDefault) { this.setToDefault = setToDefault; } - public void setFatherValue(GameRule gameRule) { + public void setFatherValue(GameRule gameRule, GameVersionNumber gameVersionNumber) { setRuleKey(gameRule.getRuleKey().get(0)); String displayName = ""; try { @@ -112,16 +114,17 @@ public void setFatherValue(GameRule gameRule) { } setDisplayName(displayName); setDisplayName(displayName); + this.gameVersionNumber = gameVersionNumber; } static final class BooleanGameRuleInfo extends GameRuleInfo { boolean currentValue; Boolean defaultValue; - public BooleanGameRuleInfo(GameRule.BooleanGameRule booleanGameRule, GameRuleNBT gameRuleNBT, Runnable onSave) { - setFatherValue(booleanGameRule); + public BooleanGameRuleInfo(GameRule.BooleanGameRule booleanGameRule, GameRuleNBT gameRuleNBT, Runnable onSave, GameVersionNumber gameVersionNumber) { + setFatherValue(booleanGameRule, gameVersionNumber); this.currentValue = booleanGameRule.getValue(); - this.defaultValue = booleanGameRule.getDefaultValue().orElse(null); + this.defaultValue = booleanGameRule.getDefaultValue(gameVersionNumber).orElse(null); this.setGameRuleNBT(gameRuleNBT); this.setOnSave(onSave); @@ -180,12 +183,12 @@ static final class IntGameRuleInfo extends GameRuleInfo { int maxValue; Integer defaultValue; - public IntGameRuleInfo(GameRule.IntGameRule intGameRule, GameRuleNBT gameRuleNBT, Runnable onSave) { - setFatherValue(intGameRule); + public IntGameRuleInfo(GameRule.IntGameRule intGameRule, GameRuleNBT gameRuleNBT, Runnable onSave, GameVersionNumber gameVersionNumber) { + setFatherValue(intGameRule, gameVersionNumber); currentValue = intGameRule.getValue(); - minValue = intGameRule.getMinValue(); - maxValue = intGameRule.getMaxValue(); - defaultValue = intGameRule.getDefaultValue().orElse(null); + minValue = intGameRule.getMinValue(gameVersionNumber); + maxValue = intGameRule.getMaxValue(gameVersionNumber); + defaultValue = intGameRule.getDefaultValue(gameVersionNumber).orElse(null); this.setGameRuleNBT(gameRuleNBT); this.setOnSave(onSave); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java index 82cef03068..62fecfa264 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java @@ -91,12 +91,12 @@ public void updateControls() { @SuppressWarnings("unchecked") GameRuleNBT typedGameRuleNBT = (GameRuleNBT) gameRuleNBT; - gameRuleList.add(new GameRuleInfo.IntGameRuleInfo(intGameRule, typedGameRuleNBT, this::saveLevelDatIfNotResettingAll)); + gameRuleList.add(new GameRuleInfo.IntGameRuleInfo(intGameRule, typedGameRuleNBT, this::saveLevelDatIfNotResettingAll, world.getGameVersion())); } else if (gameRule instanceof GameRule.BooleanGameRule booleanGameRule) { @SuppressWarnings("unchecked") GameRuleNBT typedGameRuleNBT = (GameRuleNBT) gameRuleNBT; - gameRuleList.add(new GameRuleInfo.BooleanGameRuleInfo(booleanGameRule, typedGameRuleNBT, this::saveLevelDatIfNotResettingAll)); + gameRuleList.add(new GameRuleInfo.BooleanGameRuleInfo(booleanGameRule, typedGameRuleNBT, this::saveLevelDatIfNotResettingAll, world.getGameVersion())); } }); }); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java index f82c740a79..b547be64b6 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java @@ -26,6 +26,7 @@ import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.gson.JsonSerializable; import org.jackhuang.hmcl.util.gson.JsonUtils; +import org.jackhuang.hmcl.util.versioning.GameVersionNumber; import java.io.IOException; import java.io.InputStream; @@ -61,8 +62,8 @@ public static GameRule createSimpleGameRule(String ruleKey, boolean value) { public static GameRule createSimpleGameRule(String ruleKey, int value) { IntGameRule intGameRule = new IntGameRule(Collections.singletonList(ruleKey), value); - intGameRule.maxValue = Integer.MAX_VALUE; - intGameRule.minValue = Integer.MIN_VALUE; + intGameRule.addMaxValue(Integer.MAX_VALUE); + intGameRule.addMinValue(Integer.MIN_VALUE); return intGameRule; } @@ -149,7 +150,7 @@ public String getDisplayI18nKey() { /// Implementation of a boolean-based GameRule. public static final class BooleanGameRule extends GameRule { private boolean value = false; - private Boolean defaultValue; + private final TreeMap defaultValueMap = new TreeMap<>(); private BooleanGameRule(List ruleKey, boolean value) { super(ruleKey, ""); @@ -164,16 +165,20 @@ private BooleanGameRule() { public void applyMetadata(GameRule metadataSource) { if (metadataSource instanceof BooleanGameRule source) { this.setDisplayI18nKey(source.getDisplayI18nKey()); - source.getDefaultValue().ifPresent(this::setDefaultValue); + this.defaultValueMap.putAll(source.defaultValueMap); } } - public Optional getDefaultValue() { - return Optional.ofNullable(defaultValue); + public Optional getDefaultValue(GameVersionNumber gameVersionNumber) { + return Optional.ofNullable(defaultValueMap.floorEntry(gameVersionNumber).getValue()); } - private void setDefaultValue(boolean value) { - defaultValue = value; + private void addDefaultValue(boolean value) { + defaultValueMap.put(GameVersionNumber.asGameVersion("1.4.2"), value); + } + + private void addDefaultValue(String versionName, boolean value) { + defaultValueMap.put(GameVersionNumber.asGameVersion(versionName), value); } public boolean getValue() { @@ -189,9 +194,9 @@ public void setValue(boolean value) { /// supports min/max value validation. public static final class IntGameRule extends GameRule { private int value = 0; - private Integer defaultValue; - private int maxValue = 0; - private int minValue = 0; + private final TreeMap defaultValueMap = new TreeMap<>(); + private final TreeMap maxValueMap = new TreeMap<>(); + private final TreeMap minValueMap = new TreeMap<>(); private IntGameRule(List ruleKey, int value) { super(ruleKey, ""); @@ -206,18 +211,22 @@ private IntGameRule() { public void applyMetadata(GameRule metadataSource) { if (metadataSource instanceof IntGameRule source) { this.setDisplayI18nKey(source.getDisplayI18nKey()); - source.getDefaultValue().ifPresent(this::setDefaultValue); - this.maxValue = source.getMaxValue(); - this.minValue = source.getMinValue(); + this.defaultValueMap.putAll(source.defaultValueMap); + this.maxValueMap.putAll(source.maxValueMap); + this.minValueMap.putAll(source.minValueMap); } } - public Optional getDefaultValue() { - return Optional.ofNullable(defaultValue); + public Optional getDefaultValue(GameVersionNumber gameVersionNumber) { + return Optional.ofNullable(defaultValueMap.floorEntry(gameVersionNumber).getValue()); + } + + private void addDefaultValue(int value) { + this.defaultValueMap.put(GameVersionNumber.asGameVersion("1.4.2"), value); } - private void setDefaultValue(int value) { - defaultValue = value; + private void addDefaultValue(String versionName, int value) { + this.defaultValueMap.put(GameVersionNumber.asGameVersion(versionName), value); } public int getValue() { @@ -228,20 +237,28 @@ public void setValue(int value) { this.value = value; } - public int getMaxValue() { - return maxValue; + public int getMaxValue(GameVersionNumber gameVersionNumber) { + return Optional.ofNullable(maxValueMap.floorEntry(gameVersionNumber).getValue()).orElse(Integer.MAX_VALUE); } - public void setMaxValue(int maxValue) { - this.maxValue = maxValue; + public void addMaxValue(int maxValue) { + this.maxValueMap.put(GameVersionNumber.asGameVersion("1.4.2"), maxValue); } - public int getMinValue() { - return minValue; + public void addMaxValue(String versionName, int maxValue) { + this.maxValueMap.put(GameVersionNumber.asGameVersion(versionName), maxValue); } - public void setMinValue(int minValue) { - this.minValue = minValue; + public int getMinValue(GameVersionNumber gameVersionNumber) { + return Optional.ofNullable(minValueMap.floorEntry(gameVersionNumber).getValue()).orElse(Integer.MIN_VALUE); + } + + public void addMinValue(int minValue) { + this.minValueMap.put(GameVersionNumber.asGameVersion("1.4.2"), minValue); + } + + public void addMinValue(String versionName, int minValue) { + this.minValueMap.put(GameVersionNumber.asGameVersion(versionName), minValue); } } @@ -253,39 +270,70 @@ static class GameRuleDeserializer implements JsonDeserializer { public GameRule deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext context) throws JsonParseException { JsonObject jsonObject = jsonElement.getAsJsonObject(); - GameRule gameRule = createRuleFromDefaultValue(jsonObject.get("defaultValue")); + GameRule gameRule = createRuleFromDefaultValue(jsonObject); gameRule.displayI18nKey = jsonObject.get("displayI18nKey").getAsString(); Type listType = JsonUtils.listTypeOf(String.class).getType(); gameRule.ruleKey = context.deserialize(jsonObject.get("ruleKey"), listType); if (gameRule instanceof IntGameRule intGameRule) { - if (jsonObject.has("maxValue") && jsonObject.get("maxValue") instanceof JsonPrimitive jsonPrimitive) { - intGameRule.maxValue = jsonPrimitive.getAsInt(); + if (jsonObject.get("maxValue") instanceof JsonPrimitive jsonPrimitive) { + intGameRule.addMaxValue(jsonPrimitive.getAsInt()); + } else if (jsonObject.get("maxValue") instanceof JsonObject jsonJsonObject) { + jsonJsonObject.asMap().forEach((key, value) -> { + JsonPrimitive primitive = value.getAsJsonPrimitive(); + int maxValue = primitive.isNumber() ? primitive.getAsInt() : Integer.MAX_VALUE; + intGameRule.addMaxValue(key, maxValue); + }); } else { - intGameRule.maxValue = Integer.MAX_VALUE; + intGameRule.addMaxValue(Integer.MAX_VALUE); } - if (jsonObject.has("minValue") && jsonObject.get("minValue") instanceof JsonPrimitive jsonPrimitive) { - intGameRule.minValue = jsonPrimitive.getAsInt(); + if (jsonObject.get("minValue") instanceof JsonPrimitive jsonPrimitive) { + intGameRule.addMinValue(jsonPrimitive.getAsInt()); + } else if (jsonObject.get("minValue") instanceof JsonObject jsonJsonObject) { + jsonJsonObject.asMap().forEach((key, value) -> { + JsonPrimitive primitive = value.getAsJsonPrimitive(); + int minValue = primitive.isNumber() ? primitive.getAsInt() : Integer.MIN_VALUE; + intGameRule.addMinValue(key, minValue); + }); } else { - intGameRule.minValue = Integer.MIN_VALUE; + intGameRule.addMinValue(Integer.MIN_VALUE); } } return gameRule; } - private GameRule createRuleFromDefaultValue(JsonElement defaultValueElement) { - if (defaultValueElement instanceof JsonPrimitive p && p.isNumber()) { + private GameRule createRuleFromDefaultValue(JsonObject jsonObject) { + boolean isInt = jsonObject.get("type").getAsString().equals("int"); + if (isInt) { IntGameRule rule = new IntGameRule(); - rule.setDefaultValue(defaultValueElement.getAsInt()); - return rule; + JsonElement defaultValue = jsonObject.get("defaultValue"); + if (defaultValue instanceof JsonPrimitive p && p.isNumber()) { + rule.addDefaultValue(p.getAsInt()); + return rule; + } else { + if (defaultValue instanceof JsonObject o) { + o.asMap().forEach((key, value) -> rule.addDefaultValue(key, value.getAsInt())); + return rule; + } + } } else { BooleanGameRule rule = new BooleanGameRule(); - rule.setDefaultValue(defaultValueElement.getAsBoolean()); - return rule; + JsonElement defaultValue = jsonObject.get("defaultValue"); + if (defaultValue instanceof JsonPrimitive p && p.isBoolean()) { + rule.addDefaultValue(p.getAsBoolean()); + return rule; + } else { + if (defaultValue instanceof JsonObject o) { + o.asMap().forEach((key, value) -> rule.addDefaultValue(key, value.getAsBoolean())); + return rule; + } + } } + + return null; } } diff --git a/HMCLCore/src/main/resources/assets/gamerule/gamerule.json b/HMCLCore/src/main/resources/assets/gamerule/gamerule.json index 23b330bab2..9b32af70b5 100644 --- a/HMCLCore/src/main/resources/assets/gamerule/gamerule.json +++ b/HMCLCore/src/main/resources/assets/gamerule/gamerule.json @@ -4,6 +4,7 @@ "minecraft:advance_time", "doDaylightCycle" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.advance_time", "defaultValue": true }, @@ -12,6 +13,7 @@ "minecraft:advance_weather", "doWeatherCycle" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.advance_weather", "defaultValue": true }, @@ -19,6 +21,7 @@ "ruleKey": [ "allowFireTicksAwayFromPlayer" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.allow_fire_ticks_away_from_player", "defaultValue": false }, @@ -27,6 +30,7 @@ "minecraft:allow_entering_nether_using_portals", "allowEnteringNetherUsingPortals" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.allow_entering_nether_using_portals", "defaultValue": true }, @@ -35,6 +39,7 @@ "minecraft:block_drops", "doTileDrops" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.block_drops", "defaultValue": true }, @@ -43,6 +48,7 @@ "minecraft:block_explosion_drop_decay", "blockExplosionDropDecay" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.block_explosion_drop_decay", "defaultValue": true }, @@ -51,6 +57,7 @@ "minecraft:command_block_output", "commandBlockOutput" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.command_block_output", "defaultValue": true }, @@ -60,6 +67,7 @@ "commandBlocksEnabled", "enableCommandBlocks" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.command_blocks_work", "defaultValue": true }, @@ -67,6 +75,7 @@ "ruleKey": [ "disableRaids" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.disable_raids", "defaultValue": false }, @@ -74,6 +83,7 @@ "ruleKey": [ "doFireTick" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.do_fire_tick", "defaultValue": true }, @@ -82,6 +92,7 @@ "minecraft:drowning_damage", "drowningDamage" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.drowning_damage", "defaultValue": true }, @@ -90,6 +101,7 @@ "minecraft:elytra_movement_check", "disableElytraMovementCheck" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.elytra_movement_check", "defaultValue": true }, @@ -98,6 +110,7 @@ "minecraft:ender_pearls_vanish_on_death", "enderPearlsVanishOnDeath" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.ender_pearls_vanish_on_death", "defaultValue": true }, @@ -106,6 +119,7 @@ "minecraft:entity_drops", "doEntityDrops" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.entity_drops", "defaultValue": true }, @@ -113,6 +127,7 @@ "ruleKey": [ "entitiesWithPassengersCanUsePortals" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.entities_with_passengers_can_use_portals", "defaultValue": false }, @@ -121,6 +136,7 @@ "minecraft:fall_damage", "fallDamage" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.fall_damage", "defaultValue": true }, @@ -129,6 +145,7 @@ "minecraft:fire_damage", "fireDamage" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.fire_damage", "defaultValue": true }, @@ -136,6 +153,7 @@ "ruleKey": [ "minecraft:fire_spread_radius_around_player" ], + "type": "int", "displayI18nKey": "gamerule.rule.fire_spread_radius_around_player", "defaultValue": 128, "minValue": -1 @@ -145,6 +163,7 @@ "minecraft:forgive_dead_players", "forgiveDeadPlayers" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.forgive_dead_players", "defaultValue": true }, @@ -153,6 +172,7 @@ "minecraft:freeze_damage", "freezeDamage" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.freeze_damage", "defaultValue": true }, @@ -161,6 +181,7 @@ "minecraft:global_sound_events", "globalSoundEvents" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.global_sound_events", "defaultValue": true }, @@ -169,6 +190,7 @@ "minecraft:immediate_respawn", "doImmediateRespawn" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.immediate_respawn", "defaultValue": false }, @@ -177,6 +199,7 @@ "minecraft:keep_inventory", "keepInventory" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.keep_inventory", "defaultValue": false }, @@ -185,6 +208,7 @@ "minecraft:lava_source_conversion", "lavaSourceConversion" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.lava_source_conversion", "defaultValue": false }, @@ -193,6 +217,7 @@ "minecraft:limited_crafting", "doLimitedCrafting" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.limited_crafting", "defaultValue": false }, @@ -202,6 +227,7 @@ "locatorBar", "useLocatorBar" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.locator_bar", "defaultValue": true }, @@ -210,6 +236,7 @@ "minecraft:log_admin_commands", "logAdminCommands" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.log_admin_commands", "defaultValue": true }, @@ -218,42 +245,60 @@ "minecraft:max_block_modifications", "commandModificationBlockLimit" ], + "type": "int", "displayI18nKey": "gamerule.rule.max_block_modifications", "defaultValue": 32768, - "minValue": 1 + "minValue": { + "23w03a": "INT_MIN", + "25w44a": 1 + } }, { "ruleKey": [ "minecraft:max_command_forks", "maxCommandForkCount" ], + "type": "int", "displayI18nKey": "gamerule.rule.max_command_forks", "defaultValue": 65536, - "minValue": 0 + "minValue": { + "23w41a": "INT_MIN", + "25w44a": 0 + } }, { "ruleKey": [ "minecraft:max_command_sequence_length", "maxCommandChainLength" ], + "type": "int", "displayI18nKey": "gamerule.rule.max_command_sequence_length", "defaultValue": 65536, - "minValue": 0 + "minValue": { + "17w16b": "INT_MIN", + "25w44a": 0 + } }, { "ruleKey": [ "minecraft:max_entity_cramming", "maxEntityCramming" ], + "type": "int", "displayI18nKey": "gamerule.rule.max_entity_cramming", "defaultValue": 24, - "minValue": 0 + "minValue": { + "16w38a": "INT_MIN", + "25w44a": 1, + "25w45a": 0 + } }, { "ruleKey": [ "minecraft:max_minecart_speed", "minecartMaxSpeed" ], + "type": "int", "displayI18nKey": "gamerule.rule.max_minecart_speed", "defaultValue": 8, "minValue": 1, @@ -264,16 +309,24 @@ "minecraft:max_snow_accumulation_height", "snowAccumulationHeight" ], + "type": "int", "displayI18nKey": "gamerule.rule.max_snow_accumulation_height", "defaultValue": 1, - "minValue": 0, - "maxValue": 8 + "minValue": { + "22w44a": "INT_MIN", + "25w44a": 0 + }, + "maxValue": { + "22w44a": "INT_MAX", + "25w44a": 8 + } }, { "ruleKey": [ "minecraft:mob_drops", "doMobLoot" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.mob_drops", "defaultValue": true }, @@ -282,6 +335,7 @@ "minecraft:mob_explosion_drop_decay", "mobExplosionDropDecay" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.mob_explosion_drop_decay", "defaultValue": true }, @@ -290,6 +344,7 @@ "minecraft:mob_griefing", "mobGriefing" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.mob_griefing", "defaultValue": true }, @@ -298,6 +353,7 @@ "minecraft:natural_health_regeneration", "naturalRegeneration" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.natural_health_regeneration", "defaultValue": true }, @@ -306,6 +362,7 @@ "minecraft:player_movement_check", "disablePlayerMovementCheck" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.player_movement_check", "defaultValue": true }, @@ -314,33 +371,46 @@ "minecraft:players_nether_portal_creative_delay", "playersNetherPortalCreativeDelay" ], + "type": "int", "displayI18nKey": "gamerule.rule.players_nether_portal_creative_delay", "defaultValue": 0, - "minValue": 0 + "minValue": { + "23w42a": "INT_MIN", + "25w44a": 0 + } }, { "ruleKey": [ "minecraft:players_nether_portal_default_delay", "playersNetherPortalDefaultDelay" ], + "type": "int", "displayI18nKey": "gamerule.rule.players_nether_portal_default_delay", "defaultValue": 80, - "minValue": 0 + "minValue": { + "23w42a": "INT_MIN", + "25w44a": 0 + } }, { "ruleKey": [ "minecraft:players_sleeping_percentage", "playersSleepingPercentage" ], + "type": "int", "displayI18nKey": "gamerule.rule.players_sleeping_percentage", "defaultValue": 100, - "minValue": 0 + "minValue": { + "20w51a": "INT_MIN", + "25w44a": 0 + } }, { "ruleKey": [ "minecraft:projectiles_can_break_blocks", "projectilesCanBreakBlocks" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.projectiles_can_break_blocks", "defaultValue": true }, @@ -349,6 +419,7 @@ "minecraft:pvp", "pvp" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.pvp", "defaultValue": true }, @@ -356,6 +427,7 @@ "ruleKey": [ "minecraft:raids" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.raids", "defaultValue": true }, @@ -364,15 +436,20 @@ "minecraft:random_tick_speed", "randomTickSpeed" ], + "type": "int", "displayI18nKey": "gamerule.rule.random_tick_speed", "defaultValue": 3, - "minValue": 0 + "minValue": { + "14w17a": "INT_MIN", + "25w44a": 0 + } }, { "ruleKey": [ "minecraft:reduced_debug_info", "reducedDebugInfo" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.reduced_debug_info", "defaultValue": false }, @@ -381,15 +458,20 @@ "minecraft:respawn_radius", "spawnRadius" ], + "type": "int", "displayI18nKey": "gamerule.rule.respawn_radius", "defaultValue": 10, - "minValue": 0 + "minValue": { + "15w51a": "INT_MIN", + "25w44a": 0 + } }, { "ruleKey": [ "minecraft:send_command_feedback", "sendCommandFeedback" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.send_command_feedback", "defaultValue": true }, @@ -398,6 +480,7 @@ "minecraft:show_advancement_messages", "announceAdvancements" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.show_advancement_messages", "defaultValue": true }, @@ -406,6 +489,7 @@ "minecraft:show_death_messages", "showDeathMessages" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.show_death_messages", "defaultValue": true }, @@ -413,6 +497,7 @@ "ruleKey": [ "spawnChunkRadius" ], + "type": "int", "displayI18nKey": "gamerule.rule.spawn_chunk_radius", "defaultValue": 2 }, @@ -421,6 +506,7 @@ "minecraft:spawn_mobs", "doMobSpawning" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.spawn_mobs", "defaultValue": true }, @@ -429,6 +515,7 @@ "minecraft:spawn_monsters", "spawnMonsters" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.spawn_monsters", "defaultValue": true }, @@ -437,6 +524,7 @@ "minecraft:spawn_patrols", "doPatrolSpawning" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.spawn_patrols", "defaultValue": true }, @@ -445,6 +533,7 @@ "minecraft:spawn_phantoms", "doInsomnia" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.spawn_phantoms", "defaultValue": true }, @@ -453,6 +542,7 @@ "minecraft:spawn_wandering_traders", "doTraderSpawning" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.spawn_wandering_traders", "defaultValue": true }, @@ -461,6 +551,7 @@ "minecraft:spawn_wardens", "doWardenSpawning" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.spawn_wardens", "defaultValue": true }, @@ -469,6 +560,7 @@ "minecraft:spawner_blocks_work", "spawnerBlocksEnabled" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.spawner_blocks_work", "defaultValue": true }, @@ -477,6 +569,7 @@ "minecraft:spectators_generate_chunks", "spectatorsGenerateChunks" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.spectators_generate_chunks", "defaultValue": true }, @@ -485,6 +578,7 @@ "minecraft:spread_vines", "doVinesSpread" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.spread_vines", "defaultValue": true }, @@ -493,6 +587,7 @@ "minecraft:tnt_explodes", "tntExplodes" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.tnt_explodes", "defaultValue": true }, @@ -501,6 +596,7 @@ "minecraft:tnt_explosion_drop_decay", "tntExplosionDropDecay" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.tnt_explosion_drop_decay", "defaultValue": false }, @@ -509,6 +605,7 @@ "minecraft:universal_anger", "universalAnger" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.universal_anger", "defaultValue": false }, @@ -517,6 +614,7 @@ "minecraft:water_source_conversion", "waterSourceConversion" ], + "type": "boolean", "displayI18nKey": "gamerule.rule.water_source_conversion", "defaultValue": true } From 9ee1646b7e0937d8160410a504093f560ce6b6a3 Mon Sep 17 00:00:00 2001 From: mine_ Date: Sat, 27 Dec 2025 13:30:26 +0800 Subject: [PATCH 49/69] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/GameRuleInfo.java | 40 ++--- .../org/jackhuang/hmcl/gamerule/GameRule.java | 167 +++++++++--------- .../hmcl/util/versioning/VersionedValue.java | 63 +++++++ 3 files changed, 164 insertions(+), 106 deletions(-) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/versioning/VersionedValue.java diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java index ad61512c67..2db35875fe 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java @@ -43,13 +43,28 @@ public sealed abstract class GameRuleInfo permits GameRuleInfo.BooleanGameRul private String displayName; private GameRuleNBT gameRuleNBT; private Runnable onSave; - private GameVersionNumber gameVersionNumber; //Due to the significant difference in skin between BooleanGameRuleInfo and IntGameRuleInfo, which are essentially two completely different styles, it is not suitable to update each other in Cell#updateControl. Therefore, they are directly integrated into the info. private HBox container = new HBox(); private Runnable setToDefault = () -> { }; + private GameRuleInfo(GameRule gameRule, GameRuleNBT gameRuleNBT, Runnable onSave) { + setRuleKey(gameRule.getRuleKey().get(0)); + String displayName = ""; + try { + if (StringUtils.isNotBlank(gameRule.getDisplayI18nKey())) { + displayName = i18n(gameRule.getDisplayI18nKey()); + } + } catch (Exception e) { + LOG.warning("Failed to get i18n text for key: " + gameRule.getDisplayI18nKey(), e); + } + setDisplayName(displayName); + setDisplayName(displayName); + setGameRuleNBT(gameRuleNBT); + setOnSave(onSave); + } + public void resetValue() { setToDefault.run(); } @@ -102,31 +117,14 @@ public void setSetToDefault(Runnable setToDefault) { this.setToDefault = setToDefault; } - public void setFatherValue(GameRule gameRule, GameVersionNumber gameVersionNumber) { - setRuleKey(gameRule.getRuleKey().get(0)); - String displayName = ""; - try { - if (StringUtils.isNotBlank(gameRule.getDisplayI18nKey())) { - displayName = i18n(gameRule.getDisplayI18nKey()); - } - } catch (Exception e) { - LOG.warning("Failed to get i18n text for key: " + gameRule.getDisplayI18nKey(), e); - } - setDisplayName(displayName); - setDisplayName(displayName); - this.gameVersionNumber = gameVersionNumber; - } - static final class BooleanGameRuleInfo extends GameRuleInfo { boolean currentValue; Boolean defaultValue; public BooleanGameRuleInfo(GameRule.BooleanGameRule booleanGameRule, GameRuleNBT gameRuleNBT, Runnable onSave, GameVersionNumber gameVersionNumber) { - setFatherValue(booleanGameRule, gameVersionNumber); + super(booleanGameRule, gameRuleNBT, onSave); this.currentValue = booleanGameRule.getValue(); this.defaultValue = booleanGameRule.getDefaultValue(gameVersionNumber).orElse(null); - this.setGameRuleNBT(gameRuleNBT); - this.setOnSave(onSave); buildNodes(); } @@ -184,13 +182,11 @@ static final class IntGameRuleInfo extends GameRuleInfo { Integer defaultValue; public IntGameRuleInfo(GameRule.IntGameRule intGameRule, GameRuleNBT gameRuleNBT, Runnable onSave, GameVersionNumber gameVersionNumber) { - setFatherValue(intGameRule, gameVersionNumber); + super(intGameRule, gameRuleNBT, onSave); currentValue = intGameRule.getValue(); minValue = intGameRule.getMinValue(gameVersionNumber); maxValue = intGameRule.getMaxValue(gameVersionNumber); defaultValue = intGameRule.getDefaultValue(gameVersionNumber).orElse(null); - this.setGameRuleNBT(gameRuleNBT); - this.setOnSave(onSave); buildNodes(); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java index b547be64b6..4db8277f2c 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java @@ -27,6 +27,7 @@ import org.jackhuang.hmcl.util.gson.JsonSerializable; import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.versioning.GameVersionNumber; +import org.jackhuang.hmcl.util.versioning.VersionedValue; import java.io.IOException; import java.io.InputStream; @@ -38,7 +39,7 @@ /// This class handles the logic for: /// * Defining rule types (Boolean or Integer). /// * Parsing rules from NBT tags (read from `level.dat`). -/// * Serializing/Deserializing rules via GSON. +/// * Deserializing rules form `resources/assets/gamerule/gamerule.json` /// /// It is a sealed class permitting only [BooleanGameRule] and [IntGameRule]. @JsonSerializable @@ -131,6 +132,8 @@ public static Optional getFullGameRule(Tag tag) { /// Copies metadata (e.g., descriptions, ranges) from the source rule to this instance. public abstract void applyMetadata(GameRule metadataSource); + public abstract GameRule deserialize(JsonObject jsonObject, Type type, JsonDeserializationContext context); + public void setRuleKey(List ruleKey) { this.ruleKey = ruleKey; } @@ -150,7 +153,7 @@ public String getDisplayI18nKey() { /// Implementation of a boolean-based GameRule. public static final class BooleanGameRule extends GameRule { private boolean value = false; - private final TreeMap defaultValueMap = new TreeMap<>(); + private final VersionedValue defaultValue = new VersionedValue<>(); private BooleanGameRule(List ruleKey, boolean value) { super(ruleKey, ""); @@ -165,20 +168,37 @@ private BooleanGameRule() { public void applyMetadata(GameRule metadataSource) { if (metadataSource instanceof BooleanGameRule source) { this.setDisplayI18nKey(source.getDisplayI18nKey()); - this.defaultValueMap.putAll(source.defaultValueMap); + this.defaultValue.addAll(source.defaultValue); + } + } + + @Override + public GameRule deserialize(JsonObject jsonObject, Type type, JsonDeserializationContext context) { + Type listType = JsonUtils.listTypeOf(String.class).getType(); + this.setRuleKey(context.deserialize(jsonObject.get("ruleKey"), listType)); + this.setDisplayI18nKey(jsonObject.get("displayI18nKey").getAsString()); + JsonElement defaultValue = jsonObject.get("defaultValue"); + if (defaultValue instanceof JsonPrimitive p && p.isBoolean()) { + this.addDefaultValue(p.getAsBoolean()); + } else { + if (defaultValue instanceof JsonObject o) { + o.asMap().forEach((key, value) -> this.addDefaultValue(key, value.getAsBoolean())); + } } + + return this; } public Optional getDefaultValue(GameVersionNumber gameVersionNumber) { - return Optional.ofNullable(defaultValueMap.floorEntry(gameVersionNumber).getValue()); + return defaultValue.getValue(gameVersionNumber); } private void addDefaultValue(boolean value) { - defaultValueMap.put(GameVersionNumber.asGameVersion("1.4.2"), value); + defaultValue.addValueInMinVersion("1.4.2", value); } private void addDefaultValue(String versionName, boolean value) { - defaultValueMap.put(GameVersionNumber.asGameVersion(versionName), value); + defaultValue.addValueInMinVersion(versionName, value); } public boolean getValue() { @@ -194,9 +214,9 @@ public void setValue(boolean value) { /// supports min/max value validation. public static final class IntGameRule extends GameRule { private int value = 0; - private final TreeMap defaultValueMap = new TreeMap<>(); - private final TreeMap maxValueMap = new TreeMap<>(); - private final TreeMap minValueMap = new TreeMap<>(); + private final VersionedValue defaultValue = new VersionedValue<>(); + private final VersionedValue minValue = new VersionedValue<>(); + private final VersionedValue maxValue = new VersionedValue<>(); private IntGameRule(List ruleKey, int value) { super(ruleKey, ""); @@ -211,22 +231,62 @@ private IntGameRule() { public void applyMetadata(GameRule metadataSource) { if (metadataSource instanceof IntGameRule source) { this.setDisplayI18nKey(source.getDisplayI18nKey()); - this.defaultValueMap.putAll(source.defaultValueMap); - this.maxValueMap.putAll(source.maxValueMap); - this.minValueMap.putAll(source.minValueMap); + this.defaultValue.addAll(source.defaultValue); + this.maxValue.addAll(source.maxValue); + this.minValue.addAll(source.minValue); } } + @Override + public GameRule deserialize(JsonObject jsonObject, Type type, JsonDeserializationContext context) { + Type listType = JsonUtils.listTypeOf(String.class).getType(); + this.setRuleKey(context.deserialize(jsonObject.get("ruleKey"), listType)); + this.setDisplayI18nKey(jsonObject.get("displayI18nKey").getAsString()); + + if (jsonObject.get("defaultValue") instanceof JsonPrimitive p && p.isNumber()) { + this.addDefaultValue(p.getAsInt()); + } else { + if (jsonObject.get("defaultValue") instanceof JsonObject o) { + o.asMap().forEach((key, value) -> this.addDefaultValue(key, value.getAsInt())); + } + } + + if (jsonObject.get("maxValue") instanceof JsonPrimitive jsonPrimitive) { + this.addMaxValue(jsonPrimitive.getAsInt()); + } else if (jsonObject.get("maxValue") instanceof JsonObject o) { + o.asMap().forEach((key, value) -> { + JsonPrimitive primitive = value.getAsJsonPrimitive(); + int maxValue = primitive.isNumber() ? primitive.getAsInt() : Integer.MAX_VALUE; + this.addMaxValue(key, maxValue); + }); + } else { + this.addMaxValue(Integer.MAX_VALUE); + } + + if (jsonObject.get("minValue") instanceof JsonPrimitive jsonPrimitive) { + this.addMinValue(jsonPrimitive.getAsInt()); + } else if (jsonObject.get("minValue") instanceof JsonObject o) { + o.asMap().forEach((key, value) -> { + JsonPrimitive primitive = value.getAsJsonPrimitive(); + int minValue = primitive.isNumber() ? primitive.getAsInt() : Integer.MIN_VALUE; + this.addMinValue(key, minValue); + }); + } else { + this.addMinValue(Integer.MIN_VALUE); + } + return this; + } + public Optional getDefaultValue(GameVersionNumber gameVersionNumber) { - return Optional.ofNullable(defaultValueMap.floorEntry(gameVersionNumber).getValue()); + return defaultValue.getValue(gameVersionNumber); } private void addDefaultValue(int value) { - this.defaultValueMap.put(GameVersionNumber.asGameVersion("1.4.2"), value); + this.defaultValue.addValueInMinVersion("1.4.2", value); } private void addDefaultValue(String versionName, int value) { - this.defaultValueMap.put(GameVersionNumber.asGameVersion(versionName), value); + this.defaultValue.addValueInMinVersion(versionName, value); } public int getValue() { @@ -238,27 +298,27 @@ public void setValue(int value) { } public int getMaxValue(GameVersionNumber gameVersionNumber) { - return Optional.ofNullable(maxValueMap.floorEntry(gameVersionNumber).getValue()).orElse(Integer.MAX_VALUE); + return this.maxValue.getValue(gameVersionNumber).orElse(Integer.MAX_VALUE); } public void addMaxValue(int maxValue) { - this.maxValueMap.put(GameVersionNumber.asGameVersion("1.4.2"), maxValue); + this.maxValue.addValueInMinVersion("1.4.2", maxValue); } public void addMaxValue(String versionName, int maxValue) { - this.maxValueMap.put(GameVersionNumber.asGameVersion(versionName), maxValue); + this.maxValue.addValueInMinVersion(versionName, maxValue); } public int getMinValue(GameVersionNumber gameVersionNumber) { - return Optional.ofNullable(minValueMap.floorEntry(gameVersionNumber).getValue()).orElse(Integer.MIN_VALUE); + return minValue.getValue(gameVersionNumber).orElse(Integer.MIN_VALUE); } public void addMinValue(int minValue) { - this.minValueMap.put(GameVersionNumber.asGameVersion("1.4.2"), minValue); + this.minValue.addValueInMinVersion("1.4.2", minValue); } public void addMinValue(String versionName, int minValue) { - this.minValueMap.put(GameVersionNumber.asGameVersion(versionName), minValue); + this.minValue.addValueInMinVersion(versionName, minValue); } } @@ -270,70 +330,9 @@ static class GameRuleDeserializer implements JsonDeserializer { public GameRule deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext context) throws JsonParseException { JsonObject jsonObject = jsonElement.getAsJsonObject(); - GameRule gameRule = createRuleFromDefaultValue(jsonObject); - gameRule.displayI18nKey = jsonObject.get("displayI18nKey").getAsString(); - - Type listType = JsonUtils.listTypeOf(String.class).getType(); - gameRule.ruleKey = context.deserialize(jsonObject.get("ruleKey"), listType); - - if (gameRule instanceof IntGameRule intGameRule) { - if (jsonObject.get("maxValue") instanceof JsonPrimitive jsonPrimitive) { - intGameRule.addMaxValue(jsonPrimitive.getAsInt()); - } else if (jsonObject.get("maxValue") instanceof JsonObject jsonJsonObject) { - jsonJsonObject.asMap().forEach((key, value) -> { - JsonPrimitive primitive = value.getAsJsonPrimitive(); - int maxValue = primitive.isNumber() ? primitive.getAsInt() : Integer.MAX_VALUE; - intGameRule.addMaxValue(key, maxValue); - }); - } else { - intGameRule.addMaxValue(Integer.MAX_VALUE); - } - - if (jsonObject.get("minValue") instanceof JsonPrimitive jsonPrimitive) { - intGameRule.addMinValue(jsonPrimitive.getAsInt()); - } else if (jsonObject.get("minValue") instanceof JsonObject jsonJsonObject) { - jsonJsonObject.asMap().forEach((key, value) -> { - JsonPrimitive primitive = value.getAsJsonPrimitive(); - int minValue = primitive.isNumber() ? primitive.getAsInt() : Integer.MIN_VALUE; - intGameRule.addMinValue(key, minValue); - }); - } else { - intGameRule.addMinValue(Integer.MIN_VALUE); - } - } - - return gameRule; - } - - private GameRule createRuleFromDefaultValue(JsonObject jsonObject) { boolean isInt = jsonObject.get("type").getAsString().equals("int"); - if (isInt) { - IntGameRule rule = new IntGameRule(); - JsonElement defaultValue = jsonObject.get("defaultValue"); - if (defaultValue instanceof JsonPrimitive p && p.isNumber()) { - rule.addDefaultValue(p.getAsInt()); - return rule; - } else { - if (defaultValue instanceof JsonObject o) { - o.asMap().forEach((key, value) -> rule.addDefaultValue(key, value.getAsInt())); - return rule; - } - } - } else { - BooleanGameRule rule = new BooleanGameRule(); - JsonElement defaultValue = jsonObject.get("defaultValue"); - if (defaultValue instanceof JsonPrimitive p && p.isBoolean()) { - rule.addDefaultValue(p.getAsBoolean()); - return rule; - } else { - if (defaultValue instanceof JsonObject o) { - o.asMap().forEach((key, value) -> rule.addDefaultValue(key, value.getAsBoolean())); - return rule; - } - } - } - - return null; + GameRule gameRule = isInt ? new IntGameRule() : new BooleanGameRule(); + return gameRule.deserialize(jsonObject, type, context); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/versioning/VersionedValue.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/versioning/VersionedValue.java new file mode 100644 index 0000000000..c11d4c0dfc --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/versioning/VersionedValue.java @@ -0,0 +1,63 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2025 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.util.versioning; + +import java.util.Map; +import java.util.Optional; +import java.util.TreeMap; + +/** + * Represents a value that varies based on the game version. + *

+ * This class uses a {@link TreeMap} to store values associated with minimum version thresholds. + * When retrieving a value for a specific version, it returns the value associated with the + * highest version key that is less than or equal to the requested version. + *

+ * + * @param The type of the value being versioned. + */ +public class VersionedValue { + private final TreeMap versionValues = new TreeMap<>(); + + public VersionedValue() { + } + + public VersionedValue(String version, T value) { + versionValues.put(GameVersionNumber.asGameVersion(version), value); + } + + public void addValueInMinVersion(String version, T value) { + versionValues.put(GameVersionNumber.asGameVersion(version), value); + } + + public Optional getValue(String version) { + return Optional.ofNullable(versionValues.floorEntry(GameVersionNumber.asGameVersion(version))).map(Map.Entry::getValue); + } + + public Optional getValue(GameVersionNumber version) { + return Optional.ofNullable(versionValues.floorEntry(version)).map(Map.Entry::getValue); + } + + public TreeMap getVersionValue() { + return versionValues; + } + + public void addAll(VersionedValue v) { + versionValues.putAll(v.getVersionValue()); + } +} From edb70bb4879ee90f98bca67cf04324a7d27f87d2 Mon Sep 17 00:00:00 2001 From: mine_ Date: Sat, 27 Dec 2025 16:01:05 +0800 Subject: [PATCH 50/69] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E9=87=8D?= =?UTF-8?q?=E7=BD=AE=E8=A7=84=E5=88=99=E6=98=AF=E6=98=BE=E7=A4=BA=E5=85=B7?= =?UTF-8?q?=E4=BD=93=E5=8F=98=E5=8C=96=E7=9A=84=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/GameRuleInfo.java | 57 ++++++++++++--- .../hmcl/ui/versions/GameRulePageSkin.java | 71 ++++++++++++++++--- 2 files changed, 109 insertions(+), 19 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java index 2db35875fe..44bd4f128f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java @@ -20,6 +20,10 @@ import com.github.steveice10.opennbt.tag.builtin.Tag; import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXTextField; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.Label; @@ -35,6 +39,9 @@ import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.versioning.GameVersionNumber; +import java.util.Objects; +import java.util.function.BooleanSupplier; + import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.logging.Logger.LOG; @@ -48,6 +55,7 @@ public sealed abstract class GameRuleInfo permits GameRuleInfo.BooleanGameRul private HBox container = new HBox(); private Runnable setToDefault = () -> { }; + private BooleanSupplier isDefault = () -> true; private GameRuleInfo(GameRule gameRule, GameRuleNBT gameRuleNBT, Runnable onSave) { setRuleKey(gameRule.getRuleKey().get(0)); @@ -117,18 +125,35 @@ public void setSetToDefault(Runnable setToDefault) { this.setToDefault = setToDefault; } + public BooleanSupplier getIsDefault() { + return isDefault; + } + + public void setIsDefault(BooleanSupplier isDefault) { + this.isDefault = isDefault; + } + static final class BooleanGameRuleInfo extends GameRuleInfo { - boolean currentValue; + //boolean currentValue; + BooleanProperty currentValue; Boolean defaultValue; public BooleanGameRuleInfo(GameRule.BooleanGameRule booleanGameRule, GameRuleNBT gameRuleNBT, Runnable onSave, GameVersionNumber gameVersionNumber) { super(booleanGameRule, gameRuleNBT, onSave); - this.currentValue = booleanGameRule.getValue(); + this.currentValue = new SimpleBooleanProperty(booleanGameRule.getValue()); this.defaultValue = booleanGameRule.getDefaultValue(gameVersionNumber).orElse(null); buildNodes(); } + public boolean getValue() { + return currentValue.get(); + } + + public boolean getDefaultValue() { + return defaultValue; + } + public void buildNodes() { { HBox.setHgrow(getContainer(), Priority.ALWAYS); @@ -142,7 +167,7 @@ public void buildNodes() { { toggleButton.setTitle(getDisplayName()); toggleButton.setSubtitle(getRuleKey()); - toggleButton.setSelected(currentValue); + toggleButton.selectedProperty().bindBidirectional(currentValue); toggleButton.selectedProperty().addListener((observable, oldValue, newValue) -> { getGameRuleNBT().changeValue(newValue); getOnSave().run(); @@ -153,11 +178,12 @@ public void buildNodes() { { wrapperPane.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE); resetButton.setFocusTraversable(false); - resetButton.setDisable(shouldResetButtonDisabled(currentValue)); + resetButton.setDisable(shouldResetButtonDisabled(currentValue.getValue())); resetButton.setGraphic(SVG.RESTORE.createIcon(24)); if (defaultValue != null) { setSetToDefault(() -> toggleButton.selectedProperty().set(defaultValue)); resetButton.setOnAction(event -> getSetToDefault().run()); + setIsDefault(() -> toggleButton.isSelected() == defaultValue); FXUtils.installFastTooltip(resetButton, i18n("gamerule.restore_default_values.tooltip", defaultValue)); FXUtils.installFastTooltip(wrapperPane, i18n("gamerule.now_is_default_values.tooltip")); } else { @@ -176,14 +202,14 @@ public boolean shouldResetButtonDisabled(boolean newValue) { } static final class IntGameRuleInfo extends GameRuleInfo { - int currentValue; + StringProperty currentValue; int minValue; int maxValue; Integer defaultValue; public IntGameRuleInfo(GameRule.IntGameRule intGameRule, GameRuleNBT gameRuleNBT, Runnable onSave, GameVersionNumber gameVersionNumber) { super(intGameRule, gameRuleNBT, onSave); - currentValue = intGameRule.getValue(); + currentValue = new SimpleStringProperty(String.valueOf(intGameRule.getValue())); minValue = intGameRule.getMinValue(gameVersionNumber); maxValue = intGameRule.getMaxValue(gameVersionNumber); defaultValue = intGameRule.getDefaultValue(gameVersionNumber).orElse(null); @@ -191,6 +217,14 @@ public IntGameRuleInfo(GameRule.IntGameRule intGameRule, GameRuleNBT= minValue && value <= maxValue) { getGameRuleNBT().changeValue(newValue); getOnSave().run(); - resetButton.setDisable(shouldResetButtonDisabled(value)); + resetButton.setDisable(shouldResetButtonDisabled(value.toString())); } }); @@ -235,11 +269,12 @@ public void buildNodes() { { wrapperPane.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE); resetButton.setFocusTraversable(false); - resetButton.setDisable(shouldResetButtonDisabled(currentValue)); + resetButton.setDisable(shouldResetButtonDisabled(currentValue.getValue())); resetButton.setGraphic(SVG.RESTORE.createIcon(24)); if (defaultValue != null) { setSetToDefault(() -> textField.textProperty().set(String.valueOf(defaultValue))); resetButton.setOnAction(event -> getSetToDefault().run()); + setIsDefault(() -> textField.textProperty().get().equals(String.valueOf(defaultValue))); FXUtils.installFastTooltip(resetButton, i18n("gamerule.restore_default_values.tooltip", defaultValue)); FXUtils.installFastTooltip(wrapperPane, i18n("gamerule.now_is_default_values.tooltip")); } else { @@ -254,8 +289,8 @@ public void buildNodes() { getContainer().getChildren().addAll(displayInfoVBox, rightHBox); } - public boolean shouldResetButtonDisabled(int newValue) { - return defaultValue == null || newValue == defaultValue; + public boolean shouldResetButtonDisabled(String newValue) { + return defaultValue == null || Objects.equals(Lang.toIntOrNull(newValue), defaultValue); } } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java index 01596af70d..36b81922a6 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java @@ -18,26 +18,27 @@ package org.jackhuang.hmcl.ui.versions; import com.jfoenix.controls.JFXButton; +import com.jfoenix.controls.JFXDialogLayout; import com.jfoenix.controls.JFXListView; import com.jfoenix.controls.JFXTextField; import javafx.animation.PauseTransition; import javafx.collections.transformation.FilteredList; import javafx.geometry.Insets; import javafx.geometry.Pos; +import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; import javafx.scene.control.SkinBase; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Priority; -import javafx.scene.layout.StackPane; +import javafx.scene.layout.*; import javafx.util.Duration; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; -import org.jackhuang.hmcl.ui.construct.ComponentList; -import org.jackhuang.hmcl.ui.construct.MDListCell; -import org.jackhuang.hmcl.ui.construct.MessageDialogPane; -import org.jackhuang.hmcl.ui.construct.SpinnerPane; +import org.jackhuang.hmcl.ui.construct.*; +import org.jackhuang.hmcl.util.StringUtils; + +import java.util.List; import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton2; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; @@ -62,7 +63,8 @@ class GameRulePageSkin extends SkinBase { { JFXButton resetAllButton = createToolbarButton2(i18n("gamerule.restore_default_values_all"), SVG.RESTORE, () -> { - Controllers.confirm(i18n("gamerule.restore_default_values_all.confirm"), i18n("message.warning"), MessageDialogPane.MessageType.WARNING, skinnable::resettingAllGameRule, null); + //Controllers.confirm(i18n("gamerule.restore_default_values_all.confirm"), i18n("message.warning"), MessageDialogPane.MessageType.WARNING, skinnable::resettingAllGameRule, null); + Controllers.dialog(new ResetDefaultValuesLayout(skinnable::resettingAllGameRule, skinnable.getItems())); }); searchBar = new HBox(); @@ -111,4 +113,57 @@ protected void updateControl(GameRuleInfo item, boolean empty) { getContainer().getChildren().setAll(item.getContainer()); } } + + static class ResetDefaultValuesLayout extends JFXDialogLayout { + public ResetDefaultValuesLayout(Runnable resettingAllGameRule, List> gameRules) { + setHeading(new Label(i18n("message.warning"))); + Label warnLabel = new Label(i18n("gamerule.restore_default_values_all.confirm")); + MenuUpDownButton menuUpDownButton = new MenuUpDownButton(); + { + menuUpDownButton.setText("查看具体变更"); + menuUpDownButton.setMaxWidth(USE_PREF_SIZE); + } + ScrollPane scrollPane = new ScrollPane(); + GridPane gridPane = new GridPane(); + { + gridPane.setHgap(10); + gridPane.setVgap(10); + scrollPane.setContent(gridPane); + scrollPane.visibleProperty().bind(menuUpDownButton.selectedProperty()); + scrollPane.managedProperty().bind(menuUpDownButton.selectedProperty()); + int index = 1; + gridPane.addRow(0, new Label("名称"), new Label("当前值"), new Label("->"), new Label("默认值")); + for (GameRuleInfo gameRule : gameRules) { + if (!gameRule.getIsDefault().getAsBoolean()) { + String oldValue = ""; + String newValue = ""; + if (gameRule instanceof GameRuleInfo.BooleanGameRuleInfo booleanGameRuleInfo) { + oldValue = String.valueOf(booleanGameRuleInfo.getValue()); + newValue = String.valueOf(booleanGameRuleInfo.getDefaultValue()); + } else if (gameRule instanceof GameRuleInfo.IntGameRuleInfo intGameRuleInfo) { + oldValue = intGameRuleInfo.getValue(); + newValue = intGameRuleInfo.getDefaultValue(); + } + gridPane.addRow(index, new Label(StringUtils.isNotBlank(gameRule.getDisplayName()) ? gameRule.getDisplayName() : gameRule.getRuleKey()), new Label(oldValue), new Label("->"), new Label(newValue)); + index++; + } + } + if (index == 1) { + gridPane.addRow(1, new Label("无变更")); + } + } + VBox vBox = new VBox(); + vBox.setAlignment(Pos.TOP_LEFT); + vBox.getChildren().addAll(warnLabel, menuUpDownButton, scrollPane); + setBody(vBox); + JFXButton accept = new JFXButton(i18n("button.ok")); + JFXButton reject = new JFXButton(i18n("button.cancel")); + accept.setOnAction(event -> { + resettingAllGameRule.run(); + fireEvent(new DialogCloseEvent()); + }); + reject.setOnAction(event -> fireEvent(new DialogCloseEvent())); + setActions(accept, reject); + } + } } From 8f56be843092ceb6da443763f09b51c08fad9391 Mon Sep 17 00:00:00 2001 From: mine_ Date: Sat, 27 Dec 2025 21:13:18 +0800 Subject: [PATCH 51/69] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E6=B8=B8?= =?UTF-8?q?=E6=88=8F=E8=A7=84=E5=88=99=E5=8F=98=E5=8A=A8=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E7=95=8C=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/GameRuleInfo.java | 71 +++++----- .../hmcl/ui/versions/GameRulePage.java | 10 +- .../hmcl/ui/versions/GameRulePageSkin.java | 121 ++++++++++++------ .../resources/assets/lang/I18N.properties | 6 + .../resources/assets/lang/I18N_zh.properties | 6 + .../assets/lang/I18N_zh_CN.properties | 6 + 6 files changed, 144 insertions(+), 76 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java index 44bd4f128f..eba878f934 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java @@ -20,6 +20,7 @@ import com.github.steveice10.opennbt.tag.builtin.Tag; import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXTextField; +import javafx.beans.binding.Bindings; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleStringProperty; @@ -51,12 +52,13 @@ public sealed abstract class GameRuleInfo permits GameRuleInfo.BooleanGameRul private GameRuleNBT gameRuleNBT; private Runnable onSave; - //Due to the significant difference in skin between BooleanGameRuleInfo and IntGameRuleInfo, which are essentially two completely different styles, it is not suitable to update each other in Cell#updateControl. Therefore, they are directly integrated into the info. - private HBox container = new HBox(); private Runnable setToDefault = () -> { }; private BooleanSupplier isDefault = () -> true; + //Due to the significant difference in skin between BooleanGameRuleInfo and IntGameRuleInfo, which are essentially two completely different styles, it is not suitable to update each other in Cell#updateControl. Therefore, they are directly integrated into the info. + private HBox container = new HBox(); + private GameRuleInfo(GameRule gameRule, GameRuleNBT gameRuleNBT, Runnable onSave) { setRuleKey(gameRule.getRuleKey().get(0)); String displayName = ""; @@ -68,11 +70,14 @@ private GameRuleInfo(GameRule gameRule, GameRuleNBT gameRuleNB LOG.warning("Failed to get i18n text for key: " + gameRule.getDisplayI18nKey(), e); } setDisplayName(displayName); - setDisplayName(displayName); setGameRuleNBT(gameRuleNBT); setOnSave(onSave); } + public abstract String getCurrentValueText(); + + public abstract String getDefaultValueText(); + public void resetValue() { setToDefault.run(); } @@ -134,9 +139,8 @@ public void setIsDefault(BooleanSupplier isDefault) { } static final class BooleanGameRuleInfo extends GameRuleInfo { - //boolean currentValue; - BooleanProperty currentValue; - Boolean defaultValue; + private final BooleanProperty currentValue; + private final Boolean defaultValue; public BooleanGameRuleInfo(GameRule.BooleanGameRule booleanGameRule, GameRuleNBT gameRuleNBT, Runnable onSave, GameVersionNumber gameVersionNumber) { super(booleanGameRule, gameRuleNBT, onSave); @@ -146,12 +150,18 @@ public BooleanGameRuleInfo(GameRule.BooleanGameRule booleanGameRule, GameRuleNBT buildNodes(); } - public boolean getValue() { - return currentValue.get(); + @Override + public String getCurrentValueText() { + return currentValue.getValue().toString(); + } + + @Override + public String getDefaultValueText() { + return defaultValue == null ? "" : defaultValue.toString(); } - public boolean getDefaultValue() { - return defaultValue; + public BooleanProperty currentValueProperty() { + return currentValue; } public void buildNodes() { @@ -168,21 +178,20 @@ public void buildNodes() { toggleButton.setTitle(getDisplayName()); toggleButton.setSubtitle(getRuleKey()); toggleButton.selectedProperty().bindBidirectional(currentValue); - toggleButton.selectedProperty().addListener((observable, oldValue, newValue) -> { + currentValue.addListener((observable, oldValue, newValue) -> { getGameRuleNBT().changeValue(newValue); getOnSave().run(); - resetButton.setDisable(shouldResetButtonDisabled(newValue)); }); HBox.setHgrow(toggleButton, Priority.ALWAYS); } { wrapperPane.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE); resetButton.setFocusTraversable(false); - resetButton.setDisable(shouldResetButtonDisabled(currentValue.getValue())); resetButton.setGraphic(SVG.RESTORE.createIcon(24)); if (defaultValue != null) { setSetToDefault(() -> toggleButton.selectedProperty().set(defaultValue)); resetButton.setOnAction(event -> getSetToDefault().run()); + resetButton.disableProperty().bind(Bindings.createBooleanBinding(() -> currentValue.getValue() == defaultValue, currentValue)); setIsDefault(() -> toggleButton.isSelected() == defaultValue); FXUtils.installFastTooltip(resetButton, i18n("gamerule.restore_default_values.tooltip", defaultValue)); FXUtils.installFastTooltip(wrapperPane, i18n("gamerule.now_is_default_values.tooltip")); @@ -194,37 +203,38 @@ public void buildNodes() { getContainer().getChildren().addAll(toggleButton, wrapperPane); } - - public boolean shouldResetButtonDisabled(boolean newValue) { - return defaultValue == null || newValue == defaultValue; - } - } static final class IntGameRuleInfo extends GameRuleInfo { - StringProperty currentValue; - int minValue; - int maxValue; - Integer defaultValue; + private final StringProperty currentValue; + private final Integer defaultValue; + private final int minValue; + private final int maxValue; public IntGameRuleInfo(GameRule.IntGameRule intGameRule, GameRuleNBT gameRuleNBT, Runnable onSave, GameVersionNumber gameVersionNumber) { super(intGameRule, gameRuleNBT, onSave); currentValue = new SimpleStringProperty(String.valueOf(intGameRule.getValue())); + defaultValue = intGameRule.getDefaultValue(gameVersionNumber).orElse(null); minValue = intGameRule.getMinValue(gameVersionNumber); maxValue = intGameRule.getMaxValue(gameVersionNumber); - defaultValue = intGameRule.getDefaultValue(gameVersionNumber).orElse(null); buildNodes(); } - public String getValue() { - return currentValue.get(); + @Override + public String getCurrentValueText() { + return currentValue.getValue(); } - public String getDefaultValue() { + @Override + public String getDefaultValueText() { return defaultValue == null ? null : defaultValue.toString(); } + public StringProperty currentValueProperty() { + return currentValue; + } + public void buildNodes() { { getContainer().setPadding(new Insets(8, 8, 8, 16)); @@ -254,12 +264,11 @@ public void buildNodes() { textField.setValidators( new NumberValidator(i18n("input.integer"), false), new NumberRangeValidator(i18n("input.number_range", minValue, maxValue), minValue, maxValue)); - textField.textProperty().addListener((observable, oldValue, newValue) -> { + currentValue.addListener((observable, oldValue, newValue) -> { Integer value = Lang.toIntOrNull(newValue); if (value != null && value >= minValue && value <= maxValue) { getGameRuleNBT().changeValue(newValue); getOnSave().run(); - resetButton.setDisable(shouldResetButtonDisabled(value.toString())); } }); @@ -269,7 +278,6 @@ public void buildNodes() { { wrapperPane.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE); resetButton.setFocusTraversable(false); - resetButton.setDisable(shouldResetButtonDisabled(currentValue.getValue())); resetButton.setGraphic(SVG.RESTORE.createIcon(24)); if (defaultValue != null) { setSetToDefault(() -> textField.textProperty().set(String.valueOf(defaultValue))); @@ -277,6 +285,7 @@ public void buildNodes() { setIsDefault(() -> textField.textProperty().get().equals(String.valueOf(defaultValue))); FXUtils.installFastTooltip(resetButton, i18n("gamerule.restore_default_values.tooltip", defaultValue)); FXUtils.installFastTooltip(wrapperPane, i18n("gamerule.now_is_default_values.tooltip")); + resetButton.disableProperty().bind(Bindings.createBooleanBinding(() -> Objects.equals(Lang.toIntOrNull(currentValue.getValue()), defaultValue), currentValue)); } else { resetButton.setDisable(true); FXUtils.installFastTooltip(wrapperPane, i18n("gamerule.not_have_default_values.tooltip")); @@ -288,9 +297,5 @@ public void buildNodes() { rightHBox.getChildren().addAll(textField, wrapperPane); getContainer().getChildren().addAll(displayInfoVBox, rightHBox); } - - public boolean shouldResetButtonDisabled(String newValue) { - return defaultValue == null || Objects.equals(Lang.toIntOrNull(newValue), defaultValue); - } } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java index 62fecfa264..9c9c900f54 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java @@ -19,6 +19,7 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.Tag; +import javafx.beans.Observable; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.scene.control.Skin; @@ -54,7 +55,14 @@ public GameRulePage(WorldManagePage worldManagePage) { this.worldManagePage = worldManagePage; this.world = worldManagePage.getWorld(); - gameRuleList = FXCollections.observableArrayList(); + gameRuleList = FXCollections.observableArrayList(gamerule -> { + if (gamerule instanceof GameRuleInfo.BooleanGameRuleInfo booleanGameRuleInfo) { + return new Observable[]{booleanGameRuleInfo.currentValueProperty()}; + } else if (gamerule instanceof GameRuleInfo.IntGameRuleInfo intGameRuleInfo) { + return new Observable[]{intGameRuleInfo.currentValueProperty()}; + } + return new Observable[]{}; + }); setItems(gameRuleList); this.setLoading(true); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java index 36b81922a6..a6c52ff44b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java @@ -31,6 +31,7 @@ import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; import javafx.scene.layout.*; +import javafx.stage.Stage; import javafx.util.Duration; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; @@ -38,8 +39,6 @@ import org.jackhuang.hmcl.ui.construct.*; import org.jackhuang.hmcl.util.StringUtils; -import java.util.List; - import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton2; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; @@ -49,6 +48,7 @@ class GameRulePageSkin extends SkinBase { private final JFXTextField searchField; JFXListView> listView = new JFXListView<>(); private final FilteredList> filteredList; + private final FilteredList> notInDefaultValueList; GameRulePageSkin(GameRulePage skinnable) { super(skinnable); @@ -60,11 +60,13 @@ class GameRulePageSkin extends SkinBase { root.getStyleClass().add("no-padding"); filteredList = new FilteredList<>(skinnable.getItems()); + notInDefaultValueList = new FilteredList<>(skinnable.getItems()); + notInDefaultValueList.setPredicate(gameRuleInfo -> !gameRuleInfo.getIsDefault().getAsBoolean()); { JFXButton resetAllButton = createToolbarButton2(i18n("gamerule.restore_default_values_all"), SVG.RESTORE, () -> { //Controllers.confirm(i18n("gamerule.restore_default_values_all.confirm"), i18n("message.warning"), MessageDialogPane.MessageType.WARNING, skinnable::resettingAllGameRule, null); - Controllers.dialog(new ResetDefaultValuesLayout(skinnable::resettingAllGameRule, skinnable.getItems())); + Controllers.dialog(new ResetDefaultValuesLayout(skinnable::resettingAllGameRule, notInDefaultValueList)); }); searchBar = new HBox(); @@ -115,55 +117,90 @@ protected void updateControl(GameRuleInfo item, boolean empty) { } static class ResetDefaultValuesLayout extends JFXDialogLayout { - public ResetDefaultValuesLayout(Runnable resettingAllGameRule, List> gameRules) { - setHeading(new Label(i18n("message.warning"))); - Label warnLabel = new Label(i18n("gamerule.restore_default_values_all.confirm")); - MenuUpDownButton menuUpDownButton = new MenuUpDownButton(); + public ResetDefaultValuesLayout(Runnable resettingAllGameRule, FilteredList> notInDefaultValueList) { + + { + Stage stage = Controllers.getStage(); + maxWidthProperty().bind(stage.widthProperty().multiply(0.7)); + maxHeightProperty().bind(stage.heightProperty().multiply(0.7)); + } + + setHeading(new Label(i18n("gamerule.restore_default_values_all"))); + + VBox vBox = new VBox(); { - menuUpDownButton.setText("查看具体变更"); - menuUpDownButton.setMaxWidth(USE_PREF_SIZE); + vBox.setSpacing(10); + setBody(vBox); } - ScrollPane scrollPane = new ScrollPane(); - GridPane gridPane = new GridPane(); { - gridPane.setHgap(10); - gridPane.setVgap(10); - scrollPane.setContent(gridPane); - scrollPane.visibleProperty().bind(menuUpDownButton.selectedProperty()); - scrollPane.managedProperty().bind(menuUpDownButton.selectedProperty()); - int index = 1; - gridPane.addRow(0, new Label("名称"), new Label("当前值"), new Label("->"), new Label("默认值")); - for (GameRuleInfo gameRule : gameRules) { - if (!gameRule.getIsDefault().getAsBoolean()) { - String oldValue = ""; - String newValue = ""; - if (gameRule instanceof GameRuleInfo.BooleanGameRuleInfo booleanGameRuleInfo) { - oldValue = String.valueOf(booleanGameRuleInfo.getValue()); - newValue = String.valueOf(booleanGameRuleInfo.getDefaultValue()); - } else if (gameRule instanceof GameRuleInfo.IntGameRuleInfo intGameRuleInfo) { - oldValue = intGameRuleInfo.getValue(); - newValue = intGameRuleInfo.getDefaultValue(); + Label warnLabel = notInDefaultValueList.isEmpty() ? new Label(i18n("gamerule.all_is_default")) : new Label(i18n("gamerule.restore_default_values_all.confirm")); + vBox.getChildren().add(warnLabel); + + if (!notInDefaultValueList.isEmpty()) { + + MenuUpDownButton showDetailButton = new MenuUpDownButton(); + { + showDetailButton.setText(i18n("gamerule.show_modified_details.button")); + showDetailButton.setMaxWidth(USE_PREF_SIZE); + } + + ScrollPane scrollPane = new ScrollPane(); + GridPane gridPane = new GridPane(); + { + gridPane.setHgap(10); + gridPane.setVgap(10); + scrollPane.setContent(gridPane); + scrollPane.visibleProperty().bind(showDetailButton.selectedProperty()); + scrollPane.managedProperty().bind(showDetailButton.selectedProperty()); + FXUtils.smoothScrolling(scrollPane); + + VBox.setMargin(scrollPane, new Insets(5, 10, 5, 10)); + vBox.getChildren().addAll(showDetailButton, scrollPane); + } + { + gridPane.addRow(0, + new Label(i18n("gamerule.column.name")), + new Label(i18n("gamerule.column.current")), + new Label("->"), + new Label(i18n("gamerule.column.default"))); + + for (int i = 0; i < notInDefaultValueList.size(); i++) { + GameRuleInfo gameRuleInfo = notInDefaultValueList.get(i); + String oldValue = gameRuleInfo.getCurrentValueText(); + String newValue = gameRuleInfo.getDefaultValueText(); + gridPane.addRow(i + 1, + new Label(StringUtils.isNotBlank(gameRuleInfo.getDisplayName()) ? gameRuleInfo.getDisplayName() : gameRuleInfo.getRuleKey()), + new Label(oldValue), + new Label("->"), + new Label(newValue)); } - gridPane.addRow(index, new Label(StringUtils.isNotBlank(gameRule.getDisplayName()) ? gameRule.getDisplayName() : gameRule.getRuleKey()), new Label(oldValue), new Label("->"), new Label(newValue)); - index++; + } } - if (index == 1) { - gridPane.addRow(1, new Label("无变更")); - } } - VBox vBox = new VBox(); - vBox.setAlignment(Pos.TOP_LEFT); - vBox.getChildren().addAll(warnLabel, menuUpDownButton, scrollPane); - setBody(vBox); + JFXButton accept = new JFXButton(i18n("button.ok")); + { + accept.getStyleClass().add("dialog-accept"); + if (!notInDefaultValueList.isEmpty()) { + accept.setOnAction(event -> { + resettingAllGameRule.run(); + fireEvent(new DialogCloseEvent()); + }); + } else { + accept.setOnAction(event -> { + fireEvent(new DialogCloseEvent()); + }); + } + } JFXButton reject = new JFXButton(i18n("button.cancel")); - accept.setOnAction(event -> { - resettingAllGameRule.run(); - fireEvent(new DialogCloseEvent()); - }); - reject.setOnAction(event -> fireEvent(new DialogCloseEvent())); + { + reject.setOnAction(event -> fireEvent(new DialogCloseEvent())); + reject.getStyleClass().add("dialog-cancel"); + } setActions(accept, reject); + + FXUtils.onEscPressed(this, reject::fire); } } } diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 39f49bd731..8ccb7f2221 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -687,12 +687,18 @@ game.directory=Game Path game.version=Game Instance gamerule=Game Rule +gamerule.all_is_default=All game rules are currently at default values +gamerule.column.current=Current Value +gamerule.column.default=Default Value +gamerule.column.name=Rule Name gamerule.restore_default_values_all=Reset all to game defaults gamerule.restore_default_values_all.confirm=Are you sure you want to reset all rules to the game's default? This action cannot be undone! gamerule.restore_default_values_all.finish.toast=All game rules have been reset to default values gamerule.restore_default_values.tooltip=Reset this rule to game default.\nDefault: %s gamerule.now_is_default_values.tooltip=Already at default. gamerule.not_have_default_values.tooltip=Default value not found. +gamerule.show_modified_details.button=Display Change List + gamerule.rule.advance_time=Advance Time gamerule.rule.advance_weather=Advance Weather gamerule.rule.allow_fire_ticks_away_from_player=Allow Fire Ticks Away From Player diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index f4d08f7530..4aef1810b9 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -494,12 +494,18 @@ game.directory=遊戲目錄路徑 game.version=遊戲實例 gamerule=遊戲規則 +gamerule.all_is_default=目前所有遊戲規則已處於預設狀態 +gamerule.column.current=目前值 +gamerule.column.default=預設值 +gamerule.column.name=規則名稱 gamerule.restore_default_values_all=復原遊戲預設規則 gamerule.restore_default_values_all.confirm=你確定要復原所有規則至遊戲預設嗎?該操作無法復原! gamerule.restore_default_values_all.finish.toast=已將所有遊戲規則復原至預設值 gamerule.restore_default_values.tooltip=復原此項預設規則\n預設值: %s gamerule.now_is_default_values.tooltip=目前已是預設值 gamerule.not_have_default_values.tooltip=未找到預設值 +gamerule.show_modified_details.button=顯示變更清單 + gamerule.rule.advance_time=日夜交替 gamerule.rule.advance_weather=更新天氣 gamerule.rule.allow_fire_ticks_away_from_player=允許火在遠離玩家處蔓延 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 2dd5a5461d..e365acb463 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -504,12 +504,18 @@ game.directory=游戏文件夹路径 game.version=游戏实例 gamerule=游戏规则 +gamerule.all_is_default=当前所有游戏规则已处于默认状态 +gamerule.column.current=当前值 +gamerule.column.default=默认值 +gamerule.column.name=规则名称 gamerule.restore_default_values_all=恢复游戏默认规则 gamerule.restore_default_values_all.confirm=你确定要恢复所有规则为游戏默认吗?此操作无法撤销! gamerule.restore_default_values_all.finish.toast=已将所有游戏规则恢复至默认值 gamerule.restore_default_values.tooltip=恢复此项默认规则\n默认值: %s gamerule.now_is_default_values.tooltip=现在已是默认值 gamerule.not_have_default_values.tooltip=未找到默认值 +gamerule.show_modified_details.button=显示变动列表 + gamerule.rule.advance_time=游戏内时间流逝 gamerule.rule.advance_weather=天气更替 gamerule.rule.allow_fire_ticks_away_from_player=允许火在远离玩家处蔓延 From 307c7993811be511d60fd0e93b098a50b5665e11 Mon Sep 17 00:00:00 2001 From: mine_ Date: Sat, 27 Dec 2025 22:38:10 +0800 Subject: [PATCH 52/69] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E7=9A=84=E6=B8=B8=E6=88=8F=E8=A7=84=E5=88=99=E6=95=B0?= =?UTF-8?q?=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HMCL/src/main/resources/assets/lang/I18N.properties | 1 + .../src/main/resources/assets/lang/I18N_zh.properties | 1 + .../main/resources/assets/lang/I18N_zh_CN.properties | 1 + .../src/main/resources/assets/gamerule/gamerule.json | 11 +++++++++-- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 8ccb7f2221..f38f452b2f 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -707,6 +707,7 @@ gamerule.rule.block_drops=Block Drops gamerule.rule.block_explosion_drop_decay=Block Explosion Drop Decay gamerule.rule.command_block_output=Command Block Output gamerule.rule.command_blocks_work=Enable Command Blocks +gamerule.rule.disable_elytra_movement_check=Disable Elytra Movement Check gamerule.rule.disable_raids=Disable Raids gamerule.rule.do_fire_tick=Fire Tick gamerule.rule.drowning_damage=Drowning Damage diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 4aef1810b9..260c1a13dd 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -514,6 +514,7 @@ gamerule.rule.block_drops=掉落方塊 gamerule.rule.block_explosion_drop_decay=在與方塊互動的爆炸中,部分方塊不會掉落成戰利品 gamerule.rule.command_block_output=記錄指令方塊輸出 gamerule.rule.command_blocks_work=啟用指令方塊 +gamerule.rule.disable_elytra_movement_check=停用鞘翅移動檢測 gamerule.rule.disable_raids=停用突襲 gamerule.rule.do_fire_tick=更新火焰 gamerule.rule.drowning_damage=造成溺水傷害 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index e365acb463..83174a1ab7 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -524,6 +524,7 @@ gamerule.rule.block_drops=方块掉落 gamerule.rule.block_explosion_drop_decay=在方块交互爆炸中,一些方块不会掉落战利品 gamerule.rule.command_block_output=广播命令方块输出 gamerule.rule.command_blocks_work=启用命令方块 +gamerule.rule.disable_elytra_movement_check=禁用鞘翅移动检测 gamerule.rule.disable_raids=禁用袭击 gamerule.rule.do_fire_tick=火焰蔓延 gamerule.rule.drowning_damage=溺水伤害 diff --git a/HMCLCore/src/main/resources/assets/gamerule/gamerule.json b/HMCLCore/src/main/resources/assets/gamerule/gamerule.json index 9b32af70b5..9f490f6bf8 100644 --- a/HMCLCore/src/main/resources/assets/gamerule/gamerule.json +++ b/HMCLCore/src/main/resources/assets/gamerule/gamerule.json @@ -98,13 +98,20 @@ }, { "ruleKey": [ - "minecraft:elytra_movement_check", - "disableElytraMovementCheck" + "minecraft:elytra_movement_check" ], "type": "boolean", "displayI18nKey": "gamerule.rule.elytra_movement_check", "defaultValue": true }, + { + "ruleKey": [ + "disableElytraMovementCheck" + ], + "type": "boolean", + "displayI18nKey": "gamerule.rule.disable_elytra_movement_check", + "defaultValue": false + }, { "ruleKey": [ "minecraft:ender_pearls_vanish_on_death", From 3b7e459a31ceef5601b119c834935cf2a97fd6ce Mon Sep 17 00:00:00 2001 From: mine_ Date: Mon, 29 Dec 2025 13:20:15 +0800 Subject: [PATCH 53/69] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/jackhuang/hmcl/gamerule/GameRule.java | 8 +- .../hmcl/gamerule/GameRuleNBTTest.java | 77 +++++++++++ .../jackhuang/hmcl/gamerule/GameRuleTest.java | 127 ++++++++++++++++++ .../src/test/resources/gamerule/gamerule.json | 46 +++++++ 4 files changed, 255 insertions(+), 3 deletions(-) create mode 100644 HMCLCore/src/test/java/org/jackhuang/hmcl/gamerule/GameRuleNBTTest.java create mode 100644 HMCLCore/src/test/java/org/jackhuang/hmcl/gamerule/GameRuleTest.java create mode 100644 HMCLCore/src/test/resources/gamerule/gamerule.json diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java index 4db8277f2c..ee25adbeff 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java @@ -57,11 +57,11 @@ protected GameRule(List ruleKey, String displayI18nKey) { this.displayI18nKey = displayI18nKey; } - public static GameRule createSimpleGameRule(String ruleKey, boolean value) { + private static GameRule createSimpleGameRule(String ruleKey, boolean value) { return new BooleanGameRule(Collections.singletonList(ruleKey), value); } - public static GameRule createSimpleGameRule(String ruleKey, int value) { + private static GameRule createSimpleGameRule(String ruleKey, int value) { IntGameRule intGameRule = new IntGameRule(Collections.singletonList(ruleKey), value); intGameRule.addMaxValue(Integer.MAX_VALUE); intGameRule.addMinValue(Integer.MIN_VALUE); @@ -77,7 +77,7 @@ public static GameRule createSimpleGameRule(String ruleKey, int value) { /// /// @param tag The NBT tag to parse. /// @return An Optional containing the GameRule if parsing was successful. - private static Optional createSimpleRuleFromTag(Tag tag) { + public static Optional createSimpleRuleFromTag(Tag tag) { String name = tag.getName(); if (tag instanceof IntTag intTag) { @@ -349,6 +349,8 @@ static final class GameRuleHolder { gameRules = JsonUtils.fromNonNullJson(jsonContent, JsonUtils.listTypeOf(GameRule.class)); } catch (IOException e) { throw new RuntimeException("Failed to initialize GameRuleHolder", e); + } catch (JsonParseException e) { + throw new RuntimeException("Failed to parse GameRuleHolder", e); } for (GameRule gameRule : gameRules) { for (String s : gameRule.ruleKey) { diff --git a/HMCLCore/src/test/java/org/jackhuang/hmcl/gamerule/GameRuleNBTTest.java b/HMCLCore/src/test/java/org/jackhuang/hmcl/gamerule/GameRuleNBTTest.java new file mode 100644 index 0000000000..73ade76b76 --- /dev/null +++ b/HMCLCore/src/test/java/org/jackhuang/hmcl/gamerule/GameRuleNBTTest.java @@ -0,0 +1,77 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2025 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.gamerule; + +import com.github.steveice10.opennbt.tag.builtin.ByteTag; +import com.github.steveice10.opennbt.tag.builtin.IntTag; +import com.github.steveice10.opennbt.tag.builtin.StringTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; +import org.junit.jupiter.api.Test; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; + +public class GameRuleNBTTest { + @Test + public void testByteTag() { + ByteTag tag = new ByteTag("byte_tag", (byte) 1); + GameRuleNBT gameRuleNBT = GameRule.createGameRuleNBT(tag).orElseThrow(() -> new AssertionError("Expected GameRuleNBT to be created for ByteTag")); + + GameRuleNBT.ByteGameRuleNBT byteGameRuleNBT = assertInstanceOf(GameRuleNBT.ByteGameRuleNBT.class, gameRuleNBT); + byteGameRuleNBT.changeValue(false); + assertEquals((byte) 0, tag.getValue()); + } + + @Test + public void testStringByteTag() { + StringTag tag = new StringTag("string_byte_tag", "true"); + GameRuleNBT gameRuleNBT = GameRule.createGameRuleNBT(tag).orElseThrow(() -> new AssertionError("Expected GameRuleNBT to be created for StringedByteTag")); + + GameRuleNBT.StringByteGameRuleNBT stringedByteGameRuleNBT = assertInstanceOf(GameRuleNBT.StringByteGameRuleNBT.class, gameRuleNBT); + stringedByteGameRuleNBT.changeValue(false); + assertEquals("false", tag.getValue()); + } + + @Test + public void testIntTag() { + IntTag tag = new IntTag("int_tag", 1); + GameRuleNBT gameRuleNBT = GameRule.createGameRuleNBT(tag).orElseThrow(() -> new AssertionError("Expected GameRuleNBT to be created for IntTag")); + + GameRuleNBT.IntGameRuleNBT intGameRuleNBT = assertInstanceOf(GameRuleNBT.IntGameRuleNBT.class, gameRuleNBT); + intGameRuleNBT.changeValue("2"); + assertEquals(2, tag.getValue()); + } + + @Test + public void testStringIntTag() { + StringTag tag = new StringTag("string_int_tag", "1"); + GameRuleNBT gameRuleNBT = GameRule.createGameRuleNBT(tag).orElseThrow(() -> new AssertionError("Expected GameRuleNBT to be created for StringedIntTag")); + + GameRuleNBT.StringIntGameRuleNBT stringIntGameRuleNBT = assertInstanceOf(GameRuleNBT.StringIntGameRuleNBT.class, gameRuleNBT); + stringIntGameRuleNBT.changeValue("2"); + assertEquals("2", tag.getValue()); + } + + @Test + public void testWrongTag() { + StringTag tag = new StringTag("wrong_tag", "abc"); + Optional> gameRuleNBT = GameRule.createGameRuleNBT(tag); + assertTrue(gameRuleNBT.isEmpty()); + } +} diff --git a/HMCLCore/src/test/java/org/jackhuang/hmcl/gamerule/GameRuleTest.java b/HMCLCore/src/test/java/org/jackhuang/hmcl/gamerule/GameRuleTest.java new file mode 100644 index 0000000000..e4767b3c8b --- /dev/null +++ b/HMCLCore/src/test/java/org/jackhuang/hmcl/gamerule/GameRuleTest.java @@ -0,0 +1,127 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2025 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.gamerule; + +import com.google.gson.JsonParseException; +import org.jackhuang.hmcl.util.gson.JsonUtils; +import org.jackhuang.hmcl.util.versioning.GameVersionNumber; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +public class GameRuleTest { + + @Test + public void testParseMataData() { + Map metaDataGameRuleMap = new HashMap<>(); + + List gameRules; + try (InputStream is = GameRule.class.getResourceAsStream("/gamerule/gamerule.json")) { + if (is == null) { + throw new IOException("Resource not found: /gamerule/gamerule.json"); + } + String jsonContent = new String(is.readAllBytes(), java.nio.charset.StandardCharsets.UTF_8); + gameRules = JsonUtils.fromNonNullJson(jsonContent, JsonUtils.listTypeOf(GameRule.class)); + } catch (IOException | JsonParseException e) { + throw new RuntimeException("Failed to parse GameRuleJson", e); + } + for (GameRule gameRule : gameRules) { + for (String s : gameRule.getRuleKey()) { + metaDataGameRuleMap.put(s, gameRule); + } + } + + assertEquals(4, gameRules.size()); + assertEquals(7, metaDataGameRuleMap.size()); + + } + + public void assertParseSingleIntGameRule(String jsonContent, List gameRuleKeys, Map defaultValueMap, Map minValueMap, Map maxValueMap) { + GameRule gameRules = JsonUtils.fromNonNullJson(jsonContent, GameRule.class); + GameRule.IntGameRule intGameRule = assertInstanceOf(GameRule.IntGameRule.class, gameRules); + assertEquals(intGameRule.getRuleKey(), gameRuleKeys); + defaultValueMap.forEach((key, value) -> { + assertEquals(value, intGameRule.getDefaultValue(GameVersionNumber.asGameVersion(key)).orElseThrow(() -> new AssertionError("cannot get default value for defaultValue"))); + }); + minValueMap.forEach((key, value) -> { + assertEquals(value, intGameRule.getMinValue(GameVersionNumber.asGameVersion(key))); + }); + maxValueMap.forEach((key, value) -> { + assertEquals(value, intGameRule.getMaxValue(GameVersionNumber.asGameVersion(key))); + }); + } + + public void assertParseSingleBooleanRule(String jsonContent, List gameRuleKeys, Map defaultValueMap) { + GameRule gameRules = JsonUtils.fromNonNullJson(jsonContent, GameRule.class); + GameRule.BooleanGameRule booleanGameRule = assertInstanceOf(GameRule.BooleanGameRule.class, gameRules); + defaultValueMap.forEach((key, value) -> { + assertEquals(value, booleanGameRule.getDefaultValue(GameVersionNumber.asGameVersion(key)).orElseThrow(() -> new AssertionError("cannot get default value for defaultValue"))); + }); + } + + @Test + public void testParseGameRule() { + assertParseSingleIntGameRule( + """ + { + "ruleKey": [ + "minecraft:max_snow_accumulation_height", + "snowAccumulationHeight" + ], + "type": "int", + "displayI18nKey": "gamerule.rule.max_snow_accumulation_height", + "defaultValue": 1, + "minValue": { + "22w44a": "INT_MIN", + "25w44a": 0 + }, + "maxValue": { + "22w44a": "INT_MAX", + "25w44a": 8 + } + } + """, + List.of("minecraft:max_snow_accumulation_height", "snowAccumulationHeight"), + Map.of("25w45a", 1), + Map.of("23w44a", Integer.MIN_VALUE, "25w45a", 0), + Map.of("23w44a", Integer.MAX_VALUE, "25w45a", 8) + ); + assertParseSingleBooleanRule( + """ + { + "ruleKey": [ + "minecraft:reduced_debug_info", + "reducedDebugInfo" + ], + "type": "boolean", + "displayI18nKey": "gamerule.rule.reduced_debug_info", + "defaultValue": false + } + """, + List.of("minecraft:reduced_debug_info", "reducedDebugInfo"), + Map.of("25w45a", false) + ); + } +} diff --git a/HMCLCore/src/test/resources/gamerule/gamerule.json b/HMCLCore/src/test/resources/gamerule/gamerule.json new file mode 100644 index 0000000000..1734faae1d --- /dev/null +++ b/HMCLCore/src/test/resources/gamerule/gamerule.json @@ -0,0 +1,46 @@ +[ + { + "ruleKey": [ + "minecraft:advance_weather", + "doWeatherCycle" + ], + "type": "boolean", + "displayI18nKey": "gamerule.rule.advance_weather", + "defaultValue": true + }, + { + "ruleKey": [ + "minecraft:fire_spread_radius_around_player" + ], + "type": "int", + "displayI18nKey": "gamerule.rule.fire_spread_radius_around_player", + "defaultValue": 128, + "minValue": -1 + }, + { + "ruleKey": [ + "minecraft:max_snow_accumulation_height", + "snowAccumulationHeight" + ], + "type": "int", + "displayI18nKey": "gamerule.rule.max_snow_accumulation_height", + "defaultValue": 1, + "minValue": { + "22w44a": "INT_MIN", + "25w44a": 0 + }, + "maxValue": { + "22w44a": "INT_MAX", + "25w44a": 8 + } + }, + { + "ruleKey": [ + "minecraft:reduced_debug_info", + "reducedDebugInfo" + ], + "type": "boolean", + "displayI18nKey": "gamerule.rule.reduced_debug_info", + "defaultValue": false + } +] \ No newline at end of file From b6f0cdc6f9e04d6c75414e281599fda515c5b437 Mon Sep 17 00:00:00 2001 From: mine_ Date: Mon, 29 Dec 2025 16:36:00 +0800 Subject: [PATCH 54/69] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E8=B5=84?= =?UTF-8?q?=E6=BA=90=E5=8A=A0=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/jackhuang/hmcl/gamerule/GameRule.java | 27 +++++++++--------- .../jackhuang/hmcl/gamerule/GameRuleTest.java | 28 ++++++++----------- 2 files changed, 26 insertions(+), 29 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java index ee25adbeff..3e3436c3ff 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java @@ -26,6 +26,7 @@ import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.gson.JsonSerializable; import org.jackhuang.hmcl.util.gson.JsonUtils; +import org.jackhuang.hmcl.util.io.IOUtils; import org.jackhuang.hmcl.util.versioning.GameVersionNumber; import org.jackhuang.hmcl.util.versioning.VersionedValue; @@ -34,6 +35,8 @@ import java.lang.reflect.Type; import java.util.*; +import static org.jackhuang.hmcl.util.logging.Logger.LOG; + /// Represents an abstract game rule in Minecraft (e.g., `doDaylightCycle`, `randomTickSpeed`). /// /// This class handles the logic for: @@ -340,22 +343,20 @@ static final class GameRuleHolder { private static final Map metaDataGameRuleMap = new HashMap<>(); static { - List gameRules; - try (InputStream is = GameRule.class.getResourceAsStream("/assets/gamerule/gamerule.json")) { - if (is == null) { - throw new IOException("Resource not found: /assets/gamerule/gamerule.json"); + try { + InputStream is = GameRule.class.getResourceAsStream("/assets/gamerule/gamerule.json"); + String jsonContent = IOUtils.readFullyAsString(is); + List gameRules = JsonUtils.fromNonNullJson(jsonContent, JsonUtils.listTypeOf(GameRule.class)); + + for (GameRule gameRule : gameRules) { + for (String s : gameRule.ruleKey) { + metaDataGameRuleMap.put(s, gameRule); + } } - String jsonContent = new String(is.readAllBytes(), java.nio.charset.StandardCharsets.UTF_8); - gameRules = JsonUtils.fromNonNullJson(jsonContent, JsonUtils.listTypeOf(GameRule.class)); } catch (IOException e) { - throw new RuntimeException("Failed to initialize GameRuleHolder", e); + LOG.warning("Cannot read gamerule.json" + e.getMessage()); } catch (JsonParseException e) { - throw new RuntimeException("Failed to parse GameRuleHolder", e); - } - for (GameRule gameRule : gameRules) { - for (String s : gameRule.ruleKey) { - metaDataGameRuleMap.put(s, gameRule); - } + LOG.warning("Cannot parse gamerule.json" + e.getMessage()); } } diff --git a/HMCLCore/src/test/java/org/jackhuang/hmcl/gamerule/GameRuleTest.java b/HMCLCore/src/test/java/org/jackhuang/hmcl/gamerule/GameRuleTest.java index e4767b3c8b..afe7e914b5 100644 --- a/HMCLCore/src/test/java/org/jackhuang/hmcl/gamerule/GameRuleTest.java +++ b/HMCLCore/src/test/java/org/jackhuang/hmcl/gamerule/GameRuleTest.java @@ -17,8 +17,8 @@ */ package org.jackhuang.hmcl.gamerule; -import com.google.gson.JsonParseException; import org.jackhuang.hmcl.util.gson.JsonUtils; +import org.jackhuang.hmcl.util.io.IOUtils; import org.jackhuang.hmcl.util.versioning.GameVersionNumber; import org.junit.jupiter.api.Test; @@ -28,33 +28,29 @@ import java.util.List; import java.util.Map; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.*; public class GameRuleTest { + private String getMataDataJson() throws IOException { + InputStream is = GameRule.class.getResourceAsStream("/assets/gamerule/gamerule.json"); + return IOUtils.readFullyAsString(is); + } + @Test - public void testParseMataData() { + public void testParseMataData() throws IOException { Map metaDataGameRuleMap = new HashMap<>(); - List gameRules; - try (InputStream is = GameRule.class.getResourceAsStream("/gamerule/gamerule.json")) { - if (is == null) { - throw new IOException("Resource not found: /gamerule/gamerule.json"); - } - String jsonContent = new String(is.readAllBytes(), java.nio.charset.StandardCharsets.UTF_8); - gameRules = JsonUtils.fromNonNullJson(jsonContent, JsonUtils.listTypeOf(GameRule.class)); - } catch (IOException | JsonParseException e) { - throw new RuntimeException("Failed to parse GameRuleJson", e); - } + String jsonContent = getMataDataJson(); + List gameRules = JsonUtils.fromNonNullJson(jsonContent, JsonUtils.listTypeOf(GameRule.class)); + for (GameRule gameRule : gameRules) { for (String s : gameRule.getRuleKey()) { metaDataGameRuleMap.put(s, gameRule); } } - assertEquals(4, gameRules.size()); - assertEquals(7, metaDataGameRuleMap.size()); + assertFalse(gameRules.isEmpty()); } From 41cbef8717d4998f3f9b20ddd52a277d05cb84c9 Mon Sep 17 00:00:00 2001 From: mine_ Date: Mon, 29 Dec 2025 16:39:42 +0800 Subject: [PATCH 55/69] =?UTF-8?q?feat:=20=E5=88=A0=E9=99=A4=E4=B8=8D?= =?UTF-8?q?=E5=86=8D=E4=BD=BF=E7=94=A8=E7=9A=84=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/test/resources/gamerule/gamerule.json | 46 ------------------- 1 file changed, 46 deletions(-) delete mode 100644 HMCLCore/src/test/resources/gamerule/gamerule.json diff --git a/HMCLCore/src/test/resources/gamerule/gamerule.json b/HMCLCore/src/test/resources/gamerule/gamerule.json deleted file mode 100644 index 1734faae1d..0000000000 --- a/HMCLCore/src/test/resources/gamerule/gamerule.json +++ /dev/null @@ -1,46 +0,0 @@ -[ - { - "ruleKey": [ - "minecraft:advance_weather", - "doWeatherCycle" - ], - "type": "boolean", - "displayI18nKey": "gamerule.rule.advance_weather", - "defaultValue": true - }, - { - "ruleKey": [ - "minecraft:fire_spread_radius_around_player" - ], - "type": "int", - "displayI18nKey": "gamerule.rule.fire_spread_radius_around_player", - "defaultValue": 128, - "minValue": -1 - }, - { - "ruleKey": [ - "minecraft:max_snow_accumulation_height", - "snowAccumulationHeight" - ], - "type": "int", - "displayI18nKey": "gamerule.rule.max_snow_accumulation_height", - "defaultValue": 1, - "minValue": { - "22w44a": "INT_MIN", - "25w44a": 0 - }, - "maxValue": { - "22w44a": "INT_MAX", - "25w44a": 8 - } - }, - { - "ruleKey": [ - "minecraft:reduced_debug_info", - "reducedDebugInfo" - ], - "type": "boolean", - "displayI18nKey": "gamerule.rule.reduced_debug_info", - "defaultValue": false - } -] \ No newline at end of file From ef7fd30cac6fab8bb7bf5fe6ee0bc797ead12fa0 Mon Sep 17 00:00:00 2001 From: mine_ Date: Mon, 29 Dec 2025 20:02:15 +0800 Subject: [PATCH 56/69] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/GameRuleInfo.java | 97 +++++------- .../hmcl/ui/versions/GameRulePage.java | 37 ++--- .../hmcl/ui/versions/GameRulePageSkin.java | 143 +++++++++--------- .../org/jackhuang/hmcl/gamerule/GameRule.java | 28 ++-- .../hmcl/util/versioning/VersionedValue.java | 22 ++- 5 files changed, 159 insertions(+), 168 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java index eba878f934..b71550b980 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java @@ -41,26 +41,25 @@ import org.jackhuang.hmcl.util.versioning.GameVersionNumber; import java.util.Objects; -import java.util.function.BooleanSupplier; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.logging.Logger.LOG; public sealed abstract class GameRuleInfo permits GameRuleInfo.BooleanGameRuleInfo, GameRuleInfo.IntGameRuleInfo { - private String ruleKey; - private String displayName; - private GameRuleNBT gameRuleNBT; - private Runnable onSave; + private final String ruleKey; + private final String displayName; + private final GameRuleNBT gameRuleNBT; - private Runnable setToDefault = () -> { + private final Runnable onSave; + private Runnable resetValue = () -> { }; - private BooleanSupplier isDefault = () -> true; + private final BooleanProperty modified = new SimpleBooleanProperty(this, "modified1", false); //Due to the significant difference in skin between BooleanGameRuleInfo and IntGameRuleInfo, which are essentially two completely different styles, it is not suitable to update each other in Cell#updateControl. Therefore, they are directly integrated into the info. - private HBox container = new HBox(); + private final HBox container = new HBox(); private GameRuleInfo(GameRule gameRule, GameRuleNBT gameRuleNBT, Runnable onSave) { - setRuleKey(gameRule.getRuleKey().get(0)); + ruleKey = gameRule.getRuleKey().get(0); String displayName = ""; try { if (StringUtils.isNotBlank(gameRule.getDisplayI18nKey())) { @@ -69,9 +68,9 @@ private GameRuleInfo(GameRule gameRule, GameRuleNBT gameRuleNB } catch (Exception e) { LOG.warning("Failed to get i18n text for key: " + gameRule.getDisplayI18nKey(), e); } - setDisplayName(displayName); - setGameRuleNBT(gameRuleNBT); - setOnSave(onSave); + this.displayName = displayName; + this.gameRuleNBT = gameRuleNBT; + this.onSave = onSave; } public abstract String getCurrentValueText(); @@ -79,37 +78,25 @@ private GameRuleInfo(GameRule gameRule, GameRuleNBT gameRuleNB public abstract String getDefaultValueText(); public void resetValue() { - setToDefault.run(); - } - - public String getDisplayName() { - return displayName; + resetValue.run(); } - public void setDisplayName(String displayName) { - this.displayName = displayName; + public void save() { + onSave.run(); } public String getRuleKey() { return ruleKey; } - public void setRuleKey(String ruleKey) { - this.ruleKey = ruleKey; + public String getDisplayName() { + return displayName; } public GameRuleNBT getGameRuleNBT() { return gameRuleNBT; } - public void setGameRuleNBT(GameRuleNBT gameRuleNBT) { - this.gameRuleNBT = gameRuleNBT; - } - - public void setOnSave(Runnable onSave) { - this.onSave = onSave; - } - public Runnable getOnSave() { return onSave; } @@ -118,24 +105,20 @@ public HBox getContainer() { return container; } - public void setContainer(HBox container) { - this.container = container; - } - - public Runnable getSetToDefault() { - return setToDefault; + public Runnable getResetValue() { + return resetValue; } - public void setSetToDefault(Runnable setToDefault) { - this.setToDefault = setToDefault; + public void setResetValue(Runnable resetValue) { + this.resetValue = resetValue; } - public BooleanSupplier getIsDefault() { - return isDefault; + public BooleanProperty modifiedProperty() { + return modified; } - public void setIsDefault(BooleanSupplier isDefault) { - this.isDefault = isDefault; + public boolean getModified() { + return modified.get(); } static final class BooleanGameRuleInfo extends GameRuleInfo { @@ -171,28 +154,29 @@ public void buildNodes() { getContainer().setPadding(new Insets(0, 8, 0, 0)); } - JFXButton resetButton = new JFXButton(); - StackPane wrapperPane = new StackPane(resetButton); OptionToggleButton toggleButton = new OptionToggleButton(); { toggleButton.setTitle(getDisplayName()); toggleButton.setSubtitle(getRuleKey()); + HBox.setHgrow(toggleButton, Priority.ALWAYS); toggleButton.selectedProperty().bindBidirectional(currentValue); currentValue.addListener((observable, oldValue, newValue) -> { getGameRuleNBT().changeValue(newValue); - getOnSave().run(); + save(); }); - HBox.setHgrow(toggleButton, Priority.ALWAYS); } + + JFXButton resetButton = new JFXButton(); + StackPane wrapperPane = new StackPane(resetButton); { wrapperPane.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE); resetButton.setFocusTraversable(false); resetButton.setGraphic(SVG.RESTORE.createIcon(24)); if (defaultValue != null) { - setSetToDefault(() -> toggleButton.selectedProperty().set(defaultValue)); - resetButton.setOnAction(event -> getSetToDefault().run()); - resetButton.disableProperty().bind(Bindings.createBooleanBinding(() -> currentValue.getValue() == defaultValue, currentValue)); - setIsDefault(() -> toggleButton.isSelected() == defaultValue); + setResetValue(() -> currentValue.set(defaultValue)); + resetButton.setOnAction(event -> resetValue()); + modifiedProperty().bind(Bindings.createBooleanBinding(() -> currentValue.getValue() != defaultValue, currentValue)); + resetButton.disableProperty().bind(modifiedProperty().not()); FXUtils.installFastTooltip(resetButton, i18n("gamerule.restore_default_values.tooltip", defaultValue)); FXUtils.installFastTooltip(wrapperPane, i18n("gamerule.now_is_default_values.tooltip")); } else { @@ -255,8 +239,6 @@ public void buildNodes() { rightHBox.setAlignment(Pos.CENTER_LEFT); } - JFXButton resetButton = new JFXButton(); - StackPane wrapperPane = new StackPane(resetButton); JFXTextField textField = new JFXTextField(); { textField.textProperty().bindBidirectional(currentValue); @@ -268,24 +250,27 @@ public void buildNodes() { Integer value = Lang.toIntOrNull(newValue); if (value != null && value >= minValue && value <= maxValue) { getGameRuleNBT().changeValue(newValue); - getOnSave().run(); + save(); } }); textField.maxWidth(10); textField.minWidth(10); } + + JFXButton resetButton = new JFXButton(); + StackPane wrapperPane = new StackPane(resetButton); { wrapperPane.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE); resetButton.setFocusTraversable(false); resetButton.setGraphic(SVG.RESTORE.createIcon(24)); if (defaultValue != null) { - setSetToDefault(() -> textField.textProperty().set(String.valueOf(defaultValue))); - resetButton.setOnAction(event -> getSetToDefault().run()); - setIsDefault(() -> textField.textProperty().get().equals(String.valueOf(defaultValue))); + setResetValue(() -> currentValue.set(String.valueOf(defaultValue))); + resetButton.setOnAction(event -> resetValue()); + modifiedProperty().bind(Bindings.createBooleanBinding(() -> !Objects.equals(Lang.toIntOrNull(currentValue.getValue()), defaultValue), currentValue)); + resetButton.disableProperty().bind(modifiedProperty().not()); FXUtils.installFastTooltip(resetButton, i18n("gamerule.restore_default_values.tooltip", defaultValue)); FXUtils.installFastTooltip(wrapperPane, i18n("gamerule.now_is_default_values.tooltip")); - resetButton.disableProperty().bind(Bindings.createBooleanBinding(() -> Objects.equals(Lang.toIntOrNull(currentValue.getValue()), defaultValue), currentValue)); } else { resetButton.setDisable(true); FXUtils.installFastTooltip(wrapperPane, i18n("gamerule.not_have_default_values.tooltip")); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java index 9c9c900f54..11e483c31b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java @@ -49,7 +49,7 @@ public class GameRulePage extends ListPageBase> { private CompoundTag levelDat; ObservableList> gameRuleList; - private boolean isResettingAll = false; + private boolean batchUpdating = false; public GameRulePage(WorldManagePage worldManagePage) { this.worldManagePage = worldManagePage; @@ -81,8 +81,7 @@ public GameRulePage(WorldManagePage worldManagePage) { public void updateControls() { CompoundTag dataTag = levelDat.get("Data"); - CompoundTag gameRuleCompoundTag; - gameRuleCompoundTag = dataTag.get("game_rules"); + CompoundTag gameRuleCompoundTag = dataTag.get("game_rules"); if (gameRuleCompoundTag == null) { gameRuleCompoundTag = dataTag.get("GameRules"); } @@ -96,15 +95,11 @@ public void updateControls() { GameRule.createGameRuleNBT(gameRuleTag).ifPresent(gameRuleNBT -> { GameRule.getFullGameRule(gameRuleTag).ifPresent(gameRule -> { if (gameRule instanceof GameRule.IntGameRule intGameRule) { - @SuppressWarnings("unchecked") - GameRuleNBT typedGameRuleNBT = (GameRuleNBT) gameRuleNBT; - - gameRuleList.add(new GameRuleInfo.IntGameRuleInfo(intGameRule, typedGameRuleNBT, this::saveLevelDatIfNotResettingAll, world.getGameVersion())); + @SuppressWarnings("unchecked") GameRuleNBT typedGameRuleNBT = (GameRuleNBT) gameRuleNBT; + gameRuleList.add(new GameRuleInfo.IntGameRuleInfo(intGameRule, typedGameRuleNBT, this::saveLevelDatIfNotInBatchUpdating, world.getGameVersion())); } else if (gameRule instanceof GameRule.BooleanGameRule booleanGameRule) { - @SuppressWarnings("unchecked") - GameRuleNBT typedGameRuleNBT = (GameRuleNBT) gameRuleNBT; - - gameRuleList.add(new GameRuleInfo.BooleanGameRuleInfo(booleanGameRule, typedGameRuleNBT, this::saveLevelDatIfNotResettingAll, world.getGameVersion())); + @SuppressWarnings("unchecked") GameRuleNBT typedGameRuleNBT = (GameRuleNBT) gameRuleNBT; + gameRuleList.add(new GameRuleInfo.BooleanGameRuleInfo(booleanGameRule, typedGameRuleNBT, this::saveLevelDatIfNotInBatchUpdating, world.getGameVersion())); } }); }); @@ -116,12 +111,12 @@ protected Skin createDefaultSkin() { return new GameRulePageSkin(this); } - public boolean isResettingAll() { - return isResettingAll; + public boolean isBatchUpdating() { + return batchUpdating; } - public void setIsResettingAll(boolean isResettingAll) { - this.isResettingAll = isResettingAll; + public void setBatchUpdating(boolean isResettingAll) { + this.batchUpdating = isResettingAll; } private CompoundTag loadWorldInfo() throws IOException { @@ -141,24 +136,24 @@ void saveLevelDat() { })).start(); } - void saveLevelDatIfNotResettingAll() { - if (!isResettingAll) { + void saveLevelDatIfNotInBatchUpdating() { + if (!batchUpdating) { saveLevelDat(); } } void resettingAllGameRule() { - isResettingAll = true; + batchUpdating = true; for (GameRuleInfo gameRuleInfo : getItems()) { gameRuleInfo.resetValue(); } saveLevelDat(); - isResettingAll = false; + batchUpdating = false; Controllers.showToast(i18n("gamerule.restore_default_values_all.finish.toast")); } @NotNull Predicate> updateSearchPredicate(String queryString) { - if (queryString.isBlank()) { + if (StringUtils.isBlank(queryString)) { return gameRuleInfo -> true; } @@ -168,7 +163,7 @@ void resettingAllGameRule() { Pattern pattern = Pattern.compile(StringUtils.substringAfter(queryString, "regex:")); stringPredicate = s -> s != null && pattern.matcher(s).find(); } catch (Exception e) { - return dataPack -> false; + return gameRuleInfo -> false; } } else { String lowerCaseFilter = queryString.toLowerCase(Locale.ROOT); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java index a6c52ff44b..2df903a44f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java @@ -44,63 +44,64 @@ class GameRulePageSkin extends SkinBase { - private final HBox searchBar; + private final HBox toolBar; private final JFXTextField searchField; JFXListView> listView = new JFXListView<>(); - private final FilteredList> filteredList; - private final FilteredList> notInDefaultValueList; + private final FilteredList> displayedItems; + private final FilteredList> modifiedItems; GameRulePageSkin(GameRulePage skinnable) { super(skinnable); - StackPane pane = new StackPane(); - pane.setPadding(new Insets(10)); - pane.getStyleClass().addAll("notice-pane"); - ComponentList root = new ComponentList(); - root.getStyleClass().add("no-padding"); + StackPane pane = new StackPane(root); + { + pane.setPadding(new Insets(10)); + pane.getStyleClass().addAll("notice-pane"); + root.getStyleClass().add("no-padding"); - filteredList = new FilteredList<>(skinnable.getItems()); - notInDefaultValueList = new FilteredList<>(skinnable.getItems()); - notInDefaultValueList.setPredicate(gameRuleInfo -> !gameRuleInfo.getIsDefault().getAsBoolean()); + getChildren().add(pane); + } + + displayedItems = new FilteredList<>(skinnable.getItems()); + modifiedItems = new FilteredList<>(skinnable.getItems(), GameRuleInfo::getModified); { - JFXButton resetAllButton = createToolbarButton2(i18n("gamerule.restore_default_values_all"), SVG.RESTORE, () -> { - //Controllers.confirm(i18n("gamerule.restore_default_values_all.confirm"), i18n("message.warning"), MessageDialogPane.MessageType.WARNING, skinnable::resettingAllGameRule, null); - Controllers.dialog(new ResetDefaultValuesLayout(skinnable::resettingAllGameRule, notInDefaultValueList)); - }); - - searchBar = new HBox(); - searchBar.setAlignment(Pos.CENTER); - searchBar.setPadding(new Insets(0, 5, 0, 5)); + toolBar = new HBox(); + toolBar.setAlignment(Pos.CENTER); + toolBar.setPadding(new Insets(0, 5, 0, 5)); + + JFXButton resetAllButton = createToolbarButton2(i18n("gamerule.restore_default_values_all"), SVG.RESTORE, + () -> Controllers.dialog(new ResetDefaultValuesLayout(skinnable::resettingAllGameRule, modifiedItems))); + searchField = new JFXTextField(); - searchField.setPromptText(i18n("search")); - PauseTransition pause = new PauseTransition(Duration.millis(100)); - pause.setOnFinished(event -> filteredList.setPredicate(skinnable.updateSearchPredicate(searchField.getText()))); - searchField.textProperty().addListener((observable) -> { - pause.setRate(1); - pause.playFromStart(); - }); - HBox.setHgrow(searchField, Priority.ALWAYS); - - JFXButton closeSearchBar = createToolbarButton2(null, SVG.CLOSE, - searchField::clear); + { + searchField.setPromptText(i18n("search")); + PauseTransition pause = new PauseTransition(Duration.millis(100)); + pause.setOnFinished(event -> displayedItems.setPredicate(skinnable.updateSearchPredicate(searchField.getText()))); + searchField.textProperty().addListener((observable) -> { + pause.setRate(1); + pause.playFromStart(); + }); + HBox.setHgrow(searchField, Priority.ALWAYS); + } + + JFXButton closeSearchBar = createToolbarButton2(null, SVG.CLOSE, searchField::clear); FXUtils.onEscPressed(searchField, closeSearchBar::fire); - searchBar.getChildren().addAll(resetAllButton, searchField, closeSearchBar); - root.getContent().add(searchBar); + + toolBar.getChildren().addAll(resetAllButton, searchField, closeSearchBar); + root.getContent().add(toolBar); } SpinnerPane center = new SpinnerPane(); - ComponentList.setVgrow(center, Priority.ALWAYS); - center.getStyleClass().add("large-spinner-pane"); - center.setContent(listView); - listView.setItems(filteredList); - listView.setCellFactory(x -> new GameRuleListCell(listView)); - FXUtils.ignoreEvent(listView, KeyEvent.KEY_PRESSED, e -> e.getCode() == KeyCode.ESCAPE); - root.getContent().add(center); - - pane.getChildren().add(root); - getChildren().add(pane); - + { + ComponentList.setVgrow(center, Priority.ALWAYS); + center.getStyleClass().add("large-spinner-pane"); + center.setContent(listView); + listView.setItems(displayedItems); + listView.setCellFactory(x -> new GameRuleListCell(listView)); + FXUtils.ignoreEvent(listView, KeyEvent.KEY_PRESSED, e -> e.getCode() == KeyCode.ESCAPE); + root.getContent().add(center); + } } static class GameRuleListCell extends MDListCell> { @@ -117,7 +118,7 @@ protected void updateControl(GameRuleInfo item, boolean empty) { } static class ResetDefaultValuesLayout extends JFXDialogLayout { - public ResetDefaultValuesLayout(Runnable resettingAllGameRule, FilteredList> notInDefaultValueList) { + public ResetDefaultValuesLayout(Runnable resettingAllGameRule, FilteredList> modifiedItems) { { Stage stage = Controllers.getStage(); @@ -125,38 +126,30 @@ public ResetDefaultValuesLayout(Runnable resettingAllGameRule, FilteredList"), new Label(i18n("gamerule.column.default"))); - for (int i = 0; i < notInDefaultValueList.size(); i++) { - GameRuleInfo gameRuleInfo = notInDefaultValueList.get(i); - String oldValue = gameRuleInfo.getCurrentValueText(); - String newValue = gameRuleInfo.getDefaultValueText(); + for (int i = 0; i < modifiedItems.size(); i++) { + GameRuleInfo gameRuleInfo = modifiedItems.get(i); + String displayName = StringUtils.isNotBlank(gameRuleInfo.getDisplayName()) ? gameRuleInfo.getDisplayName() : gameRuleInfo.getRuleKey(); gridPane.addRow(i + 1, - new Label(StringUtils.isNotBlank(gameRuleInfo.getDisplayName()) ? gameRuleInfo.getDisplayName() : gameRuleInfo.getRuleKey()), - new Label(oldValue), + new Label(displayName), + new Label(gameRuleInfo.getCurrentValueText()), new Label("->"), - new Label(newValue)); + new Label(gameRuleInfo.getDefaultValueText())); } + } + ScrollPane scrollPane = new ScrollPane(gridPane); + { + gridPane.setHgap(10); + gridPane.setVgap(10); + + scrollPane.visibleProperty().bind(showDetailButton.selectedProperty()); + scrollPane.managedProperty().bind(showDetailButton.selectedProperty()); + VBox.setMargin(scrollPane, new Insets(5, 10, 5, 10)); + FXUtils.smoothScrolling(scrollPane); + + vBox.getChildren().addAll(scrollPane); } } } + //action area JFXButton accept = new JFXButton(i18n("button.ok")); { accept.getStyleClass().add("dialog-accept"); - if (!notInDefaultValueList.isEmpty()) { + if (!modifiedItems.isEmpty()) { accept.setOnAction(event -> { resettingAllGameRule.run(); fireEvent(new DialogCloseEvent()); }); } else { - accept.setOnAction(event -> { - fireEvent(new DialogCloseEvent()); - }); + accept.setOnAction(event -> fireEvent(new DialogCloseEvent())); } } JFXButton reject = new JFXButton(i18n("button.cancel")); { - reject.setOnAction(event -> fireEvent(new DialogCloseEvent())); reject.getStyleClass().add("dialog-cancel"); + reject.setOnAction(event -> fireEvent(new DialogCloseEvent())); } setActions(accept, reject); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java index 3e3436c3ff..1ed75cbdec 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java @@ -171,7 +171,7 @@ private BooleanGameRule() { public void applyMetadata(GameRule metadataSource) { if (metadataSource instanceof BooleanGameRule source) { this.setDisplayI18nKey(source.getDisplayI18nKey()); - this.defaultValue.addAll(source.defaultValue); + this.defaultValue.putAll(source.defaultValue); } } @@ -197,11 +197,11 @@ public Optional getDefaultValue(GameVersionNumber gameVersionNumber) { } private void addDefaultValue(boolean value) { - defaultValue.addValueInMinVersion("1.4.2", value); + defaultValue.putMinVersion("1.4.2", value); } private void addDefaultValue(String versionName, boolean value) { - defaultValue.addValueInMinVersion(versionName, value); + defaultValue.putMinVersion(versionName, value); } public boolean getValue() { @@ -234,9 +234,9 @@ private IntGameRule() { public void applyMetadata(GameRule metadataSource) { if (metadataSource instanceof IntGameRule source) { this.setDisplayI18nKey(source.getDisplayI18nKey()); - this.defaultValue.addAll(source.defaultValue); - this.maxValue.addAll(source.maxValue); - this.minValue.addAll(source.minValue); + this.defaultValue.putAll(source.defaultValue); + this.maxValue.putAll(source.maxValue); + this.minValue.putAll(source.minValue); } } @@ -285,11 +285,11 @@ public Optional getDefaultValue(GameVersionNumber gameVersionNumber) { } private void addDefaultValue(int value) { - this.defaultValue.addValueInMinVersion("1.4.2", value); + this.defaultValue.putMinVersion("1.4.2", value); } private void addDefaultValue(String versionName, int value) { - this.defaultValue.addValueInMinVersion(versionName, value); + this.defaultValue.putMinVersion(versionName, value); } public int getValue() { @@ -301,27 +301,27 @@ public void setValue(int value) { } public int getMaxValue(GameVersionNumber gameVersionNumber) { - return this.maxValue.getValue(gameVersionNumber).orElse(Integer.MAX_VALUE); + return this.maxValue.getValue(gameVersionNumber, Integer.MAX_VALUE); } public void addMaxValue(int maxValue) { - this.maxValue.addValueInMinVersion("1.4.2", maxValue); + this.maxValue.putMinVersion("1.4.2", maxValue); } public void addMaxValue(String versionName, int maxValue) { - this.maxValue.addValueInMinVersion(versionName, maxValue); + this.maxValue.putMinVersion(versionName, maxValue); } public int getMinValue(GameVersionNumber gameVersionNumber) { - return minValue.getValue(gameVersionNumber).orElse(Integer.MIN_VALUE); + return minValue.getValue(gameVersionNumber, Integer.MIN_VALUE); } public void addMinValue(int minValue) { - this.minValue.addValueInMinVersion("1.4.2", minValue); + this.minValue.putMinVersion("1.4.2", minValue); } public void addMinValue(String versionName, int minValue) { - this.minValue.addValueInMinVersion(versionName, minValue); + this.minValue.putMinVersion(versionName, minValue); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/versioning/VersionedValue.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/versioning/VersionedValue.java index c11d4c0dfc..addad45451 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/versioning/VersionedValue.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/versioning/VersionedValue.java @@ -37,27 +37,35 @@ public class VersionedValue { public VersionedValue() { } - public VersionedValue(String version, T value) { - versionValues.put(GameVersionNumber.asGameVersion(version), value); + public VersionedValue(String minVersion, T value) { + versionValues.put(GameVersionNumber.asGameVersion(minVersion), value); } - public void addValueInMinVersion(String version, T value) { + public void putMinVersion(String version, T value) { versionValues.put(GameVersionNumber.asGameVersion(version), value); } public Optional getValue(String version) { - return Optional.ofNullable(versionValues.floorEntry(GameVersionNumber.asGameVersion(version))).map(Map.Entry::getValue); + return getValue(GameVersionNumber.asGameVersion(version)); } public Optional getValue(GameVersionNumber version) { return Optional.ofNullable(versionValues.floorEntry(version)).map(Map.Entry::getValue); } - public TreeMap getVersionValue() { + public T getValue(String version, T defaultValue) { + return getValue(version).orElse(defaultValue); + } + + public T getValue(GameVersionNumber version, T defaultValue) { + return getValue(version).orElse(defaultValue); + } + + public TreeMap asMap() { return versionValues; } - public void addAll(VersionedValue v) { - versionValues.putAll(v.getVersionValue()); + public void putAll(VersionedValue other) { + versionValues.putAll(other.asMap()); } } From 2fc339a72b1bd4408ae133ff462602ea7cedb752 Mon Sep 17 00:00:00 2001 From: mine_ Date: Mon, 29 Dec 2025 20:18:12 +0800 Subject: [PATCH 57/69] small fix --- .../main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java index b71550b980..774796af7a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java @@ -49,11 +49,11 @@ public sealed abstract class GameRuleInfo permits GameRuleInfo.BooleanGameRul private final String ruleKey; private final String displayName; private final GameRuleNBT gameRuleNBT; + private final BooleanProperty modified = new SimpleBooleanProperty(this, "modified", false); private final Runnable onSave; private Runnable resetValue = () -> { }; - private final BooleanProperty modified = new SimpleBooleanProperty(this, "modified1", false); //Due to the significant difference in skin between BooleanGameRuleInfo and IntGameRuleInfo, which are essentially two completely different styles, it is not suitable to update each other in Cell#updateControl. Therefore, they are directly integrated into the info. private final HBox container = new HBox(); From 942d1ba71f29a5e98ee89aad7ca01d946206b64f Mon Sep 17 00:00:00 2001 From: mine_ Date: Mon, 29 Dec 2025 22:57:57 +0800 Subject: [PATCH 58/69] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/GameRulePageSkin.java | 3 ++- .../jackhuang/hmcl/gamerule/GameRuleTest.java | 23 +++++++++++-------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java index 2df903a44f..985ba345ba 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java @@ -172,10 +172,11 @@ public ResetDefaultValuesLayout(Runnable resettingAllGameRule, FilteredList Date: Tue, 30 Dec 2025 23:07:47 +0800 Subject: [PATCH 59/69] =?UTF-8?q?feat:=20=E5=B0=86cell=E8=8A=82=E7=82=B9?= =?UTF-8?q?=E5=88=9B=E5=BB=BA=E6=96=B9=E6=B3=95=E4=BB=8Ecell=E7=A7=BB?= =?UTF-8?q?=E8=87=B3skin=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/GameRuleInfo.java | 149 ++++-------------- .../hmcl/ui/versions/GameRulePageSkin.java | 104 +++++++++++- 2 files changed, 127 insertions(+), 126 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java index 774796af7a..19212e750d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java @@ -18,24 +18,13 @@ package org.jackhuang.hmcl.ui.versions; import com.github.steveice10.opennbt.tag.builtin.Tag; -import com.jfoenix.controls.JFXButton; -import com.jfoenix.controls.JFXTextField; import javafx.beans.binding.Bindings; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; -import javafx.geometry.Insets; -import javafx.geometry.Pos; -import javafx.scene.control.Label; -import javafx.scene.layout.*; import org.jackhuang.hmcl.gamerule.GameRule; import org.jackhuang.hmcl.gamerule.GameRuleNBT; -import org.jackhuang.hmcl.ui.FXUtils; -import org.jackhuang.hmcl.ui.SVG; -import org.jackhuang.hmcl.ui.construct.NumberRangeValidator; -import org.jackhuang.hmcl.ui.construct.NumberValidator; -import org.jackhuang.hmcl.ui.construct.OptionToggleButton; import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.versioning.GameVersionNumber; @@ -55,9 +44,6 @@ public sealed abstract class GameRuleInfo permits GameRuleInfo.BooleanGameRul private Runnable resetValue = () -> { }; - //Due to the significant difference in skin between BooleanGameRuleInfo and IntGameRuleInfo, which are essentially two completely different styles, it is not suitable to update each other in Cell#updateControl. Therefore, they are directly integrated into the info. - private final HBox container = new HBox(); - private GameRuleInfo(GameRule gameRule, GameRuleNBT gameRuleNBT, Runnable onSave) { ruleKey = gameRule.getRuleKey().get(0); String displayName = ""; @@ -101,10 +87,6 @@ public Runnable getOnSave() { return onSave; } - public HBox getContainer() { - return container; - } - public Runnable getResetValue() { return resetValue; } @@ -130,7 +112,16 @@ public BooleanGameRuleInfo(GameRule.BooleanGameRule booleanGameRule, GameRuleNBT this.currentValue = new SimpleBooleanProperty(booleanGameRule.getValue()); this.defaultValue = booleanGameRule.getDefaultValue(gameVersionNumber).orElse(null); - buildNodes(); + currentValue.addListener((observable, oldValue, newValue) -> { + getGameRuleNBT().changeValue(newValue); + save(); + }); + + if (defaultValue != null) { + setResetValue(() -> currentValue.set(defaultValue)); + modifiedProperty().bind(Bindings.createBooleanBinding(() -> currentValue.getValue() != defaultValue, currentValue)); + } + } @Override @@ -146,47 +137,6 @@ public String getDefaultValueText() { public BooleanProperty currentValueProperty() { return currentValue; } - - public void buildNodes() { - { - HBox.setHgrow(getContainer(), Priority.ALWAYS); - getContainer().setAlignment(Pos.CENTER_LEFT); - getContainer().setPadding(new Insets(0, 8, 0, 0)); - } - - OptionToggleButton toggleButton = new OptionToggleButton(); - { - toggleButton.setTitle(getDisplayName()); - toggleButton.setSubtitle(getRuleKey()); - HBox.setHgrow(toggleButton, Priority.ALWAYS); - toggleButton.selectedProperty().bindBidirectional(currentValue); - currentValue.addListener((observable, oldValue, newValue) -> { - getGameRuleNBT().changeValue(newValue); - save(); - }); - } - - JFXButton resetButton = new JFXButton(); - StackPane wrapperPane = new StackPane(resetButton); - { - wrapperPane.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE); - resetButton.setFocusTraversable(false); - resetButton.setGraphic(SVG.RESTORE.createIcon(24)); - if (defaultValue != null) { - setResetValue(() -> currentValue.set(defaultValue)); - resetButton.setOnAction(event -> resetValue()); - modifiedProperty().bind(Bindings.createBooleanBinding(() -> currentValue.getValue() != defaultValue, currentValue)); - resetButton.disableProperty().bind(modifiedProperty().not()); - FXUtils.installFastTooltip(resetButton, i18n("gamerule.restore_default_values.tooltip", defaultValue)); - FXUtils.installFastTooltip(wrapperPane, i18n("gamerule.now_is_default_values.tooltip")); - } else { - resetButton.setDisable(true); - FXUtils.installFastTooltip(wrapperPane, i18n("gamerule.not_have_default_values.tooltip")); - } - } - - getContainer().getChildren().addAll(toggleButton, wrapperPane); - } } static final class IntGameRuleInfo extends GameRuleInfo { @@ -202,7 +152,18 @@ public IntGameRuleInfo(GameRule.IntGameRule intGameRule, GameRuleNBT { + Integer value = Lang.toIntOrNull(newValue); + if (value != null && value >= minValue && value <= maxValue) { + getGameRuleNBT().changeValue(newValue); + save(); + } + }); + + if (defaultValue != null) { + setResetValue(() -> currentValue.set(String.valueOf(defaultValue))); + modifiedProperty().bind(Bindings.createBooleanBinding(() -> !Objects.equals(Lang.toIntOrNull(currentValue.getValue()), defaultValue), currentValue)); + } } @Override @@ -219,68 +180,12 @@ public StringProperty currentValueProperty() { return currentValue; } - public void buildNodes() { - { - getContainer().setPadding(new Insets(8, 8, 8, 16)); - HBox.setHgrow(getContainer(), Priority.ALWAYS); - getContainer().setAlignment(Pos.CENTER_LEFT); - } - - VBox displayInfoVBox = new VBox(); - { - displayInfoVBox.getChildren().addAll(new Label(getDisplayName()), new Label(getRuleKey())); - displayInfoVBox.setAlignment(Pos.CENTER_LEFT); - HBox.setHgrow(displayInfoVBox, Priority.ALWAYS); - } - - HBox rightHBox = new HBox(); - { - rightHBox.setSpacing(12); - rightHBox.setAlignment(Pos.CENTER_LEFT); - } - - JFXTextField textField = new JFXTextField(); - { - textField.textProperty().bindBidirectional(currentValue); - FXUtils.setValidateWhileTextChanged(textField, true); - textField.setValidators( - new NumberValidator(i18n("input.integer"), false), - new NumberRangeValidator(i18n("input.number_range", minValue, maxValue), minValue, maxValue)); - currentValue.addListener((observable, oldValue, newValue) -> { - Integer value = Lang.toIntOrNull(newValue); - if (value != null && value >= minValue && value <= maxValue) { - getGameRuleNBT().changeValue(newValue); - save(); - } - }); - - textField.maxWidth(10); - textField.minWidth(10); - } - - JFXButton resetButton = new JFXButton(); - StackPane wrapperPane = new StackPane(resetButton); - { - wrapperPane.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE); - resetButton.setFocusTraversable(false); - resetButton.setGraphic(SVG.RESTORE.createIcon(24)); - if (defaultValue != null) { - setResetValue(() -> currentValue.set(String.valueOf(defaultValue))); - resetButton.setOnAction(event -> resetValue()); - modifiedProperty().bind(Bindings.createBooleanBinding(() -> !Objects.equals(Lang.toIntOrNull(currentValue.getValue()), defaultValue), currentValue)); - resetButton.disableProperty().bind(modifiedProperty().not()); - FXUtils.installFastTooltip(resetButton, i18n("gamerule.restore_default_values.tooltip", defaultValue)); - FXUtils.installFastTooltip(wrapperPane, i18n("gamerule.now_is_default_values.tooltip")); - } else { - resetButton.setDisable(true); - FXUtils.installFastTooltip(wrapperPane, i18n("gamerule.not_have_default_values.tooltip")); - } - - resetButton.setAlignment(Pos.BOTTOM_CENTER); - } + public int getMinValue() { + return minValue; + } - rightHBox.getChildren().addAll(textField, wrapperPane); - getContainer().getChildren().addAll(displayInfoVBox, rightHBox); + public int getMaxValue() { + return maxValue; } } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java index 985ba345ba..6e6df87383 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java @@ -39,6 +39,9 @@ import org.jackhuang.hmcl.ui.construct.*; import org.jackhuang.hmcl.util.StringUtils; +import java.util.HashMap; +import java.util.Map; + import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton2; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; @@ -46,9 +49,10 @@ class GameRulePageSkin extends SkinBase { private final HBox toolBar; private final JFXTextField searchField; - JFXListView> listView = new JFXListView<>(); + private final JFXListView> listView = new JFXListView<>(); private final FilteredList> displayedItems; private final FilteredList> modifiedItems; + private final Map cellMap = new HashMap<>(); GameRulePageSkin(GameRulePage skinnable) { super(skinnable); @@ -98,22 +102,114 @@ class GameRulePageSkin extends SkinBase { center.getStyleClass().add("large-spinner-pane"); center.setContent(listView); listView.setItems(displayedItems); - listView.setCellFactory(x -> new GameRuleListCell(listView)); + listView.setCellFactory(x -> new GameRuleListCell(listView, cellMap)); FXUtils.ignoreEvent(listView, KeyEvent.KEY_PRESSED, e -> e.getCode() == KeyCode.ESCAPE); root.getContent().add(center); } } static class GameRuleListCell extends MDListCell> { + private final Map cellMap; - public GameRuleListCell(JFXListView> listView) { + public GameRuleListCell(JFXListView> listView, Map cellMap) { super(listView); + this.cellMap = cellMap; } @Override protected void updateControl(GameRuleInfo item, boolean empty) { if (empty) return; - getContainer().getChildren().setAll(item.getContainer()); + + HBox hBox = cellMap.computeIfAbsent(item.getRuleKey(), key -> { + if (item instanceof GameRuleInfo.IntGameRuleInfo intInfo) { + return buildNodeForIntGameRule(intInfo); + } else if (item instanceof GameRuleInfo.BooleanGameRuleInfo booleanInfo) { + return buildNodeForBooleanGameRule(booleanInfo); + } + return null; + }); + if (hBox != null) { + getContainer().getChildren().setAll(hBox); + } + } + + private HBox buildNodeForIntGameRule(GameRuleInfo.IntGameRuleInfo gameRule) { + HBox cellBox = new HBox(); + { + cellBox.setPadding(new Insets(8, 8, 8, 16)); + HBox.setHgrow(cellBox, Priority.ALWAYS); + cellBox.setAlignment(Pos.CENTER_LEFT); + } + + VBox displayInfoVBox = new VBox(); + { + displayInfoVBox.getChildren().addAll(new Label(gameRule.getDisplayName()), new Label(gameRule.getRuleKey())); + displayInfoVBox.setAlignment(Pos.CENTER_LEFT); + HBox.setHgrow(displayInfoVBox, Priority.ALWAYS); + } + + HBox rightHBox = new HBox(); + { + rightHBox.setSpacing(12); + rightHBox.setAlignment(Pos.CENTER_LEFT); + } + + JFXTextField textField = new JFXTextField(); + { + textField.textProperty().bindBidirectional(gameRule.currentValueProperty()); + FXUtils.setValidateWhileTextChanged(textField, true); + textField.setValidators( + new NumberValidator(i18n("input.integer"), false), + new NumberRangeValidator(i18n("input.number_range", gameRule.getMinValue(), gameRule.getMaxValue()), gameRule.getMinValue(), gameRule.getMaxValue())); + + textField.setPrefWidth(120); + } + + rightHBox.getChildren().addAll(textField, buildResetButton(gameRule)); + cellBox.getChildren().addAll(displayInfoVBox, rightHBox); + + return cellBox; + } + + private HBox buildNodeForBooleanGameRule(GameRuleInfo.BooleanGameRuleInfo gameRule) { + HBox cellBox = new HBox(); + { + HBox.setHgrow(cellBox, Priority.ALWAYS); + cellBox.setAlignment(Pos.CENTER_LEFT); + cellBox.setPadding(new Insets(0, 8, 0, 0)); + } + + OptionToggleButton toggleButton = new OptionToggleButton(); + { + toggleButton.setTitle(gameRule.getDisplayName()); + toggleButton.setSubtitle(gameRule.getRuleKey()); + HBox.setHgrow(toggleButton, Priority.ALWAYS); + toggleButton.selectedProperty().bindBidirectional(gameRule.currentValueProperty()); + } + + cellBox.getChildren().addAll(toggleButton, buildResetButton(gameRule)); + + return cellBox; + } + + private StackPane buildResetButton(GameRuleInfo gameRule) { + JFXButton resetButton = new JFXButton(); + StackPane wrapperPane = new StackPane(resetButton); + { + wrapperPane.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE); + resetButton.setFocusTraversable(false); + resetButton.setGraphic(SVG.RESTORE.createIcon(24)); + if (StringUtils.isNotBlank(gameRule.getDefaultValueText())) { + resetButton.setOnAction(event -> gameRule.resetValue()); + resetButton.disableProperty().bind(gameRule.modifiedProperty().not()); + FXUtils.installFastTooltip(resetButton, i18n("gamerule.restore_default_values.tooltip", gameRule.getDefaultValueText())); + FXUtils.installFastTooltip(wrapperPane, i18n("gamerule.now_is_default_values.tooltip")); + } else { + resetButton.setDisable(true); + FXUtils.installFastTooltip(wrapperPane, i18n("gamerule.not_have_default_values.tooltip")); + } + } + return wrapperPane; } } From 95d491b5be95788bd2c2b1cc862ff701a08ab850 Mon Sep 17 00:00:00 2001 From: mine_ Date: Tue, 30 Dec 2025 23:33:25 +0800 Subject: [PATCH 60/69] =?UTF-8?q?feat:=20=E4=B8=BA=E4=BF=9D=E5=AD=98levelD?= =?UTF-8?q?at=E5=BC=95=E5=85=A5=E5=8E=BB=E6=8A=96=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/jackhuang/hmcl/ui/versions/GameRulePage.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java index 11e483c31b..a42b4de3ba 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java @@ -19,10 +19,12 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.Tag; +import javafx.animation.PauseTransition; import javafx.beans.Observable; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.scene.control.Skin; +import javafx.util.Duration; import org.jackhuang.hmcl.game.World; import org.jackhuang.hmcl.gamerule.GameRule; import org.jackhuang.hmcl.gamerule.GameRuleNBT; @@ -50,6 +52,7 @@ public class GameRulePage extends ListPageBase> { ObservableList> gameRuleList; private boolean batchUpdating = false; + private final PauseTransition pause; public GameRulePage(WorldManagePage worldManagePage) { this.worldManagePage = worldManagePage; @@ -77,6 +80,9 @@ public GameRulePage(WorldManagePage worldManagePage) { setFailedReason(i18n("world.info.failed")); } })).start(); + + pause = new PauseTransition(Duration.millis(300)); + pause.setOnFinished(event -> saveLevelDat()); } public void updateControls() { @@ -136,9 +142,13 @@ void saveLevelDat() { })).start(); } + void requestSaveLevelDat() { + pause.playFromStart(); + } + void saveLevelDatIfNotInBatchUpdating() { if (!batchUpdating) { - saveLevelDat(); + requestSaveLevelDat(); } } From 8fa29efb2014abca32b82928ffd42226213c97fb Mon Sep 17 00:00:00 2001 From: mine_ Date: Tue, 30 Dec 2025 23:44:25 +0800 Subject: [PATCH 61/69] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96gamerule.json?= =?UTF-8?q?=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/GameRulePageSkin.java | 2 +- .../org/jackhuang/hmcl/gamerule/GameRule.java | 24 +++++++++++-------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java index 6e6df87383..43384a31b1 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java @@ -162,7 +162,7 @@ private HBox buildNodeForIntGameRule(GameRuleInfo.IntGameRuleInfo gameRule) { new NumberValidator(i18n("input.integer"), false), new NumberRangeValidator(i18n("input.number_range", gameRule.getMinValue(), gameRule.getMaxValue()), gameRule.getMinValue(), gameRule.getMaxValue())); - textField.setPrefWidth(120); + textField.setPrefWidth(150); } rightHBox.getChildren().addAll(textField, buildResetButton(gameRule)); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java index 1ed75cbdec..f93ab1115f 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java @@ -257,11 +257,7 @@ public GameRule deserialize(JsonObject jsonObject, Type type, JsonDeserializatio if (jsonObject.get("maxValue") instanceof JsonPrimitive jsonPrimitive) { this.addMaxValue(jsonPrimitive.getAsInt()); } else if (jsonObject.get("maxValue") instanceof JsonObject o) { - o.asMap().forEach((key, value) -> { - JsonPrimitive primitive = value.getAsJsonPrimitive(); - int maxValue = primitive.isNumber() ? primitive.getAsInt() : Integer.MAX_VALUE; - this.addMaxValue(key, maxValue); - }); + o.asMap().forEach((key, value) -> this.addMaxValue(key, parseValue(value))); } else { this.addMaxValue(Integer.MAX_VALUE); } @@ -269,17 +265,25 @@ public GameRule deserialize(JsonObject jsonObject, Type type, JsonDeserializatio if (jsonObject.get("minValue") instanceof JsonPrimitive jsonPrimitive) { this.addMinValue(jsonPrimitive.getAsInt()); } else if (jsonObject.get("minValue") instanceof JsonObject o) { - o.asMap().forEach((key, value) -> { - JsonPrimitive primitive = value.getAsJsonPrimitive(); - int minValue = primitive.isNumber() ? primitive.getAsInt() : Integer.MIN_VALUE; - this.addMinValue(key, minValue); - }); + o.asMap().forEach((key, value) -> this.addMinValue(key, parseValue(value))); } else { this.addMinValue(Integer.MIN_VALUE); } return this; } + private int parseValue(JsonElement jsonElement) { + JsonPrimitive primitive = jsonElement.getAsJsonPrimitive(); + int value; + if (primitive.isNumber()) { + value = primitive.getAsInt(); + } else { + String str = primitive.getAsString(); + value = "INT_MAX".equals(str) ? Integer.MAX_VALUE : Integer.MIN_VALUE; + } + return value; + } + public Optional getDefaultValue(GameVersionNumber gameVersionNumber) { return defaultValue.getValue(gameVersionNumber); } From fd8fc3586912eb20b196ee958ce31b65ec73fb7c Mon Sep 17 00:00:00 2001 From: mine_ Date: Wed, 31 Dec 2025 10:55:42 +0800 Subject: [PATCH 62/69] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/construct/NumberRangeValidator.java | 7 ------- .../org/jackhuang/hmcl/ui/versions/GameRuleInfo.java | 11 +++++++++++ .../org/jackhuang/hmcl/ui/versions/GameRulePage.java | 12 ++---------- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/NumberRangeValidator.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/NumberRangeValidator.java index 5b5894818a..47be61a013 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/NumberRangeValidator.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/NumberRangeValidator.java @@ -32,13 +32,6 @@ public NumberRangeValidator(@NamedArg("outOfLimitMessage") String outOfLimitMess super(outOfLimitMessage); this.minValue = minValue; this.maxValue = maxValue; - if (srcControl.get() instanceof TextInputControl textInputControl) { - textInputControl.tooltipProperty().addListener((ov, t, t1) -> { - if (t1 != null) { - System.out.println("new tooltip is " + t1.getText()); - } - }); - } } @Override diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java index 19212e750d..3240ed15fb 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRuleInfo.java @@ -59,6 +59,17 @@ private GameRuleInfo(GameRule gameRule, GameRuleNBT gameRuleNB this.onSave = onSave; } + public static GameRuleInfo createGameRuleInfo(GameRule gameRule, GameRuleNBT gameRuleNBT, Runnable onSave, GameVersionNumber gameVersion) { + if (gameRule instanceof GameRule.IntGameRule intGameRule) { + @SuppressWarnings("unchecked") var typedGameRuleNBT = (GameRuleNBT) gameRuleNBT; + return new GameRuleInfo.IntGameRuleInfo(intGameRule, typedGameRuleNBT, onSave, gameVersion); + } else if (gameRule instanceof GameRule.BooleanGameRule booleanGameRule) { + @SuppressWarnings("unchecked") var typedGameRuleNBT = (GameRuleNBT) gameRuleNBT; + return new GameRuleInfo.BooleanGameRuleInfo(booleanGameRule, typedGameRuleNBT, onSave, gameVersion); + } + return null; + } + public abstract String getCurrentValueText(); public abstract String getDefaultValueText(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java index a42b4de3ba..be50f8e264 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java @@ -18,7 +18,6 @@ package org.jackhuang.hmcl.ui.versions; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.github.steveice10.opennbt.tag.builtin.Tag; import javafx.animation.PauseTransition; import javafx.beans.Observable; import javafx.collections.FXCollections; @@ -27,7 +26,6 @@ import javafx.util.Duration; import org.jackhuang.hmcl.game.World; import org.jackhuang.hmcl.gamerule.GameRule; -import org.jackhuang.hmcl.gamerule.GameRuleNBT; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.Controllers; @@ -50,7 +48,7 @@ public class GameRulePage extends ListPageBase> { private final World world; private CompoundTag levelDat; - ObservableList> gameRuleList; + private final ObservableList> gameRuleList; private boolean batchUpdating = false; private final PauseTransition pause; @@ -100,13 +98,7 @@ public void updateControls() { gameRuleCompoundTag.iterator().forEachRemaining(gameRuleTag -> { GameRule.createGameRuleNBT(gameRuleTag).ifPresent(gameRuleNBT -> { GameRule.getFullGameRule(gameRuleTag).ifPresent(gameRule -> { - if (gameRule instanceof GameRule.IntGameRule intGameRule) { - @SuppressWarnings("unchecked") GameRuleNBT typedGameRuleNBT = (GameRuleNBT) gameRuleNBT; - gameRuleList.add(new GameRuleInfo.IntGameRuleInfo(intGameRule, typedGameRuleNBT, this::saveLevelDatIfNotInBatchUpdating, world.getGameVersion())); - } else if (gameRule instanceof GameRule.BooleanGameRule booleanGameRule) { - @SuppressWarnings("unchecked") GameRuleNBT typedGameRuleNBT = (GameRuleNBT) gameRuleNBT; - gameRuleList.add(new GameRuleInfo.BooleanGameRuleInfo(booleanGameRule, typedGameRuleNBT, this::saveLevelDatIfNotInBatchUpdating, world.getGameVersion())); - } + gameRuleList.add(GameRuleInfo.createGameRuleInfo(gameRule, gameRuleNBT, this::saveLevelDatIfNotInBatchUpdating, world.getGameVersion())); }); }); }); From e7d4f439132be9a7638dfea2771e2f13364a2957 Mon Sep 17 00:00:00 2001 From: mine_ Date: Tue, 6 Jan 2026 18:59:13 +0800 Subject: [PATCH 63/69] =?UTF-8?q?feat:=20=E5=BE=AE=E8=B0=83=E6=81=A2?= =?UTF-8?q?=E5=A4=8D=E7=A1=AE=E8=AE=A4=E6=A1=86=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java index 43384a31b1..a6add18854 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java @@ -247,10 +247,11 @@ public ResetDefaultValuesLayout(Runnable resettingAllGameRule, FilteredList"), + new Label("", SVG.ARROW_FORWARD.createIcon(12)), new Label(i18n("gamerule.column.default"))); for (int i = 0; i < modifiedItems.size(); i++) { @@ -259,7 +260,7 @@ public ResetDefaultValuesLayout(Runnable resettingAllGameRule, FilteredList"), + new Label("", SVG.ARROW_FORWARD.createIcon(12)), new Label(gameRuleInfo.getDefaultValueText())); } } From 2ed6760cbca23fee9a11e99e1543b61e77eb3a7d Mon Sep 17 00:00:00 2001 From: mine_ Date: Tue, 6 Jan 2026 20:46:44 +0800 Subject: [PATCH 64/69] =?UTF-8?q?feat:=20=E5=BE=AE=E8=B0=83=E6=B8=B8?= =?UTF-8?q?=E6=88=8F=E8=A7=84=E5=88=99=E9=94=AE=E6=96=87=E6=9C=AC=E7=9A=84?= =?UTF-8?q?=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/GameRulePageSkin.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java index a6add18854..64aaf754d1 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java @@ -60,7 +60,7 @@ class GameRulePageSkin extends SkinBase { StackPane pane = new StackPane(root); { pane.setPadding(new Insets(10)); - pane.getStyleClass().addAll("notice-pane"); + pane.getStyleClass().add("notice-pane"); root.getStyleClass().add("no-padding"); getChildren().add(pane); @@ -101,9 +101,11 @@ class GameRulePageSkin extends SkinBase { ComponentList.setVgrow(center, Priority.ALWAYS); center.getStyleClass().add("large-spinner-pane"); center.setContent(listView); + listView.setItems(displayedItems); listView.setCellFactory(x -> new GameRuleListCell(listView, cellMap)); FXUtils.ignoreEvent(listView, KeyEvent.KEY_PRESSED, e -> e.getCode() == KeyCode.ESCAPE); + root.getContent().add(center); } } @@ -143,7 +145,10 @@ private HBox buildNodeForIntGameRule(GameRuleInfo.IntGameRuleInfo gameRule) { VBox displayInfoVBox = new VBox(); { - displayInfoVBox.getChildren().addAll(new Label(gameRule.getDisplayName()), new Label(gameRule.getRuleKey())); + Label displayNameLabel = new Label(gameRule.getDisplayName()); + Label ruleKeyLabel = new Label(gameRule.getRuleKey()); + ruleKeyLabel.getStyleClass().add("subtitle"); + displayInfoVBox.getChildren().addAll(displayNameLabel, ruleKeyLabel); displayInfoVBox.setAlignment(Pos.CENTER_LEFT); HBox.setHgrow(displayInfoVBox, Priority.ALWAYS); } @@ -236,7 +241,6 @@ public ResetDefaultValuesLayout(Runnable resettingAllGameRule, FilteredList Date: Wed, 7 Jan 2026 22:59:27 +0800 Subject: [PATCH 65/69] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BF=9D?= =?UTF-8?q?=E5=AD=98level.dat=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/GameRulePage.java | 9 ++------- .../hmcl/ui/versions/WorldInfoPage.java | 6 +----- .../java/org/jackhuang/hmcl/game/World.java | 19 +++++++++++++++---- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java index be50f8e264..4d779ceb4e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java @@ -121,17 +121,12 @@ private CompoundTag loadWorldInfo() throws IOException { if (!Files.isDirectory(world.getFile())) throw new IOException("Not a valid world directory"); - return world.readLevelDat(); + return world.getLevelData(); } void saveLevelDat() { LOG.info("Saving level.dat of world " + world.getWorldName()); - Task.runAsync(Schedulers.io(), () -> this.world.writeLevelDat(levelDat)) - .whenComplete(Schedulers.defaultScheduler(), ((result, exception) -> { - if (exception != null) { - LOG.warning("Failed to save level.dat of world " + world.getWorldName(), exception); - } - })).start(); + world.writeLevelDatAsync(); } void requestSaveLevelDat() { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java index 30c06ee7fa..767f70b704 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java @@ -549,11 +549,7 @@ private void setTagAndTextField(FloatTag floatTag, JFXTextField jfxTextField) { private void saveLevelDat() { LOG.info("Saving level.dat of world " + world.getWorldName()); - try { - this.world.writeLevelDat(levelDat); - } catch (IOException e) { - LOG.warning("Failed to save level.dat of world " + world.getWorldName(), e); - } + this.world.writeLevelDatAsync(); } private record Dimension(String name) { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java index 24943caf07..f19bea7d20 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java @@ -20,6 +20,8 @@ import com.github.steveice10.opennbt.NBTIO; import com.github.steveice10.opennbt.tag.builtin.*; import javafx.scene.image.Image; +import org.jackhuang.hmcl.task.Schedulers; +import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.util.io.*; import org.jackhuang.hmcl.util.versioning.GameVersionNumber; import org.jetbrains.annotations.Nullable; @@ -78,7 +80,7 @@ public String getWorldName() { public void setWorldName(String worldName) throws IOException { if (levelData.get("Data") instanceof CompoundTag data && data.get("LevelName") instanceof StringTag levelNameTag) { levelNameTag.setValue(worldName); - writeLevelDat(levelData); + writeLevelDat(); } } @@ -237,7 +239,7 @@ public void rename(String newName) throws IOException { // Change the name recorded in level.dat CompoundTag data = levelData.get("Data"); data.put(new StringTag("LevelName", newName)); - writeLevelDat(levelData); + writeLevelDat(); // then change the folder's name Files.move(file, file.resolveSibling(newName)); @@ -333,17 +335,26 @@ public FileChannel lock() throws WorldLockedException { } } - public void writeLevelDat(CompoundTag nbt) throws IOException { + public void writeLevelDat() throws IOException { if (!Files.isDirectory(file)) throw new IOException("Not a valid world directory"); FileUtils.saveSafely(getLevelDatFile(), os -> { try (OutputStream gos = new GZIPOutputStream(os)) { - NBTIO.writeTag(gos, nbt); + NBTIO.writeTag(gos, getLevelData()); } }); } + public void writeLevelDatAsync() { + Task.runAsync(Schedulers.io(), this::writeLevelDat) + .whenComplete(Schedulers.defaultScheduler(), ((result, exception) -> { + if (exception != null) { + LOG.warning("Failed to save level.dat of world " + getWorldName(), exception); + } + })).start(); + } + private static CompoundTag parseLevelDat(Path path) throws IOException { try (InputStream is = new GZIPInputStream(Files.newInputStream(path))) { Tag nbt = NBTIO.readTag(is); From 3bce489654c9a8770c397cf173096987b8cbf11d Mon Sep 17 00:00:00 2001 From: mine_ Date: Thu, 8 Jan 2026 18:45:39 +0800 Subject: [PATCH 66/69] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E6=B8=B8?= =?UTF-8?q?=E6=88=8F=E8=A7=84=E5=88=99=E5=88=97=E8=A1=A8=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/GameRulePageSkin.java | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java index 64aaf754d1..80c305add9 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java @@ -145,10 +145,15 @@ private HBox buildNodeForIntGameRule(GameRuleInfo.IntGameRuleInfo gameRule) { VBox displayInfoVBox = new VBox(); { - Label displayNameLabel = new Label(gameRule.getDisplayName()); - Label ruleKeyLabel = new Label(gameRule.getRuleKey()); - ruleKeyLabel.getStyleClass().add("subtitle"); - displayInfoVBox.getChildren().addAll(displayNameLabel, ruleKeyLabel); + if (StringUtils.isNotBlank(gameRule.getDisplayName())) { + Label displayNameLabel = new Label(gameRule.getDisplayName()); + Label ruleKeyLabel = new Label(gameRule.getRuleKey()); + ruleKeyLabel.getStyleClass().add("subtitle"); + + displayInfoVBox.getChildren().addAll(displayNameLabel, ruleKeyLabel); + } else { + displayInfoVBox.getChildren().addAll(new Label(gameRule.getRuleKey())); + } displayInfoVBox.setAlignment(Pos.CENTER_LEFT); HBox.setHgrow(displayInfoVBox, Priority.ALWAYS); } @@ -186,8 +191,12 @@ private HBox buildNodeForBooleanGameRule(GameRuleInfo.BooleanGameRuleInfo gameRu OptionToggleButton toggleButton = new OptionToggleButton(); { - toggleButton.setTitle(gameRule.getDisplayName()); - toggleButton.setSubtitle(gameRule.getRuleKey()); + if (StringUtils.isNotBlank(gameRule.getDisplayName())) { + toggleButton.setTitle(gameRule.getDisplayName()); + toggleButton.setSubtitle(gameRule.getRuleKey()); + } else { + toggleButton.setTitle(gameRule.getRuleKey()); + } HBox.setHgrow(toggleButton, Priority.ALWAYS); toggleButton.selectedProperty().bindBidirectional(gameRule.currentValueProperty()); } @@ -204,6 +213,7 @@ private StackPane buildResetButton(GameRuleInfo gameRule) { wrapperPane.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE); resetButton.setFocusTraversable(false); resetButton.setGraphic(SVG.RESTORE.createIcon(24)); + resetButton.getStyleClass().add("toggle-icon4"); if (StringUtils.isNotBlank(gameRule.getDefaultValueText())) { resetButton.setOnAction(event -> gameRule.resetValue()); resetButton.disableProperty().bind(gameRule.modifiedProperty().not()); From 70b29e33c267fede55e0a10198f8c612d0f3f629 Mon Sep 17 00:00:00 2001 From: mine_ Date: Fri, 9 Jan 2026 14:18:42 +0800 Subject: [PATCH 67/69] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=B7=B2?= =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=BF=87=E6=BB=A4=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/GameRulePage.java | 6 +- .../hmcl/ui/versions/GameRulePageSkin.java | 57 +++++++++++++++---- .../resources/assets/lang/I18N.properties | 4 ++ .../resources/assets/lang/I18N_zh.properties | 4 ++ .../assets/lang/I18N_zh_CN.properties | 4 ++ 5 files changed, 61 insertions(+), 14 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java index 4d779ceb4e..72e4146afd 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java @@ -56,10 +56,10 @@ public GameRulePage(WorldManagePage worldManagePage) { this.worldManagePage = worldManagePage; this.world = worldManagePage.getWorld(); - gameRuleList = FXCollections.observableArrayList(gamerule -> { - if (gamerule instanceof GameRuleInfo.BooleanGameRuleInfo booleanGameRuleInfo) { + gameRuleList = FXCollections.observableArrayList(gameRule -> { + if (gameRule instanceof GameRuleInfo.BooleanGameRuleInfo booleanGameRuleInfo) { return new Observable[]{booleanGameRuleInfo.currentValueProperty()}; - } else if (gamerule instanceof GameRuleInfo.IntGameRuleInfo intGameRuleInfo) { + } else if (gameRule instanceof GameRuleInfo.IntGameRuleInfo intGameRuleInfo) { return new Observable[]{intGameRuleInfo.currentValueProperty()}; } return new Observable[]{}; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java index 80c305add9..32d98a0ca2 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java @@ -17,11 +17,10 @@ */ package org.jackhuang.hmcl.ui.versions; -import com.jfoenix.controls.JFXButton; -import com.jfoenix.controls.JFXDialogLayout; -import com.jfoenix.controls.JFXListView; -import com.jfoenix.controls.JFXTextField; +import com.jfoenix.controls.*; import javafx.animation.PauseTransition; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; import javafx.collections.transformation.FilteredList; import javafx.geometry.Insets; import javafx.geometry.Pos; @@ -39,8 +38,11 @@ import org.jackhuang.hmcl.ui.construct.*; import org.jackhuang.hmcl.util.StringUtils; +import java.util.Arrays; import java.util.HashMap; +import java.util.Locale; import java.util.Map; +import java.util.stream.Collectors; import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton2; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; @@ -50,6 +52,7 @@ class GameRulePageSkin extends SkinBase { private final HBox toolBar; private final JFXTextField searchField; private final JFXListView> listView = new JFXListView<>(); + private final ObservableList> modifiedList = FXCollections.observableArrayList(); private final FilteredList> displayedItems; private final FilteredList> modifiedItems; private final Map cellMap = new HashMap<>(); @@ -66,16 +69,22 @@ class GameRulePageSkin extends SkinBase { getChildren().add(pane); } - displayedItems = new FilteredList<>(skinnable.getItems()); + modifiedList.setAll(getSkinnable().getItems()); + displayedItems = new FilteredList<>(modifiedList); modifiedItems = new FilteredList<>(skinnable.getItems(), GameRuleInfo::getModified); { toolBar = new HBox(); toolBar.setAlignment(Pos.CENTER); toolBar.setPadding(new Insets(0, 5, 0, 5)); + toolBar.setSpacing(5); - JFXButton resetAllButton = createToolbarButton2(i18n("gamerule.restore_default_values_all"), SVG.RESTORE, - () -> Controllers.dialog(new ResetDefaultValuesLayout(skinnable::resettingAllGameRule, modifiedItems))); + JFXComboBox viewFilterComboBox = new JFXComboBox<>(RuleModifiedType.items); + // Changes to the modifiedList are only applied at the time a type is manually selected; this is by design. + viewFilterComboBox.valueProperty().addListener((observable, oldValue, newValue) -> { + applyModifiedFilter(newValue); + }); + viewFilterComboBox.setValue(RuleModifiedType.ALL); searchField = new JFXTextField(); { @@ -88,11 +97,13 @@ class GameRulePageSkin extends SkinBase { }); HBox.setHgrow(searchField, Priority.ALWAYS); } + //JFXButton closeSearchBar = createToolbarButton2(null, SVG.CLOSE, searchField::clear); + //FXUtils.onEscPressed(searchField, searchField::clear); - JFXButton closeSearchBar = createToolbarButton2(null, SVG.CLOSE, searchField::clear); - FXUtils.onEscPressed(searchField, closeSearchBar::fire); + JFXButton resetAllButton = createToolbarButton2(i18n("gamerule.restore_default_values_all"), SVG.RESTORE, + () -> Controllers.dialog(new ResetDefaultValuesLayout(skinnable::resettingAllGameRule, modifiedItems, () -> applyModifiedFilter(viewFilterComboBox.getSelectionModel().getSelectedItem())))); - toolBar.getChildren().addAll(resetAllButton, searchField, closeSearchBar); + toolBar.getChildren().addAll(searchField, new Label(i18n("gamerule.filter")), viewFilterComboBox, resetAllButton); root.getContent().add(toolBar); } @@ -110,6 +121,18 @@ class GameRulePageSkin extends SkinBase { } } + private void applyModifiedFilter(RuleModifiedType type) { + switch (type) { + case ALL -> modifiedList.setAll(getSkinnable().getItems()); + case MODIFIED -> modifiedList.setAll(modifiedItems); + case UNMODIFIED -> { + modifiedList.setAll(getSkinnable().getItems().stream() + .filter(gameRuleInfo -> !modifiedItems.contains(gameRuleInfo)) + .collect(Collectors.toSet())); + } + } + } + static class GameRuleListCell extends MDListCell> { private final Map cellMap; @@ -228,8 +251,19 @@ private StackPane buildResetButton(GameRuleInfo gameRule) { } } + enum RuleModifiedType { + ALL, MODIFIED, UNMODIFIED; + + static final ObservableList items = FXCollections.observableList(Arrays.asList(values())); + + @Override + public String toString() { + return i18n("gamerule.filter." + name().toLowerCase(Locale.ROOT)); + } + } + static class ResetDefaultValuesLayout extends JFXDialogLayout { - public ResetDefaultValuesLayout(Runnable resettingAllGameRule, FilteredList> modifiedItems) { + public ResetDefaultValuesLayout(Runnable resettingAllGameRule, FilteredList> modifiedItems, Runnable callBack) { { Stage stage = Controllers.getStage(); @@ -301,6 +335,7 @@ public ResetDefaultValuesLayout(Runnable resettingAllGameRule, FilteredList { resettingAllGameRule.run(); + callBack.run(); fireEvent(new DialogCloseEvent()); }); } else { diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 2fb7038091..f36dff2734 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -692,6 +692,10 @@ gamerule.all_is_default=All game rules are currently at default values gamerule.column.current=Current Value gamerule.column.default=Default Value gamerule.column.name=Rule Name +gamerule.filter=Filter +gamerule.filter.all=All +gamerule.filter.modified=Modified +gamerule.filter.unmodified=Unmodified gamerule.restore_default_values_all=Reset all to game defaults gamerule.restore_default_values_all.confirm=Are you sure you want to reset all rules to the game's default? This action cannot be undone! gamerule.restore_default_values_all.finish.toast=All game rules have been reset to default values diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index b560cdd248..66f06c842e 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -491,6 +491,10 @@ gamerule.all_is_default=目前所有遊戲規則已處於預設狀態 gamerule.column.current=目前值 gamerule.column.default=預設值 gamerule.column.name=規則名稱 +gamerule.filter=篩選 +gamerule.filter.all=全部 +gamerule.filter.modified=已變更 +gamerule.filter.unmodified=未變更 gamerule.restore_default_values_all=復原遊戲預設規則 gamerule.restore_default_values_all.confirm=你確定要復原所有規則至遊戲預設嗎?該操作無法復原! gamerule.restore_default_values_all.finish.toast=已將所有遊戲規則復原至預設值 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 45f26a5bf2..b41d1a5e76 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -495,6 +495,10 @@ gamerule.all_is_default=当前所有游戏规则已处于默认状态 gamerule.column.current=当前值 gamerule.column.default=默认值 gamerule.column.name=规则名称 +gamerule.filter=过滤 +gamerule.filter.all=全部 +gamerule.filter.modified=已修改 +gamerule.filter.unmodified=未修改 gamerule.restore_default_values_all=恢复游戏默认规则 gamerule.restore_default_values_all.confirm=你确定要恢复所有规则为游戏默认吗?此操作无法撤销! gamerule.restore_default_values_all.finish.toast=已将所有游戏规则恢复至默认值 From 22fffd59c4ee826363c7ccb4af54c16e3c8479ca Mon Sep 17 00:00:00 2001 From: mine_ Date: Fri, 9 Jan 2026 20:15:49 +0800 Subject: [PATCH 68/69] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/GameRulePage.java | 57 ++++++++- .../hmcl/ui/versions/GameRulePageSkin.java | 56 ++------- .../org/jackhuang/hmcl/gamerule/GameRule.java | 112 ++++++++---------- 3 files changed, 107 insertions(+), 118 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java index 72e4146afd..9253929329 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java @@ -22,6 +22,7 @@ import javafx.beans.Observable; import javafx.collections.FXCollections; import javafx.collections.ObservableList; +import javafx.collections.transformation.FilteredList; import javafx.scene.control.Skin; import javafx.util.Duration; import org.jackhuang.hmcl.game.World; @@ -35,9 +36,11 @@ import java.io.IOException; import java.nio.file.Files; +import java.util.Arrays; import java.util.Locale; import java.util.function.Predicate; import java.util.regex.Pattern; +import java.util.stream.Collectors; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.logging.Logger.LOG; @@ -49,8 +52,12 @@ public class GameRulePage extends ListPageBase> { private CompoundTag levelDat; private final ObservableList> gameRuleList; + private final FilteredList> modifiedItems = new FilteredList<>(getItems(), GameRuleInfo::getModified); + private final ObservableList> modifiedList = FXCollections.observableArrayList(); + private final FilteredList> displayedItems = new FilteredList<>(modifiedList); + private boolean batchUpdating = false; - private final PauseTransition pause; + private final PauseTransition saveLevelDatPause; public GameRulePage(WorldManagePage worldManagePage) { this.worldManagePage = worldManagePage; @@ -79,8 +86,8 @@ public GameRulePage(WorldManagePage worldManagePage) { } })).start(); - pause = new PauseTransition(Duration.millis(300)); - pause.setOnFinished(event -> saveLevelDat()); + saveLevelDatPause = new PauseTransition(Duration.millis(300)); + saveLevelDatPause.setOnFinished(event -> saveLevelDat()); } public void updateControls() { @@ -102,6 +109,7 @@ public void updateControls() { }); }); }); + applyModifiedFilter(RuleModifiedType.ALL); } @Override @@ -109,6 +117,30 @@ protected Skin createDefaultSkin() { return new GameRulePageSkin(this); } + public ObservableList> getModifiedList() { + return modifiedList; + } + + public void applyModifiedFilter(RuleModifiedType type) { + switch (type) { + case ALL -> modifiedList.setAll(getItems()); + case MODIFIED -> modifiedList.setAll(modifiedItems); + case UNMODIFIED -> { + modifiedList.setAll(getItems().stream() + .filter(gameRuleInfo -> !modifiedItems.contains(gameRuleInfo)) + .collect(Collectors.toSet())); + } + } + } + + public FilteredList> getModifiedItems() { + return modifiedItems; + } + + public FilteredList> getDisplayedItems() { + return displayedItems; + } + public boolean isBatchUpdating() { return batchUpdating; } @@ -130,7 +162,7 @@ void saveLevelDat() { } void requestSaveLevelDat() { - pause.playFromStart(); + saveLevelDatPause.playFromStart(); } void saveLevelDatIfNotInBatchUpdating() { @@ -149,7 +181,11 @@ void resettingAllGameRule() { Controllers.showToast(i18n("gamerule.restore_default_values_all.finish.toast")); } - @NotNull Predicate> updateSearchPredicate(String queryString) { + void updateSearchPredicate(String queryString) { + displayedItems.setPredicate(updatePredicate(queryString)); + } + + @NotNull private Predicate> updatePredicate(String queryString) { if (StringUtils.isBlank(queryString)) { return gameRuleInfo -> true; } @@ -169,4 +205,15 @@ void resettingAllGameRule() { return gameRuleInfo -> stringPredicate.test(gameRuleInfo.getDisplayName()) || stringPredicate.test(gameRuleInfo.getRuleKey()); } + + enum RuleModifiedType { + ALL, MODIFIED, UNMODIFIED; + + static final ObservableList items = FXCollections.observableList(Arrays.asList(values())); + + @Override + public String toString() { + return i18n("gamerule.filter." + name().toLowerCase(Locale.ROOT)); + } + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java index 32d98a0ca2..a21f8e033a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java @@ -19,8 +19,6 @@ import com.jfoenix.controls.*; import javafx.animation.PauseTransition; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; import javafx.collections.transformation.FilteredList; import javafx.geometry.Insets; import javafx.geometry.Pos; @@ -38,11 +36,8 @@ import org.jackhuang.hmcl.ui.construct.*; import org.jackhuang.hmcl.util.StringUtils; -import java.util.Arrays; import java.util.HashMap; -import java.util.Locale; import java.util.Map; -import java.util.stream.Collectors; import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton2; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; @@ -52,9 +47,6 @@ class GameRulePageSkin extends SkinBase { private final HBox toolBar; private final JFXTextField searchField; private final JFXListView> listView = new JFXListView<>(); - private final ObservableList> modifiedList = FXCollections.observableArrayList(); - private final FilteredList> displayedItems; - private final FilteredList> modifiedItems; private final Map cellMap = new HashMap<>(); GameRulePageSkin(GameRulePage skinnable) { @@ -69,39 +61,30 @@ class GameRulePageSkin extends SkinBase { getChildren().add(pane); } - modifiedList.setAll(getSkinnable().getItems()); - displayedItems = new FilteredList<>(modifiedList); - modifiedItems = new FilteredList<>(skinnable.getItems(), GameRuleInfo::getModified); - { toolBar = new HBox(); toolBar.setAlignment(Pos.CENTER); toolBar.setPadding(new Insets(0, 5, 0, 5)); toolBar.setSpacing(5); - JFXComboBox viewFilterComboBox = new JFXComboBox<>(RuleModifiedType.items); + JFXComboBox viewFilterComboBox = new JFXComboBox<>(GameRulePage.RuleModifiedType.items); + viewFilterComboBox.setValue(GameRulePage.RuleModifiedType.ALL); // Changes to the modifiedList are only applied at the time a type is manually selected; this is by design. viewFilterComboBox.valueProperty().addListener((observable, oldValue, newValue) -> { - applyModifiedFilter(newValue); + getSkinnable().applyModifiedFilter(newValue); }); - viewFilterComboBox.setValue(RuleModifiedType.ALL); searchField = new JFXTextField(); { searchField.setPromptText(i18n("search")); - PauseTransition pause = new PauseTransition(Duration.millis(100)); - pause.setOnFinished(event -> displayedItems.setPredicate(skinnable.updateSearchPredicate(searchField.getText()))); - searchField.textProperty().addListener((observable) -> { - pause.setRate(1); - pause.playFromStart(); - }); + PauseTransition searchPause = new PauseTransition(Duration.millis(1000)); + searchPause.setOnFinished(event -> getSkinnable().updateSearchPredicate(searchField.getText())); + searchField.textProperty().addListener((observable) -> searchPause.playFromStart()); HBox.setHgrow(searchField, Priority.ALWAYS); } - //JFXButton closeSearchBar = createToolbarButton2(null, SVG.CLOSE, searchField::clear); - //FXUtils.onEscPressed(searchField, searchField::clear); JFXButton resetAllButton = createToolbarButton2(i18n("gamerule.restore_default_values_all"), SVG.RESTORE, - () -> Controllers.dialog(new ResetDefaultValuesLayout(skinnable::resettingAllGameRule, modifiedItems, () -> applyModifiedFilter(viewFilterComboBox.getSelectionModel().getSelectedItem())))); + () -> Controllers.dialog(new ResetDefaultValuesLayout(skinnable::resettingAllGameRule, getSkinnable().getModifiedItems(), () -> getSkinnable().applyModifiedFilter(viewFilterComboBox.getSelectionModel().getSelectedItem())))); toolBar.getChildren().addAll(searchField, new Label(i18n("gamerule.filter")), viewFilterComboBox, resetAllButton); root.getContent().add(toolBar); @@ -113,7 +96,7 @@ class GameRulePageSkin extends SkinBase { center.getStyleClass().add("large-spinner-pane"); center.setContent(listView); - listView.setItems(displayedItems); + listView.setItems(getSkinnable().getDisplayedItems()); listView.setCellFactory(x -> new GameRuleListCell(listView, cellMap)); FXUtils.ignoreEvent(listView, KeyEvent.KEY_PRESSED, e -> e.getCode() == KeyCode.ESCAPE); @@ -121,18 +104,6 @@ class GameRulePageSkin extends SkinBase { } } - private void applyModifiedFilter(RuleModifiedType type) { - switch (type) { - case ALL -> modifiedList.setAll(getSkinnable().getItems()); - case MODIFIED -> modifiedList.setAll(modifiedItems); - case UNMODIFIED -> { - modifiedList.setAll(getSkinnable().getItems().stream() - .filter(gameRuleInfo -> !modifiedItems.contains(gameRuleInfo)) - .collect(Collectors.toSet())); - } - } - } - static class GameRuleListCell extends MDListCell> { private final Map cellMap; @@ -251,17 +222,6 @@ private StackPane buildResetButton(GameRuleInfo gameRule) { } } - enum RuleModifiedType { - ALL, MODIFIED, UNMODIFIED; - - static final ObservableList items = FXCollections.observableList(Arrays.asList(values())); - - @Override - public String toString() { - return i18n("gamerule.filter." + name().toLowerCase(Locale.ROOT)); - } - } - static class ResetDefaultValuesLayout extends JFXDialogLayout { public ResetDefaultValuesLayout(Runnable resettingAllGameRule, FilteredList> modifiedItems, Runnable callBack) { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java index f93ab1115f..8b6db8fe0f 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/gamerule/GameRule.java @@ -60,15 +60,16 @@ protected GameRule(List ruleKey, String displayI18nKey) { this.displayI18nKey = displayI18nKey; } - private static GameRule createSimpleGameRule(String ruleKey, boolean value) { - return new BooleanGameRule(Collections.singletonList(ruleKey), value); - } - - private static GameRule createSimpleGameRule(String ruleKey, int value) { - IntGameRule intGameRule = new IntGameRule(Collections.singletonList(ruleKey), value); - intGameRule.addMaxValue(Integer.MAX_VALUE); - intGameRule.addMinValue(Integer.MIN_VALUE); - return intGameRule; + /// Retrieves a fully populated GameRule based on an NBT tag. + /// + /// This combines parsing the tag [#createGameRuleNBT(Tag)] and applying known metadata + /// from the provided `gameRuleMap`. + public static Optional getFullGameRule(Tag tag) { + return createSimpleRuleFromTag(tag).map(simpleGameRule -> { + Optional.ofNullable(GameRuleHolder.metaDataGameRuleMap.get(tag.getName())) + .ifPresent(simpleGameRule::applyMetadata); + return simpleGameRule; + }); } /// Parses an NBT Tag to create a corresponding [GameRule]. @@ -80,59 +81,45 @@ private static GameRule createSimpleGameRule(String ruleKey, int value) { /// /// @param tag The NBT tag to parse. /// @return An Optional containing the GameRule if parsing was successful. - public static Optional createSimpleRuleFromTag(Tag tag) { + private static Optional createSimpleRuleFromTag(Tag tag) { String name = tag.getName(); if (tag instanceof IntTag intTag) { - return Optional.of(createSimpleGameRule(name, intTag.getValue())); - } - if (tag instanceof ByteTag byteTag) { - return Optional.of(createSimpleGameRule(name, byteTag.getValue() == 1)); - } - if (tag instanceof StringTag stringTag) { + return Optional.of(new IntGameRule(name, intTag.getValue())); + } else if (tag instanceof ByteTag byteTag) { + return Optional.of(new BooleanGameRule(name, byteTag.getValue() == 1)); + } else if (tag instanceof StringTag stringTag) { String value = stringTag.getValue(); if ("true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value)) { - return Optional.of(createSimpleGameRule(name, Boolean.parseBoolean(value))); + return Optional.of(new BooleanGameRule(name, Boolean.parseBoolean(value))); } Integer intValue = Lang.toIntOrNull(value); if (intValue != null) { - return Optional.of(createSimpleGameRule(name, intValue)); + return Optional.of(new IntGameRule(name, intValue)); } } return Optional.empty(); } - /// Retrieves a fully populated GameRule based on an NBT tag. - /// - /// This combines parsing the tag [#createGameRuleNBT(Tag)] and applying known metadata - /// from the provided `gameRuleMap`. - public static Optional getFullGameRule(Tag tag) { - return createSimpleRuleFromTag(tag).map(simpleGameRule -> { - Optional.ofNullable(GameRuleHolder.metaDataGameRuleMap.get(tag.getName())) - .ifPresent(simpleGameRule::applyMetadata); - return simpleGameRule; - }); - } - /// Creates a [GameRuleNBT] wrapper around an NBT Tag. /// Used for unified changing operations back to NBT format. /// /// @see GameRuleNBT public static Optional> createGameRuleNBT(Tag tag) { - if (tag instanceof StringTag stringTag && (stringTag.getValue().equals("true") || stringTag.getValue().equals("false"))) { - return Optional.of(new GameRuleNBT.StringByteGameRuleNBT(stringTag)); - } else if (tag instanceof StringTag stringTag && Lang.toIntOrNull(stringTag.getValue()) != null) { - return Optional.of(new GameRuleNBT.StringIntGameRuleNBT(stringTag)); - } else if (tag instanceof IntTag intTag) { + if (tag instanceof IntTag intTag) { return Optional.of(new GameRuleNBT.IntGameRuleNBT(intTag)); } else if (tag instanceof ByteTag byteTag) { return Optional.of(new GameRuleNBT.ByteGameRuleNBT(byteTag)); + } else if (tag instanceof StringTag stringTag && (stringTag.getValue().equals("true") || stringTag.getValue().equals("false"))) { + return Optional.of(new GameRuleNBT.StringByteGameRuleNBT(stringTag)); + } else if (tag instanceof StringTag stringTag && Lang.toIntOrNull(stringTag.getValue()) != null) { + return Optional.of(new GameRuleNBT.StringIntGameRuleNBT(stringTag)); } return Optional.empty(); } - /// Copies metadata (e.g., descriptions, ranges) from the source rule to this instance. + /// Copies metadata (descriptions, default value and ranges) from the source rule to this instance. public abstract void applyMetadata(GameRule metadataSource); public abstract GameRule deserialize(JsonObject jsonObject, Type type, JsonDeserializationContext context); @@ -158,13 +145,12 @@ public static final class BooleanGameRule extends GameRule { private boolean value = false; private final VersionedValue defaultValue = new VersionedValue<>(); - private BooleanGameRule(List ruleKey, boolean value) { - super(ruleKey, ""); - this.value = value; - } - private BooleanGameRule() { + } + private BooleanGameRule(String ruleKey, boolean value) { + super(Collections.singletonList(ruleKey), ""); + this.value = value; } @Override @@ -177,16 +163,13 @@ public void applyMetadata(GameRule metadataSource) { @Override public GameRule deserialize(JsonObject jsonObject, Type type, JsonDeserializationContext context) { - Type listType = JsonUtils.listTypeOf(String.class).getType(); - this.setRuleKey(context.deserialize(jsonObject.get("ruleKey"), listType)); + this.setRuleKey(JsonUtils.fromNonNullJson(jsonObject.get("ruleKey").toString(), JsonUtils.listTypeOf(String.class))); this.setDisplayI18nKey(jsonObject.get("displayI18nKey").getAsString()); JsonElement defaultValue = jsonObject.get("defaultValue"); if (defaultValue instanceof JsonPrimitive p && p.isBoolean()) { this.addDefaultValue(p.getAsBoolean()); - } else { - if (defaultValue instanceof JsonObject o) { - o.asMap().forEach((key, value) -> this.addDefaultValue(key, value.getAsBoolean())); - } + } else if (defaultValue instanceof JsonObject o) { + o.asMap().forEach((key, value) -> this.addDefaultValue(key, value.getAsBoolean())); } return this; @@ -221,13 +204,14 @@ public static final class IntGameRule extends GameRule { private final VersionedValue minValue = new VersionedValue<>(); private final VersionedValue maxValue = new VersionedValue<>(); - private IntGameRule(List ruleKey, int value) { - super(ruleKey, ""); - this.value = value; - } - private IntGameRule() { + } + private IntGameRule(String ruleKey, int value) { + super(Collections.singletonList(ruleKey), ""); + this.value = value; + addMaxValue(Integer.MAX_VALUE); + addMinValue(Integer.MIN_VALUE); } @Override @@ -242,16 +226,13 @@ public void applyMetadata(GameRule metadataSource) { @Override public GameRule deserialize(JsonObject jsonObject, Type type, JsonDeserializationContext context) { - Type listType = JsonUtils.listTypeOf(String.class).getType(); - this.setRuleKey(context.deserialize(jsonObject.get("ruleKey"), listType)); + this.setRuleKey(JsonUtils.fromNonNullJson(jsonObject.get("ruleKey").toString(), JsonUtils.listTypeOf(String.class))); this.setDisplayI18nKey(jsonObject.get("displayI18nKey").getAsString()); if (jsonObject.get("defaultValue") instanceof JsonPrimitive p && p.isNumber()) { this.addDefaultValue(p.getAsInt()); - } else { - if (jsonObject.get("defaultValue") instanceof JsonObject o) { - o.asMap().forEach((key, value) -> this.addDefaultValue(key, value.getAsInt())); - } + } else if (jsonObject.get("defaultValue") instanceof JsonObject o) { + o.asMap().forEach((key, value) -> this.addDefaultValue(key, value.getAsInt())); } if (jsonObject.get("maxValue") instanceof JsonPrimitive jsonPrimitive) { @@ -274,14 +255,12 @@ public GameRule deserialize(JsonObject jsonObject, Type type, JsonDeserializatio private int parseValue(JsonElement jsonElement) { JsonPrimitive primitive = jsonElement.getAsJsonPrimitive(); - int value; if (primitive.isNumber()) { - value = primitive.getAsInt(); + return primitive.getAsInt(); } else { String str = primitive.getAsString(); - value = "INT_MAX".equals(str) ? Integer.MAX_VALUE : Integer.MIN_VALUE; + return "INT_MAX".equals(str) ? Integer.MAX_VALUE : Integer.MIN_VALUE; } - return value; } public Optional getDefaultValue(GameVersionNumber gameVersionNumber) { @@ -336,9 +315,12 @@ static class GameRuleDeserializer implements JsonDeserializer { @Override public GameRule deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext context) throws JsonParseException { JsonObject jsonObject = jsonElement.getAsJsonObject(); - - boolean isInt = jsonObject.get("type").getAsString().equals("int"); - GameRule gameRule = isInt ? new IntGameRule() : new BooleanGameRule(); + GameRule gameRule; + switch (jsonObject.get("type").getAsString()) { + case "int" -> gameRule = new IntGameRule(); + case "boolean" -> gameRule = new BooleanGameRule(); + default -> throw new JsonParseException("Unknown GameRule type: " + jsonObject.get("type").getAsString()); + } return gameRule.deserialize(jsonObject, type, context); } } From 4c4ff571a15544e6b82530dc6bb49769163bcce8 Mon Sep 17 00:00:00 2001 From: mine_ Date: Sat, 10 Jan 2026 18:53:45 +0800 Subject: [PATCH 69/69] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F=E9=94=99=E8=AF=AF=E5=92=8C=E5=88=97=E8=A1=A8=E7=BB=91?= =?UTF-8?q?=E5=AE=9A=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/jackhuang/hmcl/ui/versions/GameRulePage.java | 6 ++++-- .../org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java | 1 + HMCL/src/main/resources/assets/css/root.css | 5 +++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java index 9253929329..5de54d24ba 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePage.java @@ -52,7 +52,7 @@ public class GameRulePage extends ListPageBase> { private CompoundTag levelDat; private final ObservableList> gameRuleList; - private final FilteredList> modifiedItems = new FilteredList<>(getItems(), GameRuleInfo::getModified); + private final FilteredList> modifiedItems; private final ObservableList> modifiedList = FXCollections.observableArrayList(); private final FilteredList> displayedItems = new FilteredList<>(modifiedList); @@ -72,6 +72,7 @@ public GameRulePage(WorldManagePage worldManagePage) { return new Observable[]{}; }); setItems(gameRuleList); + modifiedItems = new FilteredList<>(getItems(), GameRuleInfo::getModified); this.setLoading(true); Task.supplyAsync(this::loadWorldInfo) @@ -185,7 +186,8 @@ void updateSearchPredicate(String queryString) { displayedItems.setPredicate(updatePredicate(queryString)); } - @NotNull private Predicate> updatePredicate(String queryString) { + @NotNull + private Predicate> updatePredicate(String queryString) { if (StringUtils.isBlank(queryString)) { return gameRuleInfo -> true; } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java index a21f8e033a..cf355695a1 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/GameRulePageSkin.java @@ -73,6 +73,7 @@ class GameRulePageSkin extends SkinBase { viewFilterComboBox.valueProperty().addListener((observable, oldValue, newValue) -> { getSkinnable().applyModifiedFilter(newValue); }); + viewFilterComboBox.setPrefWidth(100); searchField = new JFXTextField(); { diff --git a/HMCL/src/main/resources/assets/css/root.css b/HMCL/src/main/resources/assets/css/root.css index 0a3222a934..8269660942 100644 --- a/HMCL/src/main/resources/assets/css/root.css +++ b/HMCL/src/main/resources/assets/css/root.css @@ -1471,6 +1471,10 @@ -fx-text-fill: -monet-on-surface; } +.no-padding .jfx-combo-box .list-cell { + -fx-padding: 4; +} + .combo-box-popup .list-view { -fx-background-color: -monet-surface-container; } @@ -1479,6 +1483,7 @@ -fx-background-color: -monet-surface-container; -fx-text-fill: -monet-on-surface; -fx-background-insets: 0.0; + -fx-padding: 8 0 8 12; } .combo-box-popup .list-view .list-cell:odd:selected > .jfx-rippler > StackPane,