Merge pull request 'feat: 产物格里的拔刀剑可以设杀敌数与锻造数' (#1) from feature/slashblade-tag into 1.7.10

This commit was merged in pull request #1.
This commit is contained in:
layue13 committed 2026-11-11 06:06:34 +00:00
commit 505f8aaab7
6 files changed
+207

No files matched your search

+11
View File
@@ -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(<flammpfeil.slashblade:slashblade>.withTag({killCount: 1000, RepairCounter: 7}), [[...]]);
```
不装 SlashBlade 时这个选项不出现,行为和原来一致。
+8
View File
@@ -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
View File
Whitespace-only changes.
@@ -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));
}
}
@@ -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<GuiCheckBox, GuiSlider[]> 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);
@@ -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("<flammpfeil.slashblade:slashblade>" + SlashBladeTag.build(1000, 7) + " * 1"));
}
@Test
public void parseNeedsBothNumbers()
{
assertNull(SlashBladeTag.parse("<flammpfeil.slashblade:slashblade>"));
assertNull(SlashBladeTag.parse("<a>.withTag({killCount: 3})"));
assertNull(SlashBladeTag.parse(null));
assertNull(SlashBladeTag.parse("<a>.withTag({killCount: 99999999999, RepairCounter: 1})"));
}
@Test
public void replaceSwapsOrRemovesTheTag()
{
String token = "<flammpfeil.slashblade:slashblade>" + SlashBladeTag.build(1, 2) + " * 1";
assertEquals("<flammpfeil.slashblade:slashblade>.withTag({killCount: 5, RepairCounter: 6}) * 1", SlashBladeTag.replaceIn(token, SlashBladeTag.build(5, 6)));
assertEquals("<flammpfeil.slashblade:slashblade> * 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");
}
}