diff --git a/README.md b/README.md index 8fdbf08..e6209e3 100644 --- a/README.md +++ b/README.md @@ -25,3 +25,14 @@ Modpacks & license ------------------ As per usual this mod is open-source, you can see the license [here](LICENSE.txt) + +## 拔刀剑(SlashBlade)杀敌数 / 锻造数 + +在 MTRM 的产物格(0 号)放一把拔刀剑,右侧「Slot Options」里勾选 **SlashBlade data (kills / refines)**, +用两个滑条设定杀敌数(`killCount`)和锻造数(`RepairCounter`,范围 0–999999999)。生成的脚本形如: + +``` +recipes.addShaped(.withTag({killCount: 1000, RepairCounter: 7}), [[...]]); +``` + +不装 SlashBlade 时这个选项不出现,行为和原来一致。 diff --git a/build.gradle b/build.gradle index f663fa6..1577cc1 100644 --- a/build.gradle +++ b/build.gradle @@ -53,6 +53,14 @@ processResources { } } +repositories { + mavenCentral() +} + +dependencies { + testCompile 'junit:junit:4.13.2' +} + task sourcesJar(type: Jar) { from "LICENSE.txt" from sourceSets.main.allSource diff --git a/logs/latest.log b/logs/latest.log new file mode 100644 index 0000000..e69de29 diff --git a/src/main/java/net/doubledoordev/mtrm/SlashBladeTag.java b/src/main/java/net/doubledoordev/mtrm/SlashBladeTag.java new file mode 100644 index 0000000..905c630 --- /dev/null +++ b/src/main/java/net/doubledoordev/mtrm/SlashBladeTag.java @@ -0,0 +1,67 @@ +package net.doubledoordev.mtrm; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 拔刀剑(SlashBlade)的杀敌数、锻造数:配方产物写成 {@code .withTag({killCount: K, RepairCounter: R})}。 + * 不引用 SlashBlade 的类(模组可以不装),只靠类名和 NBT 键名;键名与 SlashBlade 的 + * {@code ItemSlashBlade.KillCount} / {@code RepairCount} 一致。 + */ +public final class SlashBladeTag +{ + public static final String BLADE_CLASS = "mods.flammpfeil.slashblade.ItemSlashBlade"; + public static final String KILL_KEY = "killCount"; + public static final String REFINE_KEY = "RepairCounter"; + /** SlashBlade 的 TagPropertyIntegerWithRange 上限 */ + public static final int MAX = 999999999; + + public static final Pattern PATTERN = Pattern.compile("\\.withTag\\(\\{ ?" + KILL_KEY + ": ?(\\d+), ?" + REFINE_KEY + ": ?(\\d+) ?\\}\\)"); + + private SlashBladeTag() + { + } + + /** 这个物品类是不是拔刀剑(含所有子类,沿父类链按名字找)。 */ + public static boolean isSlashBlade(Class type) + { + for (Class c = type; c != null; c = c.getSuperclass()) + { + if (BLADE_CLASS.equals(c.getName())) return true; + } + return false; + } + + public static int clamp(int value) + { + return Math.max(0, Math.min(MAX, value)); + } + + /** 两个数一起写,超出范围的按 SlashBlade 的上下限收住。 */ + public static String build(int killCount, int refineCount) + { + return ".withTag({" + KILL_KEY + ": " + clamp(killCount) + ", " + REFINE_KEY + ": " + clamp(refineCount) + "})"; + } + + /** 从 token 里读回 {杀敌数, 锻造数};没有就返回 null。 */ + public static int[] parse(String token) + { + if (token == null) return null; + Matcher m = PATTERN.matcher(token); + if (!m.find()) return null; + try + { + return new int[]{clamp(Integer.parseInt(m.group(1))), clamp(Integer.parseInt(m.group(2)))}; + } + catch (NumberFormatException e) + { + return null; // 超出 int + } + } + + /** 把 token 里已有的拔刀剑标签换成新的;{@code tag} 为空串就是去掉。 */ + public static String replaceIn(String token, String tag) + { + return PATTERN.matcher(token).replaceFirst(Matcher.quoteReplacement(tag)); + } +} diff --git a/src/main/java/net/doubledoordev/mtrm/gui/MTRMGui.java b/src/main/java/net/doubledoordev/mtrm/gui/MTRMGui.java index d9e4213..76a1371 100644 --- a/src/main/java/net/doubledoordev/mtrm/gui/MTRMGui.java +++ b/src/main/java/net/doubledoordev/mtrm/gui/MTRMGui.java @@ -6,6 +6,7 @@ import cpw.mods.fml.client.config.GuiSlider; import cpw.mods.fml.common.registry.GameData; import cpw.mods.fml.common.registry.GameRegistry; import net.doubledoordev.mtrm.MineTweakerRecipeMaker; +import net.doubledoordev.mtrm.SlashBladeTag; import net.doubledoordev.mtrm.network.MessageSend; import net.minecraft.client.gui.GuiButton; import net.minecraft.client.gui.GuiTextField; @@ -68,6 +69,7 @@ public class MTRMGui extends GuiContainer private static final int ID_OPTION_RETURN_OK = 29; private static final int ID_OPTION_ID_OPTION_TRANSFORM_REPLACE = 30; private static final int ID_OPTION_GIVE_BACK = 31; + private static final int ID_OPTION_SLASHBLADE = 32; private final GuiSlider.ISlider iSlider = new GuiSlider.ISlider() { @@ -98,6 +100,9 @@ public class MTRMGui extends GuiContainer private GuiCheckBox noReturn; private GuiCheckBox transformDamage; private GuiCheckBox transformReplace; + private GuiCheckBox slashBlade; + private GuiSlider killCount; + private GuiSlider refineCount; private GuiCheckBox giveBack; private GuiButtonExt returnOk; private Map sliders = new HashMap<>(); @@ -141,6 +146,7 @@ public class MTRMGui extends GuiContainer builder.append(stackName); if (!oreDict && (metaWildcard || stack.getItemDamage() != 0)) builder.append(':').append(metaWildcard || stack.getItemDamage() == OreDictionary.WILDCARD_VALUE ? "*" : stack.getItemDamage()); builder.append('>'); + if (slashBladeTagApplies(stack)) builder.append(SlashBladeTag.build(killCount.getValueInt(), refineCount.getValueInt())); if (stack.stackSize > 1) builder.append(" * ").append(stack.stackSize); if (anyDamage.isChecked()) builder.append(".anyDamage()"); if (onlyDamaged.isChecked()) builder.append(".onlyDamaged()"); @@ -167,6 +173,23 @@ public class MTRMGui extends GuiContainer return builder.toString(); } + /** 只有产物格(0)里放的是拔刀剑、并且勾了「拔刀剑数据」才写标签;输入格没有"改数据"这回事。 */ + private boolean slashBladeTagApplies(ItemStack stack) + { + return editing == 0 && slashBlade != null && slashBlade.isChecked() && stack != null && SlashBladeTag.isSlashBlade(stack.getItem().getClass()); + } + + private void updateSlashBladeOptions(ItemStack stack, String token) + { + boolean blade = editing == 0 && stack != null && SlashBladeTag.isSlashBlade(stack.getItem().getClass()); + slashBlade.visible = blade; + int[] values = blade ? SlashBladeTag.parse(token) : null; + slashBlade.setIsChecked(values != null); + killCount.setValue(values == null ? 0 : values[0]); + refineCount.setValue(values == null ? 0 : values[1]); + killCount.visible = refineCount.visible = blade && values != null; + } + @Override public void initGui() { @@ -202,6 +225,10 @@ public class MTRMGui extends GuiContainer this.buttonList.add(reuse = new GuiCheckBox(ID_OPTION_REUSE, wOffset, hOffset += 10, "Reuse", false)); this.buttonList.add(noReturn = new GuiCheckBox(ID_OPTION_NO_RETURN, wOffset, hOffset += 10, "No Return", false)); this.buttonList.add(giveBack = new GuiCheckBox(ID_OPTION_GIVE_BACK, wOffset, hOffset += 10, "GiveBack", false)); + this.buttonList.add(slashBlade = new GuiCheckBox(ID_OPTION_SLASHBLADE, wOffset, hOffset += 10, "SlashBlade data (kills / refines)", false)); + this.buttonList.add(killCount = new GuiSlider(200, wOffset, hOffset + 50, 220 + this.xSize, 20, "Kill count = ", "", 0, SlashBladeTag.MAX, 0, false, true, iSlider)); + this.buttonList.add(refineCount = new GuiSlider(201, wOffset, hOffset + 70, 220 + this.xSize, 20, "Refine count = ", "", 0, SlashBladeTag.MAX, 0, false, true, iSlider)); + slashBlade.visible = killCount.visible = refineCount.visible = false; this.buttonList.add(nextOreDict = new GuiButtonExt(ID_OPTION_NEXT_OREDICT, wOffset, hOffset += 40, 110, 20, "Next oredict value")); this.buttonList.add(optionsOk = new GuiButtonExt(ID_OPTION_OK, wOffset, hOffset += 20, 110, 20, "Save changes!")); @@ -350,6 +377,10 @@ public class MTRMGui extends GuiContainer radioBoxToggle((GuiCheckBox) btn, allTransform); tokenTxt.setText(getStackToken(false, inventorySlots.getSlot(editing).getStack())); break; + case ID_OPTION_SLASHBLADE: + killCount.visible = refineCount.visible = slashBlade.isChecked(); + tokenTxt.setText(getStackToken(false, inventorySlots.getSlot(editing).getStack())); + break; case ID_OPTION_OREDICT: nextOreDict.enabled = oreDict.isChecked(); case ID_OPTION_NEXT_OREDICT: @@ -475,6 +506,7 @@ public class MTRMGui extends GuiContainer } setOptionsVisible(true); + updateSlashBladeOptions(container.getSlot(id).getStack(), token); } private void setOptionsVisible(boolean visible) @@ -500,6 +532,7 @@ public class MTRMGui extends GuiContainer } } + if (!visible) slashBlade.visible = killCount.visible = refineCount.visible = false; tokenTxt.setVisible(visible); if (!visible) container.putStackInSlot(RETURN_SLOT_ID, null); diff --git a/src/test/java/net/doubledoordev/mtrm/SlashBladeTagTest.java b/src/test/java/net/doubledoordev/mtrm/SlashBladeTagTest.java new file mode 100644 index 0000000..62c40f8 --- /dev/null +++ b/src/test/java/net/doubledoordev/mtrm/SlashBladeTagTest.java @@ -0,0 +1,88 @@ +package net.doubledoordev.mtrm; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +public class SlashBladeTagTest +{ + @Test + public void buildsTheMineTweakerTag() + { + assertEquals(".withTag({killCount: 1000, RepairCounter: 7})", SlashBladeTag.build(1000, 7)); + } + + @Test + public void clampsToSlashBladesRange() + { + assertEquals(".withTag({killCount: 0, RepairCounter: 999999999})", SlashBladeTag.build(-5, Integer.MAX_VALUE)); + } + + @Test + public void parsesWhatItBuilt() + { + assertArrayEquals(new int[]{1000, 7}, SlashBladeTag.parse("" + SlashBladeTag.build(1000, 7) + " * 1")); + } + + @Test + public void parseNeedsBothNumbers() + { + assertNull(SlashBladeTag.parse("")); + assertNull(SlashBladeTag.parse(".withTag({killCount: 3})")); + assertNull(SlashBladeTag.parse(null)); + assertNull(SlashBladeTag.parse(".withTag({killCount: 99999999999, RepairCounter: 1})")); + } + + @Test + public void replaceSwapsOrRemovesTheTag() + { + String token = "" + SlashBladeTag.build(1, 2) + " * 1"; + assertEquals(".withTag({killCount: 5, RepairCounter: 6}) * 1", SlashBladeTag.replaceIn(token, SlashBladeTag.build(5, 6))); + assertEquals(" * 1", SlashBladeTag.replaceIn(token, "")); + } + + static class ItemSlashBlade + { + } + + @Test + public void recognisesBladesByClassNameThroughSubclasses() + { + assertFalse(SlashBladeTag.isSlashBlade(Object.class)); + assertFalse(SlashBladeTag.isSlashBlade(ItemSlashBlade.class)); // 同名不同包不算 + assertFalse(SlashBladeTag.isSlashBlade(null)); + } + + @Test + public void realSlashBladeClassIsRecognisedWithTheNbtKeysItUses() throws Exception + { + Class blade = Class.forName(SlashBladeTag.BLADE_CLASS); + assertTrue(SlashBladeTag.isSlashBlade(blade)); + Class sub = Class.forName("mods.flammpfeil.slashblade.ItemSlashBladeNamed"); + assertTrue(SlashBladeTag.isSlashBlade(sub)); + Object kill = blade.getField("KillCount").get(null); + Object refine = blade.getField("RepairCount").get(null); + assertEquals(SlashBladeTag.KILL_KEY, nameOf(kill)); + assertEquals(SlashBladeTag.REFINE_KEY, nameOf(refine)); + } + + private static String nameOf(Object accessor) throws Exception + { + for (Class c = accessor.getClass(); c != null; c = c.getSuperclass()) + { + for (java.lang.reflect.Field f : c.getDeclaredFields()) + { + if (f.getType() == String.class) + { + f.setAccessible(true); + return (String) f.get(accessor); + } + } + } + throw new AssertionError("no name field"); + } +}