本帖最后由 森林蝙蝠 于 2019-5-20 16:19 编辑

前言:
在我之前,有很多人做过不同版本的forge mod教程(论坛此类帖子多达135个),但抑或声名不显(例如deathwolf的教程),抑或时过境迁(例如4z的教程),抑或没啥作用,希望通过这篇贴子能带来一些不一样的东西。




想多了,我才不会做论坛不鼓励的纯Java教程呢!

可以去购买此表情包上的书籍(最近出的阿里《码出高效》也有助于快速上手,建议结合Java核心技术一起阅读)或观看口碑良好的Java视频教程(例如马士兵),不要说自己没钱买书,你有那钱买硬件氪游戏把妹子,就不能买两本技术性的书籍开卷有益一下?
那么开发用的软件怎么搞呢?可看此教程:http://www.mcbbs.net/thread-792717-1-1.html,不要害怕英文!
有些人可能会用groovy写gradle脚本或者直接写mod本身代码,教程:https://www.w3cschool.cn/groovy/


想进行forge开发,首先要搞清楚一件事情:当我们在部署forge环境时,我们在做什么?
答:是在处理forge的依赖项目和生成启动
MC和forge并不仅仅依赖Java本身,而是若干个其他程序包,下面将罗列出来:
gson:google出品的json解析器,你用来注册合成表,模型等的各种json资源文件,由它解析加载。
guava:google出品的著名Java开发框架,提供了很多实用的集合(例如Multimap<K,V>)和并发相关(例如ListenableFuture),该框架提供的EventBus类,被forge用来做事件系统。
icu4j-core-mojang:icu4j是International Components for Unicode(Unicode本地化组件)在Java上的实现,你能实现汉化,其实靠的是这个包。
authlib:mojang自己用来负责玩家登陆的工具,用于正版启动器。
realms:原版领域服相关。
paulscode:一个提供Java音效的包。
akka actor:一个用scala编写的高性能并发框架,但可惜在MC开发中形同虚设,反倒带来了scala类库下载的不便,如果你的HMCL资源下载失败了,看看是不是它的问题吧。
apache common:一个和guava对标的著名Java开发框架,提供了线程监视等实用功能(MC居然还在用common 2.5这样老旧的版本)
netty:高性能网络通信框架,MC内部的服务端(NMS)基于此实现,如果你的服务器掉线了,经常会看到来自netty的报错。
fastutil:提供了一系列高效存储大量元素的集合,例如BigArrays类,Object2ObjectOpenHashMap集合,BigList接口等。
java3d:Java自己的一个简易3d制作包。
jline:用来处理控制台输入的一个Java类库。
jna(Java native access):Java自带的调用本地(native)方法的类库。
launchwrapper:mojang自己提供的一个类加载工具,内含ITweaker和IClassTransformer这两个修改类加载和编写字节码的重要接口,以及MC原版类加载器LaunchClassLoader,因不支持Java 9+,将在1.13的forge被废弃。
log4j:最早出现的Java日志输出类库,现被广泛使用。
asm:Java的虚拟机指令只有200多个,不超过byte[]类型保存的上限,因此可以用byte来保存这些指令,故称“字节码”,asm就是目前使用最广泛的字节码操纵框架(用来动态修改其他人的代码),forge本身就是建立在asm修改MC的基础上。
scala:scala语言的标准库,将在1.13的forge中被移除。
这些依赖往往没有被充分利用,大多数情况下,就是个玩具钟——虚有其表。


这么一通交待下来,看的晕头转向了吧?总而言之,如果要把这些依赖库一一手动下载下来,不知道要浪费多少宝贵的时间和精力,最重要的是没有相应的主类启动它们(MC的主类只管自己,无视他人),因此需要gradle来管理这些依赖,并按照给定配置对它们生成主类(GradleStart和GradleStartServer)。
forge自带了forgegradle插件,当用户按照教程运行/gradlew setupDecompWorkspace时,就会自行下载gradle,并根据build.gradle文件,从forge默认的maven源(http://files.minecraftforge.net/maven)下载相应的依赖文件,对其进行构建生成主类。
然而因为某些你也知道的原因,从外网下载gradle速度会很慢很慢,时常掉线——大多数情况下这是你构建失败的元凶。而且forge依然在采用gradle 2.14这个老版本,而最新的gradle出到了5——因此,不通过forge自己的配置,自行下载gradle是个非常明智的选择。
如果你已经下载了自己的gradle,如何让forge或者IDE“知道”这件事呢?
方法1:修改gradle-wrapper.properties文件里的gradle版本(从2.14改成4.x),然后在构建开始,下载gradle时将指定文件夹里的gradle替换掉,重启构建(如果你网够好可以等待它下完)
方法2:在IDEA里指定gradle路径。

选中“Use auto import(自动导入)”选项,选择“Use local gradle distribution”选项,即可在gradle home选项中指定你安装gradle的位置,以及用来运行gradle的JVM位置。
如果你在这之前已经构建过,不想再下载一次依赖,可以选择“Offline work(离线构建)”选项,然后在下方选择上一次构建后依赖存放的位置(默认是C:/Users/用户名/.gradle)。
然后点击“File”→“New”→“Project from existing sources”,在弹出的对话框中选择build.gradle文件,会弹出一个和上图类似的对话框:

设置同上,点击OK,即可导入。
也可以点击“File”→“Open”,然后打开build.gradle,会弹出对话框问你是否以一个工程的形式打开它。

以这两种方式,IDEA都可以自动为你配置forge环境而不需要输入什么指令(记得给自己开一下魔法上网加速下载),如果部署成功,你会在externallib里看到一大堆包(botania是植物魔法的名字,如果你不做植魔附属请忽略它):

到此为止,你的forge开发环境就建立成功了。

如果你还想加入别的依赖(例如上图的botania),可能需要了解一下forge配置工程的核心文件build.gradle——其实所有gradle工程都是这个文件,这一点forge除了做了个forgegradle插件来指定自己的gradle指令之外,和一般的gradle工程并无太大区别。

buildscript {
    repositories {
        jcenter()
        maven { url = "http://files.minecraftforge.net/maven" }
        maven { url = "http://maven.ic2.player.to/" }
        //maven { url = "http://dvs1.progwml6.com/files/maven" }
}
    dependencies {
        classpath 'net.minecraftforge.gradle:ForgeGradle:2.3-SNAPSHOT'
    }
}
apply plugin: 'net.minecraftforge.gradle.forge'
//Only edit below this line, the above code adds and enables the necessary things for Forge to be setup.
version = "1.0"
group = "com.forestbat.warhammer" // http://maven.apache.org/guides/mini/guide-naming-conventions.html
archivesBaseName = "warhammer"

sourceCompatibility = 1.8
// Need this here so eclipse task generates correctly.
compileJava {
    sourceCompatibility = 1.8
}

minecraft {
    version = "1.12.2-14.23.2.2611"
    runDir = "run"
   
    // the mappings can be changed at any time, and must be in the following format.
    // snapshot_YYYYMMDD   snapshot are built nightly.
    // stable_#            stables are built at the discretion of the MCP team.
    // Use non-default mappings at your own risk. they may not always work.
    // simply re-run your setup task after changing the mappings to update your workspace.
    mappings = "snapshot_20171003"
    // makeObfSourceJar = false // an Srg named sources jar is made by default. uncomment this to disable.
}

dependencies {
    // you may put jars on which you depend on in ./libs
    // or you may define them like so..
    //compile "some.group:artifact:version:classifier"
    //compile "some.group:artifact:version"
    // real examples
    //compile 'com.mod-buildcraft:buildcraft:6.0.8:dev'  // adds buildcraft to the dev env
    //compile 'com.googlecode.efficient-java-matrix-library:ejml:0.24' // adds ejml to the dev env
    // the 'provided' configuration is for optional dependencies that exist at compile-time but might not at runtime.
deobfProvided "net.industrial-craft:industrialcraft-2:2.8.84:api"
    // the deobf configurations:  'deobfCompile' and 'deobfProvided' are the same as the normal compile and provided,
    // except that these dependencies get remapped to your current MCP mappings
    //deobfCompile 'com.mod-buildcraft:buildcraft:6.0.8:dev'
    //deobfProvided 'com.mod-buildcraft:buildcraft:6.0.8:dev'
    // for more info...
    // http://www.gradle.org/docs/curre ... ncies_tutorial.html
    // http://www.gradle.org/docs/curre ... ncy_management.html
}

processResources {
    // this will ensure that this task is redone when the versions change.
inputs.property "version", project.version
    inputs.property "mcversion", project.minecraft.version

    // replace stuff in mcmod.info, nothing else
from(sourceSets.main.resources.srcDirs) {
        include 'mcmod.info'
               
        // replace version and mcversion
expand 'version':project.version, 'mcversion':project.minecraft.version
    }
        
    // copy everything else except the mcmod.info
from(sourceSets.main.resources.srcDirs) {
        exclude 'mcmod.info'
    }
}
如上,是一个由minecraftdev插件生成的build.gradle文件,从注释中我们能看到各种内容对应的含义:
buildscripts:这个代码块内的repositories地址专门为了此build.gradle文件而服务。

repositories:该工程的依赖库地址(一般为maven),你可以从中下载到很多你所需要的依赖,类似于自选商场,区别于dependencies(一个个的jar文件,类似于你已经拿到手的商品)。如果声明在buildscripts之外,那么这些库将为这个项目本身服务。
jcenter()//区别于maven中心库mavenCentral()的库,为gradle内置库,如果下载卡顿可以选用jcenter(){url 'http://jcenter.bintray.com/'},或者其他第三方maven库,例如阿里的中心库http://maven.aliyun.com/nexus/content/groups/public/
maven { url = "http://files.minecraftforge.net/maven" }//指向http://files.minecraftforge.net的maven库,链接可打开
maven { url = "http://maven.ic2.player.to/" }//指向工业2的maven仓库,链接可打开
maven { url = "http://dvs1.progwml6.com/files/maven" } //另一个有用的maven仓库

//在这里指定你的项目隶属,版本号
version = "1.0" //版本是1.0
group = "com.forestbat.warhammer" //group的命名遵守maven“三段”规则,com是组织性质,forestbat是组织名(其实就是我),warhammer是项目名

archivesBaseName = "warhammer1.0" //该项目下的模块名,maven通过group,archive和version将每一个代码库区分开来
sourceCompatibilty:Java语言特性等级,当前选择的是8,代表Java 8


minecraft代码块下,version代表你使用的forge版本,runDir代表你的测试环境下MC客户端的运行位置,mappings代表forge使用的mcp反混淆表版本,1.12默认为snapshot_20171003。


dependencies代码块下,可以指定你的项目所依赖的jar文件,分为三个等级:
compile:你的项目强制依赖这个jar,否则编译不过;
runtime:你的项目在运行时要求这个jar,否则无法运行;
provided:你的项目和它是联动关系,没有也罢。
其deobf版本(deobfCompile,deobfRuntime,deobfProvided)是会经过forge的反混淆用作依赖的,适合那些不开源的前置mod。



首先看看“真正的”主类GradleStart(若为服务器,应为GradleStartServer):
  1. <font color="Black">public static void main(String[] args) throws Throwable { //main是除了JavaFX之外所有程序的入口点
  2.         hackNatives(); //在这里调用一些本地的库,例如lwjgl.dll
  3.         (new GradleStart()).launch(args); //进行游戏启动前的一些工作,例如搜索coremod以及加载notchname-srgname的转换表,然后启动。
  4.     }</font>
复制代码
这是一个由IDEA的minecraftdev插件生成的主类:

  1. <font color="Black">@Mod(
  2.         modid = MOD_ID,
  3.         name = MOD_NAME,
  4.         version = VERSION
  5. )
  6. public class YourMod {

  7.     public static final String MOD_ID=“yourmod”;//MOD_ID是唯一的mod标识符,不应随意更改
  8.     public static final String MOD_NAME=“yourmod”;
  9.     public static final String VERSION=“xxx”;

  10.     /**
  11.      * This is the instance of your mod as created by Forge. It will never be null.
  12.      */
  13.     @Mod.Instance(MOD_ID)
  14.     public static YourMod INSTANCE;

  15.     public static CreativeTabs YourMod=new CreativeTabs("yourmod") {//为你的mod添加创造物品栏
  16.         @MethodsReturnNonnullByDefault
  17.         public ItemStack getTabIconItem() {
  18.             return null;//指定创造物品栏的标志物品,例如林业的创造栏是个蜜蜂
  19.         }
  20.     };
  21.     //forge初始化进程包括三个阶段:预初始化(preinit),初始化(init),后初始化(postinit)
  22.     @Mod.EventHandler
  23.     public void preinit(FMLPreInitializationEvent event) {//该方法负责监听预初始化事件,并可以用event对象调用相应的方法完成一些工作
  24.         GameRegistry.registerTileEntity(TileEntityChest.class,"TileEntityChest"); //你可以在这一阶段注册TileEntity(例如箱子)
  25.         GameRegistry.registerTileEntity(TileEntityChest.class,new ResourceLocation("TileEntityChest"));
  26.         //较新版本的forge建议用resourcelocation注册实体,resourcelocation对象一般指向json文件,后面会提及
  27.         //也可以用RegistryEvent.Register<T>对象来进行注册,推荐的做法,下文会提及
  28. }

  29.     @Mod.EventHandler
  30.     public void init(FMLInitializationEvent event) {
  31.     //该方法负责监听初始化事件,并可以用该事件对象调用相应方法完成一些工作,你可以在这里注册你的合成表

  32.     }

  33.     @Mod.EventHandler
  34.     public void postinit(FMLPostInitializationEvent event) {
  35.       //该方法负责监听后初始化事件,并可以用该事件对象调用相应方法完成一些工作
  36.     }

  37. </font> <font color="Black">
  38.     @GameRegistry.ObjectHolder(MOD_ID)
  39.     public static class Blocks {//在这个内部类中以public static final形式,声明一些方块便于调用,类似于minecraft.init.Blocks类
  40.       /*
  41.           public static final MySpecialBlock mySpecialBlock = null; // placeholder for special block below
  42.       */
  43.     }

  44.     /**
  45.      * Forge will automatically look up and bind items to the fields in this class
  46.      * based on their registry name.
  47.      */
  48.     @GameRegistry.ObjectHolder(MOD_ID)
  49.     public static class Items {//在这个内部类中初始化一些物品(必须是public static final声明)便于调用,类似于minecraft.init.Items类
  50.       /*
  51.           public static final ItemBlock mySpecialBlock = null; // itemblock for the block above
  52.           public static final MySpecialItem mySpecialItem = null; // placeholder for special item below
  53.           public static YourItems xxxItem=null;
  54.       */
  55.     }

  56.     @Mod.EventBusSubscriber
  57.     public static class ObjectRegistryHandler {
  58.         /**
  59.          * 在这个类中监听注册事件,以注册你自定义的东西
  60.         */
  61.         @SubscribeEvent  //该注解负责监听各种事件,如果你传入的参数里有Event类型的MC事件,需要加上这个注解
  62.         public static void addItems(RegistryEvent.Register<Item> event) {
  63.           //通过RegistryEvent.Register<T>注册物品,每个你编写的物品都要通过这种方式注册
  64.            /*
  65.              event.getRegistry().register(new ItemBlock(Blocks.myBlock).setRegistryName(MOD_ID, "myBlock"));
  66.              event.getRegistry().register(new MySpecialItem().setRegistryName(MOD_ID, "mySpecialItem"));
  67.            */
  68.            //setRegistryName():设置物品的注册名,也是本地化名
  69.         }
  70.      
  71.         @SubscribeEvent
  72.         public static void addBlocks(RegistryEvent.Register<Block> event) {
  73.           //通过RegistryEvent.Register<T>注册方块,每个你编写的方块都要通过这种方式注册
  74.           /*
  75.              event.getRegistry().register(new MySpecialBlock().setRegistryName(MOD_ID, "mySpecialBlock"));
  76.           */
  77.           /**Register<T>具有10种泛型类型参数:<Item>,<Block>,<Potion>(药水),<Biome>(生物群系),<SoundEvent>
  78.           (声音事件),<PotionType>(药水类型),<Enchantment>(附魔),<VillagerProfession>(村民职业),<EntityEntry>
  79.            (实体),<IRecipe>(合成表),你可以在ForgeRegistries类中看到这些泛型参数,也可以不使用泛型参数,直接用
  80.             RegisterEvent监听所有事件*/
  81.     }
  82.     /* EXAMPLE ITEM AND BLOCK - you probably want these in separate files
  83.     public static class MySpecialItem extends Item {

  84.     }

  85.     public static class MySpecialBlock extends Block {

  86.     }
  87.     */
  88. }</font>
复制代码
如果一个类被打上@Mod的注解,那么它就会被FML(forge mod loader)看作是一个mod主类。
所有的class文件将会在ModClassLoader里过一遍,然后由Loader.identifyMods指定出mod,这会返回一个ModDiscover:


private ModDiscoverer identifyMods(List<String> additionalContainers)
{     
    //additionalContainers由ModClassLoader产生,复制到injectedContainers集合中进行处理
    injectedContainers.addAll(additionalContainers);
    FMLLog.log.debug("Building injected Mod Containers {}", injectedContainers);   
    //mods是一个List<ModContainer>,minecraft则被forge当作ModContainer
    mods.add(minecraft);  
    //至于什么是ModContainer,参见http://www.mcbbs.net/thread-822754-1-1.html
    //ModContainer container = Loader.instance().getIndexedModList().get(modId);   

    mods.add(new InjectedModContainer(mcp,new File("minecraft.jar"))); //把mcp加载进mods
    for (String cont : injectedContainers)
    {
        ModContainer mc;
        try
       {
            //从ModClassLoader中,反射获取所有ModContainer,有必要清楚的是,即使是同一个类,只要从不同的ClassLoader加载,也会被当成不同的   类,所以要指定从哪个ClassLoader加载
            mc = (ModContainer) Class.forName(cont,true,modClassLoader).newInstance();
        }
        catch (Exception e)
        {
            FMLLog.log.error("A problem occurred instantiating the injected mod container {}", cont, e);
            throw new LoaderException(e);
        }
        mods.add(new InjectedModContainer(mc,mc.getSource())); 把mc(所有的ModContainer)加载进mods
    }
    ModDiscoverer discoverer = new ModDiscoverer();

    //if (!FMLForgePlugin.RUNTIME_DEOBF) //Only descover mods in the classpath if we're in the dev env.
    {                                 

       //TODO: Move this to GradleStart? And add a specific mod canidate for Forge itself.
        FMLLog.log.debug("Attempting to load mods contained in the minecraft jar file and associated classes");
        discoverer.findClasspathMods(modClassLoader); //由ModDiscover进行接下来的寻找mod工作
        FMLLog.log.debug("Minecraft jar mods loaded successfully");
    }

    List<Artifact> maven_canidates = LibraryManager.flattenLists(minecraftDir);
    List<File> file_canidates = LibraryManager.gatherLegacyCanidates(minecraftDir);

    for (Artifact artifact : maven_canidates)
    {
        artifact = Repository.resolveAll(artifact);
        if (artifact != null)
        {
            File target = artifact.getFile();
            if (!file_canidates.contains(target))
                file_canidates.add(target);
        }
    }
    //Do we want to sort the full list after resolving artifacts?
    //TODO: Add dependency gathering?
    for (File mod : file_canidates)
    {
        // skip loaded coremods
        if (CoreModManager.getIgnoredMods().contains(mod.getName()))
        {
            FMLLog.log.trace("Skipping already parsed coremod or tweaker {}", mod.getName());
        }
        else
        {
            FMLLog.log.debug("Found a candidate zip or jar file {}", mod.getName());
            discoverer.addCandidate(new ModCandidate(mod, mod, ContainerType.JAR));
        }
    }

    mods.addAll(discoverer.identifyMods());
    identifyDuplicates(mods);
    namedMods = Maps.uniqueIndex(mods, ModContainer::getModId);
    FMLLog.log.info("Forge Mod Loader has identified {} mod{} to load", mods.size(), mods.size() != 1 ? "s" : "");
    return discoverer;
}

//这是ModDiscoverer
public class ModDiscoverer
{
    private List<ModCandidate> candidates = Lists.newArrayList();//ModCandidates:一个从文件名获取“备选mod”的类,顾名思义

    private ASMDataTable dataTable = new ASMDataTable();//ASMDataTable/ASMData:我觉得应该叫ModMetaData,因为这和ASM半点没关系

    private List<File> nonModLibs = Lists.newArrayList();

    public void findClasspathMods(ModClassLoader modClassLoader)
    {
        List<String> knownLibraries = ImmutableList.<String>builder()
                // skip default libs
       .addAll(modClassLoader.getDefaultLibraries())
                // skip loaded coremods
       .addAll(CoreModManager.getIgnoredMods())
                // skip reparse coremods here
       .addAll(CoreModManager.getReparseableCoremods())
                .build();
        File[] minecraftSources = modClassLoader.getParentSources(); //调用了LaunchClassLoader.getSources()
        if (minecraftSources.length == 1 && minecraftSources[0].isFile())
        {
            FMLLog.log.debug("Minecraft is a file at {}, loading", minecraftSources[0].getAbsolutePath());
            addCandidate(new ModCandidate(minecraftSources[0], minecraftSources[0], ContainerType.JAR, true, true));
        }
        else
        {
            int i = 0;
            for (File source : minecraftSources)
            {
                if (source.isFile())
                {
                    if (knownLibraries.contains(source.getName()) || modClassLoader.isDefaultLibrary(source))
                    {
                        FMLLog.log.trace("Skipping known library file {}", source.getAbsolutePath());
                    }
                    else
                   {
                        FMLLog.log.debug("Found a minecraft related file at {}, examining for mod candidates", source.getAbsolutePath());
                        addCandidate(new ModCandidate(source, source, ContainerType.JAR, i==0, true));
                    }
                }
                else if (minecraftSources.isDirectory())
                {
                    FMLLog.log.debug("Found a minecraft related directory at {}, examining for mod candidates", source.getAbsolutePath());
                    addCandidate(new ModCandidate(source, source, ContainerType.DIR, i==0, true));
                }
                i++;
            }
        }
    }

    public List<ModContainer> identifyMods()
    {
        List<ModContainer> modList = Lists.newArrayList();

        for (ModCandidate candidate : candidates)
        {
            try
           {
                List<ModContainer> mods = candidate.explore(dataTable);
                if (mods.isEmpty() && !candidate.isClasspath())
                {
                    nonModLibs.add(candidate.getModContainer());
                }
                else
               {
                    modList.addAll(mods);
                }
            }
            catch (LoaderException le)
            {
                FMLLog.log.warn("Identified a problem with the mod candidate {}, ignoring this source", candidate.getModContainer(), le);
            }
        }

        return modList;
    }
……
}

//这是FMLModContainer
private void parseSimpleFieldAnnotation(SetMultimap<String, ASMData> annotations, String annotationClassName, Function<ModContainer, Object> retriever) throws IllegalAccessException
{
    Set<ASMDataTable.ASMData> mods = annotations.get(Mod.class.getName());
    String[] annName = annotationClassName.split("\\.");
    String annotationName = annName[annName.length - 1;
    for (ASMData targets : annotations.get(annotationClassName))
    {
        String targetMod = (String)targets.getAnnotationInfo().get("value");
        String owner = (String)targets.getAnnotationInfo().get("owner");
        if (Strings.isNullOrEmpty(owner))
        {
            owner = ASMDataTable.getOwnerModID(mods, targets);
            if (Strings.isNullOrEmpty(owner))
            {
                FMLLog.bigWarning("Could not determine owning mod for @{} on {} for mod {}", annotationClassName, targets.getClassName(),                    this.getModId());
                continue;
            }
        }
        if (!this.getModId().equals(owner))
        {
            FMLLog.log.debug("Skipping @{} injection for {}.{} since it is not for mod {}", annotationClassName, targets.getClassName(),          targets.getObjectName(), this.getModId());
            continue;
        }
        Field f = null;
        Object injectedMod = null;
        ModContainer mc = this;
        boolean isStatic = false;
        Class<?> clz = modInstance.getClass();
        if (!Strings.isNullOrEmpty(targetMod))
        {
            if (Loader.isModLoaded(targetMod))
            {
                mc = Loader.instance().getIndexedModList().get(targetMod);
            }
            else
           {
                mc = null;
            }
        }
        if (mc != null)
        {
            try
        {
                clz = Class.forName(targets.getClassName(), true, Loader.instance().getModClassLoader());
                f = clz.getDeclaredField(targets.getObjectName());
                f.setAccessible(true);
                isStatic = Modifier.isStatic(f.getModifiers());
                injectedMod = retriever.apply(mc);
            }
            catch (ReflectiveOperationException e)
            {
                FMLLog.log.warn("Attempting to load @{} in class {} for {} and failing", annotationName, targets.getClassName(), mc.getModId(), e);
            }
        }
        if (f != null)
        {
            Object target = null;
            if (!isStatic)
            {
                target = modInstance;
                if (!modInstance.getClass().equals(clz))
                {
                    FMLLog.log.warn("Unable to inject @{} in non-static field {}.{} for {} as it is NOT the primary mod instance", annotationName, targets.getClassName(), targets.getObjectName(), mc.getModId());
                    continue;
                }
            }
            f.set(target, injectedMod);
        }
    }
}


不管这过程多么复杂,总之forge将会从@Mod主类加载你的mod的其他部分——理论上三个初始化方法不是必须的,但是它们用来负责监听三个阶段的事件,很有用处。



要声明一个物品,最简单的方法就是:Item yourItem=new Item();于是你写出了你的第一个物品(当然需要在主类中注册到游戏中)。当然,这样声明物品仅仅适合于没有任何行为的合成材料(例如钻石和下界星),实际情况中一般要定义一个自己的类:
public class yourItem extends Item{}
然后yourItem对象就可以调用或覆写Item下的方法被你使用,原版具有这些方法:
  1. public static final RegistryNamespaced<ResourceLocation, Item> REGISTRY = net.minecraftforge.registries.GameData.getWrapper(Item.class);
  2. //使用该字段你可以获得所有注册到游戏中的物品,Forge提供

  3. public ItemStack getDefaultInstance(){
  4. //获取物品的“默认”ItemStack,Item转ItemStack的重要方法,但是如果你有多个ItemStack使用了相同的Item,
  5. //如new ItemStack(yourItem,1,meta(0-100)),这个方法则无法得到你指定的ItemStack,例如Mekanism的输导矩阵
  6. }

  7. public static Item getByNameOrId(String id){
  8. Item item = REGISTRY.getObject(new ResourceLocation(id));
  9. /*通过传入的注册id,例如“minecraft:apple”,获得一个Item,其中的REGISTRY指代所有注册到游戏中的物品*/}

  10. public static Item getItemFromBlock(Block blockIn){
  11. Item item = BLOCK_TO_ITEM.get(blockIn);
  12. return item == null ? Items.AIR : item;
  13. /*通过传入一个方块blockIn,可以获取这个方块所对应的物品,如果没有就是空气,一般可以当做获取掉落物的方法*/
  14. /*当然也有例外,例如石头挖掘得到圆石,这时需要getItemDropped()方法获取掉落物*/}

  15. public boolean updateItemStackNBT(NBTTagCompound nbt){
  16. /*用户可以决定是否通过预先传入的NBT标签,来更新物品已有的NBT标签,例:*/
  17.    if(nbt.hasKey(“Frog”)){
  18.       nbt.setString(“ZeminJiang”,“ImAngry!”)
  19.       return true;}
  20. else return false;}

  21. public EnumActionResult onItemUseFirst(EntityPlayer player, World world, BlockPos pos, EnumFacing side, float hitX, float hitY, float hitZ, EnumHand hand){
  22. /*当玩家手持物品,右击方块时该方法被调用,返回一个点击结果*/
  23. }

  24. public EnumActionResult onItemUse(EntityPlayer player, World worldIn, BlockPos pos, EnumHand hand, EnumFacing facing, float hitX, float hitY, float hitZ) {/*当玩家手持物品,右击方块时该方法被调用,返回一个点击结果,例:*/
  25. ItemStack itemStack=player.getHeldItem(MAIN_HAND);//获取玩家主手持有的物品
  26. if(facing==EnumFacing.DOWN)
  27.    return EnumActionResult.FAIL;//限定玩家不能右击方块的下面,否则就会失败
  28. Block block=worldIn.getBlockState(pos).getBlock;//根据给定的BlockPos,即MC坐标对象,获取当前位置的方块
  29. if(block==Blocks.SAND){
  30.    spawnEntity(new EntityCreeper(),worldIn)//生成一只苦力怕
  31.    itemStack.shrink(1);//物品数量-1,如果是像鸡蛋一样的消耗品,可以调用这个
  32.    return EnumActionResult.SUCCESS;
  33. }
  34. else return EnumActionResult.FAIL;
  35. }   

  36. public ActionResult<ItemStack> onItemRightClick(World worldIn, EntityPlayer playerIn, EnumHand handIn) {
  37. /*当玩家手持物品右击时(准确来说是对空右击),返回一个点击结果*/}

  38. public float getDestroySpeed(ItemStack stack,IBlockState state){/*获取挖掘速度*/}

  39. public boolean onBlockDestroyed(ItemStack stack, World worldIn, IBlockState state, BlockPos pos, EntityLivingBase entityLiving){
  40. /*当一个方块被该物品破坏时,该方法被调用,统计物品使用次数*/
  41. if(state.getBlock()==Blocks.GOLD)//判断方块是不是金块,与worldIn.getBlockState(pos).getBlock()等效
  42. stack.damageItem(1,entityLiving);//物品耐久-1
  43. else stack.damageItem(2,entityLiving);
  44. return true;
  45. }

  46. public void onUsingTick(ItemStack stack, EntityLivingBase player, int count){
  47. //当玩家使用此物品时(例如在挖矿),在特定的某一tick做出操作
  48. if(count==6&&player.inventory.getInventory.contains(anotherStack))//判定第6个tick,anotherStack是预先声明的另一物品
  49. stack.damageItem(1,player);
  50. }

  51. public void addInformation(ItemStack stack, @Nullable World worldIn, List<String> tooltip, ITooltipFlag flagIn){
  52. /*添加自定义物品信息(tooltip),当鼠标指针移动到物品上就会显示出来*/
  53. /*IToolFlag.isAdvanced()添加的是高级调试信息,需要F3+H才会显示出来*/
  54. if(stack.getItem()instanceof YourItem)
  55. tooltip.add(“你的头,像皮球”);
  56. }

  57. public void onUpdate(ItemStack stack, World worldIn, Entity entityIn, int itemSlot, boolean isSelected){
  58. /*MC用了lwjgl的计时器,这个计时器规定1s=20tick,实现了这个方法后物品会每tick刷新一次,用户可以借刷新进行操作*/
  59. if(worldIn.provider.getDimensionType()==DimensionType.End){//判断当前世界是末地
  60. entityIn.isBurning();//携带此物品的实体会燃烧起来
  61. if(entityIn instanceof EntityPlayer)
  62. entityIn.setHealth(entityIn.getMaxHealth());//如果持有它的实体是玩家,玩家的血量会被实时设定为最大
  63. }

  64. public boolean hasEffect(ItemStack stack){/*判断物品是否被附魔*/
  65. return stack.isItemEnchanted();
  66. }

  67. protected RayTraceResult rayTrace(World worldIn, EntityPlayer playerIn, boolean useLiquids){
  68. //获取当前物品指向的方块,调用了World.rayTraceBlocks()方法
  69. }

  70. public void getSubItems(CreativeTabs tab, NonNullList<ItemStack> items){
  71. //新建一批同一id但是不同meta的物品,例如原版染料和染色羊毛
  72. if (this.isInCreativeTab(tab))
  73.      for (int i = 0; i < 16; ++i)
  74.      items.add(new ItemStack(this, 1, i));
  75. }

  76. protected boolean isInCreativeTab(CreativeTabs targetTab){
  77. //调用以判断物品是否在某个创造物品栏中
  78. }

  79. public Multimap<String, AttributeModifier> getAttributeModifiers(EntityEquipmentSlot slot, ItemStack stack){
  80. //调节各种物品参数,例如护甲值,攻速,伤害之类
  81. Multimap<String, AttributeModifier> multimap = super.getAttributeModifiers(equipmentSlot, itemStack);
  82. if (equipmentSlot == EntityEquipmentSlot.MAINHAND) {
  83. multimap.put(SharedMonsterAttributes.ATTACK_DAMAGE.getName(), new AttributeModifier(ATTACK_DAMAGE_MODIFIER,"Tool Modifier", SWORD_HARM, 0));}
  84. /*SharedMonsterAttributes.ATTACK_DAMAGE:内置的攻击力调整参数,toName()起标记作用
  85. new AttributeModifier(ATTACK_DAMAGE_MODIFIER,“ToolModifier”,SWORD_HARM,0):
  86. ATTACK_DAMAGE_MODIFIER:内置的攻击力调整器,传入AttributeModifier(属性调整器)以指定攻击力
  87. SWORD_HARM:预先定义的int,代表剑的伤害*/

  88. multimap.put(SharedMonsterAttributes.ARMOR_TOUGHNESS.getName(),SharedMonsterAttributes.readAttributeModifierFromNBT(itemStack.getSubCompound("generic.armorToughness")));
  89. /*SharedMonsterAttributes.ARMOR_TOUGHNESS:内置的护甲强度调整参数,toName()起标记作用
  90. new AttributeModifier(ARMOR_TOUGHNESS_MODIFIER,“ToolModifier”,ARMOR_TOUGH,0):
  91. ARMOR_TOUGHNESS_MODIFIER:内置的护甲强度调整器,传入AttributeModifier(属性调整器)以指定攻击力
  92. ARMOR_TOUGH:预先定义的int,代表护甲强度*/

  93. return multimap;}:
  94. }

  95. public Item setMaxDamage(int damage){/*设置物品的耐久,damage为-1即为无限耐久*/}
  96. public Item setMaxItemStack(int stack){/*设置物品的最大堆叠数量,默认64*/}
  97. public Item setHasSubItems(boolean hasSubTypes){/*设置物品是否有子物品,就像原版床与羊毛,煤炭*/}
  98. public Item setFull3D(){/*设置物品手持时是否会被3D渲染*/}
  99. public Item setUnlocalizedName(String name){/*设置物品的本地化名称,这个名称会显示给翻译者翻译*/}
  100. public Item setCreativeTab(CreativeTab creativeTab){/*设置物品的创造物品栏*/}
  101. public boolean getIsRepairable(ItemStack toRepair,ItemStack repair){/*设置物品是否能在铁砧中被修复,toRepair是待修复物品,repair是相应的修复材料,例如钻石镐会被钻石修复*/}
  102. public void setHarvestLevel(String toolClass,int level){/*设置工具的挖掘等级level,toolClass是工具类型,有“pickaxe”,“axe”,“shovel”三种;木头和金的等级是0,石头的等级是1,铁的等级是2,钻石的等级是3,你也可以设置一个更高的值*/
  103. /*如果你自定义了方块,那么方块的Material会决定你的挖掘等级是否有用,详见http://www.mcbbs.net/thread-825589-1-1.html*/}
  104. /*以上方法可以直接声明在你的物品类构造器中以设定物品的基础属性,例如:
  105. public YourItem(){
  106. setMaxDamage(300);
  107. setMaxItemStack(16);}*/

  108. public boolean isBeaconPayment(ItemStack stack){/*判定或设置该物品可以在信标中被消耗以提供buff,原版默认是铁,金,钻石和绿宝石*/}

  109. public boolean itemInteractionForEntity(ItemStack stack, EntityPlayer playerIn, EntityLivingBase target, EnumHand hand){
  110. /*设置该物品能右键用于目标实体,例如剪刀可以右键养剪羊毛*/
  111. if(playerIn.getHeldItemMainhand.getItem()instanceof YourItem){//判断你主手的物品是否是你定义的物品
  112.      if(target instanceof EntityBat){//判断目标实体是蝙蝠
  113.      target.dropItem(YourItems.BAT_FUR,2);//掉落蝙蝠毛2个
  114.      stack.damageItem(1,playerIn);
  115. return true;}
  116. else return false;}

  117. public boolean onDroppedByPlayer(ItemStack item, EntityPlayer player){/*若返回true,则该物品无法从玩家物品栏掉出去,无论是按Q丢弃还是玩家死亡*/}

  118. public NBTTagCompound getNBTShareTag(ItemStack stack){
  119. /*重写此方**导致服务器和客户端之间同步的NBT被改变,危险的方法,如果你不知道你在做什么请不要动它*/}

  120. public boolean onBlockStartBreak(ItemStack itemstack, BlockPos pos, EntityPlayer player){/*若返回true,会防止方块以正常情况被破坏,例如用镐子挖掉*/}

  121. public boolean onLeftClickEntity(ItemStack stack, EntityPlayer player, Entity entity){/*若返回true,则设置该物品无法对生物造成伤害*/}

  122. public int getEntityLifespan(ItemStack itemStack, World world){/*设置物品被丢到世界中的存在时间,默认为6000tick,也就是5分钟*/}

  123. public boolean hasCustomEntity(ItemStack stack){
  124. //若返回true,当物品被丢到世界中时,会出现一个由Item.createEntity()自定义的实体而不是物品掉落物本身
  125. //血魔感知剑就是这样,当按Q丢弃到世界中,会出现一把飞剑
  126. }

  127. public Entity createEntity(World world, Entity location, ItemStack itemstack){
  128. /*物品丢弃到世界中时尝试生成实体*/
  129. if(itemstack.getCount()>32)
  130. return new EntityCreeper(world);
  131. }

  132. public float getSmeltingExperience(ItemStack item){/*获取熔炼物品后得到的经验*/}

  133. public boolean doesSneakBypassUse(ItemStack stack, net.minecraft.world.IBlockAccess world, BlockPos pos, EntityPlayer player){
  134. /*若返回true,则当玩家潜行时,物品将会点击到下面的方块*/}

  135. public void onArmorTick(World world, EntityPlayer player, ItemStack itemStack){
  136. /*如果你想让你自定义的护甲在刷新时做些什么,只需重写这个方法,然后调用它
  137. itemStack.addEnchantment(33,1)//添加精准采集附魔,当然这没个P用
  138. }
  139. //yourArmor.onArmorTick();

  140. public boolean isValidArmor(ItemStack stack, EntityEquipmentSlot armorType, Entity entity){
  141. /*返回真,你的物品就可以当做护甲,EntityEquipmentSlot(装备栏)有6个,分别是双手,头,胸(chest),护腿和靴子。
  142. }

  143. public String getArmorTexture(ItemStack stack, Entity entity, EntityEquipmentSlot slot, String type){
  144. /*继承了ItemArmor类的护甲,可以获取你自定义的护甲材质,返回null即为默认值*/
  145. }

  146. public void renderHelmetOverlay(ItemStack stack, EntityPlayer player, net.minecraft.client.gui.ScaledResolution resolution, float partialTicks){
  147. /*当玩家戴上给定头盔时,渲染玩家的HUD,例如玩家戴上南瓜头后视野受到影响*/
  148. }

  149. public boolean showDurabilityBar(ItemStack stack){
  150. //物品是否显示耐久条,编写有能量条的物品时有必要实现
  151. Item item=(IEnergyStorage)(stack.getItem());
  152. return item.getEnergyStored()!=item.getMaxEnergyStored();
  153. //IEnergyStorage:Forge自带的能量实现接口,来自RF API
  154. }

  155. public double getDurabilityForDisplay(ItemStack stack){
  156. //默认采用“当前耐久(能量)/最大耐久(能量)”的形式显示耐久条
  157. Item item=(IEnergyStorage)(stack.getItem());
  158. return item.getEnergyStored()/item.getMaxEnergyStored();
  159. }

  160. public int getRGBDurabilityForDisplay(ItemStack stack){
  161. //默认的耐久条颜色算法,你可以指定自己的
  162. return MathHelper.hsvToRGB(Math.max(0.0F, (float) (1.0F - getDurabilityForDisplay(stack))) / 3.0F, 1.0F, 1.0F);
  163. }

  164. public void setDamage(ItemStack stack, int damage){
  165. //指定某一物品的当前耐久,简单直观
  166. }

  167. public int getItemStackLimit(ItemStack stack){
  168. //获取当前物品的堆叠数量,深渊国度mod就是靠堆叠数判断每个物品产生的PE
  169. return this.getItemStackLimit();
  170. }

  171. public int getItemEnchantability(ItemStack stack){
  172. //获取/设定物品的附魔能力,例如寰宇剑的附魔能力是200.
  173. }

  174. public boolean canApplyAtEnchantingTable(ItemStack stack, Enchantment enchantment){
  175. //指定物品能否满足一定条件,在附魔台中附魔,例如1000耐久以上可附时运2,1000耐久以下可附时运3
  176. return enchantment.type.canEnchantItem(stack.getItem());
  177. }

  178. public boolean shouldCauseBlockBreakReset(ItemStack oldStack, ItemStack newStack){
  179. //默认情况下,当玩家挖掘方块时切换手中物品,挖掘进度会被重置,如果该方法返回false,则实现该方法的物品切换时不会重置进度
  180. return !(newStack.getItem() == oldStack.getItem() && ItemStack.areItemStackTagsEqual(newStack, oldStack) && (newStack.isItemStackDamageable() || newStack.getMetadata() == oldStack.getMetadata()));
  181. }

  182. public boolean canDisableShield(ItemStack stack, ItemStack shield, EntityLivingBase entity, EntityLivingBase attacker){
  183. //指定该物品是否能破盾牌,默认是斧子
  184. return this instanceof ItemAxe;
  185. }

  186. public boolean isShield(ItemStack stack, @Nullable EntityLivingBase entity){
  187. //指定某物品是否能作为盾牌
  188. return entity instanceof EntityPig &&stack.getItem() instanceof Items.SADDLE;
  189. //当猪带着马鞍时就可以作为盾牌
  190. }

  191. public int getItemBurnTime(ItemStack itemStack){
  192. //获取/设定物品的燃烧时间,0不作为燃料,-1则是原版的燃烧时间
  193. return -1;
  194. }

复制代码
Forge具有如下方法:
  1. public String getCreatorModId(ItemStack itemStack){
  2. //获取当前物品是由哪个mod注册的
  3. return net.minecraftforge.common.ForgeHooks.getDefaultCreatorModId(itemStack);
  4. }

  5. public ICapabilityProvider initCapabilities(ItemStack stack, @Nullable NBTTagCompound nbt){
  6. //物品初始化时,加载Capability(能力)系统,让物品拥有相应的“能力”,后续介绍
  7. return null;
  8. }

复制代码


理论上你的每一个物品都可以这样声明,但是1.12及其以前的物品id数量都是有限的(原版4096,某些mod会将其扩增到int上限),如果你想写大一些的mod,就会消耗掉很多id(尤其是一些想写史诗级mod的同学),因此光用Item乱声明是行不通的,需要ItemStack类来曲线救国。
你可以简单地把ItemStack理解为一个Item在同一个id的不同实例——例如TE的铜,锡,铅,银等金属,又或者是不同耐久的两把钻石镐,就是通过ItemStack来声明的,虽然TE有9种金属锭和对应的矿物,但实际上只消耗了一个id,例子如下:

Item metal=new Item();
ItemStack iron=new ItemStack(metal,1,1);
ItemStack copper=new ItemStack(metal,1,2);
通过上述三行,你已经声明了同一个Item的两个ItemStack,会在游戏中被玩家看到。
ItemStack有7个构造器,说明了声明一个ItemStack需要的参数:
  1. public ItemStack(Block blockIn){/*通过一个方块blockIn声明相应的物品,例如半砖方块获取半砖物品*/}
  2.     public ItemStack(Block blockIn, int amount){/*你可以再指定物品堆叠数量,由于我们在创造物品栏看到的物品都是一个一个的,因此amount一般设为1*/}
  3.     public ItemStack(Block blockIn, int amount, int meta){/*meta是标签,可以是1,2,3……等int数,无论你声明多少个meta,都只消耗一个id}
  4.     public ItemStack(Item itemIn){/*通过一个物品itemIn声明相应的物品实例,例如new ItemStack(Items.DIAMOND)*/}
  5.     public ItemStack(Item itemIn, int amount){/*你可以再指定物品堆叠数量,由于我们在创造物品栏看到的物品都是一个一个的,因此amount一般设为1*/}
  6.     public ItemStack(Item itemIn, int amount, int meta){/*meta作用同上,起到一个标签的作用,这样无论你声明多少个物品实例,都只消耗一个物品ID*/}
  7.     public ItemStack(Item itemIn, int amount, int meta, @Nullable NBTTagCompound capNBT){
  8. /*为物品添加附加的NBT,例如物品一合成出来就有时运3附魔*/
  9. /*例子:
  10. NBTTagCompound nbtTagCompound=new NBTTagCompound();
  11. nbtTagCompound.setInteger(“id”,Enchantment.getEnchantmentID(Enchantments.FORTUNE));
  12. nbtTagCompound.setInteger(“lvl”,3);
  13. //不用怀疑,NBT就是这样的刻意声明,“lvl”一定代表附魔等级,自己声明NBT标签的时候注意一下
  14. ……
  15. new ItemStack(yourItem,1,1,nbtTagCompound);*/
  16. }
  17. public ItemStack(NBTTagCompound compound){}
复制代码
你可以用ItemStack.getItem()或者Item.getDefaultInstance()来实现Item和ItemStack的互转。注意,getDefaultInstance()可能会引发异常,而且无法根据用户要求返回一个具体的ItemStack,例如mekanism有4个等级的输导矩阵,在calculator(运算工艺)的原子增幅仪里复制就只能得到普通的输导矩阵,因为得到的ItemStack中,并没有指定具体的metadata,而只是getDefaultInstance()。
public ItemStack splitStack(int amount){}
//按照给定的数量,将ItemStack分成两堆

public EnumActionResult onItemUse(EntityPlayer playerIn, World worldIn, BlockPos pos, EnumHand hand, EnumFacing side, float hitX, float hitY, float hitZ){
//当把玩家手中的物品(不管哪只手)右键方块使用时,进行的操作,例如剪刀右键树叶
/*if(worldIn.provider.getDimension()=6)//6是aroma1997矿界的id,当然可能会被修改
switch(side){
case(side==EnumFacing.SOUTH):spawnEntity(……);//如果是南面,召唤一个实体,随便什么都行
}*/
}

public EnumActionResult onItemUseFirst(EntityPlayer playerIn, World worldIn, BlockPos pos, EnumHand hand, EnumFacing side, float hitX, float hitY, float hitZ){}
//与onItemUse()几乎一样,具体区别可查看Item.onItemUseFirst()

public float getDestroySpeed(IBlockState blockIn){}
//获取物品对方块的破坏速度,因为一把镐对钻石和黑曜石的破坏速度肯定不一样

//没什么可说的,这两个方法都是直接调取对应Item的方法,本质完全一样
public ActionResult<ItemStack> useItemRightClick(World worldIn, EntityPlayer playerIn, EnumHand hand)
{
    return this.getItem().onItemRightClick(worldIn, playerIn, hand);
}

public ItemStack onItemUseFinish(World worldIn, EntityLivingBase entityLiving)
{
    return this.getItem().onItemUseFinish(this, worldIn, entityLiving);
}

public NBTTagCompound writeToNBT(NBTTagCompound nbt){}
//将一段NBT写入当前ItemStack中,完成玩家所希望的属性的持久化存储

//没什么可说的都一样
public int getMaxStackSize()
{
    return this.getItem().getItemStackLimit(this);
}
public boolean isItemStackDamageable(){}
//判断一个物品是否有耐久,如果没有,它会有一个boolean类型的标签“Unbreakable”


public boolean isItemDamaged(){}
//判断物品是不是满耐久,返回true则代表已经受损,或者不是满能量


public boolean attemptDamageItem(int amount, Random rand, @Nullable EntityPlayerMP damager){}
//尝试破坏玩家手中的物品,如果该物品有耐久附魔,将会有几率取消耐久损耗,如果带来的耐久损耗(amount)大于最大耐久,物品则被破坏,该方法返回true

public boolean attemptDamageItem(int amount, Random rand, @Nullable EntityPlayerMP damager){}
损耗给定的耐久值,如果amount大于当前耐久值,物品会被直接破坏

public void hitEntity(EntityLivingBase entityIn, EntityPlayer playerIn){}
//打击实体,没什么可说的

public void onBlockDestroyed(World worldIn, IBlockState blockIn, BlockPos pos, EntityPlayer playerIn){
//在方块被破坏时做些什么
/*if(blockIn.getBlock().equals(Blocks.OBSIDIAN && playerIn.getHealth()<5)
spawnEntity(ENTITY_LIGHTNING_THUNDER);
//如果玩家在血量低于5的情况下挖黑曜石,当黑曜石被破坏时劈下闪电*/
}

public ItemStack copy(){}
//复制一个一样的ItemStack


public static boolean areItemStackTagsEqual(ItemStack stackA, ItemStack stackB){}
//判断两个ItemStack的NBT是否一致

public static boolean areItemStacksEqual(ItemStack stackA, ItemStack stackB){}
//比较两个ItemStack是否完全一致

public static boolean areItemsEqual(ItemStack stackA, ItemStack stackB)
//只比较两个ItemStack的物品和耐久值,忽略NBT

public static boolean areItemsEqualIgnoreDurability(ItemStack stackA, ItemStack stackB){}
//只比较两个ItemStack是否物品相同,忽略耐久

public String getUnlocalizedName(){}
//获得ItemStack的未本地化名

public void updateAnimation(World worldIn, Entity entityIn, int inventorySlot, boolean isCurrentItem){}
//更新动画,一般用于地图的更新

public void onCrafting(World worldIn, EntityPlayer playerIn, int amount)
{
    //无可奉告,参考Item.onCreated()
    playerIn.addStat(StatList.getCraftStats(this.item), amount);
    this.getItem().onCreated(this, worldIn, playerIn);
}

public boolean hasTagCompound(){}
//判断该ItemStack是否有NBT

public NBTTagCompound getTagCompound(){}
//获取NBT

public NBTTagCompound getOrCreateSubCompound(String key){}
//获取(如果没有则创建)一个子NBT

public void removeSubCompound(String key){}
//移除子NBT

public NBTTagList getEnchantmentTagList(){}
//根据“ench”这个key,获得该ItemStack的NBTList

public void setTagCompound(@Nullable NBTTagCompound nbt){}
//把一个NBT分配到这个ItemStack

public ItemStack setTranslatableName(String p_190924_1_){}
//在NBT里设置一个可以翻译的名字

public List<String> getTooltip(@Nullable EntityPlayer playerIn, ITooltipFlag advanced){}
//获取这个物品的tooltip,例如你为心爱姑娘在工具里写的情诗

public void addEnchantment(Enchantment ench, int level){}
//为ItemStack添加附魔,Enchantment是附魔,level是附魔等级
//Enchantment ench=ForgeRegistries.ENCHANTMENTS.getValue("Unbreaking");
//addEnchantment(ench,3);

public void setTagInfo(String key, NBTBase value)
{
    //和setTag唯一的区别在于无需用户手动判断null
    if (this.stackTagCompound == null)
    {
        this.setTagCompound(new NBTTagCompound());
    }
    this.stackTagCompound.setTag(key, value);
}

public boolean isOnItemFrame(){}
//这个物品在不在物品展示框里面

//显而易见
public void setItemFrame(EntityItemFrame frame)
{
    this.itemFrame = frame;
}

@Nullable
public EntityItemFrame getItemFrame()
{
    return this.isEmpty ? null : this.itemFrame;
}

public int getRepairCost(){}
//获取这个ItemStack的铁砧修理费

public void setRepairCost(int cost){}
//设置这个ItemStack的铁砧修理费

public Multimap<String, AttributeModifier> getAttributeModifiers(EntityEquipmentSlot equipmentSlot){
//……
//AttributeModifier attributemodifier = SharedMonsterAttributes.readAttributeModifierFromNBT(nbttagcompound);
//……
//与Item.getAtrributeModifiers不同,ItemStack.getAttributeModifiers会检查ItemStack的NBT以找到需要的属性
}

public void addAttributeModifier(String attributeName, AttributeModifier modifier, @Nullable EntityEquipmentSlot equipmentSlot){}
//直接给工具添加属性,modifier用作实参时可以用SharedMonsterAtrributes.readAttributeModifierFromNBT(NBTTagCompound compound)来从NBT获取

public boolean canDestroy(Block blockIn){}
//是否能破坏指定方块blockIn

public boolean canPlaceOn(Block blockIn){}
//Itemstack能够放在方块上,一般这样的方块只有物品展示框

public int getCount(){}
//获取ItemStack里物品的数量,例如一堆物品有35个,那得到的数量就是35

public void setCount(int size){}
//指定ItemStack中物品的数量

public void grow(int quantity){}
//让ItemStack中的物品数量增长一定数量(quantity)

public void shrink(int quantity){}
//让ItemStack中的物品减少一定数量,例如丢鸡蛋时,就需要shrink(1)


public boolean hasCapability(net.minecraftforge.common.capabilities.Capability<?> capability, @Nullable net.minecraft.util.EnumFacing facing){}
//该物品是否具有能力(capability),如果不注重facing可以为null

@Override
@Nullable
public <T> T getCapability(net.minecraftforge.common.capabilities.Capability<T> capability, @Nullable net.minecraft.util.EnumFacing facing){}
//获取相应的能力,facing一般情况下null就可以

//与readNBT和writeNBT大同小异
public void deserializeNBT(NBTTagCompound nbt)
{
    // TODO do this better while respecting new rules
final ItemStack itemStack = new ItemStack(nbt);
    this.stackTagCompound = itemStack.stackTagCompound;
    this.capNBT = itemStack.capNBT;
}

public NBTTagCompound serializeNBT()
{
    NBTTagCompound ret = new NBTTagCompound();
    this.writeToNBT(ret);
    return ret;
}

public static boolean areItemStacksEqualUsingNBTShareTag(ItemStack stackA, ItemStack stackB){}
//基于areItemStackShareTagsEqual(ItemStack stackA, ItemStack stackB)方法,比较两个ItemStack所对应的Item的NBT


有必要说清楚的一件事是,在MC里并不是真的画了10万个草方块,而是把同一个草方块画了10万遍(如果你学习OpenGL,可以了解一下实例化渲染),符合Java的“享元模式”,因此也有人说MC的方块(Block)是享元的,如果是熔炉这样,存在内容物且内容物不定,那么就不能是享元的——否则你会看到100个里面有一组生鸡肉且一起被烧熟的熔炉,这样的东西往往用TileEntity描述。
public static int getIdFromBlock(Block blockIn)
{
    return REGISTRY.getIDForObject(blockIn);
    //由方块blockIn获取一个方块的id
    //不难看出,在forge环境下,我们经常使用REGISTRY来获取各种物品,方块等,而不是原版本身声明的方式
    //public static final RegistryNamespacedDefaultedByKey<ResourceLocation, Block> REGISTRY = net.minecraftforge.registries.GameData.getWrapperDefaulted(Block.class);
}

public static int getStateId(IBlockState state){}
//由给定的IBlockState获取state的id,在MC中Block和IBlockState是密不可分的

public static Block getBlockById(int id){}
//和getIdFromBlock反过来,由id获取方块,但是非原版情况下似乎没人知道哪个id对应哪个方块

public static IBlockState getStateById(int id){}
//和getStateId反过来,由id获取IBlockState

public static Block getBlockFromItem(@Nullable Item itemIn){}
//如果Item是ItemBlock的话,就可以从这个Item获取相应的Block

public static Block getBlockFromName(String name){}
//由名称例如“Obsidian”获取相应方块

public int getMetaFromState(IBlockState state){}
//由传入的IBlockState获取相应的meta值,适合一个方块有多种meta的情况,例如末地门框架

public Block setLightOpacity(int opacity){}
//一般用在方块构造函数内部,指定方块的不透明度

public Block setLightLevel(float value){}
//一般用在方块构造函数内部,指定方块的亮度,value介于0-1之间(乘15为实际值),也可以取更大的数

public Block setResistance(float resistance){}
//一般用在方块构造函数内部,指定方块的抗爆性,resistance一般为10(乘3为实际值),也可以取别的数

public Block setHardness(float hardness){}
//一般用在方块构造函数内部,指定方块的硬度,hardness一般为6(乘5为实际值),也可以取别的数

public Block setBlockUnbreakable(){}
//一般用在方块构造函数内部,直接将方块设为不可破坏

public Block setTickRandomly(boolean shouldTick){}
//一般用在方块构造函数内部,如果shouldTick为true,方块将随机时间更新

protected static boolean isExceptionBlockForAttaching(Block attachBlock)
{
    return true;
    //if(Block.isExceptionForAttaching(attachBlock)
    //用来判断或指定不能直接左键破坏或者破坏后会丢失内容物的方块,
    //默认有潜影箱,树叶,活板门,信标,炼药锅,玻璃,萤石块,冰,海晶灯
}
public boolean isPassable(IBlockAccess worldIn, BlockPos pos)
{
    return true;
    //用来判断或指定可以直接通过的方块,例如草
}
public boolean isReplaceable(IBlockAccess worldIn, BlockPos pos){return false;}
//用来判断或指定该方块能否被其他的方块右键放置而替换掉,例如空气,草,高草

protected static void addCollisionBoxToList(BlockPos pos, AxisAlignedBB entityBox, List<AxisAlignedBB> collidingBoxes, @Nullable AxisAlignedBB blockBox){}
//如果传入的blockBox经过pos大小的平移,能够和entityBox交叉的话,就将blockBox添加到colldingBoxes中,因为一个方块可能有多个碰撞箱

public void updateTick(World worldIn, BlockPos pos, IBlockState state, Random rand)
{
//当setTickRandomly(true)时,这个方法被调用,执行方块在更新时的操作
/*if(state.getBlock(pos.up(1)).equals(DIAMOND_BLOCK)) //判断当前方块位置上方一格是不是钻石块
worldIn.getNearestAttackablePlayer(pos,128,128).attackEntityFrom(DamageSource.MAGIC,10); //搜寻xyz方向上128格内最近的玩家并给予10点药水伤害
}
public void randomDisplayTick(IBlockState stateIn, World worldIn, BlockPos pos, Random rand)
{
//由于是渲染有关,因此这个方法是客户端限定的,是的,任何涉及渲染的方法都应该是SideOnly(Side.CILENT),这个方法只在渲染tick时更新
}
public void onBlockDestroyedByPlayer(World worldIn, BlockPos pos, IBlockState state)
{
//定义了当玩家破坏这个方块之后触发的行为
if(pos.getX()==677&&pos.getZ()==788)
worldIn.getNearestAttackablePlayer(pos,2,2).setPositionAndUpdate(1,1,1);
//破坏方块后获取(4,4,4)范围内最近的玩家,传送到(1,1,1)坐标
}
public void onBlockAdded(World worldIn, BlockPos pos, IBlockState state)
{
NBTTagCompound nbt=new NBTTagCompound();
nbt.setString("player",getNearestPlayer(pos,1,1));
//方块在区块生成之后未更新TileEntity之前进行的操作
if(hasTileEntity)
world.getTileEntity(pos).writeToNBT(nbt);
}
public void breakBlock(World worldIn, BlockPos pos, IBlockState state){
//在服务端被调用,破坏方块后TileEntity被改变前起效,可以用来实现保险箱
TileEntity te=worldIn.getTileEntity(pos);
if(te!=null) {
    NBTTagCompound nbtTagCompound = new NBTTagCompound();
    nbtTagCompound.setTag("Tile", te.getTileData());
    ItemStack itemStack = new ItemStack(this);
    itemStack.writeToNBT(nbtTagCompound);
    EntityItem entityItem=new EntityItem(worldIn,player.posX,player.posY,player.posZ,itemStack);
    entityItem.setInfinitePickupDelay();        //掉落物将不会消失
    entityItem.setEntityInvulnerable(true);   //掉落在世界中的物品是EntityItem,setEntityInvulnerable(true)可确保物品不被岩浆等外力破坏,类似于匠魂工具
    worldIn.spawnEntity(entityitem);
}}

public int quantityDropped(Random random)
{   //根据传入的随机参数,指定破坏方块时掉落物品的数量
    return 1;
}

public Item getItemDropped(IBlockState state, Random rand, int fortune)
{   //保持默认实现就好,当然你可以让它掉落点别的
    //return Item.getItemFromBlock(this);
    //以下是demo
    if(rand.nextFloat()>0.99)
       return Items.DIAMOND;//1%几率得到钻石
}

public void dropBlockAsItemWithChance(World worldIn, BlockPos pos, IBlockState state, float chance, int fortune)
{
   //将方块概率掉落为物品
   List<ItemStack> drops = getDrops(worldIn, pos, state, fortune);//fortune是时运等级
   if(1<chance<100) //方便表述请勿吐槽
   for(ItemStack drop:drops)
   spawnAsEntity(worldIn, pos, drop);
}

public static void spawnAsEntity(World worldIn, BlockPos pos, ItemStack stack) //将方块掉落成掉落物(EntityItem)的方法
{
    if (!worldIn.isRemote && !stack.isEmpty() && worldIn.getGameRules().getBoolean("doTileDrops")&& !worldIn.restoringBlockSnapshots) // do not drop items while restoring blockstates, prevents item dupe
{
        if (captureDrops.get())
        {
            capturedDrops.get().add(stack);
            return;
        }
        float f = 0.5F;
        double d0 = (double)(worldIn.rand.nextFloat() * 0.5F) + 0.25D;
        double d1 = (double)(worldIn.rand.nextFloat() * 0.5F) + 0.25D;
        double d2 = (double)(worldIn.rand.nextFloat() * 0.5F) + 0.25D;
        EntityItem entityitem = new EntityItem(worldIn, (double)pos.getX() + d0, (double)pos.getY() + d1, (double)pos.getZ() + d2, stack);
        entityitem.setEntityInvulnerable(true);  //在这里重写方法,掺杂私货      
        entityitem.setDefaultPickupDelay();
        worldIn.spawnEntity(entityitem);//最后调用的核心方法
    }
}

public void dropXpOnBlockBreak(World worldIn, BlockPos pos, int amount){
   //方块被破坏时掉落amount数量的经验   
   worldIn.spawnEntity(new EntityXPOrb(worldIn, (double)pos.getX() + 0.5D, (double)pos.getY() + 0.5D, (double)pos.getZ() + 0.5D, amount));
   //经验球是实体,EntityXPOrb就是,其构造器为EntityXPOrb(World,double,double,double,amount),amount是总经验值
}

public int damageDropped(IBlockState state)
{
    //在这里根据方块的metadata值获取掉落物的meta
}

public void onBlockDestroyedByExplosion(World worldIn, BlockPos pos, Explosion explosionIn)
{
//当这个方块被爆炸破坏时做些什么
   EntityLivingBase sb=explosionIn.getExplosivePlacedBy();
   if(sb==暮色森林的红帽地精)
   sb.setDead();//弄死这个sb地精
}

@SideOnly(Side.CLIENT)
public BlockRenderLayer getBlockLayer()
{
    return BlockRenderLayer.SOLID;
    //获取方块的渲染层
}public boolean canPlaceBlockOnSide(World worldIn, BlockPos pos, EnumFacing side)
{
    //方块是否可以放在某个位置的某个面
    return this.canPlaceBlockAt(worldIn, pos);
}
public boolean canPlaceBlockAt(World worldIn, BlockPos pos)
{   //方块是否可以放在某个位置
    return worldIn.getBlockState(pos).getBlock().isReplaceable(worldIn, pos);
    //一般只有空气和草才是isReplaceable的
}

public boolean onBlockActivated(World worldIn, BlockPos pos, IBlockState state, EntityPlayer playerIn, EnumHand hand, EnumFacing facing, float hitX, float hitY, float hitZ)
{
    //用来控制玩家右击方块时做什么,是的这是一个重要方法
    playerIn.displayGui((
IInteractionObject)worldIn.getTileEntity);//打开机器的GUI,如果它真的可交互的话
    world.newExplosion(new EntityTNTPrimed(world),pos.getX(),pos.getY(),pos.getZ(),7,true,true) //在方块所在的地方引发一场烈度为7,有烟和火焰的点燃TNT爆炸
    commandBanIp.execute();//执行banip指令
}
public void onEntityWalk(World worldIn, BlockPos pos, Entity entityIn)
{
   //当实体站在这个方块上时会发生什么
   if(entityIn instanceof EntityWither)
   entityIn.setDead();
}

public void onBlockClicked(World worldIn, BlockPos pos, EntityPlayer playerIn)
{
  //决定玩家点击方块时发生什么
  worldIn.getBlockState(pos).getBlock().spawnAsEntity();
}
public Vec3d modifyAcceleration(World worldIn, BlockPos pos, Entity entityIn, Vec3d motion)
{   //传入一个3d向量,以调节方块的加速度
    return motion;
}

public void onEntityCollidedWithBlock(World worldIn, BlockPos pos, IBlockState state, Entity entityIn)
{
  //当方块与实体碰撞时做些什么
  (EntityEgg)entityIn.getThrower();
}

public void harvestBlock(World worldIn, EntityPlayer player, BlockPos pos, IBlockState state, @Nullable TileEntity te, ItemStack stack){
    //产生这个方块的掉落物,期间方块可能会被调用Block.removedByPlayer()而移除
}
protected ItemStack getSilkTouchDrop(IBlockState state){
   //获取该方块被精准采集时的掉落物
}
public void onBlockPlacedBy(World worldIn, BlockPos pos, IBlockState state, EntityLivingBase placer, ItemStack stack)
{
  //方块被玩家放置时做些什么
}
public boolean canSpawnInBlock()
{
     //该方法用于指定或判断生物是否可以生成在这个方块中,默认实现中可以看出,这个方块不能是solid也不能是liquid
    return !this.blockMaterial.isSolid() && !this.blockMaterial.isLiquid();
}

public Block setUnlocalizedName(String name)
{
    //设置这个方块的本地化名称,这个名字将会在汉化文件中被翻译者翻译
    this.unlocalizedName = name;
    return this;
}

public String getLocalizedName()
{
    return I18n.translateToLocal(this.getUnlocalizedName() + ".name");
}

public String getUnlocalizedName()
{
    return "tile." + this.unlocalizedName;
}

public void onFallenUpon(World worldIn, BlockPos pos, Entity entityIn, float fallDistance)
{   
    //决定实体在掉到这个方块上时的伤害加成
    entityIn.fall(fallDistance, 1.0F); //fallDistance:摔落高度,1.0F:伤害加成因数,设为0可以免伤
}


public void getSubBlocks(CreativeTabs itemIn, NonNullList<ItemStack> items)
{   //获取一个有相同id但是不同meta的方块列表
     //CreativeTabs:原版自带的创造物品栏标签,例如建筑方块,装饰方块,杂项等
    items.add(new ItemStack(this));
}

public void onBlockHarvested(World worldIn, BlockPos pos, IBlockState state, EntityPlayer player)
{
   //比breakBlock()晚,前者在tileentity被改变前起作用,这个方法在方块被设为空气(破坏)前起作用,但依旧是方块被破坏时进行的操作
}

public void fillWithRain(World worldIn, BlockPos pos)
{
   //和randomTick()一样,但是这个方法只在下雨时执行
   for(Entity entity:getEntitiesWithinAABBexcluding(world.getEntityById(23),AABB/*玩家指定的长方体碰撞箱,这里不特意声明*/,null)
         entity.attackEntityFrom(DamageSource.LIGHTNING_BOLT, 5.0F); //让id是23的那个实体被雷劈
}


public boolean canDropFromExplosion(Explosion explosionIn)
{   
    //指定或判定这个方块会不会在爆炸中掉落下来
    return true;
}

public boolean isAssociatedBlock(Block other)
{   
    //判断这个方块和其他方块一样,没什么用
    return this == other;
}

public BlockStateContainer getBlockState()
{   
    //获取方块状态(BlockState)
    return this.blockState;
}

protected final void setDefaultState(IBlockState state)
{   
    //设置默认的BlockState
    this.defaultBlockState = state;
}

public final IBlockState getDefaultState()
{   
    //获取默认的BlockState
    return this.defaultBlockState;
}

/**
* Get the OffsetType for this Block. Determines if the model is rendered slightly offset.
*/
public Block.EnumOffsetType getOffsetType(){     
    return Block.EnumOffsetType.NONE;}

@SideOnly(Side.CLIENT)
public void addInformation(ItemStack stack, @Nullable World player, List<String> tooltip, ITooltipFlag advanced){
    //当你在物品栏或者JEI里查看物品时,会有一些工具的描述,例如“我在东北玩泥巴”,“略显邪恶”,这就是tooltip
   tooltip.add("Hunluan"); //你的tooltip里面会出现hunluan字眼
}

public float getSlipperiness(IBlockState state, IBlockAccess world, BlockPos pos, @Nullable Entity entity)
{
    //获取这个方块的滑度,其值介于0-1之间,冰的滑度为0.98
    return slipperiness;
    //this.slipperiness=0.5;在Block构造器内指定方块的滑度
}

public void setDefaultSlipperiness(float slipperiness)
{   
    //设置这个方块的滑度
    this.slipperiness = slipperiness;
}


public int getLightValue(IBlockState state, IBlockAccess world, BlockPos pos)
{   
    //获取某一方块位置的亮度
    return state.getLightValue();
}

public boolean isLadder(IBlockState state, IBlockAccess world, BlockPos pos, EntityLivingBase entity) {
      //设置一个方块能不能当作梯子使用
      return false;

}




先上一张思维导图:

下方继承上方,绿色代表抽象类,黄色代表类(如果看不清吱一声,我发一下ppt),可以看出各种实体之间的继承关系,例如EntityMob(怪物)和EntityAnimal(动物)都派生自EntityCreature,而EntityPlayer则直接来自EntityLivingBase。
每声明一个Entity,都会有一个样板代码:
public class EntityPig extends EntityAnimal
{
    private static final DataParameter<Boolean> SADDLED = EntityDataManager.createKey(EntityPig.class, DataSerializers.BOOLEAN);//SADDLED:这头猪是否配了鞍具
    private static final DataParameter<Integer> BOOST_TIME = EntityDataManager.createKey(EntityPig.class, DataSerializers.VARINT);//BOOST_TIME:用胡萝卜钓竿给猪加速的时间
}
EntityDataManager负责服务端与客户端之间的实体数据同步,DataSerializer负责数据类型的序列化(或者说持久化保存)。
生物(也包括玩家)的属性绝大多数情况下由SharedMonsterAttributes(SMA)这个类指定:
//这些是SMA类的字段
//最大血量,默认20,最大1024,但其实可以指定更大的值例如2048
public static final IAttribute MAX_HEALTH = (new RangedAttribute((IAttribute)null, "generic.maxHealth", 20.0D, Float.MIN_VALUE, 1024.0D)).setDescription("Max Health").setShouldWatch(true);
//怪物会发现并尾随的距离,默认是32,恶魂高达100,卫道士只有12
public static final IAttribute FOLLOW_RANGE = (new RangedAttribute((IAttribute)null, "generic.followRange", 32.0D, 0.0D, 2048.0D)).setDescription("Follow Range");
//击退抗性,1可以完全抗击退
public static final IAttribute KNOCKBACK_RESISTANCE = (new RangedAttribute((IAttribute)null, "generic.knockbackResistance", 0.0D, 0.0D, 1.0D)).setDescription("Knockback Resistance");
//移动速度,默认0.7,最高1024,当然没人会真以1024的速度冲出去,保证崩溃
public static final IAttribute MOVEMENT_SPEED = (new RangedAttribute((IAttribute)null, "generic.movementSpeed", 0.7D, 0.0D, 1024.0D)).setDescription("Movement Speed").setShouldWatch(true);
//飞行速度,鹦鹉是0.4,最高是1024,有必要说明的是,玩家,末影龙,凋灵的“飞行”不是靠FLYING_SPEED指定的,换句话说,这些不是飞行
public static final IAttribute FLYING_SPEED = (new RangedAttribute((IAttribute)null, "generic.flyingSpeed", 0.4D, 0.0D, 1024.0D)).setDescription("Flying Speed").setShouldWatch(true);
//攻击伤害,默认为2也就是一颗心,最高2048
public static final IAttribute ATTACK_DAMAGE = new RangedAttribute((IAttribute)null, "generic.attackDamage", 2.0D, 0.0D, 2048.0D);
//攻击速度,默认为4,实际上的武器攻速会根据“Tool Modifier”来减慢,例如剑的的Tool Modifier就是2.4,攻速是4-2.4=1.6
public static final IAttribute ATTACK_SPEED = (new RangedAttribute((IAttribute)null, "generic.attackSpeed", 4.0D, 0.0D, 1024.0D)).setShouldWatch(true);
//护甲值(不一定要有护甲),默认是0,最高是30
public static final IAttribute ARMOR = (new RangedAttribute((IAttribute)null, "generic.armor", 0.0D, 0.0D, 30.0D)).setShouldWatch(true);
//护甲韧性,默认0,最高20
ACTUAL_DAMAGE(实际伤害) = ATTACK_DAMAGE * ( 1 - min( 20, max( ARMOR / 5, ARMOR - ATTACK_DAMAGE / ( 2 + ( ARMOR_TOUGHNESS / 4 ) ) )) / 25 ),取自minecraft wiki
public static final IAttribute ARMOR_TOUGHNESS = (new RangedAttribute((IAttribute)null, "generic.armorToughness", 0.0D, 0.0D, 20.0D)).setShouldWatch(true);
//幸运,目前只与钓鱼有关,跟时运是两回事
public static final IAttribute LUCK = (new RangedAttribute((IAttribute)null, "generic.luck", 0.0D, -1024.0D, 1024.0D)).setShouldWatch(true);

//指定属性可以在构造器中,也可以在专门的方法中
public EntityXXX(){     
     //获取EntityXXX的最大血量,设定成256
    getAttribute(SharedMonsterAttributes.MAX_HEALTH).setBaseValue(256);
    this.immuneToFire(true);//设置生物免疫火焰伤害
}

生物是有AI的,例如攻击其他生物,捡拾物品,以及和村民交配等。
要添加AI只需在你自己的Entity构造器或者专门方法中输入语句:
public void initEntityAI(){
    this
.tasks.addTask(6, new EntityAILookIdle(this))
;  //6代表优先级,数字越小越高
    //addTask负责将相应的AI添加到生物当中
}


以下是AI表:
EntityAttackAIMelee:俗称大乱斗AI,实体会主动搜寻目标并展开攻击
EntityAttackAIRanged:远程攻击AI,原版女巫,凋灵这样能远程攻击的生物拥有
EntityAttackAIRangedBow:用弓箭的远程攻击AI,很明显,默认给骷髅用的
EntityAIAvoidEntity:避开某个实体,例如苦力怕避开猫,骷髅避开狼
EntityAIBeg:狼跟着拿着骨头的玩家走的AI,没什么用处
EntityAIBreakDoor:破门AI,仅限困难模式的僵尸
EntityAICreeperSwell:专属于苦力怕膨胀准备爆炸的AI,没什么用处
EntityAIDefendVillage:保护村庄的AI,默认铁傀儡拥有
EntityAIEatGrass:吃草方块使之变成泥土的AI,默认羊拥有
EntityAIFindEntityNearest:搜寻最近实体的AI
EntityAIFindEntityNearestPlayer:搜寻附近玩家的AI
EntityAIFleeSun:躲避阳光的AI,如果生物没有着火,此AI不会执行
EntityAIFollow:跟随实体的AI
EntityAIFollowGolem:跟随铁傀儡的AI,小村民(getGrowingAge()<0)且在白天才会触发,这时小村民会从铁傀儡手中接过花
EntityAIFollowOwner:跟随主人的AI,猫,鹦鹉,狼和马等默认拥有
EntityAIFollowOwnerFlying:跟随飞行主人并在主人落地后传送的AI,默认狼拥有
EntityAIFollowParent:幼体动物跟随父母的AI
EntityAIHarvestFarmland:收割作物的AI,默认村民拥有
EntityAIHurtByTarget:回击AI,当被打击的实体拥有这个AI时,便会展开反击,如果是群居动物例如僵尸猪人,会alertOthers()呼叫同类展开攻击
EntityAILandOnOwnersShoulder:落在主人肩膀上,很明显是鹦鹉,如果你写了个宠物调用这个AI不错
EntityAILeapAtTarget:跳跃着扑向对方的AI,要在距离在4-16之间才可能起效
EntityAILlamaFollowCaravan:一群羊驼跟着拴绳走的AI,开发者可以重新实现一下变成其他生物跟着走的AI
EntityAILookAtTradePlayer:这会看向与其交易的玩家,是的,村民专属
EntityAILookAtVillager:当铁傀儡手上有花时,它会看向村民,尝试给村民花朵
EntityAILookIdle:实体什么也不做,四处瞎看
EntityAIMate:实体交配的AI
EntityAIMoveIndoors:当下雨天时,村民会尝试躲进门里的AI
EntityAIMoveThroughVillage:主动搜寻最近的门找到村庄的AI,默认僵尸和铁傀儡有
EntityAIMoveToBlock:移动到某一方块的AI
EntityAIMoveTowardsRestriction:这个AI限制了实体的活动范围,它们离开一定距离后便会向“家”走去,-1代表距离无限,实体一定会受限
EntityAIMoveTowardsTarget:这个AI让实体接近它的目标,如果目标离实体太远超过最大距离,这个AI便不会执行
EntityAINearestAttackableTarget:这个AI允许实体寻找最近的可以攻击的实体
EntityAIOcelotAttack:顾名思义,猫发起攻击的AI
EntityAIOcelotSit:猫主动寻找熔炉和箱子坐下的AI
EntityAIOpenDoor:开门的AI,默认村民专属
EntityAIOwnerHurtByTarget:主人被其他生物攻击时,被驯服的动物所采取的动作
EntityAIOwnerHurtTarget:主人攻击其他生物时,被驯服的动物采取的动作
EntityAIPanic:动物被搭上时惊慌乱跑的AI
EntityAIPlay:小村民专属的AI,他们会互相追逐打闹
EntityAIRunAroundLikeCrazy:字面意思,四处乱跑的AI
EntityAISit:实体在主人命令下坐下的AI
EntityAISkeletonRiders:生成骷髅骑士的AI
EntityAISwimming:只有具有这个AI的实体才会游泳
EntityAITempt:动物会被玩家手上的诱饵所吸引的AI
EntityAITradePlayer:和玩家交易的AI
EntityAIVillagerInteract:村民之间分享和交换物品的AI
EntityAIVillagerMate:村民交配的AI
EntityAIWander:无目的的漫游AI
EntityAIWanderAvoidWater:对于不会游泳的实体来说,建议添加这个AI,这样它们不会往水里走
EntityAIWatchClosest:看向最近的实体,如果有攻击对象,会把最近的实体设置为攻击对象
EntityAIWatchClosest2:与前一条完全一致,除了setMutexBits(3)。
注意:setMutexBits所指定的参数,规定了当不同的ai冲突时,该在什么地方互斥

大体上来说,原版提供的AI就能满足绝大多数的需求,但是如果玩家想要自定义AI,就需要实现EntityAIBase抽象类:
  1. package net.minecraft.entity.ai;

  2. public abstract class EntityAIBase
  3. {
  4. //并行因子,如果是0,这个AI可以和其他AI并行执行(例如僵尸一边追赶玩家一边踩碎海龟蛋)
  5.     private int mutexBits;

  6. //在这里指定,AI什么时候可以执行
  7.     public abstract boolean shouldExecute();

  8. //在这里指定,什么时候AI可以继续执行,一般来说和AI何时执行是同一个条件
  9.     public boolean shouldContinueExecuting()
  10.     {
  11.         return this.shouldExecute();
  12.     }

  13. //在执行这个AI时,如果这个方法返回true,那么AI将可以被优先级更高的AI打断
  14.     public boolean isInterruptible()
  15.     {
  16.         return true;
  17.     }

  18. //开始执行AI,在这里实现AI的动作
  19.     public void startExecuting()
  20.     {
  21.     }

  22. //重置AI状态,用于当前AI被高优先级AI打断时
  23.     public void resetTask()
  24.     {
  25.     }

  26. //AI的持续动作,不重要
  27.     public void updateTask()
  28.     {
  29.     }

  30. //指定并行因子,一般不用管,或者设置成0.
  31.     public void setMutexBits(int mutexBitsIn)
  32.     {
  33.         this.mutexBits = mutexBitsIn;
  34.     }

  35. //获取其并行因子
  36.     public int getMutexBits()
  37.     {
  38.         return this.mutexBits;
  39.     }
  40. }
复制代码
例程:
  1. package net.xxx.entity.ai;

  2. public abstract class EntityAISleepWithSeashell extends EntityAIBase
  3. {
  4. private int mutexBits;
  5. private YourEntity yourEntity;

  6. public EntityAISleepWithSeashell(YourEntity entity){
  7.     this.yourEntity=entity;     
  8. }


  9. public abstract boolean shouldExecute(){
  10. //获取最近的海螺实体
  11. <font color="Black"><i>EntitySeashell seashell=entity.world.findNearestEntityWithinAABB(EntitySeashell.class,new AABB(blockpos1,blockpos2),entity);</i></font>
  12. if(entity.getDistanceSq(seashell)<64)
  13. return true;//当和实体距离小于64时,AI开始执行

  14. }

  15. //在这里指定,什么时候AI可以继续执行,一般来说和AI何时执行是同一个条件
  16. public boolean shouldContinueExecuting()
  17. {
  18. return this.shouldExecute();
  19. }

  20. //在执行这个AI时,如果这个方法返回true,那么AI将可以被优先级更高的AI打断
  21. public boolean isInterruptible()
  22. {
  23. return true;
  24. }

  25. //开始执行AI,在这里实现AI的动作
  26. public void startExecuting()
  27. {
  28.     yourEntity.undress(seashell);//脱衣服……咳咳,少儿不宜
  29. }

  30. //重置AI状态,用于当前AI被高优先级AI打断时
  31. public void resetTask()
  32. {
  33.     entityZombie.setDead();//杀死那个坏你好事的僵尸
  34. }

  35. //AI的持续动作,不重要
  36. public void updateTask()
  37. {
  38. }

  39. //指定并行因子,一般不用管,或者设置成0.
  40. public void setMutexBits(int mutexBitsIn)
  41. {
  42. this.mutexBits = mutexBitsIn;
  43. }

  44. //获取其并行因子
  45. public int getMutexBits()
  46. {
  47. return this.mutexBits;
  48. }
  49. }
复制代码
  1. public Set<String> getTags()
  2.     {
  3. //获取实体所有的NBT,如果NBT长度超过1024,你将不能添加更多的标签
  4.         return this.tags;
  5.     }
复制代码
  1. public void setDead()
  2.     {
  3. //直接杀死实体
  4.         this.isDead = true;
  5.     }
复制代码














1.12的forge有三种注册的方式——有点乱,但是值得介绍。
方法1:forge推荐的方法,就是看上去比较麻烦:
@Mod.EventBusSubscriber
public static class ObjectRegistryHandler {
    @SubscribeEvent
    public static void addItems(RegistryEvent.Register<Item> event) { //注册物品,因此泛型参数是Item,下同         
         event.getRegistry().register(new Item().setRegistryName(MOD_ID,"ustc_zzzz").setCreativeTab(yourCreativeTab).setUnlocalizedName("ustc_zzzz"));   
         //setRegistryName():注册名,setCreativeTab():指定创造物品栏,setUnlocalizedName():指定本地化名称
    }

    @SubscribeEvent
    public static void addBlocks(RegistryEvent.Register<Block> event) {注册方块,泛型参数是Block
         event.getRegistry().register(new Block().setRegistryName(modid,"ustc_zzzz").setCreativeTab(yourCreativeTab));
    }

}
方法2:ForgeRegistries注册:
ForgeRegistries.Items.register(new Item().setRegistryName());
方法3:GameData注册:
GameData.register_impl(new Item().setRegistryName());
(2和3本质上是一回事,但所属的类不同)
那么为什么forge会有这样的注册系统呢?这算是历史遗留了(1.13之后forge痛定思痛,把GameRegistry砍掉,GameData转为内部测试类,失去价值),真正有用的是IForgeRegistry<T>接口和它的实现类IForgeRegistry<T>.Impl,不信请看Item类的声明:
public class Item extends net.minecraftforge.registries.ForgeRegistryEntry<Item> implements IItemProvider, net.minecraftforge.common.extensions.IForgeItem {
//一堆又臭又长跟魔熊的裹脚布一样的代码
}
public abstract class ForgeRegistryEntry<V extends IForgeRegistryEntry<V>> implements IForgeRegistryEntry<V>{
//这次不是裹脚布了
}
原版所有实现了IForgeRegistryEntry<V>的类被罗列在ForgeRegistries类中:
public static final IForgeRegistry<Block>        BLOCKS       = GameRegistry.findRegistry(Block.class);
public static final IForgeRegistry<Item>         ITEMS        = GameRegistry.findRegistry(Item.class);
public static final IForgeRegistry<Potion>       POTIONS      = GameRegistry.findRegistry(Potion.class);
public static final IForgeRegistry<Biome>        BIOMES       = GameRegistry.findRegistry(Biome.class);
public static final IForgeRegistry<SoundEvent>   SOUND_EVENTS = GameRegistry.findRegistry(SoundEvent.class);
public static final IForgeRegistry<PotionType>   POTION_TYPES = GameRegistry.findRegistry(PotionType.class);
public static final IForgeRegistry<Enchantment>  ENCHANTMENTS = GameRegistry.findRegistry(Enchantment.class);
public static final IForgeRegistry<VillagerProfession> VILLAGER_PROFESSIONS = GameRegistry.findRegistry(VillagerProfession.class);
public static final IForgeRegistry<EntityEntry>  ENTITIES     = GameRegistry.findRegistry(EntityEntry.class);
public static final IForgeRegistry<IRecipe>      RECIPES      = GameRegistry.findRegistry(IRecipe.class);方块,物品,药水,群系,声音事件,药水类型,附魔,村民职业,实体,合成。
那么没有实现IForgeRegistryEntry<V>的怎么办呢?就要靠GameRegistry类了,例如:
public static void registerWorldGenerator(IWorldGenerator generator, int modGenerationWeight)
{
   //modGenerationWeight:mod生成物权重
}
public static void addShapedRecipe(ResourceLocation name, ResourceLocation group, @Nonnull ItemStack output, Object... params)
{
    ShapedPrimer primer = CraftingHelper.parseShaped(params);
     //GameRegistry.register()在1.12较新版本变成private,开发者无法直接调用以注册,以此促进event.getRegister()的使用
    GameRegistry.register(new ShapedRecipes(group == null ? "" : group.toString(), primer.width, primer.height, primer.input, output).setRegistryName(name));
}
public static void registerTileEntity(Class<? extends TileEntity> tileEntityClass, String key) //该方法在1.13被移除
{
    // As return is ignored for compatibility, always check namespace
GameData.checkPrefix(new ResourceLocation(key).toString());
    TileEntity.register(key, tileEntityClass);//这一点使用了原版方法,也就是说原版注册不是不可使用的
}
private static <K extends IForgeRegistryEntry<K>> K register(K object)
{
    //通过GameRegistry.register注册到游戏中的实现了IForgeRegistryEntry的东西,本质上是GameData注册的
    return (K)GameData.register_impl(object);
}

一些开发者表示:我爱简洁,不想要那劳什子的event.getRegistry(),GameRegistry.register()不让用,那我就GameData好了,多简洁!然后你就不得不面对这样的问题:不同的开发者会把东西注册到不同的加载阶段导致一些不可预料的后果,在这种背景之下,forge推出了RegistryEvent.Register类,谋求一个统一的加载。


虽然MC是有一些onXXX()方法,来监听游戏中发生的特定动作,但是这并不够,而且你也没办法从这些返回void的方法中获取到什么——想想一个很常见的需求,我只需要监听苦力怕死亡的瞬间,来做出一个特定的动作(例如复活),但是你又没办法覆写原版的方法去更改苦力怕死亡时的行为——于是事件系统应运而生,所有的事件(Event)都是forge把相关的代码打补丁写进MC源码中进而起效的,你可以在ForgeHooks类中看到forge的“小动作”,看看原版方法都会触发哪些事件。
使用一个事件其实很简单,只需要这么做:
@SubcribeEvent //forge用来监听事件的注解
public void doSomething(Event event){
     //todo 实现你想要做的
     //监听事件不需要返回什么东西,所以一律是void
}
是的,就是这么简单,让我们看看默认提供的事件(比罗列一大堆AI还要恶心):





















[groupid=128]HAYO Studio[/groupid]