如何以一个优雅的姿势禁用特定物品特定附魔

Minecraft version:1.10.2
FML version:12.18.3.2316
目标:禁用特定物品特定附魔
大致方向:作为MT的附属mod存在
由于1.10.2的CraftTweaker是MineTweaker的一个Fork版本,所以以下均称为MT

首先在com.seraphjack.banenchantment包下创建Mod主类:BanEnchantment

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.seraphjack.banenchantment;

import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;

@Mod(modid = BanEnchantment.MODID, dependencies = "required-after:MineTweaker3")
public class BanEnchantment {
public static final String MODID = "banenchantment";

@Mod.EventHandler
public void preInit(FMLPreInitializationEvent e){
//TODO
}

@Mod.EventHandler
public void init(FMLInitializationEvent e) {
//TODO
}
}

在Mod主类中使用@EventHandler注释可以订阅到Forge发的5个加载阶段的Event,此处不再赘述。由于是MT的附属mod,所以@Mod注解中的参数添加了dependencies = “required-after:MineTweaker3”,这样就可以保证这个mod的加载会在MT加载之后,并且如果缺失前置会优雅的显示缺少MT,而不是直接崩溃。
新建一个类,用于记录禁止物品的信息和附魔信息
在com.seraphjack.banenchantment.handler包下新建类:BanItem

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.seraphjack.banenchantment.handler;

import net.minecraft.item.Item;

import java.util.LinkedList;
import java.util.List;

public class BanItem {
public Item banItem;
public List<Integer> banEncList = new LinkedList<Integer>();
public String name;

public BanItem(Item item, String name) {
this.banItem = item;
this.name = name;
}

public void addBanList(int encId) {
banEncList.add(encId);
}
}

可以看到该类一共有3个field:public Item banitem,public List banEncList,public String name
banItem是指该对象所对应的物品,即哪个物品需要禁用下列附魔
banEncList是需要禁用的附魔的id
name是该对象在MT中的一个标识
对这三个field封装的方法十分简单,不再赘述。
由于Minecraft没有提供附魔相关的Event,所以只能牺牲性能,在LivingUpdateEvent的时候检测玩家背包
在com.seraphjack.banenchantment.handler包下新建类:EventHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package com.seraphjack.banenchantment.handler;

import com.seraphjack.banenchantment.common.ConfigLoader;
import net.minecraft.enchantment.Enchantment;
import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagList;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.entity.living.LivingEvent;
import net.minecraftforge.event.entity.player.PlayerInteractEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;

import java.util.LinkedList;
import java.util.List;
import java.util.Map;

public class EventHandler {
private List<BanItem> banItemList;

public EventHandler() {
MinecraftForge.EVENT_BUS.register(this);
banItemList = new LinkedList<BanItem>();
}

public void addBanEnchantment(BanItem item) {
banItemList.add(item);
}

@SubscribeEvent
public void onLiving(LivingEvent.LivingUpdateEvent e) {
if (e.getEntity() instanceof EntityPlayer) {
EntityPlayer player = (EntityPlayer) e.getEntity();
for (ItemStack stack : player.inventory.armorInventory) {
if (stack != null)
checkBanItem(stack);
}
for (ItemStack stack : player.inventory.mainInventory) {
if (stack != null)
checkBanItem(stack);
}
for (ItemStack stack : player.inventory.offHandInventory) {
if (stack != null)
checkBanItem(stack);
}
}
}

private void checkBanItem(ItemStack stack) {
for (BanItem banItem : banItemList) {
if (stack.getItem() == banItem.banItem) {
Map<Enchantment, Integer> enchantments = EnchantmentHelper.getEnchantments(stack);
for (int id : banItem.banEncList) {
if (enchantments.containsKey(Enchantment.getEnchantmentByID(id))) {
NBTTagList enchantmentTagList = stack.getEnchantmentTagList();
for(int i=0;i<enchantmentTagList.tagCount();i++){
if(enchantmentTagList.getCompoundTagAt(i).getShort("id")==id) {
enchantmentTagList.removeTag(i);
if(ConfigLoader.debug)
System.out.println("Detected banned item");
}
}
}
}
}
}
}

public void addBanEnchantment(String name, int encId) {
for (BanItem banedItem : banItemList) {
if (banedItem.name.equals(name))
banedItem.addBanList(encId);
}
}
}

在构造函数中,我调用了MinecraftForge.EVENT_BUS.register(this)来订阅事件
此外,我使用了一个链表:banItemList,来存放所有的BanItem实例
接下来看checkBanItem方法
参数很简单,只有一个ItemStack,没有返回值,用于检测该stack是否合法,如果不合法直接删掉不合法的附魔

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void checkBanItem(ItemStack stack) {
for (BanItem banItem : banItemList) {
if (stack.getItem() == banItem.banItem) {
Map<Enchantment, Integer> enchantments = EnchantmentHelper.getEnchantments(stack);
for (int id : banItem.banEncList) {
if (enchantments.containsKey(Enchantment.getEnchantmentByID(id))) {
NBTTagList enchantmentTagList = stack.getEnchantmentTagList();
for(int i=0;i<enchantmentTagList.tagCount();i++){
if(enchantmentTagList.getCompoundTagAt(i).getShort("id")==id)
enchantmentTagList.removeTag(i);
}
}
}
}
}
}

之后调用了EnchantmentHelper获得了该ItemStack的所有附魔,然后二重循环分别遍历BanItemList,BanItem实例中的BanEnchantmentList,检查Enchantment的Map中是否包含非法附魔,如果包含再调用enchantmentTagList.removeTag()删除非法附魔
接下来就是MT相关的支持部分了,首先肯定要把MT import进workspace的Library,方法不再赘述
在com.seraphjack.banenchantment.handler包下新建类:ZenScripts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.seraphjack.banenchantment.handler;

import com.seraphjack.banenchantment.BanEnchantment;
import minetweaker.MineTweakerAPI;
import minetweaker.api.item.IItemStack;
import net.minecraft.item.ItemStack;
import stanhebben.zenscript.annotations.ZenClass;
import stanhebben.zenscript.annotations.ZenMethod;

@ZenClass("mods.banenchantment")
public class ZenScripts {
public ZenScripts(){
MineTweakerAPI.registerClass(this.getClass());
}

@ZenMethod
public static void createBanItem(IItemStack stack, String name) {
if(stack.getInternal() instanceof ItemStack){
BanEnchantment.eventHandler.addBanEnchantment(new BanItem(((ItemStack) stack.getInternal()).getItem(),name));
}
else {
MineTweakerAPI.logError("BAN ENCHANTMENT ERROR");
}
}

@ZenMethod
public static void addEnchantment(int encId, String name) {
BanEnchantment.eventHandler.addBanEnchantment(name,encId);
}
}

只是把之前实现的方法和MT提供的接口对接,没有什么需要解释的。
完善一下mcmod.info:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[
{
"modid": "banenchantment",
"name": "BanEnchantment",
"description": "Ban enchantments.",
"version": "${version}",
"mcversion": "${mcversion}",
"url": "",
"updateUrl": "",
"authorList": ["Seraph_JACK"],
"credits": "Seraph_JACK",
"logoFile": "",
"screenshots": [],
"dependencies": []
}
]

在主类中注册以上的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.seraphjack.banenchantment;

import com.seraphjack.banenchantment.handler.EventHandler;
import com.seraphjack.banenchantment.handler.ZenScripts;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;

@Mod(modid = BanEnchantment.MODID, dependencies = "required-after:MineTweaker3")
public class BanEnchantment {
public static final String MODID = "banenchantment";
public static EventHandler eventHandler;

@Mod.EventHandler
public void preInit(FMLPreInitializationEvent e){
//TODO
}

@Mod.EventHandler
public void init(FMLInitializationEvent e) {
eventHandler = new EventHandler();
new ZenScripts();
}
}

至此已经基本完成了,但依然不完美,使用者并不能很方便的获得附魔id,于是我又提供了一个Debug模式:
在com.seraphjack.banenchantment下新建包common,并新建类ConfigLoader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.seraphjack.banenchantment.common;

import net.minecraftforge.common.config.Configuration;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;

public class ConfigLoader {
private static Configuration config;
public static boolean debug;
public ConfigLoader(FMLPreInitializationEvent e){
config = new Configuration(e.getSuggestedConfigurationFile());
load();
}

public static void load(){
debug = config.getBoolean("debug","banenchantment.debug",false,"Enable Debug");
config.save();
}
}

有关Config的使用可以参考 zzzz的教程,此处不再赘述。
然后修改EventHandler部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
private void checkBanItem(ItemStack stack) {
for (BanItem banItem : banItemList) {
if (stack.getItem() == banItem.banItem) {
Map<Enchantment, Integer> enchantments = EnchantmentHelper.getEnchantments(stack);
for (int id : banItem.banEncList) {
if (enchantments.containsKey(Enchantment.getEnchantmentByID(id))) {
NBTTagList enchantmentTagList = stack.getEnchantmentTagList();
for(int i=0;i<enchantmentTagList.tagCount();i++){
if(enchantmentTagList.getCompoundTagAt(i).getShort("id")==id) {
enchantmentTagList.removeTag(i);
if(ConfigLoader.debug)
System.out.println("Detected banned item");
}
}
}
}
}
}
}

public void addBanEnchantment(String name, int encId) {
for (BanItem banedItem : banItemList) {
if (banedItem.name.equals(name))
banedItem.addBanList(encId);
}
}

@SubscribeEvent
public void onPlayerInteract(PlayerInteractEvent e) {
if (ConfigLoader.debug && e.getItemStack() != null) {
Map<Enchantment, Integer> enchantments = EnchantmentHelper.getEnchantments(e.getItemStack());
for (Map.Entry<Enchantment, Integer> entry : enchantments.entrySet()) {
System.out.print(entry.getKey().getName() + ':');
System.out.println(Enchantment.getEnchantmentID(entry.getKey()));
}
}
}

在主类中注册ConfigLoader:

1
2
3
4
@Mod.EventHandler
public void preInit(FMLPreInitializationEvent e){
new ConfigLoader();
}

之后在workspace目录/run/scripts下新建MT脚本文件
BETest.zs

1
2
mods.banenchantment.createBanItem(<minecraft:diamond_pickaxe>,"DiamondPick");
mods.banenchantment.addEnchantment(34,"DiamondPick");

进入游戏测试,发现给钻石镐附魔的耐久三被瞬间删除
至此需要实现的功能已经完成了。
这个mod同时也放在了我的GitHub仓库