├── .gitattributes ├── .gitignore ├── LICENSE ├── Pipfile ├── Pipfile.lock ├── README.md ├── docs ├── animation │ ├── armature.md │ ├── asm.md │ ├── implementing.md │ └── intro.md ├── blocks │ ├── blocks.md │ ├── interaction.md │ └── states.md ├── concepts │ ├── internationalization.md │ ├── jarsigning.md │ ├── registries.md │ ├── resources.md │ └── sides.md ├── config │ └── annotations.md ├── conventions │ ├── loadstages.md │ ├── locations.md │ └── versioning.md ├── datastorage │ ├── capabilities.md │ ├── extendedentityproperties.md │ └── worldsaveddata.md ├── effects │ └── sounds.md ├── events │ └── intro.md ├── forgedev │ ├── index.md │ └── prguidelines.md ├── gettingstarted │ ├── autoupdate.md │ ├── debugprofiler.md │ ├── dependencymanagement.md │ ├── index.md │ └── structuring.md ├── index.md ├── items │ ├── items.md │ └── loot_tables.md ├── models │ ├── advanced │ │ ├── extended-blockstates.md │ │ ├── ibakedmodel.md │ │ ├── icustommodelloader.md │ │ ├── imodel.md │ │ ├── imodelstate+part.md │ │ ├── introduction.md │ │ ├── itemoverridelist.md │ │ └── perspective.md │ ├── blockstates │ │ ├── example.png │ │ ├── forgeBlockstates.md │ │ └── introduction.md │ ├── color.md │ ├── files.md │ ├── introduction.md │ ├── overrides.md │ └── using.md ├── networking │ ├── entities.md │ ├── index.md │ ├── overview.md │ └── simpleimpl.md ├── rendering │ └── teisr.md ├── styleguide.md ├── tileentities │ ├── tesr.md │ └── tileentity.md └── utilities │ ├── oredictionary.md │ ├── permissionapi.md │ └── recipes.md ├── forge_theme ├── 404.html ├── base.html ├── browserconfig.xml ├── css │ ├── styles_dark.css │ └── styles_light.css ├── images │ ├── apple-touch-icon.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── logo.svg │ └── safari-pinned-tab.svg ├── js │ ├── theme-switch.js │ └── theme.js ├── main.html ├── manifest.json ├── mkdocs_theme.yml ├── nav.html └── search.html ├── mkdocs.yml └── requirements.txt /.gitattributes: -------------------------------------------------------------------------------- 1 | // general 2 | * text auto 3 | *.sh text eol=lf 4 | *.txt eol=lf 5 | *.md eol=lf 6 | *.xml text 7 | *.bat text eol=crlf 8 | *.png binary 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | site/ 2 | .vscode/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [packages] 2 | mkdocs = "*" 3 | pymdown-extensions = "*" 4 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "28fbf145980141e0c8ed0f850953920deda9ffcfaa88b693ac470c57bbd81c33" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": {}, 8 | "sources": [ 9 | { 10 | "name": "pypi", 11 | "url": "https://pypi.org/simple", 12 | "verify_ssl": true 13 | } 14 | ] 15 | }, 16 | "default": { 17 | "click": { 18 | "hashes": [ 19 | "sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d", 20 | "sha256:f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b" 21 | ], 22 | "version": "==6.7" 23 | }, 24 | "jinja2": { 25 | "hashes": [ 26 | "sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd", 27 | "sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4" 28 | ], 29 | "version": "==2.10" 30 | }, 31 | "livereload": { 32 | "hashes": [ 33 | "sha256:583179dc8d49b040a9da79bd33de59e160d2a8802b939e304eb359a4419f6498", 34 | "sha256:dd4469a8f5a6833576e9f5433f1439c306de15dbbfeceabd32479b1123380fa5" 35 | ], 36 | "markers": "python_version >= '2.7' and python_version != '3.1.*' and python_version != '3.0.*' and python_version != '3.3.*' and python_version != '3.2.*'", 37 | "version": "==2.5.2" 38 | }, 39 | "markdown": { 40 | "hashes": [ 41 | "sha256:9ba587db9daee7ec761cfc656272be6aabe2ed300fece21208e4aab2e457bc8f", 42 | "sha256:a856869c7ff079ad84a3e19cd87a64998350c2b94e9e08e44270faef33400f81" 43 | ], 44 | "version": "==2.6.11" 45 | }, 46 | "markupsafe": { 47 | "hashes": [ 48 | "sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665" 49 | ], 50 | "version": "==1.0" 51 | }, 52 | "mkdocs": { 53 | "hashes": [ 54 | "sha256:1b4d46cd1cb517cd743358da96a3efc588fd86f81512fb9c28214597b6dc731f", 55 | "sha256:cd7264ea42d76f5bc1a0bd8b0a2c6c6e6be3a8742f5e78f47104a452dbe93600" 56 | ], 57 | "index": "pypi", 58 | "version": "==0.17.5" 59 | }, 60 | "pymdown-extensions": { 61 | "hashes": [ 62 | "sha256:20f2ae1067ab850cab92fcf57487267a7fd1365a7b1e7c5394e1e0778455eec1", 63 | "sha256:7d3fcbb4c5d70a78d1f4c2c7eef02dbe7e1ba08b06cb72e08b3d1027eb77458b" 64 | ], 65 | "index": "pypi", 66 | "version": "==4.12" 67 | }, 68 | "pyyaml": { 69 | "hashes": [ 70 | "sha256:3d7da3009c0f3e783b2c873687652d83b1bbfd5c88e9813fb7e5b03c0dd3108b", 71 | "sha256:3ef3092145e9b70e3ddd2c7ad59bdd0252a94dfe3949721633e41344de00a6bf", 72 | "sha256:40c71b8e076d0550b2e6380bada1f1cd1017b882f7e16f09a65be98e017f211a", 73 | "sha256:558dd60b890ba8fd982e05941927a3911dc409a63dcb8b634feaa0cda69330d3", 74 | "sha256:a7c28b45d9f99102fa092bb213aa12e0aaf9a6a1f5e395d36166639c1f96c3a1", 75 | "sha256:aa7dd4a6a427aed7df6fb7f08a580d68d9b118d90310374716ae90b710280af1", 76 | "sha256:bc558586e6045763782014934bfaf39d48b8ae85a2713117d16c39864085c613", 77 | "sha256:d46d7982b62e0729ad0175a9bc7e10a566fc07b224d2c79fafb5e032727eaa04", 78 | "sha256:d5eef459e30b09f5a098b9cea68bebfeb268697f78d647bd255a085371ac7f3f", 79 | "sha256:e01d3203230e1786cd91ccfdc8f8454c8069c91bee3962ad93b87a4b2860f537", 80 | "sha256:e170a9e6fcfd19021dd29845af83bb79236068bf5fd4df3327c1be18182b2531" 81 | ], 82 | "version": "==3.13" 83 | }, 84 | "six": { 85 | "hashes": [ 86 | "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", 87 | "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" 88 | ], 89 | "version": "==1.11.0" 90 | }, 91 | "tornado": { 92 | "hashes": [ 93 | "sha256:5ef073ac6180038ccf99411fe05ae9aafb675952a2c8db60592d5daf8401f803", 94 | "sha256:6d14e47eab0e15799cf3cdcc86b0b98279da68522caace2bd7ce644287685f0a", 95 | "sha256:92b7ca81e18ba9ec3031a7ee73d4577ac21d41a0c9b775a9182f43301c3b5f8e", 96 | "sha256:ab587996fe6fb9ce65abfda440f9b61e4f9f2cf921967723540679176915e4c3", 97 | "sha256:b36298e9f63f18cad97378db2222c0e0ca6a55f6304e605515e05a25483ed51a" 98 | ], 99 | "version": "==4.5.3" 100 | } 101 | }, 102 | "develop": {} 103 | } 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Forge文档 2 | ========= 3 | 4 | 这里是使用[Read the docs](https://readthedocs.org/)构建的MinecraftForge文档的中文翻译。网址为: [https://mcforge-cn.rtfd.io/](https://mcforge-cn.rtfd.io/) 5 | 6 | - 原版的文档可以在这里找到: [http://mcforge.readthedocs.io/](http://mcforge.readthedocs.io/) 7 | - 原版的Github工程: [https://github.com/MinecraftForge/Documentation](https://github.com/MinecraftForge/Documentation) 8 | 9 | 本文档旨在为Forge开发提供详细的文档。Javadocs将不会放在这里。 10 | 11 | 本文档的目标是给予使用Forge开发的mod一个有组织的并且**明确的**参考资料。这并不是一个Java语言的教程,而且Java的基本概念也不应该是本文档的一部分。 12 | 13 | 你可以自由地提交关于Forge开发文档的Pull Requests。 14 | 15 | 不要期待这个文档会经常的更新,但我们会努力完善这个文档,并且很快解决很严重的错误。 -------------------------------------------------------------------------------- /docs/animation/armature.md: -------------------------------------------------------------------------------- 1 | 骨骼文件 2 | ============== 3 | 4 | 骨骼文件用于定义模型动画的关节和剪辑。 5 | 6 | 文件结构 7 | ---------------- 8 | 9 | 一个骨骼文件示例,取自forge debug mod 10 | ```json 11 | { 12 | "joints": { 13 | "stick": {"2": [1.0]}, 14 | "cube": {"3": [1.0]} 15 | }, 16 | "clips": { 17 | "default": { 18 | "loop": true, 19 | "joint_clips": { 20 | "stick": [ 21 | { 22 | "variable": "offset_x", 23 | "type": "uniform", 24 | "interpolation": "linear", 25 | "samples": [0, 0.6875, 0] 26 | } 27 | ], 28 | "cube": [ 29 | { 30 | "variable": "offset_x", 31 | "type": "uniform", 32 | "interpolation": "linear", 33 | "samples": [0, 0.6875, 0] 34 | }, 35 | { 36 | "variable": "axis_z", 37 | "type": "uniform", 38 | "interpolation": "nearest", 39 | "samples": [ 1 ] 40 | }, 41 | { 42 | "variable": "origin_x", 43 | "type": "uniform", 44 | "interpolation": "nearest", 45 | "samples": [ 0.15625 ] 46 | }, 47 | { 48 | "variable": "origin_y", 49 | "type": "uniform", 50 | "interpolation": "nearest", 51 | "samples": [ 0.40625 ] 52 | }, 53 | { 54 | "variable": "angle", 55 | "type": "uniform", 56 | "interpolation": "linear", 57 | "samples": [0, 120, 240, 0, 120, 240] 58 | } 59 | ] 60 | }, 61 | "events": {} 62 | } 63 | } 64 | } 65 | 66 | ``` 67 | 68 | 该文件分为两部分,即关节和剪辑。 69 | 70 | 关节 71 | -------- 72 | 每个关节定义一个在动画中作为一个整体一起动的对象。 使用原本JSON模型,这意味着多个元素可以属于同一个关节。 73 | 74 | 格式如下 75 | ```javascript 76 | { 77 | "joints": { 78 | , ... 79 | } 80 | } 81 | 82 | --- 83 | 84 | ::= { 85 | : { // joint_name 86 | , ... 87 | } 88 | } 89 | 90 | ::= { 91 | "": [ ] // index_model, joint_weight (只能指定一个) 92 | } 93 | 94 | ``` 95 | 96 | - `joint_name`是关节的名字 97 | - `index_model`是一个0索引号(其中0是模型中定义的第一个元素),表示此关节控制的模型元素。 必须是一个字符串(参见示例) 98 | - `joint_weight`是一个权重(0-1),表示如果在多个关节中使用该元素,该关节将对该元素的最终转换做出多少贡献。 99 | 100 | !!! note "提示" 101 | 102 | 对于更简单的动画,权重通常可以设置为1.0,但如果您希望剪辑中的多个关节以不同方式设置动画,可以用这种方法实现。 103 | 104 | 并非所有元素都需要关节,只有你运动的元素。 105 | 如果元素出现在多个关节中,则最终运动是每个关节的变换的加权平均值。 106 | 107 | 剪辑 108 | ------- 109 | 110 | 剪辑本质上是关于如何使用值来动画某些关节集合的说明。 111 | 它们还包括在某些点发射的事件。 112 | 113 | 格式如下: 114 | ```javascript 115 | 116 | { 117 | "clips": { 118 | "clip_name": { 119 | "loop": , 120 | "joint_clips": { 121 | , ... 122 | }, 123 | "events": { 124 | ... 125 | } 126 | 127 | } 128 | } 129 | } 130 | 131 | ------- 132 | 133 | ::= { 134 | "joint_name": [ 135 | , ... 136 | ] 137 | } 138 | 139 | ::= { 140 | "variable": , 141 | "type": "uniform", 142 | "interpolation": , 143 | "samples": [ float, ... ] 144 | } 145 | 146 | 147 | ``` 148 | 149 | - loop:如果为真,则当参数值超过1时,动画将会循环,否则它将仅限于最后的状态。 150 | 151 | ### Joint Clips 152 | 每个`joint_clip`是改变关节的一组变量。 `type`属性目前被忽略,但必须是`uniform`。 153 | 154 | `samples`定义动画将采用什么值(类似于传统动画中的关键帧),它取决于`interpolation`的值解释。 155 | 156 | `interpolation`,即如何将`samples`列表转换为(尽可能)连续动画,可以是以下之一: 157 | 158 | - `nearest` - 如果值<0.5,则使用第一个samples,否则使用第二个samples。 如果只给出一个值,则对静态变量很有用。 159 | - `linear` - 在samples之间线性插值。 samples之间的时间是1 /samples数. 160 | 161 | `variable` 可以是下面之一: 162 | 163 | - `offset_x`, `offset_y`, `offset_z` - 平移 164 | - `scale` - 均匀缩放 165 | - `scale_x`, `scale_y`, `scale_x` - 延坐标轴缩放 166 | - `axis_x`, `axis_y`, `axis_z` - 旋转轴 167 | - `angle` - 旋转角度 168 | - `origin_x`, `origin_y`, `origin_z` - 旋转起点(rotation origin) 169 | 170 | ### Events 171 | 172 | 每个剪辑都可以触发事件,格式如下: 173 | ```javascript 174 | :: { 175 | : "event_text" 176 | } 177 | ``` 178 | 有关事件和`event_text`含义的更多信息,请参阅[动画状态机][asm]页面。 179 | 180 | `event_time`是一个表示何时触发事件的值(通常介于0和1之间)。 当控制此剪辑的参数达到等于或大于`event_time`的点时,将触发该事件。 181 | 182 | [asm]: asm.md 183 | -------------------------------------------------------------------------------- /docs/animation/asm.md: -------------------------------------------------------------------------------- 1 | 动画状态机文件 2 | =========== 3 | 4 | 动画状态机 (ASM) 文件是动画API的核心. 它们定义了动画的执行方式以及如何使用骨架文件中定义的剪辑。 5 | 6 | 概念 7 | ---------- 8 | 9 | ASM包含_参数_,_剪辑_,_状态_和_转换_(parameters, clips, states 和transitions)。 10 | 11 | ### 状态(States ) 12 | 13 | 动画_状态_ 机可以在许多不同的_状态_中。 您可以在状态部分中定义哪些状态。 14 | 15 | ### 转换(Transitions) 16 | 17 | 转换定义允许哪些状态进入其他状态,例如允许“关闭”状态进入“打开”状态。 18 | 19 | !!! note "提示" 20 | 21 | 但是,转换 _不会_ 定义状态之间播放的动画。 如果要这样做,则必须创建一个播放动画的附加状态,然后使用事件转到下一个状态。 22 | 23 | ### 参数(Parameters) 24 | 25 | !!! note "提示" 26 | 27 | 参数在代码中称为`TimeValues`,因此这是SomethingValue的命名约定。 28 | 29 | 所有参数都采用输入,通常以秒为单位的当前游戏时间作为浮点数(考虑特定tick)并输出另一个时间。 此输出用作剪辑的输入,告诉它当前动画的进展。 30 | 31 | 每个参数都可以在ASM中定义,也可以在代码中加载ASM时定义。 加载时参数通常是`VariableValue`类型,它返回一个代码内可变的值,忽略其输入。 32 | 其他类型允许你对输入进行数学运算(`SimpleExprValue`),返回一个常量(`ConstValue`),引用其他参数(`ParameterValue`),返回 33 | 输入unmodified(`IdentityValue`)并执行两个参数的组合(`CompositionValue`)。 34 | 35 | ### 剪辑(Clips) 36 | 37 | !!! note "提示" 38 | 39 | 剪辑可以是ASM剪辑,在ASM中定义的剪辑,也可以是电枢剪辑,在骨架文件中定义的剪辑。 40 | 对于本页的其余部分,除非另有说明,否则“剪辑”将引用ASM剪辑。 41 | 42 | 剪辑接收输入,通常是时间,并使用它对模型执行某些操作。 不同类型的剪辑做不同的事情,最简单的是动画电影剪辑(`ModelClip`)。 您还可以覆盖另一个ASM剪辑的输入(`TimeClip`)。如果输入为正,则在动画另一个剪辑时触发事件(`TriggerClip`)。在两个剪辑之间平滑混合(`SlerpClip`)。 在ASM参考另一个剪辑(`ClipReference`)或什么也不做(`IdentityClip`)。 43 | 44 | ### 事件(Events) 45 | 46 | Various things can trigger events in the ASM. Events in the ASM are represented using only text. 47 | Some events are special, with text that is formatted like this: `!event_type:event_value`. Right now there is only one kind of `event_type`, namely `transition`. This tries to transition to whatever state is defined in the `event_value`. Anything else is a normal event and can be used from the `pastEvents` callback, but more information about that is on the [implementing][] page. 48 | 49 | 各种东西可以触发ASM中的事件。 ASM中的事件仅使用文本表示。 50 | 有些事件很特殊,文本格式如下:`!event_type:event_value`。 现在只有一种`event_type`,即`transition`。 这会尝试转换为`event_value`中定义的任何状态。 其他任何东西都是正常事件,可以从`pastEvents`回调中使用,但有关它的更多信息在[使用API][implementation]页面上。 51 | 52 | 53 | 编程API 54 | ---------- 55 | 56 | !!! warning "警告" 57 | 58 | ASM代码API只能用于 _客户端_ 。 在代码中存储ASM时,请使用一端不可知的“IAnimationStateMachine”接口。 59 | 60 | 可以通过调用`ModelLoaderRegistry.loadASM`来加载ASM。 它需要两个参数,第一个是`ResourceLocation`表示 61 | 存储ASM的位置,以及第二个加载时定义参数的`ImmutableMap`。 62 | 63 | 一个例子: 64 | ```java 65 | @Nullable 66 | private final IAnimationStateMachine asm; 67 | private final VariableValue cycle = new VariableValue(4); 68 | 69 | public Spin() { 70 | asm = proxy.loadASM(new ResourceLocation(MODID, "asms/block/rotatest.json"), ImmutableMap.of("cycle_length", cycle)); 71 | } 72 | ``` 73 | 74 | 在这里,使用一个名为`cycle_length`的额外参数加载ASM(用端代理SidedProxy以避免在服务器上崩溃)。 这个参数的类型是`VariableValue`,所以我们可以从我们的代码中设置它。 75 | 76 | 使用ASM实例,您可以使用`.currentState()`获取当前状态,并使用`.transition(nextState)`转换到另一个状态。 77 | 78 | `VariableValue`参数可以通过调用`.setValue`来设置它们的值,但是你不能读回这个值。 无需通知ASM此更改,它会自动更改。 79 | 80 | 文件格式 81 | ------------- 82 | 83 | ASM存储在json文件中。 位置无关紧要,但它们通常放在`asms`文件夹中。 84 | 85 | 首先是一个例子: 86 | ```json 87 | { 88 | "parameters": { 89 | "anim_cycle": ["/", "#cycle_length"] 90 | }, 91 | "clips": { 92 | "default": ["apply", "forgedebugmodelanimation:block/rotatest@default", "#anim_cycle" ] 93 | }, 94 | "states": [ 95 | "default" 96 | ], 97 | "transitions": {}, 98 | "start_state": "default" 99 | } 100 | ``` 101 | 102 | 如上所述,文件具有参数,剪辑,状态和转换,以及ASM的起始状态。 103 | 104 | 所有这些标签都是必需的,即使它们是空的。 105 | 106 | ### 参数(Parameters) 107 | 108 | ```javascript 109 | { 110 | "name": 111 | } 112 | ``` 113 | 114 | ``不同类型的参数有不同的格式,简单的参数是: 115 | 116 | - `IdentityValue`:字符串`#identity`, 117 | - `ParameterValue`: 要引用的参数,前缀为`#`,例如`#my_awesome_parameter` 118 | - `ConstValue`: 一个数字用作返回的常量 119 | 120 | #### 数学表达式 (`SimpleExprValue`) 121 | 122 | 格式: `[ regex("[+\\-*/mMrRfF]+"), , ... ]` 123 | 124 | ##### 示例: 125 | ```json 126 | [ "+", 4 ] 127 | [ "/+", 5, 1] 128 | [ "++", 2, "#other" ] 129 | [ "++", "#other", [ "compose", "#cycle", 3] ] 130 | ``` 131 | 132 | ##### 说明 133 | 134 | `SimpleExprValue`获取其输入并对其应用操作。 135 | 第一个参数是要应用的操作序列,其余参数表示这些操作的操作数。每个操作的输入是整个参数的输入(对于第一个操作)或前一个操作的结果。 136 | 137 | ##### 操作(区分大小写): 138 | 139 | | 操作 | 含义 | 140 | | --- | --- | 141 | | `+` | `输出 = 输入 + 参数` | 142 | | `-` | `输出 = 输入 - 参数` | 143 | | `*` | `输出 = 输入 * 参数` | 144 | | `/` | `输出 = 输入 / 参数` | 145 | | `m` | `输出 = min(输入, 参数)` | 146 | | `M` | `输出 = max(输入, 参数)` | 147 | | `r` | `输出 = floor(输入 / 参数) * 参数` (向下取整) | 148 | | `R` | `输出 = ceil(输入 / 参数) * 参数` (向上取整) | 149 | | `f` | `输出 = 输入 - floor(输入 / 参数) * 参数` (取余) | 150 | | `F` | `输出 = ceil(输入 / 参数) * 参数 - 输入` (参数减余数) | 151 | 152 | ##### 示例说明: 153 | - 输入 + 4 154 | - (输入 / 5) + 1 155 | - 输入 + 2 +参数 `other`的值 156 | - 输入 + 参数`other` 的值+ 参数 `cycle`的值 赋值为 3 157 | 158 | #### 功能组件 (`CompositionValue`) 159 | 160 | 格式: `[ "compose", , ]` 161 | 162 | ##### 示例: 163 | ```json 164 | [ "compose", "#cycle", 3] 165 | [ "compose", "#test", "#other"] 166 | [ "compose", [ "+", 3], "#other"] 167 | [ "compose", [ "compose", "#other2", "#other3"], "#other"] 168 | ``` 169 | ##### 说明 170 | 171 | `CompositionValue`将两个 参数定义 作为输入,并执行`value1(value2(input))`。 换句话说,它连接两个输入,用给定的输入调用第二个函数,用第二个输出调用第一个函数。 172 | 173 | ##### 示例说明: 174 | - `cycle(3)` 175 | - `test(other(输入))` 176 | - `3 + other(输入) ` 177 | - `other2(other3(other(输入)))` 因为 `value1` = `other2(other3(输入))` ,`value2` = `other(输入)` 178 | 179 | ### 剪辑(Clips) 180 | 181 | ```javascript 182 | { 183 | "name": 184 | } 185 | ``` 186 | 187 | 与参数一样,不同类型的剪辑`格式不同,但简单的剪辑是: 188 | 189 | - `IdentityClip`: 字符串 `#identity` 190 | - `ClipReference`: 要引用的剪辑名,前缀为`#`,例如`#my_amazing_clip` 191 | - `ModelClip`: 模型资源位置+`@`+骨骼剪辑的名称,例如 `mymod:block/test@default` ,`mymod:block/test#facing=east@moving` 192 | 193 | #### 覆盖输入 (`TimeClip`) 194 | 195 | 格式: `[ "apply", , ]` 196 | 197 | ##### 示例: 198 | ```json 199 | ["apply", "mymod:block/animated_thing@moving", "#cycle_time"] 200 | ["apply", [ "apply", "mymod:block/animated_thing@moving", [ "+", 3 ] ], "#cycle"] 201 | ``` 202 | 203 | ##### 说明 204 | 205 | `TimeClip`采用另一个剪辑并使用自定义参数而不是当前时间来调用它。 通常用于使用参数而不是当前时间来调用`ModelClip`。 206 | 207 | ##### 示例说明: 208 | 209 | - `mymod:block/animated_thing@moving(#cycle_time)` 210 | - `mymod:block/animated_thing@moving(#cycle + 3)` 211 | 212 | #### 触发事件 (`TriggerClip`) 213 | 214 | 格式: `[ "trigger_positive", , , ""]` 215 | 216 | ##### 示例 217 | 218 | ```json 219 | [ "trigger_positive", "#default", "#end_cycle", "!transition:moving" ] 220 | [ "trigger_positive", "mymod:block/animated_thing@moving", "#end_cycle", "boop" ] 221 | ``` 222 | 223 | ##### 说明 224 | 225 | `TriggerClip`在看起来上相当于`TimeClip`,但当`parameter_description`变为正时,也会在`event_text`中触发事件。 226 | 同时,它将`clip_definition`中的剪辑应用于相同的`parameter_description`。 227 | 228 | ##### 示例说明 229 | 230 | - 在给定参数`end_cycle`的输入的情况下应用名为`default`的剪辑,当`end_cycle`为正变为到`moving`状态 231 | - 在给定参数`end_cycle`的输入的情况下应用名为`mymod:block/animated_thing@moving`的剪辑,当`end_cycle`为正触发`boop`事件 232 | 233 | #### 两个剪辑之间的混合 (`SlerpClip`) 234 | 235 | 格式: `[ "slerp", , , , ]` 236 | 237 | ##### 示例 238 | 239 | ```json 240 | [ "slerp", "#closed", "#open", "#identity", "#progress" ] 241 | [ "slerp", [ "apply", "#move", "#mover"], "#end", "#identity", "#progress" ] 242 | ``` 243 | 244 | ##### 说明 245 | 246 | `SlerpClip`在两个独立的剪辑之间执行球形线性混合(spherical linear blend)。 换句话说,它会将一个剪辑平滑地变换为另一个剪辑。 247 | 248 | 两个`clip_definition`分别是要混合的剪辑。 第一个`parameter_definition`是“输入”。 from和to剪辑都以当前动画时间传递此参数的输出。 第二个`parameter_definition`是“progress”,一个介于0和1之间的值,表示我们在混合中的距离。 将此剪辑与trigger_positive和转换特殊事件组合可以允许两个固态之间的简单转换。 249 | 250 | ###### 示例说明 251 | 252 | - 将“关闭”剪辑混合到“打开”剪辑中,为两个剪辑提供未更改的时间作为输入并混合进度`#progress`。 253 | - 当输入参数`mover`给定结束剪辑时,将移动剪辑的结果混合,其中未改变的时间作为混合进度`#progress`的输入。 254 | 255 | ### 状态(States) 256 | 257 | 状态部分只是所有可能状态的列表。 258 | 259 | 例如 260 | 261 | ```json 262 | "states": [ 263 | "open", 264 | "closed", 265 | "opening", 266 | "closing", 267 | "dancing" 268 | ] 269 | ``` 270 | 定义5 种状态: open, closed, opening, closing 和 dancing. 271 | 272 | ### 转换(Transitions) 273 | 274 | The transitions section defines which states can go to what other states. A state can go to 0, 1, or many other states. 275 | To define a state as going to no other states, omit it from the section. To define a state as going to only one other state, create a key with the value of the state it can go to, for example `"open": "opening"`. To define a state as going to many other states, do the same as if it were going to only one other state but make the value a list of all possible recieving states instead, for example: `"open": ["closed", "opening"]`. 276 | 277 | 转换部分定义哪些状态可以转到其他状态。 状态可以进入0,1或许多其他状态。 278 | 若要一个状态不能进入其他状态,请从该部分中省略它。 若要一个状态仅能转到另一个状态,请创建一个具有其可以进入的状态值的键,例如`"open": "opening"`。 要将状态定义为转到许多其他状态,请执行相同的操作,就好像它只转到另一个状态,而是将值作为所有可能的接收状态的列表,例如:`"open": ["closed", "opening"]`。 279 | 280 | 一个完整的例子: 281 | 282 | ```json 283 | "transitions": { 284 | "open": "closing", 285 | "closed": [ "dancing", "opening" ], 286 | "closing": "closed", 287 | "opening": "open", 288 | "dancing": "closed" 289 | } 290 | ``` 291 | 292 | 这个例子是说: 293 | 294 | - open状态可以进入closing状态 295 | - closed状态可以进入dancing和opening状态 296 | - closing状态可以进入closed状态 297 | - opening状态可以进入open状态 298 | - dancing状态可以进入closed状态 299 | 300 | [implementing]: implementing.md 301 | -------------------------------------------------------------------------------- /docs/animation/implementing.md: -------------------------------------------------------------------------------- 1 | 使用 API 2 | ====================== 3 | 4 | 根据您希望使用API制作动画不同,代码端实现有点不同。 5 | 有关ASM API本身(用于控制动画)的文档可在[ASM][asm]页面上找到,因为它与您动画的内容无关。 6 | 7 | 方块 8 | -------- 9 | 10 | 方块的动画是用`AnimationTESR`完成的,它是一个`FastTESR`。 因此,必须为您的块设置`TileEntity`。 你的`TileEntity`必须提供`ANIMATION_CAPABILITY`,它通过你的ASM调用它的`.cast`方法来接收。 如果你没有在方块的块状态中提供`StaticProperty`,那么你的方块也必须在`ENTITYBLOCK_ANIMATED`渲染层中渲染。 11 | 12 | `StaticProperty`是一个属性,你可以通过在`createBlockState()`里面的方块的属性列表中添加`Properties.StaticProperty`来添加到方块的块状态。 渲染方块时,`AnimationTESR`检查属性的值是否为真; 如果为真,则块渲染继续正常进行。 否则,`AnimationTESR`动画分配给方块状态json中的`static=false`变体的方块模型。 模型所有的静态部分应该静态渲染的,因为这是它的目的。 13 | 14 | `handleEvents()`回调位于`AnimationTESR` 中,因此在注册`TileEntity`时必须重载。 15 | 16 | 这是一个注册TESR的示例: 17 | 18 | ```java 19 | ClientRegistry.bindTileEntitySpecialRenderer(Chest.class, new AnimationTESR() 20 | { 21 | @Override 22 | public void handleEvents(Chest chest, float time, Iterable pastEvents) 23 | { 24 | chest.handleEvents(time, pastEvents); 25 | } 26 | }); 27 | ``` 28 | 29 | 在这个例子中,我们在注册TESR时重写了`handleEvents()`回调,因为实现很简单,但你可以轻松地继承`AnimationTESR`以实现相同的效果。 方块的`handleEvents()`回调有两个参数:正在渲染的tile实体和一个可迭代的事件。 30 | 对`chest.handleEvents()`的调用调用位于虚构的`Chest`TileEntity中的方法,因为在`handleEvents()`方法中无法访问ASM。 31 | 32 | 物品 33 | ------- 34 | 35 | 物品的动画完全使用功能系统完成。 您的物品必须通过`ICapabilityProvider`提供`ANIMATION_CAPABILITY`。 你可以创建这个功能的一个实例,它使用你的ASM的`.cast`方法,它通常存储在`ICapabilityProvider`对象本身。 下面是一个例子: 36 | 37 | ```java 38 | private static class ItemAnimationHolder implements ICapabilityProvider 39 | { 40 | private final VariableValue cycleLength = new VariableValue(4); 41 | 42 | private final IAnimationStateMachine asm = proxy.load(new ResourceLocation(MODID.toLowerCase(), "asms/block/engine.json"), ImmutableMap.of( 43 | "cycle_length", cycleLength 44 | )); 45 | 46 | @Override 47 | public boolean hasCapability(@Nonnull Capability capability, @Nullable EnumFacing facing) 48 | { 49 | return capability == CapabilityAnimation.ANIMATION_CAPABILITY; 50 | } 51 | 52 | @Override 53 | @Nullable 54 | public T getCapability(@Nonnull Capability capability, @Nullable EnumFacing facing) 55 | { 56 | if(capability == CapabilityAnimation.ANIMATION_CAPABILITY) 57 | { 58 | return CapabilityAnimation.ANIMATION_CAPABILITY.cast(asm); 59 | } 60 | return null; 61 | } 62 | } 63 | ``` 64 | 65 | 无法在当前实现中的物品上接收事件。 66 | 67 | 实体 68 | ---------- 69 | 70 | 为了使用动画API为实体设置动画,实体的渲染器必须将`AnimationModelBase`作为其模型。 该模型的构造函数采用两个参数,即实际模型的位置(如JSON或B3D文件的路径,而不是方块状态引用)和`VertexLighter`。 71 | 可以使用`new VertexLighterSmoothAo(Minecraft.getMinecraft().getBlockColors())`创建`VertexLighter`对象。 72 | 实体还必须提供`ANIMATION_CAPABILITY`,它可以通过传递ASM以`.cast`方法创建。 73 | 74 | `handleEvents()`回调位于`AnimationModelBase`类中,如果你想使用该事件,你必须继承`AnimationModelBase`类。 回调有三个参数:正在渲染的实体,当前时间(tick),以及已发生事件的可迭代对象。 75 | 76 | 创建渲染器的示例如下所示: 77 | 78 | ```java 79 | ResourceLocation location = new ModelResourceLocation(new ResourceLocation(MODID, blockName), "entity"); 80 | return new RenderLiving(manager, new net.minecraftforge.client.model.animation.AnimationModelBase(location, new VertexLighterSmoothAo(Minecraft.getMinecraft().getBlockColors())) 81 | { 82 | @Override 83 | public void handleEvents(EntityChest chest, float time, Iterable pastEvents) 84 | { 85 | chest.handleEvents(time, pastEvents); 86 | } 87 | }, 0.5f) { 88 | 89 | // ... getEntityTexture() ... 90 | 91 | }; 92 | ``` 93 | 94 | [asm]: asm.md 95 | -------------------------------------------------------------------------------- /docs/animation/intro.md: -------------------------------------------------------------------------------- 1 | 概述 2 | =============================== 3 | 4 | Forge 动画API允许您用JSON(和B3D)模型设置动画。 5 | 在开始阅读本文之前,您应该知道如何创建原本JSON模型 6 | 阅读有关方块状态的文档。 7 | 8 | !!! note "提示" 9 | 10 | 虽然您也可以用B3D模型,但本文档的大部分内容都假设您使用的是JSON文件。 11 | (未完成)有关详细信息,请参阅有关使用B3D的页面 12 | 13 | Animation API由两个主要组件组成:骨架(Armature)文件和动画状态机(ASM)文件。 14 | 15 | 骨架文件定义JSON文件的关节和剪辑。 关节是模型文件中最主要的长方体的名字(有关详细信息,请参见[骨骼][arm]),而剪辑是一组随时间推移应用于关节的变换(例如,想想电影中的剪辑)。 ASM文件定义动画可以处于的各种状态,以及什么这些状态之间存在过渡。 它们还定义了动画的参数(返回浮点数的函数),这些参数通常用作剪辑的输入,但也可以触发事件。 事件本质上是一种在动画到达某个点或触发转换时接收代码内通知的方法。 16 | 17 | 文件结构约定 18 | ----------------------- 19 | 20 | ASM文件,方块通常存储在`asms/block`,item储存在`asms/item`等等。您可以指定从哪里加载它们,因此它们的位置有你决定。 21 | 22 | 骨骼文件_必须_存储在`armatures`文件夹中。 通过模型文件的路径来查找它们,删除`models/`下的`armatures/`,如`models/block/test.json`中的模型放在`armatures/block/test.json`。 23 | 24 | [arm]: armature.md 25 | -------------------------------------------------------------------------------- /docs/blocks/blocks.md: -------------------------------------------------------------------------------- 1 | 方块 2 | ========== 3 | 4 | 很显然,方块(Block)在Minecraft世界中非常重要。它们组成了所有的地形,结构,以及机械装置。如果你对制作mod很感兴趣,那么你很可能会想加一些方块。这一节将指导你创建方块,并让你了解一些方块的功能。 5 | 6 | 创建一个方块 7 | ----------- 8 | 9 | ### 简单方块 10 | 11 | 对于简单的方块,或者说那些没有特殊功能的方块(如圆石和木板等),你并不需要对它们单独创建一个类。你只需要实例化一个 `Block` 类并调用一些Setter方法,就能创建出很多不同种类的方块。比如说: 12 | 13 | - `setHardness` - 控制破坏方块所需要的时间。它可以是任意的值。方便参考起见,石头(Stone)的硬度(Hardness)为1.5,泥土(Dirt)的硬度为0.5。如果方块应该是不可破坏的,你可以直接调用 `setBlockUnbreakable`。 14 | - `setResistance` - 控制方块的爆炸抗性。虽然这个值是与硬度分开的,但是 `setHardness` 同样会将方块的爆炸抗性(Resistance)设置为5倍的硬度,如果爆炸抗性低于这个值的话。(译注:这个方法设置后的实际 `blockResistance` 是传入参数的3倍。 比如说石头的爆炸抗性(`blockResistance`)为30,但传入的参数为 `10.0f`。而`setHardness` 检测的是这个 `blockResistance`。但一般初始化方块的时候一般都先设置硬度再设置爆炸抗性) 15 | - `setSoundType` - 控制方块被击打、破坏、或放置时的声音。需要传入一个 `SoundType` 参数,具体请看[音效]那一节。 16 | - `setLightLevel` - 控制方块的发光程度。**注意:**这个方法接受一个0-1的值,而不是0-15。如果想要计算这个值,请用你需要的光亮等级除以16。比如说,如果一个方块的亮度为5,那么这里的参数需要为 `5 / 16f`。 17 | - `setLightOpacity` - 控制方块阻碍光线传播的程度。不像 `setLightLevel`,这个值的范围是0-15。比如说,将这个值设定为 `3` 时,每当光线穿过方块,光线的光亮等级将会减少3级。 18 | - `setUnlocalizedName` - 设置方块未本地化时(Unlocalized)的名字。这个名字会被前缀 "tile.",并后缀 ".name" 以方便本地化。比如说,`setUnlocalizedName("foo")` 会将方块的本地化条目设置为 "tile.foo.name"。如果你需要更高级的本地化控制,那么你还需要一个自定义的物品(Item)类,我们将会在之后进行介绍。 19 | - `setCreativeTab` - 控制方块所在的创造面板。面板选项可以在 `CreativeTabs` 类下找到。 20 | 21 | 所有的这些方法都是可以**可链的**(Chainable),也就是说你可以以一个序列调用这些方法。如果想要一个例子,请看 `Block#registerBlocks` 方法。 22 | 23 | ### 高级方块 24 | 25 | 当然,上面介绍的东西仅仅只能创建一个非常简单的方块。如果你想添加更多的功能,比如说玩家互动,你需要一个自定义的类。然而,`Block` 类有太多方法了,很遗憾这里不能将每一个都列在这里并加以解释。请阅读这节的剩余部分来看看你可以对方块添加什么功能。 26 | 27 | 注册方块 28 | ------- 29 | 30 | 方块必须要[注册]至函数中。 31 | 32 | !!! important "重要" 33 | 34 | 世界中的方块和物品栏中的“方块”截然不同。世界中的方块由 `IBlockState` 来表示,它的行为由 `Block` 的实例来定义。而物品栏中的物品是一个 `ItemStack`,由 `Item` 来控制。作为 `Block` 与 `Item` 之间的桥梁,`ItemStack` 类诞生了。`ItemStack` 是 `Item` 的一个子类,但它封装了其对应 `Block` 的引用。`ItemBlock` 定义了一个“方块”作为物品的行为,比如说右键如何能放置这个方块。一个 `Block` 没有 `ItemBlock` 也是可以的。(比如说 `minecraft:water` 存在有这个方块,但没有这个物品。所以不能将它放在物品栏中。) 35 | 36 | 当你注册一个方块的时候,你**仅**注册了这个方块。这个方块不会自动有一个 `ItemBlock`。为了给你的方块添加一个最基础的 `ItemBlock`,你应该使用 `new ItemBlock(block).setRegistryName(block.getRegistryName())`。它的非本地化名称和方块的是一样的。你也可以使用自定义的 `ItemBlock` 子类。 当 `ItemBlock` 注册到一个方块上之后,可以使用 `Item.getItemFromBlock` 来获取它。如果这个 `Block` 没有 `ItemBlock`,`Item.getItemFromBlock` 将会返回 `null`,所以如果你不确定你要使用的方块是否有 `ItemBlock`,先检查 `null` 值。 37 | 38 | 拓展阅读 39 | ------- 40 | 41 | 对于更多方块属性的信息,比如说原版的木头类型、栅栏、墙等,请阅读[方块状态]小节。 42 | 43 | [音效]: ../effects/sounds.md 44 | [注册]: ../concepts/registries.md#registering-things 45 | [方块状态]: states.md -------------------------------------------------------------------------------- /docs/blocks/interaction.md: -------------------------------------------------------------------------------- 1 | 方块互动 2 | ========================== 3 | 4 | 玩家可以与方块有很多种互动(Interaction),比如说右击、左击、碰撞、在方块上行走、还有挖掘等。 5 | 6 | 这一节将会覆盖与方块最常见的互动类型。 7 | 8 | 玩家右击 9 | -------- 10 | 11 | 由于左击,或者说“击打”,一个方块一般并不会发生什么独特的行为,应该可以说右击,或者“激活”一个方块是最常用的一种互动方式。很幸运,这也是最简单的一种互动方式。 12 | 13 | ### `onBlockActivated` 14 | 15 | 这个方法控制了右键的行为,它可能会有点复杂。下面是这个方法的定义: 16 | 17 | 18 | ```java 19 | public boolean onBlockActivated(World worldIn, 20 | BlockPos pos, 21 | IBlockState state, 22 | EntityPlayer playerIn, 23 | EnumHand hand, 24 | @Nullable ItemStack heldItem, 25 | EnumFacing side, 26 | float hitX, 27 | float hitY, 28 | float hitZ) 29 | ``` 30 | 31 | 下面是对这个方法的讨论。 32 | 33 | #### 参数 34 | 35 | 前面几个参数很明显,它们是方块的当前世界,位置,和状态。接下来是激活方块的玩家,以及激活时的左右手。 36 | 37 | 接下来的一个参数 `heldItem` 是玩家激活方块时拿着的 `ItemStack`。注意这个参数为 `@Nullable`,也就是说这个参数可以为null(即手里没拿东西)。 38 | 39 | !!! important "重要" 40 | 41 | 如果你需要检测玩家手里的物品,请使用方法传进来的这个 `ItemStack`。检测玩家当前握着的物品是不可靠的,因为它可能在激活方块以后已经变了。 42 | 43 | 最后四个参数都是相互联系的。`side` 很明显是方块被激活的面。 `hitX`,`hitY`,和 `hitZ` 可能没这么明显,他们是方块的碰撞箱上激活点的坐标。它们的范围为0-1,代表玩家点击方块上的确切位置。 44 | 45 | #### 返回值 46 | 47 | 返回的那个boolean到底是什么呢?简单来说,它代表了这个方法是否“做了”什么。如果任何执行了任何动作,那就返回true,这将防止之后更多的事情发生,比如说激活物品。 48 | 49 | !!! important "重要" 50 | 51 | 在客户端返回 `false` 将会防止这个方法在服务端被调用。通常,我们会检查 `worldIn.isRemote` 并返回 `true`,否则进入正常的激活逻辑。原版中有很多这样的例子,比如说箱子(Chest) 52 | 53 | ### 用处 54 | 55 | 激活的用处可以说是无穷的。然而我们仍将介绍一些常用的用处。 56 | 57 | #### GUI 58 | 59 | 方块激活时最常见的用处就是打开一个GUI。许多原版的方块都是这样的,比如说箱子,漏斗,熔炉等。更多的GUI信息可以在[这里(未完成)](GUI)找到。 60 | 61 | #### 激活 62 | 63 | 激活的另一个用处,是激活。这里指的是打开一个开关或者是激活方块执行一个特定的行为。比如说,一个方块可以在激活时发光。原版中可以参考按钮或者拉杆的例子。 64 | 65 | !!! important "重要" 66 | 67 | `onBlockActivated` 在客户端和服务端都会被调用,所以一定要记住代码所处的[端]。许多东西,比如说打开GUI和变动世界,应该只在服务端调用。 68 | 69 | 玩家破坏 70 | ------- 71 | 72 | **未完成** 73 | 74 | 玩家高亮 75 | ------- 76 | 77 | **未完成** 78 | 79 | 实体碰撞 80 | ------- 81 | 82 | **未完成** 83 | 84 | `onBlockClicked` 85 | ---------------- 86 | 87 | ```java 88 | public void onBlockClicked(World worldIn, BlockPos pos, EntityPlayer playerIn) 89 | ``` 90 | 91 | 当玩家点击一个方块时会被触发。 92 | 93 | !!! note "提示" 94 | 95 | 这个方法是在玩家**左击**一个方块时调用的。 96 | 不要将它和`onBlockActivated`弄混了,它是当玩家右击一个方块才会激活的。 97 | 98 | ### 参数 99 | 100 | | 类型 | 名称 | 描述 | 101 | |:---------------:|:------------:|:----------------------------------------------| 102 | | `World` | `worldIn` | 被点击方块所处的世界 | 103 | | `BlockPos` | `pos` | 被点击方块所处的位置 | 104 | | `EntityPlayer` | `playerIn` | 点击的玩家 | 105 | 106 | ### 使用范例 107 | 108 | 这个方法可以很方便地在玩家点击一个方块时添加一些自定义的事件。 109 | 110 | 默认情况下这个方法不会做任何操作。 111 | 复写了这一方法的两个方块是**音符盒**(Note Block)和**红石矿石**(Redstone Ore)。 112 | 113 | 音符盒会在左击的时候播放一个音效。红石矿石方块会在左击的时候发光几秒钟。 114 | 115 | `onBlockDestroyedByPlayer` 116 | --- 117 | 118 | ```java 119 | public void onBlockDestroyedByPlayer(World worldIn, BlockPos pos, IBlockState state) 120 | ``` 121 | 122 | 当玩家摧毁一个方块时会被触发。 123 | 124 | ### 参数: 125 | 126 | | 类型 | 名称 | 描述 | 127 | | :-----------: | :-------: | :--------------------- | 128 | | `World` | `worldIn` | 被摧毁的方块所在的世界 | 129 | | `BlockPos` | `pos` | 被摧毁方块的坐标 | 130 | | `IBlockState` | `state` | 被摧毁的方块的状态 | 131 | 132 | !!! warning "警告" 133 | 134 | 参数`pos`可能不保持指示的状态 135 | 136 | ### 使用范例 137 | 138 | 此方法非常适合添加自定义方块破坏事件。 139 | 140 | 该方法默认为空。 141 | 142 | **TNT 方块**为了在摧毁时引发爆炸重写了该方法。 143 | 激活的活塞使用该方法因为激活的活塞由两部分(头部和基部)组成, 144 | 当**移动的活塞**块被破坏时,它利用这个方法来破坏基部。 145 | 146 | [端]: ../concepts/sides.md -------------------------------------------------------------------------------- /docs/blocks/states.md: -------------------------------------------------------------------------------- 1 | 方块状态 2 | ============ 3 | 4 | 在开始写代码前,请**读完**本指南。读完这篇指南你能理解的更加全面。本指南是介绍方块状态的入门介绍。 如果你知道什么是扩展状态,你会注意到我在下面做了一些简化的假设。 它们是有意的,为了避免初学者一下理解不了全部信息。 如果你不知道它们是什么,不必担心,最后会有另一份文件给他们。 5 | 6 | 背景 7 | ---------- 8 | 9 | 在Minecraft 1.8及更高版本中,对方块和metadata值的直接操作已被抽象成所谓的blockstates。 10 | 该系统的前提是删除对没有意义的原始metadata的使用和操作。 11 | 12 | 例如,考虑这个有方向并占据一半空间的方块的switch语句: 13 | 14 | ```Java 15 | switch(meta) { 16 | case 0: // 它朝南,在下部 17 | case 1: // 它朝南,在上部 18 | case 2: // 它朝北,在下部 19 | case 3: // 它朝北,在下部 20 | ... etc. 21 | } 22 | ``` 23 | 24 | 这些数字本身没有任何意义!如果没有注释,就根本不知道这些数字是什么意思。 25 | 26 | 新的思考方式 27 | --------------------- 28 | 29 | 那么,我们是不是可以不要到处都和数字打交道,而是使用一些系统从方块本身的语义中抽象出保存的细节?这就是`IProperty`的用武之地。每个方块都有一组零个或多个这些对象,可以说这些对象描述了块具有的*属性*。 例如颜色(`IProperty`),朝向(`IProperty`),整数和布尔值等。每个属性都可以表示为* IProperty*类型的*值*。 例如,对于上面的例子,我们可以用值`EnumDyeColor.WHITE`, `EnumFacing.EAST`, `1`, 或 `false`。 30 | 31 | 那么,这样的话,我们发现方块和metadata可以被唯一的三元组(块,属性的集合,属性的值的集合)替代。 现在,我们不再使用“minecraft:stone_button meta 9”,而是用“minecraft:stone_button[facing=east,powered=true]”。 想想哪个更有意义? 32 | 33 | 这个三元组有一个特别的名字,叫`IBlockState`。 34 | 35 | 用这些魔法属性制作你的方块 36 | ------------------------------------------------- 37 | 38 | 既然我已经成功地说服了你键值对优于数字,那么让我们继续讨论实际的操作方法。 39 | 40 | In your Block class, create static final `IProperty<>` objects for every property that your Block has. Vanilla provides us several convenience implementations: 41 | 在你的Block类中,为Block的每个属性创建静态的final的`IProperty<>`对象。原版客户端为我们提供了几种便利实现: 42 | 43 | * `PropertyInteger`: 实现了 `IProperty`.。调用 `PropertyInteger.create("", , )`来创建; 44 | * `PropertyBool`: 实现了 `IProperty`. 调用 `PropertyBool.create("")`来创建; 45 | * `PropertyEnum>`: 实现了 `IProperty`, 定义可以采用Enum类列举的值的属性。 调用`PropertyEnum.create("name", )`来创建; 46 | * 你也可以只使用Enum的一个子集 (例如,你只要使用16个`EnumDyeColor`中的4个。 可以看看 `PropertyEnum.create`的其它重载) 47 | * `PropertyDirection`: 这是对 `PropertyEnum`的一个方便的实现 48 | * 还有几个方便的实现. 例如,获取表示基本方向的属性, 你可以调用`PropertyDirection.create("", EnumFacing.Plane.HORIZONTAL)`. 或者获得X方向, `PropertyDirection.create("", EnumFacing.Axis.X)` 49 | 50 | In addition, note that you can share the same `IProperty` object between different blocks if you wish. Vanilla generally has separate ones for every single block, but it is merely personal preference. 51 | 请注意,您可以自由地创建自己的`IProperty<>`的实现,但本文不涉及实现方法。 52 | 另外,请注意,如果您愿意,可以在不同块之间共用相同的“IProperty”对象。 原本通常每个方块都有单独的`IProperty`,但它只是个人喜好。 53 | 54 | !!! Note "提示" 55 | 如果你的mod有一个API或者想要与其他mod进行交互,那么建议你在你的API中放置你的`IProperty`(和任何用作值的类)。 这样,其他人可以使用属性和值来设置世界中的块,而不必像以前那样使用任意数字。 56 | 57 | 在创建了`IProperty<>`对象之后,在Block类中重写`createBlockState`方法。 在该方法中,只需写`return new BlockState()`。 首先将`BlockState`用构造函数传递给你的Block,`this`,然后在它下面写你要声明的`IProperty`。 请注意,在1.9及更高版本中,`BlockState`类已重命名为`BlockStateContainer`,更符合此类实际执行的操作。 58 | 59 | 你刚创建的对象是一个非常神奇的对象 - 它管理着上面所有三元组的生成。 也就是说,它为每个属性生成每个值的所有可能组合(对于学过数学的人,它采用每个属性的可能值集合并计算这些集合的笛卡尔积)。 因此,给定任意的属性`IBlockState`它可以生成对应的(块,属性,值)。 60 | 61 | 如果您没有将其中一个`IBlockState`设置为您的Block的默认状态,那么将自动为您选择一个。 你可能不希望这样(因为在大多数情况下它会导致奇怪的事情发生),所以在你的Block的构造函数调用`setDefaultState()`结束时,传入你想成为默认值的`IBlockState`。 使用`this.blockState.getBaseState()`获取为您选择的那个,然后使用`withProperty`为*每个*属性设置一个值。 62 | 63 | 因为`IBlockState`是不可变的预生成的,所以调用`IBlockState.withProperty()`将简单地转到`BlockState` /`BlockStateContainer`并使用你想要的值集请求IBlockState, 而不是构建一个新的“IBlockState”。 64 | 65 | 由此可以很容易地得出,因为基本的`IBlockState`是在启动时生成固定集的,所以你能够并且鼓励使用引用比较(==)来检查它们是否相等! 66 | 67 | 68 | 使用`IBlockState` 69 | --------------------- 70 | 正如你现在所知,`IBlockState`是一个强大的对象。 你可以通过调用,`getValue()`传递你要测试的`IProperty<>`来获取属性的值。 71 | 如果你想获得一个具有不同值的IBlockState,只需调用上面提到的`withProperty(, )`。 这将使用您请求的值返回另一个预生成的`IBlockState`。 72 | 73 | 你可以使用`setBlockState()`和`getBlockState()`来获取和放置`IBlockState`。 74 | 75 | 76 | 幻想杀手 77 | ---------------- 78 | 遗憾的是,抽象是他们的核心所在。 我们仍然有责任将每个`IBlockState`转换回0到15之间的数字,这些数字将存储在世界中,反之亦然,以便加载。 79 | 80 | 如果你声明任何`IProperty`,你**必须**重写`getMetaFromState`和`getStateFromMeta` 81 | 82 | 在这里,您将读取属性的值并返回0到15之间的适当整数,或者反过来; 读者可以自己查看原本的例子。 83 | 84 | !!! Warning "警告" 85 | 你的 `getMetaFromState` 和 `getStateFromMeta` 方法必须是一对一的!换句话说,相同的属性与值必须映射到同一meta值,反之亦然。如果你没有做到这个,游戏将不会崩溃。它只会让所有东西都表现得很奇怪。 86 | 87 | 88 | “实际”状态 89 | ------------- 90 | 你可能会注意到栅栏并不将连接属性记录到meta中,但在F3菜单中你仍可以看到它的属性和值!这是为什么呢? 91 | 92 | 方块可以声明不保存到Metadata中的属性。这些通常用作渲染目的,但也可以有一些其他的用处。 93 | 94 | 你仍可以在 `createBlockState` 中声明它们,并且使用 `setDefaultState` 设置值。然而,这些值在 `getMetaFromState` 和 `getStateFromMeta` 里都**不要**动。 95 | 96 | 然而,你需要在你的 `Block` 类中重写 `getActualState`。这里你将会接收到对应世界中Metadata值的 `IBlockState` ,你需要返回另一个包含缺失信息的 `IBlockState`,比如说栅栏链接,红石链接等。你需要用 `withProperty` 来附加属性。你也可以用这个来对一个值读TileEntity数据(当然你需要恰当的安全检查!)。 97 | 98 | !!! warning "警告" 99 | 100 | 当你在 `getActualState` 中读取TileEntity的数据时,你必须采取进一步的安全检查。默认情况下,`getTileEntity` 将会在TileEntity不存在时尝试创建这个TileEntity。然而,`getActualState` 和 `getExtendedState` 可以并且会被另外的线程调用,这将会导致在它尝试创建丢失TIleEntity时,世界的TileEntity列表会抛出 `ConcurrentModificationException`。所以,你必须检查 `IBlockAccess` 参数是否为 `ChunkCache`(传给其它线程的对象),如果是的话,将其强制转换为 `ChunkCache` 类型,并使用 `getTileEntity` 不可写的变种。安全检查的例子可以在 `BlockFlowerPot.getActualState()` 中找到。 101 | 102 | !!! note "提示" 103 | 104 | 调用 `world.getBlockState()` 将会返回代表metadata的 `IBlockState`。所以返回的 `IBlockState` 将不会包含 `getActualState` 内的数据。如果这写数据对你的代码很重要,请务必调用 `getActualState`! 105 | 106 | [拓展状态](../models/advanced/extended-blockstates.md) 107 | ------------------------ 108 | 扩展的方块状态是一种在呈现方块间将任意数据传递到`IBakedModel`的方法。 它们主要用于渲染的上下文中,因此在“模型”部分中有记录。 109 | -------------------------------------------------------------------------------- /docs/concepts/internationalization.md: -------------------------------------------------------------------------------- 1 | 国际化和本地化 2 | ===================================== 3 | 4 | 国际化, 简称为i18n,是一种可以适用于各种语言的代码设计思路。本地化是指显示适用于用户语言的文字的过程 。 5 | 6 | I18n用 _翻译密钥(translation keys)_实现。 翻译密钥是一个字符串,用于标识一段没有特定语言的可显示文本。 例如, 翻译密钥`tile.dirt.name`指的是泥土方块的名字。 这样,可以引用可显示的文本而不关心特定语言。 加入新语言时,代码不需要修改。 7 | 8 | 本地化将在游戏的本地设置中发生。 在Minecraft客户端中,位置由语言设置指定。 在专用服务器上,唯一受支持的语言环境是en_US。 可以在 [Minecraft Wiki](https://minecraft.gamepedia.com/Language#Available_languages)上找到可用语言环境的列表。 9 | 10 | 语言文件 11 | -------------- 12 | 13 | 语言文件放在 `assets/[namespace]/lang/[locale].lang`下 (例如 `examplemod`的英语翻译应放在 `assets/examplemod/lang/en_us.lang`下). 资源包格式 3 (在pack.mcmeta中指定)需要小写的区域名。该文件格式只是由`=`分隔的键值对的行组成的。 以`#`开头的行将被忽略。 没有分隔符的行将被忽略。 该文件必须以UTF-8编码。 14 | 15 | ```properties 16 | # items 17 | item.examplemod.example_item.name=Example Item Name 18 | 19 | # blocks 20 | tile.examplemod.example_block.name=Example Block Name 21 | 22 | # commands 23 | commands.examplemod.examplecommand.usage=/example 24 | ``` 25 | 26 | 若文件中任何地方包含`#PARSE_ESCAPES`注释,会启用更复杂的Java配置文件格式。这种格式支持多行字符串。 27 | 28 | !!! note "提示" 29 | 启用`#PARSE_ESCAPES`文件很多地方将会不同。 最重要的是,`:`字符将被视为键值分隔符。 键值对中要使用它必须用`\:`进行转义。 30 | 31 | Blocks 和 Items 的用法 32 | --------------------------- 33 | 34 | !!! note "提示" 35 | 2018年7月14日之后,“unlocalized name”一词已被MCP中的“翻译密钥(translation key)”取代。 36 | 37 | Block, Item 和一些 Minecraft 的其它类已经内置的显示其名字的翻译密钥。翻译密钥可以通过调用`setTranslationKey(String)`来指定或者重写 `getTranslationKey()`方法。 Item也可以重写`getTranslationKey(ItemStack)`方法来实现不同的 damage 和 NBT有不同的翻译密钥。 38 | 39 | 默认`getTranslationKey()`会返回 `tile.` 或 `item.` 前提是设置了`setTranslationKey(String)`, ItemBlock会继承它Block的翻译密钥. 另外, `.name` 会把翻译密钥加在它的域之后。例如`item.setTranslationKey("examplemod.example_item")`需要在语言文件中这样设置: 40 | 41 | ```properties 42 | item.examplemod.example_item.name=Example Item Name 43 | ``` 44 | 45 | 不像注册表名,翻译密钥没有命名空间。因此,为了避免冲突,强烈建议在翻译密钥前加上modid。 (例如 `examplemod.example_item`) 。 否则,一旦冲突,一个对象的本地化名会覆盖另一个。 46 | 47 | !!! note "提示" 48 | 翻译密钥的唯一目的是国际化。 不要将它们用于逻辑。 若要用于逻辑,请改用注册表名称。 49 | 50 | 常见的方法是用 `getUnlocalizedName().substring(5)`来分配注册表名. 这并不好,它很脆弱. 请先考虑设置注册表名称,然后使用 `MODID + "." + getRegistryName().getResourcePath()` 来设置翻译密钥. 51 | 52 | 本地化方法 53 | -------------------- 54 | 55 | !!! warning "警告" 56 | 一个常见问题是让服务器本地化为客户端。 服务器只能在其自己的区域设置中进行本地化,该区域设置不一定与已连接客户端的区域设置相匹配。 57 | 58 | 要考虑客户端的语言设置,服务器应该让客户端使用`TextComponentTranslation`或其他保留语言中翻译密钥的方法在自己的语言环境中本地化文本。 59 | 60 | ### `net.minecraft.client.resources.I18n` (仅客户端) 61 | 62 | **该类仅用于客户端!** 它仅供在客户端上运行的代码使用。 在服务器上使用它会引发异常并崩溃。 63 | 64 | - `format(String, Object...)`用格式化在客户端的语言环境中进行本地化. 第一个参数是翻译密钥,其余参数是`String.format(String,Object ...)`的格式化参数。 65 | 66 | ### `net.minecraft.util.text.translation.I18n` (已废弃) 67 | 68 | **该类已被废弃,应避免使用。** 它是用于在逻辑服务器上运行的代码。 由于本地化很少发生在服务器上,因此应考虑其他替代方案。 69 | 70 | - `translateToLocal(String)`在没有格式化的情况下,在游戏的语言环境中进行本地化。 该参数是翻译密钥。 71 | - `translateToLocalFormatted(String, Object...)` 用格式化在服务端的语言环境中进行本地化. 第一个参数是翻译密钥,其余参数是`String.format(String,Object ...)`的格式化参数。 72 | 73 | ### `TextComponentTranslation` 74 | 75 | `TextComponentTranslation`是一个`ITextComponent`,它是进行本地化和格式化的简便方法。 在向玩家发送消息时它非常有用,因为它会自动本地化在他们自己的语言环境中。 76 | 77 | `TextComponentTranslation(String, Object...)`构造函数的第一个参数是翻译密钥,其余参数用于格式化。 唯一支持的格式说明符是`%1$s`, `%2$s`, `%3$s`等。格式化参数可以是另一个`ITextComponent`,它们的所有属性将被插入到格式化文本的结果中。 78 | 79 | ### `TextComponentHelper` 80 | 81 | - `createComponentTranslation(ICommandSender, String, Object...)`用接收器构造一个`ITextComponent` 区域和本地化设置. 如果接收者是原本客户端,则立刻进行本地化和格式化。 若不是,会用`TextComponentTranslation`完成本地化和格式化。这仅在服务器应允许原本客户端连接时才有用。 82 | -------------------------------------------------------------------------------- /docs/concepts/jarsigning.md: -------------------------------------------------------------------------------- 1 | Jar签名 2 | ======= 3 | 4 | Java允许开发者对他们的Jar文件签名。然而,这些签名并不是为了安全而设计的,而且也不应该这样使用。签名是用来检查完整性的,这样子开发者就能够确认是否在运行他们自己未修改的代码。 5 | 6 | !!! note "提示" 7 | 8 | 请再一次记住这个系统并不能作为一个安全手段。签名的检查能够被恶意手段所绕开。 9 | 10 | 创建一个密钥库 11 | ------------- 12 | 13 | **密钥库**(Keystore)是一个私有的数据库文件,它包含了签名Jar文件所需的信息。要想签名一个Jar,你需要一个公钥和一个私钥。公钥将会在之后被用来检测Jar是否被正确签名。 14 | 15 | 要生成一个密钥的话,请执行下面的指令,并遵循 keytool 的指示。 16 | 密钥需要是**SHA-1编码**(SHA-1 Encoded)的。 17 | 18 | ```shell 19 | keytool -genkey -alias signFiles -keystore examplestore.jks 20 | ``` 21 | 22 | - `keytool` 是Java开发包(JDK)的一部分,它可以在 `bin` 目录下找到 23 | - `alias signFiles` 指定了将来引用这个密钥库的别名(Alias) 24 | - `-keystore examplestore.jks` 指的是密钥库将会被存储在 `examplestore.jks` 文件中 25 | 26 | ### 获取公钥 27 | 28 | 要获取Forge所需的公钥的话,执行下面的指令: 29 | 30 | ```shell 31 | keytool -list -alias signFiles -keystore examplestore.jks 32 | ``` 33 | 34 | 现在复制这个公钥,并删除所有的冒号以及将所有的大写字母改为小写字母来保证FML能够识别这个密钥。 35 | 36 | ### 运行时检查 37 | 38 | 如果要让FML对比密钥的话,将公钥添加到 `@Mod` 注解中的 `certificateFingerprint` 参数中。 39 | 40 | 当FML检测到密钥不匹配时,它将会触发一个持续整个Mod生命周期的时间。如何处理密钥不匹配这一事件将由开发者来决定。 41 | 42 | - `event.isDirectory()` - 当Mod在开发环境下允许是返回true。 43 | - `event.getSource()` - 返回不匹配密钥的文件。 44 | - `event.getExpectedFingerprint()` - 返回公钥。 45 | - `event.getFingerprints()` - 返回发现的所有公钥。 46 | 47 | 构建脚本设置 48 | ----------- 49 | 50 | 最后,要让Gradle使用生成的密钥对来签名Jar文件的话,我们需要在 `build.gradle` 中新创建一个任务(Task)。 51 | 52 | 53 | ```groovy 54 | task signJar(type: SignJar, dependsOn: reobfJar) { 55 | inputFile = jar.archivePath 56 | outputFile = jar.archivePath 57 | } 58 | 59 | build.dependsOn signJar 60 | ``` 61 | 62 | - `dependsOn: reobfJar` - 这个片段非常重要,因为Gradle必须要在ForgeGradle重混淆Jar**之后**来对Jar签名。 63 | - `jar.archivePath` - 档案(Jar)构造时所处的路径。 64 | - `build.dependsOn signJar` - 这一行告诉Gradle这个任务是 `build` 任务的一部分,通过 `gradlew build`来启动。 65 | 66 | 下面 `signJar` 中的这些值必须要有定义,来保证Gradle能够找到密钥库从而给Jar签名。这必须要在 `gradle.properties` 文件中来定义。 67 | 68 | - `keyStore` - 这个值告诉Gradle该去哪里找之前生成的密钥库。 69 | - `alias` - 必须要提供之前定义的那个别名来让Gradle对Jar签名。 70 | - `storePass` - 创建密钥库时使用的密码,Gradle需要它来访问这个文件。 71 | - `keyPass` - 创建密钥库时生成的密钥的密码,Gradle需要它来访问这个文件。这个密码可能与 `storePass` 相同,但也可能不同。 -------------------------------------------------------------------------------- /docs/concepts/registries.md: -------------------------------------------------------------------------------- 1 | 注册表 2 | ===== 3 | 4 | 注册是一个让游戏了解一个MOD中的对象(物品、方块、声音等)的过程。注册对象是非常重要的,因为如果没有注册的话,游戏将不能知道Mod中的对象,并会展现出大量不可预料的行为(并且可能会崩溃)。通常需要注册的东西有`Block`、`Item`、`Biome`。 5 | 6 | 游戏中大部分需要注册的东西都是由Forge注册表(Registry)来处理的。注册表是一个类似于键值映射的对象。它会自动将整数ID分配到值上。Forge采用的是使用 [`ResourceLocation`][ResourceLocation] 作为键的注册表来注册对象。这就让 `ResourceLocation` 变成了对象的“注册表名"。对象的注册表名可以通过 `get`/`setRegistryName` 来访问。Setter只可以被调用一次,如果调用第二次的话会导致异常。每一种可注册对象都有它自己的注册表,在两个不同注册表中的名字将不会冲突。(比如说 `Block` 有一个注册表, `Item` 也有一个注册表,而有着相同名字 `mod:example` 的一个 `Block` 和一个 `Item` 注册时将不会冲突。然而,如果是两个方块使用同一个名字注册,则会导致抛出异常。) 7 | 8 | 注册对象 9 | ------- 10 | 11 | 注册对象推荐的方式是使用 `RegistryEvent`。这些[事件](../events/intro.md)将会在预初始化之后触发。在 `RegistryEvent.NewRegistry` 中,注册表应该会被创建。之后,`RegistryEvent.Register` 会在每一个注册表注册的时候被触发。由于 `Register` 是一个泛型事件,事件处理器应该将类型参数设置为被注册对象的类型。这个事件将会包含可以注册对象的注册表(`getRegistry`),对象可以在注册表中使用 `register`(或 `registerAll`)注册。下面是一个注册方块的事件处理器例子: 12 | 13 | ```java 14 | @SubscribeEvent 15 | public void registerBlocks(RegistryEvent.Register event) { 16 | event.getRegistry().registerAll(block1, block2, ...); 17 | } 18 | ``` 19 | 20 | `RegistryEvent.Register` 事件触发的顺序是按照字母顺序的,除了 `Block` **一定**会第一个被触发,并且 `Item` **一定**会在 `Block` 之后,第二个触发。在 `Register` 事件被触发之后,所有的[`ObjectHolder`][ObjectHolder] 注解将会被刷新,在 `Register` 被触发之后,它们会被再次刷新。当其它**所有的** `Register` 事件都触发之后,它们会刷新第三次。 21 | 22 | `RegistryEvent`当前支持以下类型:`Block`、`Item`、`Potion`、`Biome`、`SoundEvent`、`PotionType`、`Enchantment`、`IRecipe`、`VillagerProfession`、`EntityEntry`。 23 | 24 | 也有另外一种老的方法可以用来注册对象到注册表,使用 `GameRegistry.register`。任何时候有人建议你使用这个方法,你都应该将其替换为对应正确注册表事件的事件处理器。这个方法使用 `IForgeRegistryEntry::getRegistryType` 找到对应于 `IForgeRegistryEntry` 的注册表,再将对象注册到这个注册表中。还有一个方便的重载需求一个 `IForgeRegistryEntry`(`ifre`)和一个 `ResourceLocation`,它等同于 `IForgeRegistryEntry::setRegistryName` 接一个 `GameRegistry.register` 调用。 25 | 26 | 创建注册表 27 | --------- 28 | 29 | 所有的注册表将储存在一个全局注册表中。通过注册表所储存的`Class`或者它的`ResourceLocation`名称,我们可以从这个全局注册表中获取对应的注册表。比如说,我们可以使用`GameRegistry.findRegistry(Block.class)`来获取方块的注册表。任何Mod都可以创建它们自己的注册表,并且任何Mod都可以注册东西到其它Mod的注册表中。注册表可以使用 `RegistryEvent.NewRegistry` 中的 `RegistryBuilder` 来创建。这个类需要一些参数来描述它将生成的注册表,比如说名字,值的 `Class`,以及一些回调函数用于提示注册表的改动。在调用 `RegistryBuilder::create` 的时候,这个注册表就成功创建了,它会被注册至元注册表,并返回到调用者处。 30 | 31 | 如果想让一个类拥有一个注册表,它需要实现`IForgeRegistryEntry`。这个接口定义了`getRegistryName(ResourceLocation)`、`setRegistryName(ResourceLocation)`和`getRegistryType()`。`getRegistryType`是对象需要注册至的注册表的基类`Class`。建议是继承默认的`IForgeRegistryEntry.Impl`类而不是直接实现`IForgeRegistryEntry`。这个类还提供了`setRegistryName`的两个方便实现:一个的参数有一个字符串,另一个有两个字符串参数。需要一个字符串的重载检查输入是否包含一个`:`(即它检查传入的字符串化的`ResourceLocation`是否有域),如果没有,则使用当前的ModID作为资源域。有两个参数的重载会使用`modID`作为域名,`name`作为路径,构建一个注册表名。 32 | 33 | 注入注册表值到字段中 34 | ------------------ 35 | 36 | Forge可以直接将注册表中的值注入(Inject)到类的 `public static final` 字段中。这可以通过使用 `@ObjectHolder` 注解类或字段来实现。如果一个类有这个注解,那么它其中所有的 `public static final` 字段都将被作为对象的容器(Object Holder),并且注解的值为容器的域(即每一个字段都将其作为注入对象注册表名的默认域)。如果一个字段有这个注解,并且值没有域,那么域将会从封装其的类中的 `@ObjectHolder` 注解中获取。如果类也没有包含域,那么这个字段将会被忽略,并会有警告。如果它包含有域的话,那么注入到这个字段的对象就是对应于那个名字的对象。如果类有注解,但其中一个 `public static final` 字段没有的话,对象名字的资源路径将会作为字段的名字。注册表的类型将会为字段的类型。 37 | 38 | !!! note "提示" 39 | 40 | 如果无法找到一个对象,可能是由于对象本身没有被注册,也可能是由于注册表不存在,在此情况下会留下一个调试信息,字段将不会有变动。 41 | 42 | 由于这些规则有一些复杂,所以下面给出一些例子: 43 | 44 | ```java 45 | @ObjectHolder("minecraft") // 资源域 "minecraft" 46 | class AnnotatedHolder { 47 | public static final Block diamond_block = null; // public static final 是必须的 48 | // 类型为 Block 意味着 Block 的注册表将会被调用 49 | // diamond_block 是字段名称,由于字段没有被注解,它会作为资源路径 50 | // 由于没有显式的域,"minecraft" 从类中继承下来 51 | // 注入的对象:Block注册表中的 "minecraft:diamond_block" 52 | 53 | @ObjectHolder("ender_eye") 54 | public static final Item eye_of_ender = null; // 类型为 Item 意味着 Item 的注册表将会被调用 55 | // 由于注解有值为 "ender_eye",这将会覆盖字段的名称 56 | // 由于没有显式的域,"minecraft" 从类中继承下来 57 | // 注入的对象:Item注册表中的 "minecraft:ender_eye" 58 | 59 | @ObjectHolder("neomagicae:coffeinum") 60 | public static final ManaType coffeinum = null; // 类型为 ManaType 意味着 ManaType 的注册表将会被调用。显然这是一个Mod中的注册表 61 | // 由于注解有值为 "neomagicae:coffeinum",这将会重写字段的名称 62 | // 域 "neomagicae" 是显式的,这将会覆盖类的默认值 "minecraft" 63 | // 注入的对象:ManaType注册表中的 "neomagicae:coffeinum" 64 | 65 | public static final Item ENDER_PEARL = null; // 注意实际的名称是 "minecraft:ender_pearl",而不是 "minecraft:ENDER_PEARL" 66 | // 然而,由于在构建一个 ResourceLocation 时会让所有字母变为小写,所以这个能够正常工作 67 | } 68 | 69 | class UnannotatedHolder { // 注意这个类没有注解 70 | @ObjectHolder("minecraft:flame") 71 | public static final Enchantment flame = null; // 这个类没有注解意味着没有能够继承的域 72 | // 字段的注解提供了对象的所有信息 73 | // 注入的对象:Enchantment注册表中的 "minecraft:flame" 74 | 75 | public static final Biome ice_flat = null; // 类与字段都没有注解 76 | // 所以这个字段将会被忽略 77 | 78 | @ObjectHolder("levitation") 79 | public static final Potion levitation = null; // 注解中没有资源域,并且也没有通过类注解指定的默认值 80 | // 所以,注入将会失败,这个字段需要一个域,或者类需要一个注解 81 | } 82 | ``` 83 | 84 | [ResourceLocation]: resources.md#resourcelocation 85 | [ObjectHolder]: #_4 -------------------------------------------------------------------------------- /docs/concepts/resources.md: -------------------------------------------------------------------------------- 1 | 资源 2 | ==== 3 | 4 | 资源(Resource)是游戏使用的附加数据,它被储存在数据文件,而不是代码里。资源被储存在类路径(Classpath)的 `assets` 目录中。在默认的 Mod 开发包中,它位于工程的 `src/main/resources` 目录。它包含了模型、纹理、本地化文件等资源。 5 | 6 | 当多个资源包被应用时,它们会合并。通常状况下,在栈顶的资源包会覆盖它下面的资源包。然而,对于某些文件,比如说本地化文件,多个资源包中数据的内容将会合并。Mod本身其实也定义了一个资源包,在它的 `resources` 目录中,但它们会被认为是“默认”资源包的子集。Mod资源包不能被禁用,但它们可以被其他的资源包所覆盖。 7 | 8 | 所有的资源都应该使用 snake_case 路径和文件名(所有字母小写,使用“_”作为词语的分隔符),这一项规则在1.11以上是强制的。 9 | 10 | `ResourceLocation` 11 | ------------------ 12 | 13 | Minecraft使用 `ResourceLocation` 来识别资源。`ResourceLocation` 包含有两个部分:一个域和一个路径。它通常会指向 `assets///` 中的资源,其中 `ctx` 指的是上下文相关的路径部分,它决定于 `ResourceLocation` 是在何处使用的。当 `ResourceLocation` 从一个字符串写入或读取时,它会是 `:` 这样的形式。如果没有使用域的名字和冒号,当这个字符串由 `ResourceLocation` 解析出来时域几乎都会默认为 `"minecraft"`。Mod需要将它的资源放到与其 modid 相同的域中(比如一个 id 为 `examplemod` 的Mod需要将它的资源放在 `assets/examplemod` 中,并且指向这些文件的 `ResourceLocation` 将会是 `examplemod:`)。这并不是一个必须的要求,并且在某些情况下使用一个不同的(或者多于一个的)域名可能会更好。`ResourceLocation` 在资源系统之外也有应用,它用于识别唯一的对象是一个很棒的方式(比如[注册表][registries])。 14 | 15 | [registries]: registries.md -------------------------------------------------------------------------------- /docs/concepts/sides.md: -------------------------------------------------------------------------------- 1 | Minecraft中的Side 2 | ================= 3 | 4 | 要想理解Minecraft Mod制作,Side是一个非常重要的概念。一共有两个Side:**客户端**(Client)和**服务端**(Server)。关于Side有很多很多常见的误解和错误,这些错误可能不会崩溃游戏,但是能产生一些不可预料效果。 5 | 6 | 不同的Side 7 | --------- 8 | 9 | 当我们说“客户端”和“服务端”的时候,应该能很明确地知道我们说的是游戏的哪一部分。如果你还是不能理解,客户端是用户互动的地方,服务端是用户连接多人游戏的地方。 10 | 11 | 然而事实证明这两个术语仍然会有些模糊。为了消除歧义,在这里我们区分“客户端”和“服务端”四个可能的定义: 12 | 13 | - 物理客户端(Physical Client):**物理客户端**就是你启动Minecraft时运行的整个程序。游戏运行周期中所有的线程,进程,服务,都是物理客户端的一部分。 14 | - 物理服务端(Physical Server):通常称之为专用服务器(Dedicated Server)。**物理服务端**就是你运行 `minecraft_server.jar` 之类服务器时的整个程序,它不会有可操作的GUI。 15 | - 逻辑服务端(Logical Server):**逻辑服务端**运行着游戏的逻辑:生物生成、天气、更新背包、生命、AI等游戏机制。逻辑服务端存在于物理服务端之中,但它也可以和逻辑客户端一齐运行在物理客户端之中,作为一个单人游戏世界。逻辑服务端永远运行在一个叫做 `Server Thread` 的线程中。 16 | - 逻辑客户端(Logical Client):**逻辑客户端**从玩家那里接收输入,并将其转发至逻辑服务端中。除此之外,它也同样从逻辑服务端那里接收信息,并将其以图形化的方式呈现给玩家。逻辑客户端运行在 `Client Thread` 当中,当然通常一些其它的线程也会生成来处理音频和区块渲染等工作。 17 | 18 | 执行单端(Side-Specific)操作 19 | ------------- 20 | 21 | ### `world.isRemote` 22 | 23 | 这个boolean检查是最常用的检查Side的方式。对一个 `World` 对象查询这个值将会得到这个世界所处的**逻辑**端。也就是说,当这个值为 `true` 的时候,世界正在运行在逻辑客户端内。如果这个值为 `false`,世界正在运行在逻辑服务器上。在物理服务端中这个值永远为 `false`,但是 `false` 并不意味着世界一定在物理服务端中,因为这个值也可以在物理客户端的逻辑服务端内为 `false`(即单人游戏世界中)。 24 | 25 | 你可以使用这个来检查游戏逻辑和其他机制是否需要被运行。比如说,如果你想要让玩家在点击你的方块时受到伤害,或者让你的机器加工泥土为钻石,你首先需要确认 `world.isRemote` 为 `false`。将游戏逻辑应用到逻辑客户端将轻则造成不同步(幽灵实体、数据不同步等),重则崩溃游戏。 26 | 27 | 这个检查将会成为你的首选。除了Proxy之外,很少你会需要其它判定Side和调整行为的方式。 28 | 29 | ### `@SidedProxy` 30 | 31 | 考虑一下,客户端和服务端mod只使用了一个单独的jar,而物理端却又分成了两个jar,一个很重要的问题产生了:我们怎样使用只在一个物理端存在的代码?所有在 `net.minecraft.client` 包中的代码只存在于物理客户端中,所有在 `net.minecraft.server.dedicated` 包中的代码只存在于物理服务端中。如果你引用了这些包里的类,当这个类加载在特定的环境时游戏将会崩溃。新手最常见的错误就是在方块或者实体类中调用 `Minecraft.getMinecraft().()`,一旦这种类加载了,他们就会使物理服务端崩溃。 32 | 33 | 我们该怎么解决这个问题呢?幸运的是,FML提供了 `@SidedProxy` 这个注解(Annotation),我们只需要给它两个类的名字(一个在 `serverSide`,另一个在 `clientSide`),并且用这个注解修饰一个字段。当mod启动时,FML将根据**物理**端实例化其中一个类。 34 | 35 | !!! note "提示" 36 | 37 | 非常重要的是,你需要理解FML实例化挑选的Proxy是基于**物理端**的。一个单人游戏世界(逻辑服务端 + 逻辑客户端在同一个物理客户端中),Proxy将仍然会使用 `clientSide` 中的类型。 38 | 39 | 一个很常见的使用情况就是注册渲染器(Renderer)和模型(Model),它们必须在主初始化方法(`preInit`,`init`,或 `postInit`)中调用。然而,许多渲染相关的类和注册在物理服务端上都不存在,调用它们还会使游戏崩溃。因此,我们将这些行为都放在客户端的Proxy中,保证他们只会在物理客户端中运行。 40 | 41 | 记住,指定的两个Proxy都需要能被赋值到被注解为 `@SidedProxy` 的一个字段上。通常我们需要有一个 `IProxy` 接口作为字段类型,和两个实现,`ClientProxy` 和 `ServerProxy` 分别对应两个物理端。 42 | 43 | ### `getEffectiveSide` 44 | 45 | `FMLCommonHandler.getEffectiveSide()` 调用时将返回**逻辑**端,它应该在你没法获得 `World` 对象并检查 `isRemote` 的时候被调用。这个方法通过检测当前正在运行的线程名称**猜测(Guess)**你所在的逻辑端。由于它只是一个猜测,这个方法只应该在其他选项都不可行的时候才应使用。几乎任何情况下你都应优先检查 `world.isRemote` 而不是这个方法。 46 | 47 | ### `getSide` 和 `@SideOnly` 48 | 49 | `FMLCommonHandler.getSide()` 可以用来获取你代码所运行的**物理**端。由于这个是在启动时就决定的,它不依赖于猜测以得到结果。然而这个方法的调用情况很少。 50 | 51 | 用 `@SideOnly` 注解一个方法或字段将告诉加载器(Loader)对应的成员应当在指定的**物理**端中不要定义。通常情况下,这些注解将会只在浏览反编译后的Minecraft源码时能看到,标识Mojang混淆器所剥离出来的方法。这个注解不应直接使用在你的代码中。只有你在重写(Override)已经使用 `@SideOnly`定义的原版方法时你能使用它。其它的大部分情况下请使用 `@SidedProxy` 或者检查 `getSide()`。 52 | 53 | 常见错误 54 | ------- 55 | 56 | ### 越逻辑端访问 57 | 58 | 每当你想从一个逻辑端传输信息到另一个时,你**必须**使用网络数据包(Packet)。尽管在单人游戏情况下直接从逻辑服务端到逻辑客户端传输数据很方便。 59 | 60 | 这其实经常是不经意间通过静态字段(Static Field)造成的。由于单人游戏中逻辑客户端和逻辑服务端共享同一个JVM,两个线程读写同一个静态字段将会导致各种各样的竞争情况和多线程的经典问题。 61 | 62 | 这个错误也会由于从运行在或能运行在逻辑服务端上的共享代码访问仅限物理客户端的类所造成。这个错误经常会被新手所忽略,因为他们常常只在物理客户端上调试。尽管代码可以运行在物理客户端上,但它们会在物理服务器上崩溃。 -------------------------------------------------------------------------------- /docs/config/annotations.md: -------------------------------------------------------------------------------- 1 | `@Config` 2 | ======= 3 | 4 | `@Config` 注解可以代替 `Configuration`. 5 | 6 | 目录 7 | ------------- 8 | 9 | * [基础][basics] 10 | * [@Config 用法][configuse] 11 | * [@Comment 用法][commentuse] 12 | * [@Name 用法][nameuse] 13 | * [@RangeInt 用法][rangeintuse] 14 | * [@RangeDouble 用法][rangedoubleuse] 15 | * [@LangKey 用法][langkeyuse] 16 | * [@RequiresMcRestart 用法][requiresmcrestartuse] 17 | * [@RequiresWorldRestart 用法][requiresworldrestartuse] 18 | * [子类别][subcategories] 19 | * [@Ignore][ignoreuse] 20 | 21 | 基础 22 | ------ 23 | 24 | 用`@Config`注释的类将把任何字段变成配置选项。 可以使用`@Config`类中提供的过多注释来注释所述字段以添加信息。 25 | 26 | !!! note "提示" 27 | 28 | 枚举配置值生成的注释与枚举值一样长。 29 | 30 | 示例请见[`Forge's Test`][forgetest] 31 | 32 | @Config 用法 33 | ----------- 34 | 35 | 这个注解表示一个类里面有配置选项。 36 | 37 | 有4个参数: 38 | 39 | | 参数 | 类型 | 默认值 | 描述 | 40 | | -------: | :------- | :-------------: | :----------------------------------------------------------- | 41 | | modid | `String` | N/A | 使用该配置的modid | 42 | | name | `String` | `""` | 用户友好的配置文件名,默认为modid | 43 | | type | `Type` | `Type.INSTANCE` | 配置的类型,现在只有一个值`Type.INSTANCE`。该参数为了对Forge控制文件向上兼容。 | 44 | | category | `String` | `"general"` | 根元素类别。 如果这是一个空字符串,则禁用根类别。 | 45 | 46 | !!! important "重要" 47 | 48 | 如果你禁用根类型,你要创建[子类别][subcategories],否则会报错。 49 | 50 | @Comment 用法 51 | ------------ 52 | 53 | `@Comment` 用于注解字段,用于在配置文件中生成注释 54 | 55 | 它有一个参数: 56 | 57 | | 参数 | 类型 | 默认值 | 描述 | 58 | |---------:|:--------------------|:-------------:|:------------------------------------------------------------------------------------------------------------------------------| 59 | | value | `String[]`/`String` | N/A | 可以通过`String[]`传值,并且每一个元素会生成新的一行。 | 60 | 61 | ### 示例 62 | ```java 63 | @Comment({ 64 | "你可以这样增加注释", 65 | "数组可以创建多行注释" 66 | }) 67 | public static boolean doTheThing = true; 68 | ``` 69 | 会生成如下配置: 70 | ``` 71 | # 你可以这样增加注释 72 | # 数组可以创建多行注释 73 | B:doTheThing=true 74 | ``` 75 | 76 | @Name 用法 77 | --------- 78 | 79 | `@Name`注解用于在配置文件中生成用户友好的名称。 80 | 81 | 它有1个参数: 82 | 83 | | 参数 | 类型 | 默认值 | 84 | |---------:|:---------|:-------------:| 85 | | value | `String` | null | 86 | 87 | ### 示例 88 | ```java 89 | @Name("FE/T for the thing") 90 | public static int thingFE = 50; 91 | ``` 92 | 会生成如下配置: 93 | ``` 94 | I:"FE/T for the thing"=50 95 | ``` 96 | 97 | @RangeInt 用法 98 | ------------- 99 | 100 | 你可以用`@RangeInt`限制配置中`int` 或`Integer`的取值范围。 101 | 102 | 它有2个参数: 103 | 104 | | 参数 | 类型 | 默认值 | 105 | |---------:|:------|:-------------------:| 106 | | min | `int` | `Integer.MIN_VALUE` | 107 | | max | `int` | `Integer.MAX_VALUE` | 108 | 109 | ### 示例 110 | ```java 111 | @RangeInt(min = 0) 112 | public static int thingFECapped = 50; 113 | ``` 114 | 它会生成如下配置: 115 | 116 | ``` 117 | # Min: 0 118 | # Max: 2147483647 119 | I:thingFECapped=50 120 | ``` 121 | 122 | @RangeDouble 用法 123 | ---------------- 124 | 125 | 你可以用`@RangeDouble`限制配置中`double` 或`Double `的取值范围。 126 | 127 | 它有2个参数: 128 | 129 | | 参数 | 类型 | 默认值 | 130 | |---------:|:---------|:------------------:| 131 | | min | `double` | `Double.MIN_VALUE` | 132 | | max | `double` | `Double.MAX_VALUE` | 133 | 134 | ### 示例 135 | ```java 136 | @RangeDouble(min = 0, max = Math.PI) 137 | public static double chanceToDrop = 2; 138 | ``` 139 | 它会生成如下配置: 140 | ``` 141 | # Min: 0.0 142 | # Max: 3.141592653589793 143 | D:chanceToDrop=2.0 144 | ``` 145 | 146 | !!! note "提示" 147 | 148 | 现在(1.12.2中)没有`@RangedFloat` , `@RangedLong`或其它类型的限制值。 149 | 150 | @LangKey 用法 151 | ------------ 152 | 153 | 如果你要在mod配置菜单中添加翻译,那么给字段加上`@LangKey`。 154 | 155 | 它有1个参数: 156 | 157 | | 参数 | 类型 | 默认值 | 158 | |---------:|:---------|:-------------:| 159 | | value | `String` | null | 160 | 161 | @RequiresMcRestart 用法 162 | ---------------------- 163 | `@RequiresMcRestart`注解用于必须要重启才能生效的字段。 164 | 165 | ### 示例 166 | ```java 167 | @RequiresMcRestart 168 | public static boolean overlayEnabled = false; 169 | ``` 170 | 这会导致在配置菜单里更改该值之后,游戏必须重启。 171 | 172 | @RequiresWorldRestart 用法 173 | ------------------------- 174 | 175 | 加上该注解,如果在mod配置菜单中更改该值,世界会强制重启。 176 | 177 | ### 示例 178 | ```java 179 | @RequiresWorldRestart 180 | public static boolean someOtherworldlyThing = false; 181 | ``` 182 | 如果在mod配置菜单中更改该值,世界会强制重启。 183 | 184 | 子类别 185 | -------------- 186 | 子类别是一种把一组相关的配置放在一起的方式,可以更好的组织你的配置文件。要创建一个子类别,要把一个对象作为静态字段加入到主类别类中。该对象的成员变量会变成子类型的配置。 187 | 188 | 一个设置子类型的示例: 189 | ```java 190 | @Config(modid = "modid") 191 | public class Configs { 192 | public static SubCategory subcat = new SubCategory(); 193 | 194 | private static class SubCategory { 195 | public boolean someBool; 196 | public int relatedInt; 197 | } 198 | } 199 | ``` 200 | 在配置文件中,会是这样: 201 | ``` 202 | subcat { 203 | B:someBool=false 204 | I:relatedInt=0 205 | } 206 | ``` 207 | 208 | @Ignore 用法 209 | ----------- 210 | 加上在配置类的字段中加上`@Ignore`注解之后, `ConfigManager` 在解析你的配置文件时会跳过该字段。 211 | 212 | !!! note "提示" 213 | 214 | 只在 forge 版本 >= 1.12.2-14.23.1.2602 有效, 因为这个功能在[这次更新][ignoreupdate]中加入 215 | 216 | [basics]: #basics 217 | [configuse]: #config-use 218 | [commentuse]: #comment-use 219 | [nameuse]: #name-use 220 | [rangeintuse]: #rangeint-use 221 | [rangedoubleuse]: #rangedouble-use 222 | [langkeyuse]: #langkey-use 223 | [requiresmcrestartuse]: #requiresmcrestart-use 224 | [requiresworldrestartuse]: #requiresworldrestart-use 225 | [ignoreuse]: #ignore-use 226 | [forgetest]: https://github.com/MinecraftForge/MinecraftForge/blob/603903db507a483fefd90445fd2b3bdafeb4b5e0/src/test/java/net/minecraftforge/debug/ConfigTest.java 227 | [ignoreupdate]: https://github.com/MinecraftForge/MinecraftForge/commit/ca7a5eadc05c427a21fb7ae745e5fd9a5d906267 228 | [subcategories]: #sub-categories 229 | -------------------------------------------------------------------------------- /docs/conventions/loadstages.md: -------------------------------------------------------------------------------- 1 | 加载阶段 2 | ======= 3 | 4 | Forge加载mod主要分为三个阶段:预初始化(Pre-Initialization)、初始化(Initialization)、后初始化(Post-Initialization)。通常简写为preInit,init,postInit。根据你mod功能的不同,其他的一些事件可能也很重要。由于这三个阶段在不同时间点发生,在每个阶段内能做的事情都是不同的。 5 | 6 | !!! note "提示" 7 | 8 | 加载阶段的事件只能在你的 `@Mod` 类中使用,并且对应的方法上要加上 `@EventHandler` 注解(Annotation)。 9 | 10 | !!! important "重要" 11 | 12 | 之前在加载阶段事件处理器中注册的很多对象(方块、物品、合成表等)现在都应该使用[RegistryEvent](../concepts/registries.md#_2)来注册。 13 | 这就能让我们在运行时动态重载Mod,而使用加载阶段则无法达到这一目的(因为它们只在程序启动时被调用一次)。 14 | RegistryEvent会在预初始化之后被触发。 15 | 16 | 17 | 预初始化 18 | ------- 19 | 20 | 在预初始化阶段中,你需要让游戏知道你的mod中添加的所有方块、物品等东西。这个阶段对应的事件是 `FMLPreInitializationEvent`。在preInit中一般你可以: 21 | 22 | - 注册方块(Block)和物品(Item)到 `GameRegistry` 23 | - 注册Tile Entity 24 | - 注册实体(Entity) 25 | - 分配矿物词典(Ore Dictionary)名字 26 | 27 | 初始化 28 | ------ 29 | 30 | 在初始化阶段中,你需要注册或者完成一些依赖于物品和方块(在preInit中注册)的东西。这个阶段的事件是 `FMLInitializationEvent`。在init中一般你可以: 31 | 32 | - 注册世界生成器(World Generator) 33 | - 注册合成配方(Recipe) 34 | - 注册事件处理器(Event Handler) 35 | - 发送IMC(Intermod Communication,Mod间交互)信息 36 | 37 | 后初始化 38 | -------- 39 | 40 | 在后初始化阶段中,你的mod通常会进行一些依赖或被依赖于其它mod的动作。这个阶段的事件是 `FMLPostInitializationEvent`。在postInit中一般你可以: 41 | 42 | - Mod兼容,或者任何需要其它mod的init阶段完成后进行的东西 43 | 44 | 其它重要的事件 45 | ------------ 46 | 47 | - `IMCEvent`:处理接收到的IMC信息 48 | - `FMLServerStartingEvent`:注册指令(Command) -------------------------------------------------------------------------------- /docs/conventions/locations.md: -------------------------------------------------------------------------------- 1 | 文件位置 2 | ======= 3 | 4 | Minecraft会要求你将工程的某些文件放在特定的位置上,比如说材质文件和JSON。 5 | 6 | 这节教程中所有的位置以及文件都是对于 `./src/main/resources/` 文件夹的**相对位置**. 7 | 8 | ### mcmod.info 9 | 10 | `mcmod.info` 文件放在根目录。 11 | 12 | ### 方块状态 13 | 14 | 方块状态(Blockstates)定义文件(JSON格式)放在 `./assets//blockstates/` 文件夹中。 15 | 16 | ### 本地化文件 17 | 18 | 本地化文件(Localization)是后缀名为 `.lang` 格式的纯文本文件,它们的文件名为语言各自的[语种代码](https://msdn.microsoft.com/en-us/library/ee825488(v=cs.20).aspx),比如说 `en_US`。 19 | 20 | 它们放在 `./assets//lang/` 文件夹中。 21 | 22 | ### 模型 23 | 24 | 模型(Models)文件为JSON格式,分别放在 `./assets//models/block/`(方块)或 `./assets//models/item/`(物品)文件夹内。 25 | 26 | ### 材质 27 | 28 | 材质(Textures)文件为PNG格式,分别放在 `./assets//textures/blocks/`(方块)或 `./assets//textures/items/`(物品)文件夹内。 -------------------------------------------------------------------------------- /docs/conventions/versioning.md: -------------------------------------------------------------------------------- 1 | 版本命名 2 | ======= 3 | 4 | 在一般的工程当中, 通常使用[语义化版本](http://semver.org/)(Semantic Versioning),其格式为`主版本号.次版本号.修订号`(`MAJOR.MINOR.PATCH`)。然而,对于mod的场景来说,使用`MC版本-MOD主版本号.API主版本号.次版本号.修订号` (`MCVERSION-MAJORMOD.MAJORAPI.MINOR.PATCH`)会更好一点。因为这能够区分一个mod的世界不兼容和API不兼容的改变。 5 | 6 | 示例 7 | ---- 8 | 9 | 这里是一个(不完全的)列表,它告诉你什么时候该递增不同的变量。 10 | 11 | - `MC版本`(`MCVERSION`) 12 | - 总是与该mod工作的Minecraft版本对应 13 | - `MOD主版本号`(`MAJORMOD`) 14 | - 删除物品、方块、TileEntity等 15 | - 改变或者删除之前存在的机制 16 | - 更新到新的Minecraft版本 17 | - `API主版本号`(`MAJORAPI`) 18 | - 改变Enum的顺序或者变量 19 | - 改变方法的返回值 20 | - 删除public方法 21 | - `次版本号`(`MINOR`) 22 | - 添加物品,方块,TileEntity等 23 | - 添加新的机制 24 | - 不建议使用(译注:添加@Deprecated标注的)public方法(这不是一个`API主版本号`的递增,因为它并没有使API不兼容) 25 | - `修订号`(`PATCH`) 26 | - 修复Bug 27 | 28 | 当递增任意一个变量,比它次要的变量都应重置为 `0`。例如,如果`次版本号`递增了,`修订号`将会变成 `0`; 如果`MOD主版本号`递增了,剩下的所有变量都要变成 `0`。 29 | 30 | ### 正在进行的开发 31 | 32 | 如果你正在你的mod开发的初始开发阶段(在官方发布之前),`MOD主版本号`和`API主版本号`都应该是 `0`。只有`次版本号`需要在你每次构建的时候更新。一旦你构建了官方的发布(通常有一个稳定的API),你需要将你的`MOD主版本号`递增至 `1.0.0.0`。对于更深入的开发阶段,请看[预发布](#_5)和[候选发布](#_6)部分。 33 | 34 | ### 多个Minecraft版本 35 | 36 | 如果mod升级到了一个新的Minecraft版本,并且旧版本只会得到bug修复,`修订号`需要在升级之前根据版本更新。如果mod仍然同时对新旧两个Minecraft版本活跃开发,我们建议您在两个版本号前**都**加上Minecraft的版本。例如,如果mod因为新版本Minecraft升级到了 `3.0.0.0`,旧的mod也应该升级到 `3.0.0.0`。例如,旧的版本将会是 `1.7.10-3.0.0.0`,而新的版本是 `1.8-3.0.0.0`。如果在升级到新的Minecraft版本的时候代码没有任何变动,所有的变量(除了Minecraft版本)都应该保持不变。 37 | 38 | ### 最终版本 39 | 40 | 当对一个Minecraft版本放弃支持时,对于这个版本的最后一个构建应该加上一个 `-final` 后缀。这说明该`MC版本`的mod将不会得到支持,玩家需要升级到更新的mod版本才能继续收到更新和bug修复。 41 | 42 | ### 预发布 43 | 44 | 预发布一个正在进行开发的特性也是可能的,也就是说新特性在没有完全完成之前被发布。这也可以被看做是beta版本。这些版本需要加上 `-betaX` 的后缀,X是预发布的数量(这个指南没有使用 `-pre` 因为,在写作的时候,根据Forge它并不是一个合法的`-beta`的别名)。注意已经发布的版本和还没有进行初始发布的版本不能够进入预发布。在添加 `-beta` 后缀前,变量(通常是`次版本号`,但是`API主版本号`和`MOD主版本号`也可以进行预发布)应该被对应地更新。在初始发布的版本前应该简单地使用正在开发的(WIP)构建。 45 | 46 | ### 候选发布 47 | 48 | 候选发布版本在一个正式的版本变化前充当预发布版的作用。候选发布版本应该加上 `-rcX` 的后缀,其中X候选发布的数量,它理论上只能在bug修复时候被递增。已经发布的版本不能有候选发布版。在添加 `-rc` 后缀前,变量(通常是`次版本号`,但是`API主版本号`和`MOD主版本号`也可以进行候选发布)应该被对应地更新。当将一个候选发布版本发布为一个稳定的构建时,它可以是与最后一个候选发布版本完全一样的构建也可以是添加了一些bug修复的版本。 -------------------------------------------------------------------------------- /docs/datastorage/capabilities.md: -------------------------------------------------------------------------------- 1 | 能力系统 2 | ======== 3 | 4 | 能力(Capability)使特性更动态和灵活地展现(Expose),而不必直接实现很多接口(Interface)。 5 | 6 | 总的来说,每个能力以接口形式提供了一个特性,一个可被调用的默认实现,和一个至少对于默认实现的储存处理器(Storage Handler)。储存管理器可以支持其它的实现,但是这个是取决于能力的实现者,所以你应该先看一下它们的文档之后再决定是否对非默认实现使用默认储存。 7 | 8 | Forge对TileEntity、实体、ItemStack、世界和区块添加了能力支持。它们可以通过事件附加上去,也可以通过在你的实现中重写能力的方法来展现。这将在后面的小节中进行更详细地解释。 9 | 10 | Forge提供的能力 11 | -------------- 12 | 13 | Forge提供了三种能力:IItemHandler、IFluidHandler和IEnergyStorage。 14 | 15 | IItemHandler展现了一个处理背包格子的接口。它可以被应用到TileEntity(箱子,机器)、实体(玩家更多的背包格子,生物的背包)、或ItemStack(便携背包等)。它通过一个自动化友好的系统代替了以前的 `IInventory` 和 `ISidedInventory`。 16 | 17 | IFluidHandler展现了一个处理液体存储的接口。它可以被应用到TileEntity、实体或ItemStack中。它通过一个更一致和自动化友好的系统代替了以前的`IFluidHandler` 18 | 19 | IEnergyStorage展现了一个用于处理能量容器的接口。它可以被应用到TileEntity、实体或ItemStack中。这个能力是基于TeamCoFH的RedstoneFlux API而制作的。 20 | 21 | 使用现有能力 22 | ----------- 23 | 24 | 正如之前提到的那样,TileEntity、实体、和ItemStack通过 `ICapabilityProvider` 实现了能力Provider特性。这一接口添加了两个方法:`hasCapability` 和 `getCapability`,它们可以用来获取对象中存在的能力。 25 | 26 | 要想获取能力,你首先要用其唯一的实例(Instance)引用它。在这里是Item Handler,这个能力一般储存在 `CapabilityItemHandler.ITEM_HANDLER_CAPABILITY`,但是我们也可以用 `@CapabilityInject` 这个注解(Annotation)获取其它的实例。 27 | 28 | ```java 29 | @CapabilityInject(IItemHandler.class) 30 | static Capability ITEM_HANDLER_CAPABILITY = null; 31 | ``` 32 | 33 | 这个注解可以被应用在字段(Field)和方法(Method)上。当应用到字段上的时候,它会在能力注册时将能力的实例(同一个实例赋值到所有字段中)赋值到字段上,如果该能力没有被注册,它将保持现有值(`null`)。因为本地静态字段访问会更快,所以您最好保留一份能力对象的引用。这个注解也可以同样应用到方法中,应用的方法将在能力注册时被调用,以便某些特性的应用。 34 | 35 | `hasCapability` 和 `getCapability` 这两个方法都有第二个参数,类型为 `EnumFacing`,他们可以用来对特定面请求特定的实例。如果传入的是 `null`,那么就可以认定请求是从方块内或者从一个与面无意义的地方来的,比如说另一个维度(Dimension)。这时候一个不考虑面的通用能力实例将会被请求。`getCapability` 的返回类型将会对应能力声明时的类型。对于Item Handler这个能力,它是 `IItemHandler`。 36 | 37 | 展现一个能力 38 | ----------- 39 | 40 | 要想展现(Expose)一个能力,你需要先获得一个潜在能力类型的实例。注意你需要对每个存有能力的对象赋值不同的实例,因为能力很可能会和包含的对象联系起来。 41 | 42 | 有两种方式能获得实例,第一种是通过 `Capability` 本身,第二种是显式实例化一个它的实现。第一种方法是为使用默认实现设计的,如果那些默认的值对你很有用的话,你可以选择使用它。Item Handler这个能力的默认实现只会展现一个单格背包(Inventory),可能不是你想要的结果。 43 | 44 | 第二种方法可以用自定义的实现。在 `IItemHandler` 这个例子中,默认实现使用的是 `ItemStackHandler` 类,它有一个可选的带参数的构造器,参数为格子的个数。然而,我们不应认定默认实现是一直存在的,因为能力系统设计初衷就是为了防止能力不存在时的加载错误。所以实例化之前我们需要检查能力是否被注册了(见上面关于 `@CapabilityInject` 的内容)。 45 | 46 | 一旦你有了自己的能力接口实例,你会希望在你展现能力的时候通知能力系统的用户。这个可以通过重写 `hasCapability` 方法进行实现,并将实例和你展示的能力进行对比。如果你的机器根据面的不同有不同数量的格子,你可以通过 `facing` 参数进行判定。对于实体和ItemStack,这个参数可以忽略掉,但是你仍然可以自己对面进行定义以使用这一参数,比如说玩家不同的装备格(顶面 => 头部格子?),或者背包中四周的格子(西面 => 左边的格子?)。不要忘记回溯到 `super` 中,否则附加的能力将会停止工作。 47 | 48 | ```java 49 | @Override 50 | public boolean hasCapability(Capability capability, EnumFacing facing) { 51 | if (capability == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) { 52 | return true; 53 | } 54 | return super.hasCapability(capability, facing); 55 | } 56 | ``` 57 | 58 | 同样,当接收到请求的时候,你需要提供能力实例的接口引用。一样,不要忘记回溯到 `super`。 59 | 60 | ```java 61 | @Override 62 | public T getCapability(Capability capability, EnumFacing facing) { 63 | if (capability == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) { 64 | return (T) inventory; 65 | } 66 | return super.getCapability(capability, facing); 67 | } 68 | ``` 69 | 70 | 我们强烈建议您使用直接检查来判定能力而不是尝试依靠于地图和其他数据结构,因为能力判定每个tick可能对多个对象进行,它们需要以最快速度完成以避免游戏卡顿。 71 | 72 | 附加能力 73 | ------- 74 | 75 | 之前说过,对实体和ItemStack附加能力可以通过 `AttachCapabilityEvent` 来完成。所有提供了能力的对象使用的都是这同一个事件。`AttachCapabilityEvent` 有五个泛型(Generic),分别提供了以下几个事件 76 | 77 | - `AttachCapabilityEvent`: 仅对实体触发 78 | - `AttachCapabilityEvent`: 仅对TileEntity触发 79 | - `AttachCapabilityEvent`: 仅对ItemStack触发 80 | - `AttachCapabilityEvent`: 仅对世界触发 81 | - `AttachCapabilityEvent`:仅对区块触发 82 | 83 | 泛型的类型只能是以上几个,不能够更加细化。比如说,如果你想附加一个能力到 `EntityPlayer` 上,你必须要订阅的是 `AttachCapabilityEvent`,之后在附加相应能力之前判断所提供的对象是否是 `EntityPlayer`。 84 | 85 | 每个事件都有一个方法 `addCapacity`,它可以用来附加能力到目标对象上。你在能力列表中添加的是能力Provider,而不是能力本身,它可以从特定面返回相应的能力。Provider只需要实现 `ICapabilityProvider`,如果能力需要持久储存数据,你需要实现 `ICapabilitySerializable`,它不仅能返回能力,还能提供NBT存储与读取函数。 86 | 87 | 要想获得实现 `ICapabilityProvider` 的更多信息,请看上面的[展现一个能力](#_2)小节。 88 | 89 | 创建你自己的能力 90 | -------------- 91 | 92 | 一般来说,一个能力通过一个单独的 `CapabilityManager.INSTANCE.register()` 方法调用即可同时声明及注册。还有一种可能是在能力单独的一个类中定义一个静态的 `register()` 方法,但这个能力系统并不做要求。在这个文档中,我们将会将每一个部分都分成单独的命名类,尽管你也可以使用匿名类。 93 | 94 | ```java 95 | CapabilityManager.INSTANCE.register(能力接口类, storage, 默认实现Factory); 96 | ``` 97 | 98 | 第一个参数是描述能力特性的类型,在这个例子中是 `IExampleCapability.class`。 99 | 100 | 第二个参数是一个实现 `Capability.IStorage` 的类实例,T是第一个参数中的类。这个储存(Storage)类将帮助我们对默认实现进行储存和读取管理,它也可以支持其它的实现 101 | 102 | ```java 103 | private static class Storage 104 | implements Capability.IStorage { 105 | 106 | @Override 107 | public NBTBase writeNBT(Capability capability, IExampleCapability instance, EnumFacing side) { 108 | // 返回一个NBT Tag 109 | } 110 | 111 | @Override 112 | public void readNBT(Capability capability, IExampleCapability instance, EnumFacing side, NBTBase nbt) { 113 | // 从NBT Tag读取 114 | } 115 | } 116 | ``` 117 | 118 | 最后一个参数是一个可调用的Factory,它会返回默认实现的新建实例。 119 | 120 | ```java 121 | private static class Factory implements Callable { 122 | 123 | @Override 124 | public IExampleCapability call() throws Exception { 125 | return new Implementation(); 126 | } 127 | } 128 | ``` 129 | 130 | 最后我们需要一个默认实现,以能在Factory中实例化。这个类的设计完全取决于你,但它至少应该能提供一个简单的骨架让别人测试这个能力(如果它本身不是完全可用的话)。 131 | 132 | !!! warning "警告" 133 | 134 | 和其它拥有能力的对象不同,区块只会在该区块被标记(Mark Dirty)的时候才会写入磁盘。`Chunk`的能力实现必须要保证在改变状态时,对区块进行标记。 135 | 136 | 137 | 与客户端同步数据 138 | -------------- 139 | 140 | 默认情况下,能力数据将不会被发送到客户端。为了修复这个问题,Mod需要用网络数据包(Packet)进行自己的同步。 141 | 142 | 对于下面三种可选的情况你可能会想发送同步数据包: 143 | 144 | 1. 当实体生成在世界时,你可能需要与客户端同步初始值 145 | 2. 当储存的数据改变时,你可能需要通知一些看到实体的客户端 146 | 3. 当一个新的客户端开始看到实体,你可能需要提醒它现有的数据 147 | 148 | 如果你想要更多网络数据包的实现和信息,请去看[网络](../networking/index.md)部分的教程。 149 | 150 | 在玩家死亡时保持数据 151 | ------------------ 152 | 153 | 默认情况下,实体的能力数据在死亡的时候就会消失。为了改变这一点,在玩家实体重生被克隆时数据需要被复制下来。 154 | 155 | 这个可以通过处理 `PlayerEvent.Clone` 这个事件来完成。这个事件中,`wasDead` 这个字段可以用来判别是死亡后的重生还是从末地之路返回到主世界。这个检测很重要,因为如果是从末地返回的话数据仍然是存在的。 156 | 157 | 从IExtendedEntityProperties的移植 158 | -------------------------------- 159 | 160 | 尽管能力系统可以完成IEEPs(IExtendedEntityProperties)所实现的所有,甚至是更多的功能,这两个概念并不是1:1对应的。在这一节中,我将解释如何将已有的IEEPs转换为能力系统。 161 | 162 | 下面这个列表将给出IEEP概念的对应能力系统等价: 163 | 164 | - Property名称/ID(`String`):Capability键(`ResourceLocation`) 165 | - 注册(Registration, `EntityConstructing`):附属(Attaching, `AttachCapabilityEvent`),Capability真正的注册发生在pre-init的时候。 166 | - NBT读写方法:不会自动发生。你需要在事件中附属一个 `ICapabilitySerializable`,并运行 `serializeNBT`/ `deserializeNBT` 来读写NBT数据。 167 | 168 | 你可能不会需要的特性(如果IEEP只在内部使用): 169 | 170 | - 能力系统提供了一个默认实现概念,用来简化第三方用户的使用,但它对一个内部使用的能力就没那么重要的。如果这个能力只是为了内部使用的话,你可以安全地从Factory返回 `null` 值。 171 | - 能力系统提供了一个 `IStorage` 系统,用来从默认实现中读写数据。如果你选择不提供默认实现,那么 `IStorage` 系统将永远不会被调用,所以它可以留空。 172 | 173 | 在使用下面这几个步骤之前请先阅读本教程的其它部分,并理解能力系统的概念。 174 | 175 | 转换指南: 176 | 177 | 1. 转换IEEP的key/id字符串到 `ResourceLocation`(并使用你的Mod ID作为域) 178 | 2. 在你的处理器(Handler)类中(不是你实现能力接口的那个类)创建一个用来储存 `Capability` 实例的字段 179 | 3. 将 `EntityConstructing` 事件改为 `AttachCapabilityEvent`,并且你需要依附一个 `ICapabilityProvider`(可能是 `ICapabilitySerializable`,让你能够读写NBT),而不是查询IEEP。 180 | 4. 如果你没有注册方法的话,创建一个(你可能已经有一个用来注册IEEP事件处理器(Event Handler)的了),并在其中运行能力的注册函数。 181 | 182 | !!! note "译注" 183 | 184 | 这篇文档翻译可能有点混乱,如果有看不懂的建议去看[原文](http://mcforge.readthedocs.org/en/latest/datastorage/capabilities/)(虽然原文也不怎么清楚)。 185 | 186 | 另外,建议去看Forge这个测试的mod:[TestCapabilityMod.java](https://github.com/MinecraftForge/MinecraftForge/blob/master/src/test/java/net/minecraftforge/test/TestCapabilityMod.java) 187 | 188 | 还有capabilities包里的内容:[capabilities包](https://github.com/MinecraftForge/MinecraftForge/tree/master/src/main/java/net/minecraftforge/common/capabilities) 189 | 190 | ustc_zzzz也写了一篇关于Capability很棒的教程,也可以来[这里](https://fmltutor.ustc-zzzz.net/3.3.1-Capability%E7%B3%BB%E7%BB%9F%E4%B8%8E%E5%B7%B2%E6%9C%89%E5%AE%9E%E4%BD%93%E9%99%84%E5%8A%A0%E5%B1%9E%E6%80%A7.html)看一下。 -------------------------------------------------------------------------------- /docs/datastorage/extendedentityproperties.md: -------------------------------------------------------------------------------- 1 | 拓展实体属性 2 | =========== 3 | 4 | 拓展实体属性(Extended Entity Properties, EEP)使我们能对实体(Entity)附加数据。 5 | 6 | !!! warning "警告" 7 | 8 | 这个系统已经弃用,被[能力](capabilities.md)(Capability)系统所代替。 9 | 10 | 声明与注册 11 | --------- 12 | 13 | EEP的基础是 `IExtendedEntityProperties` 这个接口(Interface)。这个结构提供了管理拓展数据的一些基本的方法: 14 | 15 | - `init`:使实现(Implementation)知道附加实体的信息,和实体加载的世界 16 | - `saveNBTData`:允许实现在save文件中保存数据,以便实体载入回世界之后读取 17 | - `loadNBTData`:允许实现读取之前保存的数据 18 | 19 | 实现是一个实现(Implement)这个接口的类,这个类的实例将会附加到实体上,以备存储任何需要的数据。 20 | 21 | 实现的简单骨架会像这样: 22 | 23 | ```java 24 | public class ExampleEntityProperty implements IExtendedEntityProperties { 25 | public static final String PROP_NAME = ExampleMod.MODID + "_ExampleEntityData"; 26 | 27 | public static void register() { 28 | MinecraftForge.EVENT_BUS.register(new Handler()); 29 | } 30 | 31 | // IExtendedEntityProperties内的方法实现 32 | 33 | public static class Handler { 34 | // 事件处理器(Event Handler) 35 | } 36 | } 37 | ``` 38 | 39 | 附加实现到实体上 40 | -------------- 41 | 42 | 附加拓展属性到实体上是通过处理(Handle) `EntityEvent.EntityConstructing` 这个事件(Event)来完成的,如果检测到目标实体,就调用 `Entity#registerExtendedProperties` 这个方法。 43 | 44 | 为了能识别你的属性并且避免重复,这个方法需要填写一个String参数作为属性的识别码(Identifier)。一个很好的做法就是将你的modid包括在这个String中,以防与其他mod中的属性冲突。 45 | 46 | !!! warning "警告" 47 | 48 | 如果同一个属性识别码被添加了两次,Forge将会在识别码上添加一个数字,并通过 `registerExtendedProperties` 方法的返回值返回修改后的识别码。如果你不想这种事情发生,你可以使用 `Entity#getExtendedProperties` 来检查是否这个名字的IEEP被添加过了。 49 | 50 | 为了处理这个事件,你需要添加如下类似的代码: 51 | 52 | ```java 53 | @SubscribeEvent 54 | public void entityConstruct(EntityEvent.EntityConstructing e) { 55 | if (e.entity instanceof EntityPlayer) { 56 | if (e.entity.getExtendedProperties(PROP_NAME) == null) { 57 | e.entity.registerExtendedProperties(PROP_NAME, new ExampleEntityProperty()); 58 | } 59 | } 60 | } 61 | ``` 62 | 63 | 使用实现 64 | ------- 65 | 66 | 为了使用拓展数据,你需要先从实体上获取IEEP实现的一个实例,由于实体可能会被卸载或者切换维度(Dimension),缓冲实例的引用并不安全。 67 | 68 | 为了获取IEEP引用,我们需要使用 `Entity#getExtendedProperties`,并附上相应的属性ID。返回值如果不是 `null` 的话就是实体构造时对应的 `IExtendedEntityProperties`了。 69 | 70 | 在你的IEEP实现中创建一个静态 `get` 方法是一个很好的主意,它将自动获取实例,并将其到你的实现类中: 71 | 72 | ```java 73 | public static ExampleEntityProperty get(Entity p) { 74 | return (ExampleEntityProperty) p.getExtendedProperties(PROP_NAME); 75 | } 76 | ``` 77 | 78 | 保存和读取数据 79 | ------------- 80 | 81 | Forge允许所有的附加在实体上的IEEP自己有保存和读取功能。然而,注意 `saveNBTData` 和 `loadNBTData` 方法所提供的NBT Tag是实体的全局Tag,它可能保存了其他IEEP的数据,和一些实体本身的数据。 82 | 83 | 一些情况下访问这些全局数据可能会很有用,但是大多数情况下,防止与现有数据冲突是很重要的,所以我们优先选择将数据储存在嵌套Tag内,并给他一个唯一的名字(比如说IEEP的识别码)。 84 | 85 | 你的代码会像这样: 86 | 87 | ```java 88 | @Override 89 | public void saveNBTData(NBTTagCompound compound) { 90 | NBTTagCompound propertyData = new NBTTagCompound(); 91 | 92 | // 写入数据到propertyData 93 | 94 | compound.setTag(PROP_NAME, propertyData); 95 | } 96 | 97 | @Override 98 | public void loadNBTData(NBTTagCompound compound) { 99 | if(compound.hasKey(PROP_NAME, Constants.NBT.TAG_COMPOUND)) { 100 | NBTTagCompound propertyData = compound.getCompoundTag(PROP_NAME); 101 | 102 | // 从propertyData中读取数据 103 | } 104 | } 105 | ``` 106 | 107 | 与客户端同步数据 108 | -------------- 109 | 110 | 默认情况下,实体的数据将不会被发送到客户端。为了修复这个问题,Mod需要用数据包(Packet)进行自己的同步。 111 | 112 | 对于下面三种可选的情况你可能会想发送同步数据包: 113 | 114 | 1. 当实体生成在世界时,你可能需要与客户端同步初始值 115 | 2. 当储存的数据改变时,你可能需要通知一些看到实体的客户端 116 | 3. 当一个新的客户端开始看到实体,你可能需要提醒它现有的数据 117 | 118 | 如果你想要更多网络数据包的实现和信息,请去看[网络](../networking/index.md)部分的教程。 119 | 120 | 可能的代码: 121 | 122 | ```java 123 | private void dataChanged() { 124 | if(!world.isRemote) { 125 | EntityTracker tracker = ((WorldServer)world).getEntityTracker(); 126 | ExampleEntityPropertySync message = new ExampleEntityPropertySync(this); 127 | 128 | for (EntityPlayer entityPlayer : tracker.getTrackingPlayers(entity)) { 129 | ExampleMod.channel.sendTo(message, (EntityPlayerMP)entityPlayer); 130 | } 131 | } 132 | } 133 | 134 | private void entitySpawned() { 135 | dataChanged(); 136 | } 137 | 138 | private void playerStartedTracking(EntityPlayer entityPlayer) { 139 | ExampleMod.channel.sendTo(new ExampleEntityPropertySync(this), (EntityPlayerMP)entityPlayer); 140 | } 141 | ``` 142 | 143 | 对应的事件处理器: 144 | 145 | ```java 146 | @SubscribeEvent 147 | public void entityJoinWorld(EntityJoinWorldEvent e) { 148 | ExampleEntityProperty data = ExampleEntityProperty.get(e.entity); 149 | if (data != null) 150 | data.entitySpawned(); 151 | } 152 | 153 | @SubscribeEvent 154 | public void playerStartedTracking(PlayerEvent.StartTracking e) { 155 | ExampleEntityProperty data = ExampleEntityProperty.get(e.target); 156 | if (data != null) 157 | data.playerStartedTracking(e.entityPlayer); 158 | } 159 | ``` 160 | 161 | 在玩家死亡时保持数据 162 | ------------------ 163 | 164 | 默认情况下,实体的数据在死亡的时候就会消失。为了改变这一点,在玩家实体重生被克隆时数据需要被复制下来。 165 | 166 | 这个可以通过处理 `PlayerEvent.Clone` 这个事件来完成。这个事件中,`wasDead` 这个字段可以用来判别是死亡后的重生还是从末地之路返回到主世界。这个检测很重要,因为如果是从末地返回的话数据仍然是存在的。 167 | 168 | ```java 169 | @SubscribeEvent 170 | public void onClonePlayer(PlayerEvent.Clone e) { 171 | if(e.wasDeath) { 172 | NBTTagCompound compound = new NBTTagCompound(); 173 | ExampleEntityProperty.get(e.original).saveNBTData(compound); 174 | ExampleEntityProperty.get(e.entityPlayer).loadNBTData(compound); 175 | } 176 | } 177 | ``` -------------------------------------------------------------------------------- /docs/datastorage/worldsaveddata.md: -------------------------------------------------------------------------------- 1 | World Saved Data 2 | ================ 3 | 4 | World Saved Data系统使你能对世界附加数据,你既可以对某个维度(Dimension)进行附加,也可以对整个世界进行附加。 5 | 6 | 声明 7 | ---- 8 | 9 | 这个系统的核心是 `WorldSavedData` 类。这个类提供了以下几个基本方法以供管理数据: 10 | 11 | - `writeToNBT`: 允许实现对世界写入数据 12 | - `readFromNBT`: 允许实现对世界读取之前写入的数据 13 | - `markDirty`: 这个方法并不是要由实现重写(Override)的。它需要在数据改变之后被调用,以提醒Minecraft写入这些改变。如果这个方法没有被调用,现有数据将会被暂时保存,`writeToNBT` 将不会被调用。 14 | 15 | 这个类需要被一个实现(Implementation)进行重写,并且这个实现的实例(Instance)将会被附加到 `World` 对象上,以备存储任何需要的数据。 16 | 17 | 实现的简单骨架会像这样: 18 | 19 | ```java 20 | public class ExampleWorldSavedData extends WorldSavedData { 21 | private static final String DATA_NAME = MODID + "_ExampleData"; 22 | 23 | // 必须的构造器 24 | public ExampleWorldSavedData() { 25 | super(DATA_NAME); 26 | } 27 | public ExampleWorldSavedData(String s) { 28 | super(s); 29 | } 30 | 31 | // WorldSavedData方法 32 | } 33 | ``` 34 | 35 | 注册及使用 36 | --------- 37 | 38 | `WorldSavedData` 类会加载并/或附加在对应的世界上。一个很好的做法是创建一个静态的get方法来加载数据,如果不存在就新建一个新的实例。 39 | 40 | 附加数据有两种方法:单维度(Per-dimension),或全局(Global)。全局数据将会被附加在一个公共的地图上,它将在 `World` 类任何一个实例下都可获取,而单世界的数据将不会跨维度共享。注意客户端和服务端是分离的,它们将得到不同实例的全局数据,所以如果数据在两端都需要的话我们需要进行手动同步。 41 | 42 | 代码中,这些存储位置通过 `MapStorage` 的两个实例呈现在 `World` 对象上。全局的数据通过 `World#getMapStorage()` 获得,单世界地图通过 `World#getPerWorldStorage()` 获得。 43 | 44 | 现存数据可以通过 `MapStorage#getOrLoadData` 获取,新的数据可以通过 `MapStorage#setData` 附加到世界上。 45 | 46 | ```java 47 | public static ExampleWorldSavedData get(World world) { 48 | // IS_GLOBAL常量只是为了解释清楚使用的,你应该将其简化为对应的方法 49 | MapStorage storage = IS_GLOBAL ? world.getMapStorage() : world.getPerWorldStorage(); 50 | ExampleWorldSavedData instance = (ExampleWorldSavedData) storage.getOrLoadData(ExampleWorldSavedData.class, DATA_NAME); 51 | 52 | if (instance == null) { 53 | instance = new ExampleWorldSavedData(); 54 | storage.setData(DATA_NAME, instance); 55 | } 56 | return instance; 57 | } 58 | ``` -------------------------------------------------------------------------------- /docs/effects/sounds.md: -------------------------------------------------------------------------------- 1 | 音效 2 | =========== 3 | 4 | 术语 5 | ---- 6 | 7 | 8 | | 术语 | 描述 | 9 | |-----|-----| 10 | | 音效事件 | 触发音效效果的东西,比如说 `"minecraft:block.anvil.hit"` 或 `"botania:spreaderFire"`。 | 11 | | 音效类别 | 音效的类别,比如说 `"player"`, `"block"` 或者是 `"master"`。声音设置GUI里面的滑动条代表了这些类别。 | 12 | | 音效文件 | 硬盘上被播放的文件,通常是一个 .ogg 文件。 | 13 | 14 | `sounds.json` 15 | ------------- 16 | 17 | 这个JSON定义了音效事件,并且也定义了播放的音效文件以及字幕等信息。声音事件是通过 [`ResourceLocation`][ResourceLocation] 来判定的。`sounds.json` 应该放在资源域(Resource Domain)的根目录中(`assets//sounds.json`),并且它定义的是仅仅那一个域中的音效事件(`assets//sounds.json` 定义了 `domain` 这个域下的音效事件)。 18 | 19 | 完整的格式可以在原版的[wiki]上找到,但这个例子中只强调重要的部分: 20 | 21 | ```json 22 | { 23 | "open_chest": { 24 | "category": "block", 25 | "subtitle": "mymod.subtitle.openChest", 26 | "sounds": [ "mymod:open_chest_sound_file" ] 27 | }, 28 | "epic_music": { 29 | "category": "record", 30 | "sounds": [ 31 | { 32 | "name": "mymod:music/epic_music", 33 | "stream": true 34 | } 35 | ] 36 | } 37 | } 38 | ``` 39 | 40 | 在顶级对象之下,每个key都对应一个声音事件。注意,这里并没有加域的名字,因为这是从JSON的域中所得的。每个事件都有一个类别(Category),以及一个本地化key,它将在字幕打开时显示。最后指定的是需要播放的音效文件。注意这个值是一个数组。如果指定了多个音效文件,游戏将会在音效事件触发时随机选择一个进行播放。 41 | 42 | 上面的两个例子代表了两种不同的指定音效文件的办法。[Wiki]中提供了详细的资料,但通常来说,对于像是BGM和音乐唱片之类的长音效文件,应该使用第二个形式,因为 `"stream"` 参数告诉Minecraft不要加载整个音效文件到内存中,而是从硬盘中串流(Stream)。使用第二种形式同样允许你指定音量,音调,和音效文件的随机权重。 43 | 44 | 所有情况下,一个域为 `domain`,路径为 `path` 的音效文件的路径为 `assets//sounds/.ogg`。所以,`mymod:open_chest_sound_file` 指向的是 `assets/mymod/sounds/open_chest_sound_file.ogg`,而 `mymod:music/epic_music` 指向的是 `assets/mymod/sounds/music/epic_music.ogg`。 45 | 46 | 创建音效事件 47 | ----------- 48 | 49 | 为了能够真正地播放音效,我们必须要创建一个对应于 `sounds.json` 中条目的 `SoundEvent`。这个 `SoundEvent` 之后需要[注册]。通常情况下,用于创建音效事件的位置应该设置为它的注册表(Registry)名。 50 | 51 | 创建一个 `SoundEvent` 52 | 53 | ```Java 54 | ResourceLocation location = new ResourceLocation("mymod", "open_chest"); 55 | SoundEvent event = new SoundEvent(location); 56 | ``` 57 | 58 | 这个 `SoundEvent` 会作为这个音效的一个引用,并且会被当做参数传递用来播放音效。所以 `SoundEvent` 需要被存在某个地方。如果一个mod有API,那么它应该在API中展现(Expose)它的 `SoundEvent`。 59 | 60 | 播放音效 61 | ------- 62 | 63 | 原版有很多用来播放音效方法,然而我们通常会不清楚到底该用哪个。 64 | 65 | !!! note "提示" 66 | 67 | 下面的信息是通过研究并归类这几个不同方法得来的。它们在Forge 1907都是最新的,如果这些东西过时了请告诉我。 68 | 69 | 注意每个方法都需求一个 `SoundEvent`,也就是你上面注册的那个。而且,**服务端行为**(Server Behavior)和**客户端行为**(Client Behavior)这两个术语都指的是对应的[**逻辑**(Logical)端][sides]。 70 | 71 | ### `World` 72 | 73 | 1. `playSound(EntityPlayer, BlockPos, SoundEvent, SoundCategory, volume, pitch)` 74 | - 仅仅转发到了[重载(Overload)(2)](#world-playsound-pxyzecvp),对每个 `BlockPos` 中的坐标都加了0.5 75 | 2. `playSound(EntityPlayer, double x, double y, double z, SoundEvent, SoundCategory, volume, pitch)` 76 | - **客户端行为**:如果传入的玩家是客户端玩家,播放音效事件到客户端玩家。 77 | - **服务端行为**:对除了被传入的那个玩家以外的任何玩家播放音效事件。 Player可以被设置成 `null`。 78 | - **用处**:上面两个行为的互补性说明这两个方法都可以从一些在两个逻辑端同时运行,由玩家发起的音效代码中被调用——逻辑客户端将音效播放给用户,逻辑服务端让其他人都听见音效而不让对原始用户重复播放。
79 | 它们也可以同样用来在客户端任一指定地点播放任何声音,只需要传递 `null` 玩家,让所有人都听见即可。 80 | 3. `playSound(double x, double y, double z, SoundEvent, SoundCategory, volume, pitch, distanceDelay)` 81 | - **客户端行为**:仅仅播放音效事件到客户端世界。如果 `distanceDelay` 为 `true`,那么音效的延迟将会取决于声源距玩家的距离。 82 | - **服务端行为**:无。 83 | - **用处**:这个方法只在客户端有效,所以它对那些在自定义数据包(Custom Packet)中发送的音效,或其它只在客户端有效的特效类型音效很有用。打雷的时候用的就是这个方法。 84 | 85 | ### `WorldClient` 86 | 87 | 1. `playSound(BlockPos, SoundEvent, SoundCategory, volume, pitch, distanceDelay)` 88 | - 转发到 `World` 的[重载(3)](#world-playsound-xyzecvpd),对每个 `BlockPos` 中的坐标都加了0.5。 89 | 90 | ### `Entity` 91 | 92 | 1. `playSound(SoundEvent, volume, pitch)` 93 | - 转发到 `World` 的[重载(2)](#world-playsound-pxyzecvp),player的参数传递为 `null`。 94 | - **客户端行为**:无。 95 | - **服务端行为**:对处于该实体位置的所有人都播放音效事件。 96 | - **用处**:在服务端从任何非玩家实体发出声音。 97 | 98 | ### `EntityPlayer` 99 | 100 | 1. `playSound(SoundEvent, volume, pitch)`(重写(Override)了 [`Entity`](#entity-playsound-evp) 内的那个方法) 101 | - 转发到 `World` 的[重载(2)](#world-playsound-pxyzecvp),player的参数传递为 `this`。 102 | - **客户端行为**:无。可以看一下在 `EntityPlayerSP` 内的[重写](#entityplayersp-playsound-evp)。 103 | - **服务端行为**:对**除了**这个玩家以外所有玩家播放音效。 104 | - **用处**:见 [`EntityPlayerSP`](#entityplayersp-playsound-evp) 105 | 106 | ### `EntityPlayerSP` 107 | 108 | 1. `playSound(SoundEvent, volume, pitch)` (重写了 [`EntityPlayer`](#entityplayer-playsound-evp) 内的那个方法) 109 | - 转发到 `World` 的[重载(2)](#world-playsound-pxyzecvp),player的参数传递为 `this`。 110 | - **客户端行为**:播放音效事件。 111 | - **服务端行为**:该方法仅限客户端。 112 | - **用处**:就像 `World` 内的方法一样,这玩家类内重写的两个方法可以用在同时运行在两端的代码中。客户端负责播放音效到用户,服务端负责让其他人都听见而不对原始用户重新播放。 113 | 114 | [wiki]: http://minecraft.gamepedia.com/Sounds.json 115 | [注册]: ../concepts/registries.md#_2 116 | [ResourceLocation]: ../concepts/resources.md#resourcelocation 117 | [sides]: ../concepts/sides.md -------------------------------------------------------------------------------- /docs/events/intro.md: -------------------------------------------------------------------------------- 1 | 事件 2 | ==== 3 | 4 | Forge使用的是一种事件总线(Event Bus)的机制,使mod能够从原版或者mod行为中截取事件(Event)。 5 | 6 | 例子:一个事件可以用来使在原版木棍右键的时候执行一个行为。 7 | 8 | 对于大部分事件的主事件总线位于 `MinecraftForge.EVENT_BUS`。有一些对于特定事件的(比如说地形生成)其它的总线也在同一个类中。 9 | 10 | 一个事件处理器(Event Handler)是一个包含有一个或者多个 `public void` 成员方法,并且每一个方法都有 `@SubscribeEvent` 注解的类。 11 | 12 | 创建一个事件处理器 13 | ---------------- 14 | 15 | ```java 16 | public class MyForgeEventHandler { 17 | @SubscribeEvent 18 | public void pickupItem(EntityItemPickupEvent event) { 19 | System.out.println("Item picked up!"); 20 | } 21 | } 22 | ``` 23 | 24 | 这个事件处理器监听了 `EntityItemPickupEvent`。正如名字所述,这个事件将会在一个 `Entity` 捡起一个物品的时候发送到事件总线。 25 | 26 | 要想注册一个事件处理器,请使用 `MinecraftForge.EVENT_BUS.register()`,并将你的事件处理器的一个实例传进去。 27 | 28 | !!! note "提示" 29 | 30 | 在更早的Forge版本中存在有两个分开的事件总线。一个是Forge的,一个是FML的。然而这个系统已经被弃用很长时间了,所以你以后不需要再使用FML的事件总线了。 31 | 32 | ### 静态事件处理器 33 | 34 | 事件处理器也可以是静态的。用于处理的方法仍是用 `@SubscribeEvent` 来注解,和实例处理器(Instance Handler)唯一的不同只是这个方法使用 `static` 标记了。要想注册一个静态事件处理器,你不能传入类的实例,你必须要将 `Class` 本身传递进去。一个例子: 35 | 36 | ```java 37 | public class MyStaticForgeEventHandler { 38 | @SubscribeEvent 39 | public static void arrowNocked(ArrowNockEvent event) { 40 | System.out.println("Arrow nocked!"); 41 | } 42 | } 43 | ``` 44 | 45 | 它必须要使用 `MinecraftForge.EVENT_BUS.register(MyStaticForgeEventHandler.class)` 来注册。 46 | 47 | ### 自动注册静态事件处理器 48 | 49 | 一个类也可以使用 `@Mod.EventBusSubscriber` 来进行注解。当 `@Mod` 类构造时,这样的类将会自动注册至 `MinecraftForge.EVENT_BUS`。这完全等同于在 `@Mod` 类的构造器最后加上 `MinecraftForge.EVENT_BUS.register(AnnotatedClass.class);` 50 | 51 | !!! note "提示" 52 | 53 | 使用这个注解并不会注册一个类的实例。它注册的是类本身(即事件处理方法必须是静态的)。 54 | 55 | 取消 56 | ---- 57 | 58 | 如果一个事件可以被取消(Candel),它将会被注解为 `@Cancelable`,并且 `Event#isCancelable()` 方法将会返回 `true`。一个可取消事件的取消状态可以通过调用 `Event#setCanceled(boolean canceled)` 来变更,在这个函数内传入 `true` 将取消这个事件,而传入 `false` 的话则重新启用这个事件。然而,如果这个事件不能被取消(通过 `Event#isCancelable()` 来定义),不论传入什么boolean值都将抛出 `UnsupportedOperationException`,因为不可取消的事件中的取消状态是不可变的。 59 | 60 | !!! important "重要" 61 | 62 | 不是所有的事件都可以被取消!尝试取消一个不可被取消的事件将会导致抛出 `UnsupportedOperationException`,这很可能会导致游戏崩溃!在尝试取消一个事件之前确保先使用 `Event#isCancelable()` 来检测它是否能够被取消。 63 | 64 | 结果 65 | ---- 66 | 67 | 一些事件会有一个 `Event.Result` 结果(Result)。可能的结果有三个:`DENY` 停止事件、`DEFAULT` 使用原版行为、`ALLOW` 强制动作发生,不论原本动作是否要发生。一个事件的结果可以通过调用 `setResult`,并传入一个 `Event.Result` 参数。并不是所有的事件都有结果,一个有结果的事件将会被注解为 `@HasResult`。 68 | 69 | !!! important "重要" 70 | 71 | 不同的事件可能对结果(Result)有着不同的用法,使用结果之前请先查看事件对应的JavaDoc。 72 | 73 | 优先级 74 | ------ 75 | 76 | 事件处理器方法(被 `@SubscribeEvent`注解的方法)有一个优先级(Priority)。你可以对事件处理器方法的注解加上 `priority` 值作为参数从而设置其优先级。优先级可以是 `EventPriority` 里的任何值(`HIGHEST`、`HIGH`、`NORMAL`、`LOW`、`LOWEST`)。有着 `HIGHEST` 优先级的事件处理器将会最先被执行,之后沿着降序执行直到 `LOWEST` 的事件处理器被最后执行。 77 | 78 | 子事件 79 | ------ 80 | 81 | 很多事件都有它们自己不同的变种。一个事件的变种虽然不同但它们都基于共同的因子(比如说 `PlayerEvent`),或者是一个有多个阶段的事件(比如说 `PotionBrewEvent`)。要注意的是,如果你监听了父事件类,这个事件监听器将会在该父事件的**任一**子类触发时被调用。 -------------------------------------------------------------------------------- /docs/forgedev/index.md: -------------------------------------------------------------------------------- 1 | 入门 2 | ==== 3 | 4 | 如果你想参与Forge的开发的话,在开始写代码之前你还需要进行一些特殊的步骤。普通的Mod开发环境是不能直接对Forge代码进行开发的。这篇指南将告诉你该如何配置环境,以及如何对Forge进行改进! 5 | 6 | Fork并Clone仓库 7 | --------------- 8 | 9 | 和大部分开源项目一样,Forge也是托管在[GitHub](https://www.github.com)上的。如果你曾经对其它的工程贡献了代码,那么你应该对这一部分很熟悉了,你可以直接跳到下一个部分中。 10 | 11 | 如果你是使用Git的新手,那么下面这两个步骤能帮助你快速入门。 12 | 13 | !!! note "提示" 14 | 15 | 这篇指南假设你已经注册了一个GitHub账号。如果你没有的话,访问它的[注册页面](https://www.github.com/join)来创建一个账号。另外,这篇指南并不是Git的使用教程,如果你不能让其正常工作的话,请查阅其它的资源。 16 | 17 | ### Fork 18 | 19 | 首先,你需要Fork一下[MinecraftForge的仓库](https://www.github.com/MinecraftForge/MinecraftForge)(Repository),点击页面右上角的“Fork”按钮。如果你是一个组织(Organization),请选择一个需要Fork进的账号。 20 | 21 | Fork这个仓库是必要的步骤,因为并不是所有的GitHub用户对每个仓库拥有所有的权限的。你需要创建原仓库的一个拷贝,之后通过所谓的Pull Request来提交你的改动,我们将在后面再讨论它。 22 | 23 | ### Clone 24 | 25 | 在Fork这个仓库之后,我们需要将代码下载到本地,并做出一些修改。你需要将这个仓库Clone到本地机器上。 26 | 27 | 使用你最喜欢的Git客户端,将你的Fork Clone到一个自选的目录中。下面这个代码片段应该能在所有配置好Git的系统上工作,它会Clone这个仓库到当前目录中的一个叫做`Forge`的目录中(注意你需要将 `/MinecraftForge Forge 31 | ``` 32 | 33 | ### Check out正确的分支 34 | 35 | 只有Fork并Clone仓库是Forge开发的必要步骤。但是,为了方便创建Pull Request(PR),最好是需要分支(Branch)的。 36 | 37 | 建议是对每个要提交的PR都创建并Check Out到一个新的分支的。这样子,你就能让新的PR保持Forge改动的最新,而仍能够对更老的补丁(Patch)进行开发。 38 | 39 | 在完成这一步骤之后,你就准备好配置开发环境了。 40 | 41 | 配置环境 42 | ------- 43 | 44 | 取决于你的IDE,你需要采取不同的推荐步骤来配置一个开发环境。 45 | 46 | ### Eclipse 47 | 48 | 由于Eclipse的工作空间(Workspace)的工作方式,ForgeGradle能够帮你做大部分的工作,创建一个Forge工作空间。 49 | 50 | 1. 打开一个终端/命令提示符,移动到Clone的目录下 51 | 2. 输入`./gradlew setupForge`并按下回车。等待ForgeGradle工作完成 52 | 3. 打开你的Eclipse工作空间,选择`File -> Import -> General -> Existing Projects into workspace` 53 | 4. 在打开的选项框中,将根目录(Root Directory)设置为`/projects/` 54 | 5. 保证“Forge”和“Clean”选项框是勾选状态,并且根据你的喜好调整其它的设置 55 | 6. 点击“Finish”按钮完成导入 56 | 57 | 这就是Eclipse配置的全部过程了,不需要有额外的步骤就能让测试Mod运行。只需要和其它项目一样点击Run按钮并选择所需的运行配置就可以了。 58 | 59 | ### IntelliJ IDEA 60 | 61 | 62 | JetBrains的旗舰级IDE对[Gradle](https://www.gradle.org),Forge所用的构建系统,有着不错的支持。然而,由于Minecraft Mod开发的特殊,我们仍需要进行一些附加的步骤来让它正常工作。 63 | 64 | 如果你更喜欢视频教程,cpw上传了一个[视频](https://www.youtube.com/watch?v=yanCpy8p2ZE),里面介绍了非常相似的步骤,也能配置成功。 65 | 66 | !!! note "提示" 67 | 68 | 这些步骤只在IDEA 2016版之后能够稳定工作。更旧的版本没有正式的Gradle支持,而且也不支持Forge的开发工作空间。 69 | 70 | 1. 将Forge的`build.gradle`文件导入为一个IDEA工程。点击`File -> Open`,之后切换至Clone的目录,选择`build.gradle`文件。如果弹出一个对话框,选择“Open as Project” 71 | 2. 在之后的向导中,保证“Create separate module per source set”选项是选中状态,并且“Use default gradle wrapper”也是选中状态。点击确认 72 | 3. 在IDEA导入工程和索引文件完成之后,打开屏幕右边的Gradle的侧边栏 73 | 4. 打开“forge”工程树,选择“Tasks”,之后是“forgegradle”,之后右击“Create Forge [setup]”选项 74 | 5. 在配置对话框打开之后,修改“tasks”字段,写入“clean setup”,并将`-Xmx3G -Xms3G`添加到“VM Options”中。后面那个选项保证了资源开销很大的反编译过程能有足够的内存 75 | 6. 点击“Okay”,并运行刚创建的运行配置。这可能会需要运行一段时间。 76 | 7. 在配置任务完成之后,再次打开Gradle的侧边栏,点击顶部的“Attach Gradle project”按钮(那个加号图标) 77 | 8. 切换至Clone的目录,打开`projects`目录,双击里面的`build.gradle`文件。在之后的对话框中选择“Use gradle wrapper task configuration”,并确认 78 | 9. 导入IDEA所有建议的Mod 79 | 10. 要想使用工程的运行配置,在文件浏览器中打开`projects`目录,进入`.idea`目录(根据系统不同,这个目录可能是隐藏的)。复制`runConfigurations`目录到Clone根目录的`.idea`目录中 80 | 11. 当IDEA识别到添加的配置后,对每个配置都执行以下的步骤 81 | - 将配置的Mod改为`_main`,其中``就是配置名称的第一部分 82 | - 将运行目录改为`/projects/run` 83 | 84 | 这就是在IntelliJ IDEA中创建Forge开发环境的全部步骤了。然而,你现在还不能直接运行测试和Forge自带的调试Mod。这还需要一些附加的配置。 85 | 86 | #### 启用测试Mod 87 | 88 | 要想启用Forge自带的测试Mod,你需要将编译器输出添加到Classpath。同样,cpw也发布了一个[视频](https://www.youtube.com/watch?v=pLWQk6ed56Q)来解释这些步骤。 89 | 90 | 1. 在工程视图中选择`src/main/test`目录,并从菜单中运行`Build -> Build module 'Forge_test'` 91 | 2. 打开`File -> Project Structure`内的“Project Structure”窗口 92 | 3. 前往“Modules”选项,展开`Forge`Mod 93 | 4. 选择`Forge_test`子Mod,前往“Paths”面板 94 | 5. 记住"Test output path"标签内的路径,从树中选择`Forge_main`子Mod 95 | 6. 打开“Dependencies”面板,点击右侧的绿色加号按钮,并选择“JARs or directories” 96 | 7. 选择之前显示在`Forge_test`输出路径中的路径,点击确认 97 | 8. 将新添加依赖项的“Scope”设置为“Runtime”(当前为“Compile”),因为主代码编译并不依赖于测试代码 98 | 99 | 现在你已经添加测试Mod到类路径中了,你需要在每次做出变动时重新构建它们,它们是不会自动构建的。要想构建的话,重复步骤1,或者,如果你对测试Mod文件进行了修改,想要重新构建,只需要点击`Build -> Rebuild project`或者对应的键盘快捷键(默认是Ctrl+F9)就可以了。 100 | 101 | #### 使用已有的Mod测试 102 | 103 | 你可能会想在已有的工程中测试Forge的变动。在测试Mod部分中提到的cpw的视频也讨论了这一部分。让Mod运行需要和测试Mod类似的步骤,但添加你的工程到工作空间还需要一些附加的工作。 104 | 105 | 1. 打开`File -> Project Structure`内的“Project Structure”窗口 106 | 2. 前往“Modules”部分,按下树视图上方的绿色加号图标 107 | 3. 选择“Import Module”,选择你的工程的`build.gradle`文件,并确认选择以及导入设置 108 | 4. 关闭“Project Structure”窗口,点击“OK”按钮 109 | 5. 在IDEA导入完成之后,重新打开窗口,并从树中选择你工程的`_main`Mod 110 | 6. 打开“Dependencies”面板,点击右侧的绿色加号按钮,并选择“Module dependency” 111 | 7. 在刚打开的窗口中,选择`Forge_main`Mod 112 | 8. 在这之后,重复测试Mod部分中的步骤,只是记得将`Forge_test`替换为你的工程的`_main`Mod 113 | 114 | !!! note "提示" 115 | 116 | 你可能会想要移除正常开发环境中的已有的依赖项(主要是指的`forgeSrc`这个Jar),或者将Forge Mod移动到依赖项列表的上方。 117 | 118 | 你现在应该就能够使用对Forge和原版代码的改动,对你的Mod进行测试了。 119 | 120 | 作出改动和Pull Request 121 | --------------------- 122 | 123 | 一旦配置好了开发环境,就是时候对Forge的代码库进行一些改动了。然而,在修改工程的代码时你应该避免这些事项。 124 | 125 | 最重要的一点就是,如果你希望修改Minecraft的源代码,你必须在“Forge”这个子工程中进行。任何在“Clean”工程中的改动都将损坏ForgeGradle和生成补丁的过程。这可能会带来灾难性的后果,并且让你的开发环境变得完全无用。如果你希望有一个完美的体验,请保证仅仅修改“Forge”工程中的代码。 126 | 127 | ### 生成补丁 128 | 129 | 在你对代码作出变动并仔细测试之后,你就可以生成补丁(Patch)了。这仅仅在你对Minecraft代码(即在“Forge”工程中)进行开发时才需要,但这一步是让你的变动在其它机器上能够正常运行的重要步骤。Forge是通过对原版Minecraft注入改动的代码来工作的,所以它需要将改动储存为一个合适的格式。幸运的是,ForgeGradle能够为你生成改动集,你所需要做的只是commit它们。 130 | 131 | 如果需要开始生成补丁,请在IDE中或者命令行中运行`genPatches`这个Gradle任务。在它完成之后,你就可以commit所有的变动(保证你没有添加任何不需要的文件)并提交你的Pull Request了。 132 | 133 | ### Pull Request 134 | 135 | 提交变动到Forge前的最后一步就是Pull Request了(简写PR)。这是将你Fork的代码融入到正式代码库的一个正式请求。创建PR是很容易的,只需要前往[这个GitHub页面](https://github.com/MinecraftForge/MinecraftForge/compare),并按照指定的步骤操作即可。在这里分支的重要性就体现出来了,你能够准确地选择你需要提交的改动。 136 | 137 | !!! note "提示" 138 | 139 | Pull Request是遵循一些规则的,不是所有的请求都会被盲目地接受。请参考[这篇文档](https://github.com/MinecraftForge/MinecraftForge/blob/1.10.x/CONTRIBUTING.md)来获得更多的信息,并保证你的PR的质量!如果你希望将PR被接受的几率最大化,请遵循这个[PR指南](prguidelines.md)! -------------------------------------------------------------------------------- /docs/forgedev/prguidelines.md: -------------------------------------------------------------------------------- 1 | Pull Request指南 2 | ================ 3 | 4 | Mod是在Forge之上诞生的,但Forge仍然还不支持一些东西,并且会限制Mod能够做的事情。每当Mod作者遇到这样的事情时,它们就可以对Forge进行改动来支持,并通过Pull Request将变动提交到GitHub。 5 | 6 | 为了节约你和Forge团队的时间,我们建议您在准备Pull Request时遵循一些基本的规则。如果想要写一个好的Pull Request的话,下面这些点都是非常重要的。 7 | 8 | 什么是Forge? 9 | ------------ 10 | 11 | 从整体来说,Forge是在Minecraft之上的一个Mod兼容层。 12 | 早期的Mod会直接修改Minecraft的代码(和现在的Coremod一样),但是在它们修改相同的东西时,就会遇到冲突。在一个Mod修改了一些不能预料到的行为的时候(和现在的Coremod一样),也会造成很奇怪的问题。 13 | 14 | 通过使用像Forge这样的东西,Mod就能中心化常见的变动并避免冲突了。 15 | Forge也为了一些常用的Mod功能加入了一些辅助结构,包括能力、注册表、流体处理和矿物词典等来让Mod能更好在一起工作的东西。 16 | 17 | 当写作一个优秀的Forge Pull Request的时候,你还需要知道Forge在更低的层次上是什么。 18 | Forge中主要有两种代码:Minecraft补丁和Forge代码。 19 | 20 | 补丁 21 | ---- 22 | 23 | 补丁在应用后会直接修改Minecraft的源码,它应当是越少越好。 24 | 每当Minecraft代码有改动的时候,Forge所有的补丁都会被仔细检查修改,并正确应用到新的代码上。 25 | 这就意味着改动了很多东西的大补丁将会很难去维护,所以Forge希望尽可能的将补丁做得越小越好。 26 | 除此了让代码易于理解之外,补丁的审查将会注重于减少它的大小。 27 | 28 | 有很多技巧能够帮助你制作一个较小的补丁,审查过程中也经常会有人指出完成一个目标的更简单方式。 29 | Forge的补丁通常会插入一行代码,触发一个事件或者一个代码钩子(Hook),如果事件满足一些条件的话,它将会影响之后的代码。 30 | 这将允许大部分的代码都处于补丁之外,保证了补丁的简洁。 31 | 32 | 如果你想知道更多关于创建补丁的信息,见[GitHub Wiki]((https://github.com/MinecraftForge/MinecraftForge/wiki/If-you-want-to-contribute-to-Forge#conventions-for-coding-patches-for-a-minecraft-class-javapatch))。 33 | 34 | Forge代码 35 | -------- 36 | 37 | 除了补丁之外,Forge代码就是普通的Java代码。它可能是事件代码、兼容特性或其它不会直接修改Minecraft的代码。 38 | 当Minecraft更新的时候,和其它东西一样,Forge代码也需要更新。然而,它会更简单一些,因为它没有直接混杂在Minecraft代码之中。 39 | 40 | 因为这些代码是独立的,它没有补丁一样的大小限制。 41 | 42 | 除了要让代码易于理解,代码的审查通常会关注于让代码更干净,使用正确的格式和Java文档。 43 | 44 | 解释你的目的 45 | ----------- 46 | 47 | 所有的Pull Request都需要回答一个问题:为什么它是必须的? 48 | 向Forge加入的任何代码都必须要进行维护,更多的代码意味着Bug产生的可能性更大,所以加入新的代码都必须有合适的理由。 49 | 50 | Pull Request的一个常见的问题是,没有提供任何的解释,或者提供了很奇怪的演示例子。 51 | 这样只会拖延Pull Request的过程。 52 | 对于通常情况的清晰解释是很有帮助的,但请还要给一个充分的例子,展示你的Mod为什么需要这个Pull Request。 53 | 54 | 为了达成你的目标,有时候会有更好的办法,或者是根本不需要Pull Request的办法。只有当这些可能性都被排除之后,代码的改动才能被接受。 55 | 56 | 展示它能正常工作 57 | -------------- 58 | 59 | 你提交给Forge的代码应该能正常工作,而说服审查者的工作在你的身上。 60 | 61 | 最好的方式就是添加一个范例Mod或者JUnit测试到Forge中,使用你的新代码,并且展示它们是如何工作的。 62 | 63 | 如果想要配置并运行一个包含范例Mod的Forge环境,请参考[这篇指南](index.md)。 64 | 65 | 对Forge的重大改动 66 | ---------------- 67 | 68 | Forge不能在作出改变之后让已有的Mod不能正常工作。 69 | 这就意味着Pull Request必须要保证它不能破坏与之前Forge版本的二进制兼容性(Binary Compatibility)。 70 | 破坏了二进制兼容性的改动将称之为重大改动(Breaking Change)。 71 | 72 | 对这一规则仍有一些例外,Forge在新Minecraft版本的初期会接受重大改动,Minecraft本身就已经对Mod作者产生重大改动了。 73 | 在这个时间段之外,有些时候紧急的重大改动也是必须的,这是非常不常见的,而且它可能会对Minecraft Mod社区中的每个人都造成麻烦。 74 | 75 | 除了这些例外时期,拥有重大改动的Pull Request都将不被接受,它们必须要进行修改以适配旧的版本,或者等待下个Minecraft版本的到来。 76 | 77 | 耐心、文明、有同情心 78 | ----------------- 79 | 80 | 当提交Pull Request的时候,你需要熬过代码审查,作出一些改动,直到它是最完美的状态。请记住,代码审查并不是针对你的判断。你代码中的Bug并不是个人的错误。没有人是完美的,所以我们需要合作。 81 | 82 | 负面情绪是不会有任何帮助的,威胁要放弃你的Pull Request转而去写Coremod只会让人感到失望,让整个Mod生态系统更糟。 83 | 合作的时候很重要的是,要假设审查Pull Request的人是有好的意图的,不要感情用事。 84 | 85 | 审查 86 | ---- 87 | 88 | 如果你能尽最大努力理解Pull Request过程中的缓慢和完美主义,我们也会尽力理解你的观点。 89 | 90 | 在你的Pull Request尽所有人的努力审查并清理之后,它将会被标记,留给Lex进行最终的审核,他会决定这个Pull Request是否会包含在工程中。 -------------------------------------------------------------------------------- /docs/gettingstarted/autoupdate.md: -------------------------------------------------------------------------------- 1 | Forge更新检查器 2 | ============= 3 | 4 | Forge提供了一个可选的轻量级更新检查框架。它所做的只有检查mod的更新,如果任何mod有可用的更新,在主菜单的Mods按钮上和mod列表中将会显示一个闪光的图标,并会显示对应的更新日志。它**并不会**自动下载更新。 5 | 6 | 入门 7 | ------ 8 | 9 | 你需要做的第一件事情是在 `@Mod` 注解中设定 `updateJSON` 的参数。它的值为一个有效的URL地址指向一个更新JSON文件。这个文件可以放在你的网页服务器中、GitHub上或是任何地方,只要保证mod的所有玩家都能访问这个文件即可。 10 | 11 | 更新JSON的格式 12 | ----------------------- 13 | 14 | JSON本身的格式很简单,如下所示: 15 | 16 | ```javascript 17 | { 18 | "homepage": "<你的Mod主页/下载地址>", 19 | "": { 20 | "": "<这个版本的更新日志>", 21 | // 列出对应Minecraft版本的所有Mod版本,以及它们对应的更新日志 22 | ... 23 | }, 24 | ... 25 | "promos": { 26 | "-latest": "", 27 | // 这是填对应Minecraft版本的Mod的最新版本 28 | "-recommended": "", 29 | // 这里填对应Minecraft版本的Mod的最新稳定版本 30 | ... 31 | } 32 | } 33 | ``` 34 | 35 | 这个JSON文件已经很清楚了,但要注意以下几点: 36 | 37 | - `homepage` 下的链接将会在mod有更新的时候显示给用户。 38 | - Forge使用了一个内部的算法来决定一个版本String是否比另一个更“新”。这个算法应该兼容大部分版本命名方式,如果你不确定你的命名方式是否被支持,请参见 `ComparableVersion` 类。我们强烈建议您使用[语义化版本](http://semver.org/)。 39 | - 更新日志String可以使用 `\n` 分行。一些人可能会选择仅包含一个简略的更新日志,并提供一个链接到完整的更新日志。 40 | - 手动输入这些东西可能会很麻烦。你可以设置一下 `build.gradle`,在构建的时候自动更新这个文件,Groovy有原生的JSON解析支持。这个就留给读者当做练习了。 41 | 42 | 你可以参考[Charset](https://gist.githubusercontent.com/Meow-J/fe740e287c2881d3bf2341a62a7ce770/raw/bf829cdefc84344d86d1922e2667778112b845b1/update.json)和[Botania Unofficial](https://gist.githubusercontent.com/Meow-J/1299068c775c2b174632534a18b65fb8/raw/42c578cf2303aa76d8900f5fdc6366122549d2a8/update.json)的例子构建你自己的JSON。 43 | -------------------------------------------------------------------------------- /docs/gettingstarted/debugprofiler.md: -------------------------------------------------------------------------------- 1 | 调试分析器 2 | ========== 3 | 4 | Minecraft提供了调试分析器(Debug Profiler),可用于查找耗时的代码。 特别是像`TileEntities`和刷新`TileEntities`这样的东西,对于mod开发者和服务器服主找到耗时代码非常有用。 5 | 6 | 使用调试分析器 7 | ---------------------- 8 | 9 | 调试分析器用起来很简单. 它用 `/debug start`开始分析, 用 `/debug stop`停止。 10 | 重要的是,收集数据的时间越多,结果就越好。 11 | 建议至少让它收集一分钟的数据。 12 | 13 | !!! note "提示" 14 | 当然,您只能分析可用的代码。要分析的实体和TileEntities必须存在于世界中,以显示在结果中。 15 | 16 | 在你结束调试后,它会在运行文件夹的`debug`子文件夹下创建一个新文件。 17 | 它用时间和日期以`profile-results-yyyy-mm-dd_hh.mi.ss.txt`的格式命名。 18 | 19 | 阅读分析结果 20 | ---------------------------- 21 | 22 | 文件顶部是运行时间(以毫秒为单位)和在那段时间内运行了多少tick。 23 | 24 | 在它下面, 是类似于下面的代码段的信息: 25 | ```js 26 | [00] levels - 96.70%/96.70% 27 | [01] | World Name - 99.76%/96.47% 28 | [02] | | tick - 99.31%/95.81% 29 | [03] | | | entities - 47.72%/45.72% 30 | [04] | | | | regular - 98.32%/44.95% 31 | [04] | | | | blockEntities - 0.90%/0.41% 32 | [05] | | | | | unspecified - 64.26%/0.26% 33 | [05] | | | | | minecraft:furnace - 33.35%/0.14% 34 | [05] | | | | | minecraft:chest - 2.39%/0.01% 35 | ``` 36 | 下面解释一下每部分的意义: 37 | 38 | | [02]| tick | 99.31% | 95.81% | 39 | | :----------------------- | :---------------------- | :----------- | :----------- | 40 | | section的深度 | Section的名字 | 它占上一级Section的时间百分比。 对于第0层,它是tick所用时间的百分比,而对于第1层,它是其父级Section所用时间的百分比 | 第二个百分比是在整个tick中花了多少时间。 | 41 | 42 | 分析你自己的代码 43 | ---------------- 44 | 45 | 调试分析器基本支持`Entity`和`TileEntity`。 如果您想要分析其他内容,您可能需要手动创建sections,如下所示: 46 | 47 | ```JAVA 48 | Profiler#startSection(yourSectionName : String); 49 | //你想要分析的代码 50 | Profiler#endSection(); 51 | ``` 52 | 你可以从`World`, `MinecraftServer`或 `Minecraft` 对象中获取 `Profiler` 对象。 53 | 现在,您只需要在生成文件中找到你的Section名称。 -------------------------------------------------------------------------------- /docs/gettingstarted/dependencymanagement.md: -------------------------------------------------------------------------------- 1 | 依赖管理 2 | ===================== 3 | 4 | Forge对管理和加载mod依赖项有一些支持。 库,甚至其他mod都可以嵌入到构建中,使Forge能够以兼容的方式在运行时提取和加载它们。 5 | 6 | mod存储库 7 | ------------------ 8 | 9 | mod存储库是一个类似Maven的存储库,包含mods和库。 此存储库中的工件由其Maven坐标标识:`groupId:groupId:artifactId:version:classifier@extention`。 分类和扩展是可选的。 Forge可以存档,管理和加载此存储库中的mod和库。 mod存储库可能包含多个版本的mod和库,包括快照版本。 10 | 11 | 如果定义了`Maven-Artifact`清单属性,Forge可以在存储库中存档jar。 此属性的值应为其Maven坐标。 12 | 13 | mod存储库支持快照工件。 如果工件版本以`-SNAPSHOT`结尾,则工件将被解析为具有最新时间戳的版本。 时间戳可以设置成清单中`Timestamp`属性,该属性应该是自纪元以来的时间(以毫秒为单位)。 14 | 15 | 16 | 依赖扩展 17 | --------------------- 18 | 19 | Forge提供了一种在mod中嵌入依赖项和运行时提取它们的简单方法。 通过将依赖jar包放在您自己的jar包中,Forge可以将从mod存储库提取到并加载它们。 这可以用作shading的替代方法,并具有解决依赖项版本冲突的潜在好处。 20 | 21 | jar包的包含依赖项由`ContainedDeps`清单属性标记。 它的值应该是一个空格分隔的列表,其中包含将要提取的jar包的名称。 这些jar包应该放在`/META-INF/libraries/{entry}`中。 22 | 23 | Forge将检查清单中所包含的jar包,以确定其Maven坐标,以便它可以存档。 如果存在文件`/META-INF/libraries/{entry}.meta`,Forge将把它读作jar包的清单。 依赖项将根据其`Maven-Artifact`清单属性存储到本地存储库中。 24 | 25 | -------------------------------------------------------------------------------- /docs/gettingstarted/index.md: -------------------------------------------------------------------------------- 1 | Forge入门 2 | ======== 3 | 4 | 这是一个让你了解如何从零开始构建一个基本mod的简单指南。这个文档剩下的内容都是基于本篇教程出发的。 5 | 6 | 从零起步制作mod 7 | ----------- 8 | 9 | 1. 从Forge[下载站点][files]获取Forge的源码发布版(即Mdk版本,如果是1.8/1.7的旧版本则是Src)。 10 | 2. 解压刚下载的源码到一个空文件夹中。你应该能看见有一些文件在里面,我们在 `src/main/java` 中准备了一个范例mod供您参考。只有下面这几个文件是在mod开发中必须的,你可以在你所有的工程中重复使用这些文件: 11 | * `build.gradle` 12 | * `gradlew` (`.bat`和`.sh`) 13 | * `gradle` 文件夹 14 | 3. 将上述文件复制到一个新的文件夹中,它将会是你的mod工程文件夹。 15 | 4. 在步骤(3)创建的文件夹中打开命令提示符,运行 `gradlew setupDecompWorkspace` (译注: 如果在Linux系统和powershell下替换 `gradlew` 为 `./gradlew`,需要自己添加运行权限)。这个指令会从互联网上下载很多的文件,这些文件会用来反编译和构建Minecraft和Forge。由于它会下载一些东西并且反编译Minecraft,这也许会需要很长时间。 16 | 5. 选择你的IDE: Forge官方支持使用Eclipse或者是IntelliJ环境进行开发,但你可以使用任何开发环境,不论是NetBeans还是vi/emacs,都可以正常工作 17 | * 对于Eclipse用户,你需要运行 `gradlew eclipse` - 这会下载更多文件以能够让工程在Eclipse中构建,并且将Eclipse工程输出到你当前的目录 18 | * 对于IntelliJ用户,直接导入build.gradle文件就可以了(译注:IDEA启动界面Import Project选build.gradle) 19 | 6. 加载你的工程到IDE 20 | * 对于Eclipse用户,在任意地方创建一个工作空间(Workspace)(当然最方便的就是在工程文件夹的上一级目录中创建)。之后以工程的形式导入你的工程文件夹,之后的事情软件都会自动处理 21 | * 对于IntelliJ用户,你只需要创建运行配置就行了。你可以运行 `gradlew genIntellijRuns` 来自动生成 22 | 23 | !!! note "提示" 24 | 25 | 如果你在运行第4步时看到在 `:decompileMC` 这个任务报错 26 | 27 | ``` 28 | Execution failed for task ':decompileMc'. 29 | GC overhead limit exceeded 30 | ``` 31 | 32 | 请分配更多的内存给Gradle,在 `~/.gradle/gradle.properties` 文件中(如果没有请创建一个)加入 `org.gradle.jvmargs=-Xmx2G` 参数。`~` 符号代表用户的[Home目录][home directory]。 33 | 34 | !!! note "提示" 35 | 36 | 注意,通常情况下,`gradlew setupDecompWorkspace` 的文件只需要被下载并且反编译一次,除非你删除了Gradle的产物缓存。 37 | 38 | 无需控制台的IntelliJ IDEA配置 39 | ---------------------------- 40 | 41 | 在开始这部分之前,请按照步骤1到3先将工程文件夹创建好。也正因为这个原因,这一部分的序号将从4开始。 42 | 43 | !!! note "译注" 44 | 45 | 国内用户如果使用代理可以在导入工程的时候在Global Gradle settings的Gradle VM options中输入 `-Dhttp.proxyHost=[ip] -Dhttp.proxyPort=[port] -Dhttps.proxyHost=[ip] -Dhttps.proxyPort=[port]` 等参数来设定。在运行Gradle任务的时候修改运行配置文件,在VM options中填入上述参数。 46 | 47 | 4. 启动IDEA,并选择打开(Open)或导入(Import) `build.gradle` 文件,使用默认的Gradle Wrapper设置。当这个步骤完成之后,你可以打开右边的Gradle面板,当导入完成后里面将会有所有的Gradle任务(Task)。 48 | 5. 运行 `setupDecompWorkspace` 这个任务(在 `forgegradle` 任务组中)。这应该会需要一点时间,并且会占用很大的内存。如果失败的话,在IDEA的Gradle设置窗口的 `Gradle VM options` 中添加 `-Xmx3G`,或者也可以修改你的全局Gradle配置。 49 | 6. 这个配置任务完成后,你需要运行 `genIntellijRuns` 这个任务,它将会配置工程的运行/调试对象。 50 | 7. 完成后,你需要点击**Gradle面板中的**蓝色刷新按钮(在工具栏上也有一个刷新按钮,但不是那个)。这将会重新同步IDEA工程与Gradle数据,保证所有的依赖项与配置都是最新的。 51 | 8. 最后,如果你用的是IDEA 2016或者更新版本,你需要修复类路径模块。进入 `Edit configurations`,在 `Minecraft Client` 和 `Minecraft Server` 中,将 `Use classpath of module` 指向类似于 `_main` 这样名字的模块。 52 | 53 | 如果所有步骤都没有问题,你现在应该可以从下拉列表中选择Minecraft的运行任务,接下来点击Run/Debug按钮来测试你的配置。 54 | 55 | 自定义你的mod信息 56 | --------------- 57 | 58 | 修改 `build.gradle` 文件从而自定义你的Mod的构建(文件名,版本或者是其它东西)。 59 | 60 | !!! important "重要" 61 | 62 | **不要**修改build.gradle文件里的 `buildscript {}` 部分,默认的代码对ForgeGradle的运行至关重要。 63 | 64 | 在 `apply project: forge` 和 `// EDITS GO BELOW HERE` 下面的几乎任何东西都可以被修改,许多东西都可以被删除并且自定义修改。 65 | 66 | 这里有一个站点来介绍Forge的 `build.gradle` 文件 - [ForgeGradle cookbook][] ([中文版](http://forgegradle-cn.readthedocs.org/zh/latest/))。 一旦你熟悉你mod的设置,你会发现那里很多有用的配方。 67 | 68 | [forgegradle cookbook]: https://forgegradle.readthedocs.org/en/latest/cookbook/ "The ForgeGradle cookbook" 69 | 70 | ### 简单的 `build.gradle` 自定义设置 71 | 72 | 这些自定义设置是非常推荐所有工程都应用的。 73 | 74 | * 改变构建文件的文件名 - 修改 `archivesBaseName` 的值 75 | * 改变你的“Maven坐标” - 修改 `group` 的值 76 | * 改变版本号 - 修改 `version` 的值 77 | 78 | 构建并测试你的mod 79 | --------------- 80 | 81 | 1. 如果你想构建你的mod,运行 `gradlew build`。这将会输出一个文件到 `build/libs` 目录,它的名字是 `[archivesBaseName]-[version].jar`。这个文件可以放到一个装有Forge的Minecraft的 `mods` 文件夹,并且可以发布出去。 82 | 2. 如果你想测试你的mod,最简单的方法是使用在配置工程时生成的运行配置。或者也可以运行 `gradlew runClient`。这将会从 `` 位置启动Minecraft,以及你的mod代码。当然,这个指令也有不同的自定义设置。请在 [ForgeGradle cookbook][]里面找更多的信息。 83 | 3. 你也可以通过运行配置启动一个专门的服务器,或者使用 `gradlew runServer` 指令。这将会启动一个带有GUI的Minecraft服务器。。 84 | 85 | 86 | !!! note "提示" 87 | 88 | 如果你想让你的mod运行在服务器上,我们始终建议您在专门的服务器环境下测试您的mod。 89 | 90 | [files]: http://files.minecraftforge.net "Forge文件发布站" 91 | [home directory]: https://en.wikipedia.org/wiki/Home_directory#Default_home_directory_per_operating_system "不同系统中默认的用户Home目录位置" 92 | -------------------------------------------------------------------------------- /docs/gettingstarted/structuring.md: -------------------------------------------------------------------------------- 1 | Mod的结构 2 | ========= 3 | 4 | 我们将在这一节学习如何将你的mod整理至不同的文件,以及这些文件能干什么。 5 | 6 | 包 7 | ------------- 8 | 9 | 选择一个独一无二的包(Package)的名字。如果你拥有一个与你的工程关联的URL,你可以把它当做你顶级包(Top-level package)。比如说你拥有一个网站 "example.com" ,你可以将 `com.example` 当做你的顶级包。 10 | 11 | !!! important "重要" 12 | 13 | 如果你没有一个域名,不要用它当做你的顶级包的名字。用任何东西命名你的包都是可以接受的,比如说你的名字/昵称,或者是mod的名字。 14 | 15 | 在顶级包之后(如果你有一个的话)你需要为你的Mod添加一个唯一的名字,比如说 `examplemod`。在这种情况下这个包将会最终是 `com.example.examplemod`。 16 | 17 | `mcmod.info` 文件 18 | ---------------- 19 | 20 | 这个文件定义了你的mod的元数据(Metadata)。玩家可以从游戏主菜单的Mods按钮内看到这些信息。一个info文件可以描述多个Mod的信息。当一个Mod使用 `@Mod` 注解(Annotation)的时候,它可以定义一个 `useMetadata` 属性,默认是为 `false` 的。当 `useMetadata` 为 `true` 的时候,`mcmod.info` 文件中的元数据将会覆盖注解中所定义的信息。 21 | 22 | `mcmod.info` 文件为JSON格式,它的根元素为一系列的对象,每个对象描述一个modid。这个文件应该储存在 `src/main/resources/mcmod.info`。一个简单的描述单mod的 `mcmod.info` 可能会像这样子: 23 | 24 | ```json 25 | [{ 26 | "modid": "examplemod", 27 | "name": "Example Mod", 28 | "description": "Lets you craft dirt into diamonds. This is a traditional mod that has existed for eons. It is ancient. The holy Notch created it. Jeb rainbowfied it. Dinnerbone made it upside down. Etc.", 29 | "version": "1.0.0.0", 30 | "mcversion": "1.10.2", 31 | "logoFile": "assets/examplemod/textures/logo.png", 32 | "url": "minecraftforge.net/", 33 | "updateJSON": "minecraftforge.net/versions.json", 34 | "authorList": ["Author"], 35 | "credits": "I'd like to thank my mother and father." 36 | }] 37 | ``` 38 | 39 | 默认的Gradle配置将会把 `${version}` 替换为工程的版本,并把 `${mcversion}` 替换为Minecraft版本,但替换**仅**会发生在 `mcmod.info` 文件中,所以你应该使用这些替代字符串而不是明文写出来。下面这个表格列出了所有可能的属性,其中 `必填` 表示没有默认值可用,如果留空的话则会造成错误。除了必填的属性,你至少还应该定义 `description`,`version`,`mcversion`,`url` 和 `authorList`。 40 | 41 | | 属性 | 类型 | 默认值 | 简介 | 42 | |-------------:|:--------:|:--------:|:------------| 43 | | modid | string | 必填 | 这篇介绍所链接的modid。如果这个Mod没有加载,那么这篇介绍将会被忽略 | 44 | | name | string | 必填 | Mod显示的名字 | 45 | | description | string | `""` | 1-2段话的Mod简介 | 46 | | version | string | `""` | Mod的版本 | 47 | | mcversion | string | `""` | Minecraft的版本 | 48 | | url | string | `""` | Mod主页的链接 | 49 | | updateUrl | string | `""` | 有定义但没有实际用处。现在被updateJSON取代了 | 50 | | updateJSON | string | `""` | [version JSON](autoupdate#forge-update-checker)的地址 | 51 | | authorList | [string] | `[]` | Mod作者的列表 | 52 | | credits | string | `""` | 可以包含任何你想感谢的东西 | 53 | | logoFile | string | `""` | Mod Logo的地址。它会根据classpath来解析,所以你可以把它放在任何不会造成命名冲突的位置,比如说你的assets文件夹中 | 54 | | screenshots | [string] | `[]` | 在信息页面会显示的图片,现在还没有实现 | 55 | | parent | string | `""` | 父Mod的modid,如果可用的话。使用这个可以让别的Mod的模块在信息页面列在这个Mod的下面,比如说BuildCraft中就有用到 | 56 | | useDependencyInformation | boolean | `false` | 如果设置为true并且 `Mod.useMetadata` 也设置为true,Mod将会使用下面这3个依赖列表。如果不是true的话下面3个值则不会生效 | 57 | | requiredMods | [string] | `[]` | 一个modid列表。如果缺少一个,游戏将会弹出提示并停止加载。**这并不会影响到Mod加载顺序!**如果想要同时注明加载顺序和依赖,请在 `dependencies` 中添加相同的条目 | 58 | | dependencies | [string] | `[]` | 一个modid列表。所有列出的Mod将会在当前Mod**加载前**加载。如果这一项为空,则什么都不会发生。 | 59 | | dependants | [string] | `[]` | 一个modid列表。所有列出的Mod将会在当前Mod**加载后**加载。如果这一项为空,则什么都不会发生。 | 60 | 61 | [BuildCraft](http://gist.github.com/anonymous/05ad9a1e0220bbdc25caed89ef0a22d2) 中有很好的例子,它使用了上述的很多属性。 62 | 63 | Mod文件 64 | ------ 65 | 66 | 通常情况下,我们将会首先创建一个以你的mod名字来命名的文件,将其放入你的包下面。这将作为你的mod的**入口点**(Entry Point),并且将会包含一些特殊的指示来标记它。 67 | 68 | 什么是 `@Mod`? 69 | ------------- 70 | 71 | 这是一个注解,它会告诉Forge Mod Loader(FML)这个类(Class)是一个Mod的入口点。它可以包含不同的关于这个mod的元数据(Metadata)。它也同样指示了这个类将会收到 `@EventHandler` 事件。 72 | 73 | 下面是`@Mod`的属性表 74 | 75 | | 属性 | 类型 | 默认值 | 简介 | 76 | |---------------------------------:|:------------------:|:--------------:|:------------| 77 | |modid |String|必填| 指定mod的唯一标识符。 它必须是小写的,并且将被截断为64个字符的长度。 | 78 | |name |String|`""`| 指定mod的对用户友好的名称。 | 79 | |version |String|`""`| 指定mod的版本. 它只能是被点分隔开的数字, 应该符合 [Semantic Versioning](https://semver.org/)标准。 即使`useMetadata` 设为 `true`, 也建议在这里设置版本 | 80 | |dependencies |String | `""` | 指定该mod的依赖. Forge `@Mod`的 javadoc中描述了该规范

依赖关系字符串可以有以下四个前缀: `"before"`, `"after"`, `"required-before"`, `"required-after"`; 然后加上`":"` 和 `modid`.

你可以用`“@”`设置版本范围,可以为mod指定版本范围。[\*](#version-ranges)

如果一个"必选"的mod缺失,或者一个mod的版本不在版本范围之内,游戏将无法启动,并且会显示一个错误界面告诉用户需要哪个版本。

| 81 | |useMetadata | boolean | `false` | 如果为`true`, `@Mod`的参数会被`mcmod.info`覆盖。 | 82 | | clientSideOnly
serverSideOnly | boolean
boolean | `false`
`false` | 如果其中任意一个为 `true`,jar包会在另一端被跳过,不会被加载. 如果都为`true`,游戏会崩溃。 | 83 | | acceptedMinecraftVersions | String | `""` | 指定可适用的minecraft的版本范围.[\*](#version-ranges) 空字符串表示适用所有minecraft版本。 | 84 | | acceptableRemoteVersions | String | `""` | 指定此mod允许的服务器版本范围[*](#version-ranges). `""` 匹配当前版本, `"*"` 匹配所有版本. 注意: `"*"` 即使服务器上根本不存在该mod,也会匹配. | 85 | | acceptableSaveVersions | String | `""` | 指定兼容的保存版本信息的版本范围.[\*](#version-ranges)如果您遵循不寻常的版本约定,请改用`SaveInspectionHandler`。 | 86 | | certificateFingerprint | String | `""` | 参见教程 [Jar签名](../concepts/jarsigning.md). | 87 | | modLanguage | String | `"java"` | 指定该mod所用的编程语言. 可以是 `"java"` 或 `"scala"`. | 88 | | modLanguageAdapter | String | `""` | 指定mod的语言适配器的路径. 该类必须具有默认构造函数,并且必须实现`ILanguageAdapter`. 否则,forge会崩溃.如果设置了该属性, 将会覆盖`modLanguage`属性. | 89 | | canBeDeactivated | boolean | `false` | 这不会生效,但如果mod可以被停用(如小地图mod),可以将其设为`true`,那么该mod将会[收到](../events/intro.md#creating-an-event-handler)`FMLDeactivationEvent`来执行清理任务。 | 90 | | guiFactory | String | `""` | 指定该mod的GUI factory的路径(如果存在). GUI factory用于制作自定义配置界面, 必须实现`IModGuiFactory`. 例如参考 `FMLConfigGuiFactory`. | 91 | | updateJSON | String | `""` | 指定更新的JSON文件发URL. 参考[Forge更新检查器](autoupdate.md) | 92 | 93 | \* 所有版本的取值范围可以在 [Maven Version Range Specification](https://maven.apache.org/enforcer/enforcer-rules/versionRanges.html)查看. 94 | 95 | 你可以在 [Forge src download](http://files.minecraftforge.net/)找到一个示例mod。 96 | 97 | 使用子包保持代码整洁 98 | ------------------ 99 | 100 | 我们推荐您分解你的mod到不同的子包,而不是堆在单独一个类和包里面。 101 | 102 | 我们通常把包分成 `common` 和 `client` 两部分,分别对应运行在服务器/客户端(`common`)与客户端(`client`)的代码。在 `common` 包里面应该放物品、方块、和Tile Entity(可以每个都再分到一个子包里面)。而GUI和渲染相关代码应该放在 `client` 包里面。 103 | 104 | !!! note "提示" 105 | 106 | 这种分包风格只是一种建议,但这是非常常用的一种风格。你也可以自由地选择你自己的分包风格。 107 | 108 | 如果你将代码干净地分在子包中,你可以更快地发展你的mod。 109 | 110 | 命名风格 111 | ------- 112 | 113 | 一个通用的命名风格能让你更容易知道一个类是干什么用的,并且能方便你的合作者来寻找东西。 114 | 115 | 例如: 116 | 117 | * 一个叫做 `PowerRing` 的 `Item`(物品)应该放在 `item` 包下,并且类名为 `ItemPowerRing` 118 | * 一个叫做 `NotDirt` 的 `Block`(方块)应该放在 `block` 包下,类名为 `BlockNotDirt` 119 | * 最后,一个叫做 `SuperChewer` 的方块的 `TileEntity` 应该放在 `tile` 或者是 `tileentity`包下,类名为 `TileSuperChewer` 120 | 121 | 在你的类名前面加上这个对象所属的**种类**可以让人更容易弄清楚这个类是什么,或者通过一个对象来猜测类的名字。 -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | MinecraftForge文档 2 | ================== 3 | 4 | 这里是MinecraftForge的官方文档的中文翻译。[MinecraftForge]为Minecraft的Modding API。 5 | 6 | 这个文档 _仅仅_ 是Forge的教程,**而不是Java教程**。如果你不会Java,请先学习之后再来看这个文档。 7 | 8 | 可以在[GitHub]上投稿. 9 | 10 | !!! note "译者注" 11 | 翻译也许并不能准确传达原文意思,如果觉得哪里有困惑,可以去[readthedocs]参考原文。 12 | 13 | 本文档会在原版文档更新之后及时更新,中文翻译最后更新时间:5/4/2019 00:15 AM CST 14 | 15 | mod开发入门路线可以参照[Mouse0w0/MinecraftDeveloperGuide] 16 | 17 | [MinecraftForge]: http://minecraftforge.net 18 | [GitHub]: https://github.com/MinecraftForge/Documentation 19 | [readthedocs]: https://mcforge.readthedocs.io/ 20 | [Mouse0w0/MinecraftDeveloperGuide]: https://github.com/Mouse0w0/MinecraftDeveloperGuide -------------------------------------------------------------------------------- /docs/items/items.md: -------------------------------------------------------------------------------- 1 | 物品 2 | ==== 3 | 4 | 和方块一样,物品也是大部分Mod的关键组成部分之一。方块组成你身边的世界,而物品则能够让你改变世界。 5 | 6 | 创建物品 7 | ------- 8 | 9 | ### 简单物品 10 | 11 | 没有特殊功能的简单物品(比如木棍或者糖)不需要自定义的类。你可以直接实例化一个 `Item`,再调用Setter去设置物品一些简单的属性。 12 | 13 | | 方法 | 描述 | 14 | |:----------------------:|:----------------------------------------------| 15 | | `setCreativeTab` | 设置物品所在的创造标签页。如果你想让这个物品在创造模式菜单中显示,这个方法必须要调用。原版的标签页能在 `CreativeTabs` 类中找到 | 16 | | `setMaxDamage` | 设置物品的最大损害值(Damage Value)。如果这个值大于 `0`,将会添加2个物品属性 "damaged" 和 "damage" | 17 | | `setMaxStackSize` | 设置最大的堆(Stack)大小 | 18 | | `setNoRepair` | 让这个物品无法修复,即使它是可损害的(Damagable) | 19 | | `setUnlocalizedName` | 设置物品的未本地化名,有 "item." 前缀 | 20 | | `setHarvestLevel` | 添加或者移除一对挖掘(Harvest)类别(`"shovel"`,`"axe"`)和挖掘等级。这个方法是不可链(Not Chainable)的 | 21 | 22 | 除非特殊说明,上面的方法都是可链的(Chainable),也就是说它们会 `return this` 从而让你能够连续调用它们。 23 | 24 | ### 高级物品 25 | 26 | 上面设置属性的方法只能够用于简单的物品。如果你希望有一个更复杂的物品,你应该创建一个 `Item` 的子类并重写它的方法。 27 | 28 | 注册物品 29 | ------- 30 | 31 | 物品必须要[注册][registering]到函数中。 32 | 33 | 为物品着色 34 | --------- 35 | 36 | 物体的纹理是可以在程序中着色的。许多原版的物品都用到了这一功能。比如说:皮革帽子、生成蛋、药水等。 37 | 38 | ### 物品颜色处理器 39 | 40 | 物体颜色处理器是对一个物体着色所必须的东西,它是`IItemColor`的一个实例,它添加了以下方法: 41 | 42 | ```java 43 | int getColorFromItemstack( 44 | ItemStack stack, 45 | int tintIndex) 46 | ``` 47 | 48 | #### 返回值 49 | 50 | 这个方法返回一个十六进制值(Hex),用整数表示一个颜色。 51 | 52 | #### 参数 53 | 54 | 色调指数(Tint Index)是定义在物品模型JSON文件中的一个元素的面中的。没有色调指数的面是不会被着色的,所以它的颜色处理器将不会被调用。当物品的模型是从`builtin/generted`继承而来时,层指数会被用作色调指数。 55 | 56 | ### 注册物体颜色处理器 57 | 58 | 物体颜色处理器必须要通过调用`ItemColors#registerItemColorHandler(IItemColor, Item...)`函数来注册。`ItemColors`的实例可以通过调用`Minecraft#getItemColors()`来获取。 59 | 60 | 这必须要在初始化阶段中,仅在客户端调用。 61 | 62 | [registering]: ../concepts/registries.md#_2 -------------------------------------------------------------------------------- /docs/items/loot_tables.md: -------------------------------------------------------------------------------- 1 | 战利品表 2 | ======= 3 | 4 | 战利品表(Loot Table)可以非常容易地以指定的随机分布生成战利品。它在原版中用于生成随机的宝箱战利品以及生物掉落。 5 | 6 | 原版的[Wiki][wiki]非常详细地描述了战利品表的JSON格式,所以这篇文章将会着重于在Mod中操作战利品表的代码。如果想要完全理解这篇文章,请在阅读本文之前先仔细阅读之前提到的[Wiki页][wiki]。 7 | 8 | 注册一个Mod战利品表 9 | ------------------ 10 | 11 | 如果想要让Minecraft加载你的战利品表,可以调用 `LootTableList.register(new ResourceLocation("modid", "loot_table_name"))`,这将会解析并加载 `/assets/modid/loot_tables/loot_table_name.json`。你可以在preInit、init和postInit三个阶段中任意一个阶段调用这个方法。你也可以将战利品表整理到多个文件夹内。 12 | 13 | !!! note "提示" 14 | 15 | 战利品表中的战利品随机池(Loot Pool)必须要附加一个 `name` 标签从而能够在表中唯一识别出这个随机池。通常情况下是使用这个随机池包含的物品种类来命名的。 16 | 如果你对多个条目(Entry)使用了同一个 `name` 标签(比如同一个物品但施加不同的函数(Function),那么你必须要给每一个条目一个 `entryName` 标签,以唯一识别出随机池中的条目。对于那些没有冲突的 `name` 标签,`entryName` 将会自动设置为 `name` 的值。 17 | 这些附加的要求都是由Forge实施的,用于在加载的时候使用 `LootTableLoadEvent` 辅助战利品表的修改(见下)。 18 | 19 | 注册自定义对象 20 | ------------- 21 | 22 | 除了原版提供的那些,你也可以自己注册你自己的战利品条件(Condition)、战利品函数以及实体属性(Entity Properties)。 23 | 24 | 实体属性仅仅用于 `minecraft:entity_properties` 这个战利品条件,它可以用来检测参与掠夺(Looting)的实体(被掠夺的实体或者是杀手(Killer))是否拥有特定的属性。原版中唯一的属性是 `minecraft:on_fire`。 25 | 26 | 这三个条目的注册非常相似,只需要分别调用 `LootConditionManager.registerCondition`,`LootFunctionManager.registerFunction()` 或 `EntityPropertyManager.registerProperty()` 就可以了。 27 | 28 | 这些方法都需要传入一个 `Serializer` 的实例,构建它需要对象的ID(`ResourceLocation`)以及代码中实现这些行为的 `Class` —— `LootCondition`,`LootFunction` 或 `EntityProperty`。 29 | 30 | 由于你注册了JSON的序列化器(Serializer)以及反序列化器(Deserializer),你可以添加自定义的字段到条件、函数和属性中。见原版 `net.minecraft.world.storage.loot.{conditions, functions, properties}` 包中的实现。 31 | 32 | 接下来,为了能够使用你的条件、函数、和条件,你需要将它的注册表名传入 `Serializer` 的构造器。下面是一个战利品条目的例子: 33 | 34 | ```javascript 35 | { 36 | "type": "item", 37 | "name": "mymod:myitem", 38 | "conditions": [ 39 | { 40 | "condition": "mymod:mycondition", 41 | "foo": 1, // 可以添加自定义的参数到反序列化器中 42 | }, 43 | { 44 | "condition": "minecraft:entity_properties", 45 | "entity": "this", 46 | "properties": { 47 | "mymod:my_property": { // 右边的结构完全取决于反序列化器 48 | "bar": 2 49 | } 50 | } 51 | } 52 | ], 53 | "functions": [ 54 | { 55 | "function": "mymod:myfunction", 56 | "foobar": 3 // 可以添加自定义的参数到反序列化器中 57 | } 58 | ] 59 | } 60 | ``` 61 | 62 | 修改原版的战利品 63 | --------------- 64 | 65 | ### 综述 66 | 67 | 你不仅可以添加你自己的战利品表、条件、函数和实体属性,你也可以在加载的时候修改这些项目。 68 | 69 | !!! note "提示" 70 | 71 | 原版允许用户在世界的存档目录中添加自己的战利品表来覆盖游戏(以及Mod)自己的表。这些都被视为是配置(Config)文件,并且**设计上**不能够被以下的方法所修改。 72 | 73 | 运行时修改战利品表的入口点是 `LootTableLoadEvent`,它会在每个表加载的时候触发。在这里,你可以通过名称查询以及移除随机池,或者添加一个 `Loot Pool` 的实例。这就是为什么Mod中的战利品随机池必须拥有一个名字。 74 | 75 | 你可能会在想,我们如何才能修改原版的战利品表呢,他们并没有名字。Forge通过对所有的原版战利品表生成名字来解决这个问题。第一个随机池被命名为 `main`,因为大部分的表只有一个随机池。之后的随机池会根据位置命名:第二个随机池是 `pool1`,第三个是 `pool2`,以此类推。删除一个随机池并不会将名字移到别的随机池中。 76 | 77 | 在每一个 `LootPool` 中,你可以修改随机池的Roll以及Bonus Roll(战利品表将会调用这个随机池多少次),以及通过名字查询和删除条目,或者添加 `LootEntry` 的名字。 78 | 79 | 和随机池一样,条目也需要有唯一的名字供获取和移除使用。Forge通过增加一个隐藏的 `entryName` 字段到所有的战利品条目中来解决这个问题。如果条目的 `name` 字段在随机池中是唯一的,那么 `entryName` 将会自动设置为 `name`。如果不是的话,则需要手动在Mod的战利品表条目中添加一个,原版的条目则会自动生成。对于每一次的重复,数字会递增。比如说,如果原版中有三个条目,每个都是 `name: "minecraft:stick"`,那么生成的三个 `entryName` 标签则会是 `minecraft:stick`,`minecraft:stick#0` 和 `minecraft:stick#1`。同样,删除一个条目并不会将名字移到别的条目中。 80 | 81 | !!! note "提示" 82 | 83 | 你必须要在战利品表的 `LootTableLoadEvent` 中完成所有对该战利品表所需的改动,之后的任何改动将会被安全检查禁止,如果安全检查被跳过的话则会造成未定义的行为。 84 | 85 | ### 添加地牢战利品 86 | 87 | 接下来是修改原版战利品的一个非常常见的案例:添加地牢的物品生成、 88 | 89 | 首先需要监听要修改的战利品表的事件: 90 | 91 | ```Java 92 | @SubscribeEvent 93 | public void lootLoad(LootTableLoadEvent evt) { 94 | if (evt.getName().toString().equals("minecraft:chests/simple_dungeon")) { 95 | // 使用 evt.getTable() 来获取战利品表 96 | } 97 | } 98 | ``` 99 | 100 | 在这里,我们添加到的是潜在的生成,但我们不想干涉已有随机池的权重。解决这个问题最灵活也是最简单的方法是添加另一个随机池,其中只有一个战利品条目,引用你自己的战利品表,战利品条目可以递归提取自完全不同的战利品表。 101 | 102 | 比如说,你的Mod可能会包含一个 `/assets/mymod/loot_tables/inject/simple_dungeon.json`: 103 | 104 | ```javascript 105 | { 106 | "pools": [ 107 | { 108 | "name": "main", 109 | "rolls": 1, 110 | "entries": [ 111 | { 112 | "type": "item", 113 | "name": "minecraft:nether_star", 114 | "weight": 40 115 | }, 116 | { 117 | "type": "empty", 118 | "weight": 60 119 | } 120 | ] 121 | } 122 | ] 123 | } 124 | ``` 125 | 126 | !!! note "提示" 127 | 128 | 你仍然需要通过 `LootTableList.register()` 来注册这个表。 129 | 130 | 这样子战利品条目和随机池就被创建并添加了,地牢箱子将会有一个新的随机池,60%几率什么都不产生,40%的几率产生一个下界之星。 131 | 132 | ```Java 133 | LootEntry entry = new LootEntryTable(new ResourceLocation("mymod:inject/simple_dungeon"), , , , ); // weight 在这里不重要,因为随机池里只有一个条目。其它的参数按你的喜好来设置。 134 | 135 | LootPool pool = new LootPool(new LootEntry[] {entry}, , , , ); // 其它的参数按你的喜好来设置。 136 | 137 | evt.getTable().addPool(pool); 138 | ``` 139 | 140 | 当然,如果你添加的战利品不能在这之前决定,你也可以利用类似上面的调用自己构造并添加 `LootPool` 和 `LootEntry` 的实现到事件处理器中。 141 | 142 | 这种方法实际的例子可以在Botania中找到。事件管理器能在[这里](https://github.com/Vazkii/Botania/blob/e38556d265fcf43273c99ea1299a35400bf0c405/src/main/java/vazkii/botania/common/core/loot/LootHandler.java)找到,注入的战利品表在[这里](https://github.com/Vazkii/Botania/tree/e38556d265fcf43273c99ea1299a35400bf0c405/src/main/resources/assets/botania/loot_tables/inject)。 143 | 144 | 改变生物掉落 145 | ----------- 146 | 147 | `EntityLiving` 的子类自动支持死亡时从战利品表中提取战利品。你可以复写 `getLootTable`,将返回值改为想要的战利品表的 `ResourceLocation`。它将是这个生物的默认战利品表,通过设置 `deathLootTable` 的值,不论是你的Mod还是别人的Mod中的生物,你都可以对单独一个实体复写它的战利品表。 148 | 149 | 在代码中生成战利品 150 | ---------------- 151 | 152 | 偶尔你也可能会想在自己的代码中从战利品表生成 `ItemStack`。 153 | 154 | 首先,你需要获取战利品表本身(你需要能获取到一个 `World`): 155 | 156 | ```Java 157 | LootTable table = this.world.getLootTableManager().getLootTableFromLocation(new ResourceLocation("mymod:my_table")); // 解析至 /assets/mymod/loot_tables/my_table.json 158 | ``` 159 | 160 | 接下来使用提供的 `LootContextBuilder` 创建一个 `LootContext`,它封装了掠夺的上下文(Context),比如杀手,掠夺者的幸运(Luck)、以及伤害源。 161 | 162 | ```Java 163 | LootContext ctx = new LootContext.Builder(world) 164 | .withLuck(...) // 调整幸运值,通常是 EntityPlayer.getLuck() 165 | .withLootedEntity(...) // 被掠夺的实体 166 | .withPlayer(...) // 将玩家设置为掠夺者 167 | .withDamageSource(...) // 打击(Killing Blow)和非玩家杀手 168 | .build(); 169 | ``` 170 | 171 | 最后获取一个 `ItemStack` 的集合: 172 | 173 | ```Java 174 | List stacks = table.generateLootForPools(world.rand, ctx); 175 | ``` 176 | 177 | 或者填充物品栏(Inventory): 178 | 179 | ```Java 180 | table.fillInventory(iinventory, world.rand, ctx); 181 | ``` 182 | 183 | !!! note "提示" 184 | 185 | 目前只能使用 `IInventory` 186 | 187 | [wiki]: https://minecraft-zh.gamepedia.com/%E6%88%98%E5%88%A9%E5%93%81%E8%A1%A8 -------------------------------------------------------------------------------- /docs/models/advanced/extended-blockstates.md: -------------------------------------------------------------------------------- 1 | Extended Blockstates 2 | ==================== 3 | 4 | Extended blockstates provide a way for blocks to pass arbitrary data to their models. Ordinary blockstates and properties occupy only a fixed set of possible states, while extended states can form infinite sets. This accomplished through the use of unlisted properties (`IUnlistedProperty`), extended block states (`IExtendedBlockState`), and extended block state containers (`ExtendedBlockState`). 5 | 6 | Ordinary block state containers (`BlockStateContainer`) take a set of listed properties (`IProperty`) that define all possible values for themselves and use those to create all possible combinatons of values. These combinations are then turned into `IBlockState`s and stored in the finite set of all possible states. The properties are called "listed" as they appear on the F3 debug screen, all the way to the right. 7 | 8 | Extended block state containers take a set of listed properties, and also a set of *unlisted* properties. Unlisted properties' values can be anything that matches their type, and are not limited to a finite set. However, values are still required to satisfy the predicate `IUnlistedProperty::isValid`. The listed properties are again used to create the set of states, this time `IExtendedBlockState`s, but the unlisted properties remain unset. The unlisted properties are only set when `IExtendedBlockState::withProperty` is called to do so. 9 | 10 | Most of the methods on `IExtendedBlockState` are self-explanatory, maybe with the exception of `getClean`. `getClean` returns the base `IBlockState` with none of the unlisted properties set. 11 | 12 | Why Extended Blockstates 13 | ------------------------ 14 | 15 | Why use extended blockstates at all? Why not simply pass an `IBlockAccess` and `BlockPos` into the rendering system directly and have the [`IBakedModel`][IBakedModel] itself deal with it? Indeed, extended blockstates allow this to happen anyway! Why have the system at all when we could just do that? The reason is that it makes the system more flexible. External code can take an `IExtendedBlockState` and fill in some data by itself, overriding the block's own data. This allows that code to alter the model in ways that would be impossible if it was just a black box that took a world and a position and spat out some geometry. 16 | 17 | One such use case is advanced camouflaging blocks. The camouflaging block may have a tile entity that holds the `IBlockState` representative of the camouflagee, and an unlisted property to hold that state during rendering. This property can then be filled by `getExtendedState`. Finally, a custom [`IBakedModel`][IBakedModel] can steal the model for that state and use it instead of using the uncamouflaged model. 18 | 19 | 20 | Declaring Unlisted Properties 21 | ------------------------------------ 22 | 23 | Unlisted properties are declared in `Block.createBlockState`, the same place as regular ("listed") properties. Instead of returning a `BlockStateContainer`, one must return an `ExtendedBlockState`. Forge provides a builder `BlockStateContainer.Builder`, which will automatically handle returning an `ExtendedBlockState` for you. 24 | 25 | Example: 26 | ```Java 27 | @Override 28 | public BlockStateContainer createBlockState() { 29 | return new BlockStateContainer.Builder(this).add(LISTED_PROP).add(UNLISTED_PROP).build(); 30 | } 31 | ``` 32 | 33 | Note that you do not need to set default values for your unlisted properties. 34 | 35 | 36 | Filling Extended States 37 | ---------------------------- 38 | 39 | Before an `IBlockState` is passed to an `IBakedModel`, it will always have `Block.getExtendedState` called on it first. In this method, you will give all your unlisted properties values. Assuming you registered at least one unlisted property in the previous section, the `IBlockState` parameter can be safely casted to `IExtendedBlockState`, which has a `withProperty` method for unlisted properties analogous to its listed property cousin. Here, you can query whatever you want from the World, the Tile Entity, etc. (with appropriate safety checks, of course) and insert it into the extended blockstate. 40 | 41 | !!! warning "警告" 42 | It is highly recommended that your unlisted property values be immutable. Baked model implementations will use the extended state and unlisted values on multiple threads, so any value must be used in a threadsafe manner. The easiest way is to simply make that information an immutable snapshot. Anything you might possibly want to know in your custom `IBakedModel`, you should be passing an immutable snapshot of through `Block.getExtendedState`. 43 | 44 | Example: 45 | ```Java 46 | @Override 47 | public IExtendedBlockState getExtendedState(IBlockState state, IBlockAccess world, BlockPos pos) { 48 | IExtendedBlockState ext = (IExtendedBlockState) state; 49 | TileEntity te = world.getTileEntity(pos); 50 | if (te instanceof MyTE) { 51 | ext = ext.withProperty(UNLISTED_PROP, ((MyTE) te).getSomeImmutableData()); 52 | } 53 | return ext; 54 | } 55 | ``` 56 | 57 | Using Extended States 58 | -------------------------- 59 | 60 | In a custom `IBakedModel`, the `IBlockState` parameter passed to you will be exactly the object you returned in `Block.getExtendedState`, and you can pull data out of it and use it to affect your rendering as you wish. 61 | 62 | Here is a basic example of using an unlisted property to determine which model to render, assuming `UNLISTED_PROP` is an `IUnlistedProperty`: 63 | ```Java 64 | // in custom IBakedModel 65 | private final Map submodels = new HashMap<>(); // populated in a custom manner out of the scope of this article 66 | 67 | @Override 68 | public List getQuads(@Nullable IBlockState state, EnumFacing facing, long rand) { 69 | IBakedModel fallback = Minecraft.getMinecraft().getBlockRendererDispatcher().getBlockModelShapes().getModelManager().getMissingModel(); 70 | if (state == null) 71 | return fallback.getQuads(state, facing, rand); 72 | 73 | IExtendedBlockState ex = (IExtendedBlockState) state; 74 | String id = ex.getValue(UNLISTED_PROP); 75 | return submodels.getOrDefault(id, fallback).getQuads(state, facing, rand); 76 | } 77 | ``` 78 | 79 | Since there are no longer a fixed set of `IExtendedBlockState` generated at startup, comparisons involving an extended state must use `getClean()`, which is a method on `IExtendedBlockState` that returns the vanilla state (i.e. the fixed set of `IBlockState` with only the listed properties). 80 | ```Java 81 | IExtendedBlockState state = ...; 82 | IBlockState worldState = world.getBlockState(pos); 83 | if(state.getClean() == worldState) { 84 | ... 85 | } 86 | ``` 87 | 88 | [IBakedModel]: ibakedmodel.md 89 | -------------------------------------------------------------------------------- /docs/models/advanced/ibakedmodel.md: -------------------------------------------------------------------------------- 1 | `IBakedModel` 2 | ============= 3 | 4 | `IBakedModel` is the result of calling [`IModel::bake`][IModel::bake]. Unlike `IModel`, which purely represents a shape without any concept of items or blocks, `IBakedModel` is not as abstract; it represents geometry that has been optimized and reduced to a form where it is (almost) ready to go to the GPU. It can also process the state of an item or block to change the model. 5 | 6 | In a majority of cases, it is not really necessary to implement this interface manually. One can instead use one of the existing implementations. 7 | 8 | ### `getOverrides` 9 | 10 | Returns the [`ItemOverrideList`][ItemOverrideList] to use for this model. This is only used if this model is being rendered as an item. 11 | 12 | ### `isAmbientOcclusion` 13 | 14 | If the model is being rendered as a block in the world, the block in question does not emit any light, and ambient occlusion is enabled, this causes the model to be rendered with ambient occlusion. 15 | 16 | ### `isGui3D` 17 | 18 | If the model is being rendered as an item in an inventory, on the ground as an entity, on an item frame, etc. this makes it look "flat." In GUIs this also disables the lighting. 19 | 20 | ### `isBuiltInRenderer` 21 | 22 | !!! important "重要" 23 | Unless you know what you're doing and are OK with using deprecated features, just `return false` from this and continue on. 24 | 25 | When rendering this as an item, returning `true` causes the model to not be rendered, instead falling back to `TileEntityItemStackRenderer::renderItem`. For certain vanilla items such as chests and banners, this method is hardcoded to copy data from the item into a `TileEntity`, before using a `TileEntitySpecialRenderer` to render that TE in place of the item. For all other items, it will use the `TileEntityItemStackRenderer` instance provided by `Item#setTileEntityItemStackRenderer`; refer to [TileEntityItemStackRenderer][teisr] page for more information. 26 | 27 | ### `getParticleTexture` 28 | 29 | Whatever texture should be used for the particles. For blocks, this shows when an entity falls on it, when it breaks, etc. For items, this shows when it breaks or when it's eaten. 30 | 31 | ### `getItemCameraTransforms` 32 | 33 | Deprecated in favor of implementing `handlePerspective`. The default implementation is fine if `handlePerspective` is implmented. See [Perspective][]. 34 | 35 | ### `handlePerspective` 36 | 37 | See [Perspective][]. 38 | 39 | ### `getQuads` 40 | 41 | This is the main method of `IBakedModel`. It returns `BakedQuad`s, which contain the low-level vertex data that will be used to render the model. If the model is being rendered as a block, then the `IBlockState` passed in is non-null. Additionally, [`Block::getExtendedState`][extended blockstates] is called to create the passed `IBlockState`, which allows for arbitrary data to be passed from the block to the model. If the model is being rendered as an item, the `ItemOverrideList` returned from `getOverrides` is responsible for handling the state of the item, and the `IBlockState` parameter will be `null`. 42 | 43 | The `EnumFacing` passed in is used for face culling. If the block against the given side of the block being rendered is opaque, then the faces associated with that side are not rendered. If that parameter is `null`, all faces not associated with a side are returned (that will never be culled). 44 | 45 | Note that this method is called very often: once for every combination of non-culled face and supported block render layer (anywhere between 0 to 28 times) *per block in world*. This method should be as fast as possible, and should probably cache heavily. 46 | 47 | The `long` parameter is a random number. 48 | 49 | [IModel::bake]: imodel.md#bake 50 | [Perspective]: perspective.md 51 | [ItemOverrideList]: itemoverridelist.md 52 | [extended blockstates]: extended-blockstates.md 53 | [teisr]: ../../rendering/teisr.md 54 | -------------------------------------------------------------------------------- /docs/models/advanced/icustommodelloader.md: -------------------------------------------------------------------------------- 1 | `ICustomModelLoader` 2 | ==================== 3 | 4 | Recall that when a model is requested for a `ModelResourceLocation`, every `ICustomModelLoader` is queried to find the one that volunteers to load the model from the `ModelResourceLocation`. In order to actually use a custom implementation of [`IModel`][IModel], whether it be a full blown model format like OBJ models or a completely in-code model generator like `ModelDynBucket`, it must be done through an `ICustomModelLoader`. Even though it has "loader" in the name, there is no need for it to actually load anything; for in-code models like `ModelDynBucket`, the `ICustomModelLoader` will normally be a dummy that just instantiates the `IModel` without touching any files. 5 | 6 | If multiple `ICustomModelLoader`s attempt to load the same `ResourceLocation`, the game will crash with a `LoaderException`. Therefore, care must be taken to keep the namespace of an `ICustomModelLoader` safe from being infringed upon. The Forge OBJ and B3D loaders do so by requiring that the namespace of a `ResourceLocation` be registered to them beforehand, and they only match `ResourceLocation`s with the appropriate file extension. 7 | 8 | In order for an `ICustomModelLoader` to actually be used, it must be registered with `ModelLoaderRegistry.registerLoader`. 9 | 10 | ### `accepts` 11 | 12 | Tests whether this `ICustomModelLoader` is willing to load the given `ResourceLocation`. Preferably, this should be based on the `ResourceLocation` alone and not on the file contents. If two `ICustomModelLoader`s accept the same `ResourceLocation`, a `LoaderException` is thrown. Therefore, care should be taken to make sure that the namespace of the `ICustomModelLoader` is unique enough to avoid collisions. 13 | 14 | ### `loadModel` 15 | 16 | Get the model for the given `ResourceLocation`. Note that it doesn't need to "load" anything. For example, completely in-code models will simply instantiate the `IModel` class and totally ignore the file. 17 | 18 | ### `onResourceManagerReload` 19 | 20 | Called whenever the resource packs are (re)loaded. In this method, any caches the `ICustomModelLoader` keeps should be dumped. Following this, `loadModel` will be called again to reload all the `IModel`s, so if the *`IModel`s* kept some caches in themselves, they do not need to be cleared. 21 | 22 | [IModel]: imodel.md 23 | -------------------------------------------------------------------------------- /docs/models/advanced/imodel.md: -------------------------------------------------------------------------------- 1 | `IModel` 2 | ======== 3 | 4 | `IModel` is a type that represents a model in its raw state. This is how a model is represented right after it has been loaded. Usually this directly represents the source of the model (e.g. an object deserialized from JSON, or an OBJ container). 5 | 6 | At this high level, a model has no concept of items, blocks, or anything of that sort; it purely represents a shape. 7 | 8 | !!! important "重要" 9 | `IModel` is immutable. Methods such as `process` that alter the model should never mutate the `IModel`, as they should construct new `IModel`s instead. 10 | 11 | ### `getDependencies` 12 | 13 | This is a collection of the `ResourceLocation`s of all the models this model depends on. These models are guaranteed to be loaded before this one is baked. For example, a model deserialized from a blockstate JSON will depend on the models defined within. Only models that are directly mapped to a block/item are loaded normally; to ensure loading of other models, they must be declared as dependencies of another. Cyclic dependencies will cause a `LoaderException` to be thrown. 14 | 15 | ### `getTextures` 16 | 17 | This is a collection of the `ResourceLocation`s of all the textures this model depends on. These textures are guaranteed to be loaded before this model is baked. For example, a vanilla JSON model depends on all the textures defined within. 18 | 19 | ### `bake` 20 | 21 | This is the main method of `IModel`. It takes an [`IModelState`][IModelState], a `VertexFormat`, and a function `ResourceLocation` → `TextureAtlasSprite`, to return an [`IBakedModel`][IBakedModel]. `IBakedModel` is less abstract than `IModel`, and it is what interacts with blocks and items. The function `ResourceLocation → TextureAtlasSprite` is used to get textures from `ResourceLocation`s (i.e. the `ResourceLocation`s of textures are passed to this function and the returned `TextureAtlasSprite` contains the texture). 22 | 23 | ### `process` 24 | 25 | This method allows a model to process extra data from external sources. The Forge blockstate variant format provides a way to define this data in the resource pack. Within the Forge blockstate format, the property that is used to pass this data is called `custom`. First, an example: 26 | 27 | ```json 28 | { 29 | "forge_marker": 1, 30 | "defaults": { 31 | "custom": { 32 | "__comment": "These can be any JSON primitives with any names, but models should only use what they understand.", 33 | "meaningOfLife": 42, 34 | "showQuestion": false 35 | }, 36 | "model": "examplemod:life_meaning" 37 | }, 38 | "variants": { 39 | "dying": { 40 | "true": { 41 | "__comment": "Custom data is inherited. Therefore, here `meaningOfLife` is inherited but `showQuestion` is overriden. The model itself remains inherited.", 42 | "custom": { 43 | "showQuestion": true 44 | } 45 | }, 46 | "false": {} 47 | } 48 | } 49 | } 50 | ``` 51 | 52 | As seen above, custom data can be of any type. Additionally, it is inherited from the defaults into the variants. The custom data is passed in as an `ImmutableMap`. This is a map where the keys are the property names (in the above example, "meaningOfLife", "showQuestion", and "title"). Astute observers may notice that numeric and boolean data were defined in within the blockstate but this method only receives `String`s. This is because all data is converted into strings before being processed. If a model does not understand what a property means, it should just ignore it. 53 | 54 | ### `smoothLighting` 55 | 56 | In vanilla, smooth lighting enables ambient occlusion. This flag can be controlled by the `smooth_lighting` property in a Forge blockstate (which can appear wherever a `model` property can and is inherited). The default implementation does nothing. 57 | 58 | ### `gui3D` 59 | 60 | `gui3D` controls whether a model looks "flat" in certain positions (e.g. with `gui3d` set to `true`, `EntityItem` renders a stack with multiple items as several layers of the model. With `gui3d` set to `false`, the item is always one layer), and also controls lighting inside GUIs. This flag can be controlled by the `gui3d` property in a Forge blockstate. The default implementation does nothing. 61 | 62 | ### `retexture` 63 | 64 | This method is used to change the textures a model might use. This is similar to how texture variables in vanilla JSON models work. A model can start out with certain faces with certain textures, and then by setting/overriding texture variables these faces can be changed. An example: 65 | 66 | ```json 67 | { 68 | "forge_marker": 1, 69 | "defaults": { 70 | "textures": { 71 | "varA": "examplemod:items/hgttg", 72 | "varB": "examplemod:blocks/earth", 73 | "varC": "#varA", 74 | "varZ": null 75 | }, 76 | "model": "examplemod:universe" 77 | } 78 | } 79 | ``` 80 | 81 | In this example, the `textures` block will be deserialized as-is into an `ImmutableMap` with the exception that `null`s are turned into `""` (i.e. the final result is `"varA" → "examplemod:items/hgttg", "varB" → "examplemod:blocks/earth", "varC" → "#varA", "varZ" → ""`). Then, `retexture` is called to change the textures as needed. How this is done is up to the model. It may be advisable, however, to support resolving texture variables such as "#var" (like vanilla JSON models) instead of taking them literally. The default implementation does nothing. 82 | 83 | ### `uvlock` 84 | 85 | This method is used to toggle UV lock. UV lock means that when the model itself rotates, the textures applied to the model do not rotate with it. The default implementation does nothing. This can be controlled with the `uvlock` property in a Forge blockstate. An example: 86 | 87 | ```json 88 | { 89 | "forge_marker": 1, 90 | "defaults": { 91 | "model": "minecraft:half_slab", 92 | "textures": { 93 | "__comment": "Texture definitions here..." 94 | } 95 | }, 96 | "variants": { 97 | "a": [{ "__comment": "No change" }], 98 | "b": [{ 99 | "__comment": "This is like literally taking the slab and flipping it upside down. The 'side' texture on the side faces is cropped to the bottom half and rotated 180 degrees, just as if a real object were turned upside down.", 100 | "x": 180 101 | }], 102 | "c": [{ 103 | "__comment": "Now this is more interesting. The UV vertices are altered so that the texture won't rotate with the model, so that the side faces have the side texture rightside up and cropped to the top half.", 104 | "x": 180, 105 | "uvlock": true 106 | }] 107 | } 108 | } 109 | ``` 110 | 111 | [IModelState]: imodelstate+part.md 112 | [IBakedModel]: ibakedmodel.md 113 | -------------------------------------------------------------------------------- /docs/models/advanced/imodelstate+part.md: -------------------------------------------------------------------------------- 1 | `IModelState` and `IModelPart` 2 | ============================== 3 | 4 | `IModelState` and `IModelPart` are a way to transform models (or parts thereof). An `IModelPart` represents the part of the model being transformed. An `IModelState` represents a function `IModelPart` → `TRSRTransform`. By applying the `IModelState` to an `IModelPart`, we get a `TRSRTransform` representing how to transform that part. Note that passing `Optional.absent()` to `IModelState::apply` has a different meaning than usual. Doing so means getting the transform for the _entire_ model, instead of just a part of it. 5 | 6 | One of the uses of this is animation. An `IModelState` can represent a certain frame of the animation and supply transforms to turn the original model into the current frame. By supplying different `IModelState`s over time, an animation can be performed. For example, the B3D model format supports this kind of animation directly through its nodes; however, the animation system is still WIP. Another, more common use case is Forge blockstate JSONs. The models within the blockstates can be transformed with `transform` tags, which translate into simple `IModelState`s that are passed into the contained models. Finally, another use case is [perspective aware models][]. 7 | 8 | Which `IModelPart`s a certain model will use is dependent on the model itself. If I had a `B3DState` that dealt with B3D `NodeJoint`s and tried to use it on a vanilla JSON model, it wouldn't work as vanilla models have no idea what a `NodeJoint` is and will not even ask about them. 9 | 10 | [perspective aware models]: perspective.md 11 | -------------------------------------------------------------------------------- /docs/models/advanced/introduction.md: -------------------------------------------------------------------------------- 1 | 高级模型介绍 2 | ======================== 3 | 4 | 尽管简单的模型和方块状态很好,但他们不是动态的。例如,Forge的通用桶可以装各种mod添加的液体。它有动态的模型,基于基础桶模型和液体。它是怎么做的呢?让我们进入`IModel`。 5 | 6 | 为了理解这是如何工作的,让我们看看内部的模型系统。在本节中,您可能需要参考此内容来清楚地了解正在发生的事情。反过来也是如此。 你可能不会理解这里发生的一切,但是当你浏览这一部分时,你应该能够掌握越来越多的内容,直到一切都清楚。 7 | 8 | !!! important "重要" 9 | 10 | 如果你是第一次通读,请不要跳过_任何东西_!**为了**有全面的理解,你_必须_读所有的东西!同样,如果这是您第一次阅读,请不用在意页面上的链接,因为那是更深层次的东西。 11 | 12 | 1. 通过`ModelLoader`加载用`ModelResourceLocation`标记的模型集合 13 | 14 | * 对于物品,他们的模型必须手动标记由`ModelLoader.registerItemVariants`加载(用`ModelLoader.setCustomModelResourceLocation`)完成。 15 | 16 | * 对于方块,它由状态映射得到一个`Map`。迭代所有的方块,然后映射中的所有值会被加载。 17 | 18 | 2. [`IModel`][IModel]由每个`ModelResourceLocation`加载,并缓存至一个`Map`。 19 | 20 | * `IModel`仅由[`ICustomModelLoader`][ICustomModelLoader]加载得到。(多个加载器尝试加载一个模型会报`LoaderException`)。如果没有找到模型,并且`ResourceLocation`实际上是一个`ModelResourceLocation`(也就是,它不是一个普通的模型,而是一个方块状态的变体),那么他会调用方块状态加载器(`VariantLoader`)。否则的话,该模型是一个正常的原版JSON模型,它会用原版的方式加载(`VanillaLoader`)。 21 | 22 | * 一个原版JSON模型(`models/item/*.json` 或 `models/block/*.json`)加载后,它是一个`ModelBlock`(即使它是物品)。这是一个原版的类,与`IModel`无关。为了纠正它,它被包装到一个`VanillaModelWrapper`里,`VanillaModelWrapper`*实现了*`IModel`。 23 | 24 | * 一个原版/Forge的方块变体加载时,先加载它的完整的方块状态JSON。这个JSON解析到一个`ModelBlockDefinition`里,那里面缓存着JSON的路径。然后变体定义的列表由`ModelBlockDefinition`导出到`WeightedRandomModel`。 25 | 26 | * 当加载一个原版JSON物品模型 (`models/item/*.json`)时,模型需要一个带有变体名`inventory`的`ModelResourceLocation`(例如,泥土方块物品的模型是`minecraft:dirt#inventory`);从而导致模型由`VariantLoader`加载(尽管它是`ModelResourceLocation`),若`VariantLoader`加载失败了,则再用`VanillaLoader`。 27 | * 最重要的副作用是,如果`VariantLoader`加载出错,它会尝试用`VanillaLoader`加载。如果这也出错了,它会导致*两个*stacktrace。第一个是`VanillaLoader`的,第二个是`VariantLoader`的。当调试模型错误时,分析正确的stacktrace很重要。 28 | 29 | * 一个`IModel`可由`ResourceLocation`加载或通过调用`ModelLoaderRegistry.getModel`从缓存中检索或做为其中一个异常处理备选方案。 30 | 31 | 3. 加载的模型的所有材质依赖会被加载,并放入材质集。材质集是一个巨大的材质,它把所有模型材质粘贴在一起。当模型要渲染一个材质时,它会加上额外的UV偏移,来匹配材质集中材质的坐标。 32 | 33 | 4. 每个模型调用`model.bake(model.getDefaultState(), ...)`来贴图。返回值[`IBakedModel`][IBakedModel]会被缓存进一个`Map`。 34 | 35 | 5. 此映射(map)然后被存入`ModelManager`。`ModelManager`是一个存在`Minecraft::modelManager`里的单例,它是私有的,没有getter。 36 | 37 | * `ModelManager`可以不通过反射或访问转换获得,通过`Minecraft.getMinecraft().getRenderItem().getItemModelMesher().getModelManager()` 或 `Minecraft.getMinecraft().getBlockRenderDispatcher().getBlockModelShapes().getModelManager()`。与它们的名字相反,它们是一样的。 38 | 39 | * 可以用`ModelManager`的`ModelManager::getModel`从缓存中获取一个`IBakedModel`(没有加载或/贴图的模型,仅获取已有的缓存) 40 | 41 | 6. 最后,`IBakedModel`会渲染。这是由`IBakedModel::getQuads`完成的。它的返回值是`BakedQuad`的列表(quadrilaterals: 四边形)。然后它们可以传递给GPU渲染。物品和方块在这里有所不同,但它逻辑相对简单。 42 | 43 | * 原版物品有属性和重载。为了实现它,`IBakedModel`定义了`getOverrides`,它返回一个`ItemOverrideList`。`ItemOverrideList`定义了`handleItemState`,它里面有原始的模型、实体、世界和栈,来找到最终的模型。重载在模型其它所有操作之前完成,包括`getQuads`。因为`IBlockState`不适用于物品,当渲染物品时,`IBakedModel::getQuads`的state参数接受`null`值。 44 | 45 | * 方块有方块状态,当一个方块的`IBakedModel`被渲染时,`IBlockState`直接传入`getQuads`方法。仅在模型的上下文中,方块状态可以有额外的属性,参考[unlisted properties][extended states]。 46 | 47 | [IModel]: imodel.md 48 | [IBakedModel]: ibakedmodel.md 49 | [ICustomModelLoader]: icustommodelloader.md 50 | [extended states]: extended-blockstates.md 51 | -------------------------------------------------------------------------------- /docs/models/advanced/itemoverridelist.md: -------------------------------------------------------------------------------- 1 | `ItemOverrideList` 2 | ================== 3 | 4 | `ItemOverrideList` provides a way for an [`IBakedModel`][IBakedModel] to process the state of an `ItemStack` and return a new `IBakedModel`; thereafter, the returned model replaces the old one. `ItemOverrideList` represents an arbitrary function `(IBakedModel, ItemStack, World, EntityLivingBase)` → `IBakedModel`, making it useful for dynamic models. In vanilla, it is used to implement item property overrides. 5 | 6 | ### `ItemOverrideList()` 7 | 8 | Given a list of `ItemOverride`s, the constructor copies that list and stores the copy. The list may be accessed with `getOverrides`, and it is used to implement the vanilla `applyOverride`, which, in turn, is used in the vanilla `handleItemState`. 9 | 10 | ### `applyOverride` 11 | 12 | This is a deprecated vanilla method. It is only called in the vanilla `handleItemState`, and in almost all cases can be safely ignored. 13 | 14 | ### `handleItemState` 15 | 16 | This takes an `IBakedModel`, an `ItemStack`, a `World`, and an `EntityLivingBase` to produce another `IBakedModel` to use for rendering. This is where models can handle the state of their items. 17 | 18 | This should not mutate the world. 19 | 20 | ### `getOverrides` 21 | 22 | Returns an immutable list containing all the [`ItemOverride`][ItemOverride]s used by this `ItemOverrideList`. If none are applicable, this returns the empty list. 23 | 24 | ## `ItemOverride` 25 | 26 | This class represents a vanilla item override, which holds several predicates for the properties on an item and a model to use in case those predicates are satisfied. They are the objects in the `overrides` array of a vanilla item JSON model: 27 | 28 | ```json 29 | { 30 | "__comment": "Inside a vanilla JSON item model.", 31 | "overrides": [ 32 | { 33 | "__comment": "This is an ItemOverride.", 34 | "predicate": { 35 | "__comment": "This is the Map, containing the names of properties and their minimum values.", 36 | "example1:prop": 4 37 | }, 38 | "__comment": "This is the 'location', or target model, of the override, which is used if the predicate above matches.", 39 | "model": "example1:item/model" 40 | }, 41 | { 42 | "__comment": "This is another ItemOverride.", 43 | "predicate": { 44 | "prop": 1 45 | }, 46 | "model": "example2:item/model" 47 | } 48 | ] 49 | } 50 | ``` 51 | 52 | [IBakedModel]: ibakedmodel.md 53 | [ItemOverride]: #itemoverride 54 | -------------------------------------------------------------------------------- /docs/models/advanced/perspective.md: -------------------------------------------------------------------------------- 1 | Perspective 2 | =========== 3 | 4 | When an [`IBakedModel`][IBakedModel] is being rendered as an item, it can apply special handling depending on which perspective it is being rendered in. "Perspective" means in what context the model is being rendered. The possible perspectives are represented in code by the `ItemCameraTransforms.TransformType` enum. There are two systems for handling perspective: the deprecated vanilla system, constituted by `IBakedModel::getItemCameraTransforms`, `ItemCameraTranforms`, and `ItemTransformVec3f`, and the Forge system, embodied by the method `IBakedModel::handlePerspective`. The vanilla code is patched to favor using `handlePerspective` over the vanilla system whenever possible. 5 | 6 | `TransformType` 7 | --------------- 8 | 9 | `NONE` - Unused. 10 | 11 | `THIRD_PERSON_LEFT_HAND`/`THIRD_PERSON_RIGHT_HAND`/`FIRST_PERSON_LEFT_HAND`/`FIRST_PERSON_RIGHT_HAND` - The first person values represent when the player is holding the item in their own hand. The third person values represent when another player is holding the item and the client is looking at them in the 3rd person. Hands are self-explanatory. 12 | 13 | `HEAD` - Represents when any player is wearing the item in the helmet slot (e.g. pumpkins). 14 | 15 | `GUI` - Represents when the item is being rendered in a GUI. 16 | 17 | `GROUND` - Represents when the item is being rendered in the world as an `EntityItem`. 18 | 19 | `FIXED` - Used for item frames. 20 | 21 | This enum is also patched to implement [`IModelPart`][IModelState]. This allows `IModelState`s to alter the perspective handling of models. However, the model itself must implement this behavior. (See [below][state perspective].) 22 | 23 | The Vanilla Way 24 | --------------- 25 | 26 | The vanilla way of handling perspective is through `IBakedModel::getItemCameraTransforms`. This method returns an `ItemCameraTransforms`, which is a simple object that contains various `ItemTransformVec3f`s as `public final` fields. An `ItemTransformVec3f` represents a rotation, a translation, and a scale to be applied to the model. The `ItemCameraTransforms` is a container for these, holding one for each of the `TransformType`s, sans `NONE`. In the vanilla implementation, calling `getTransform` for `NONE` results in the default transform, `ItemTransformVec3f.DEFAULT`. 27 | 28 | The entire vanilla system for handling transforms is deprecated by Forge, and most implementations of `IBakedModel` should simply `return ItemCameraTransforms.DEFAULT` (which is the default implementation) from `IBakedModel::getItemCameraTransforms`. Instead, they should implement `handlePerspective`. 29 | 30 | The Forge Way 31 | ------------- 32 | 33 | The Forge way of handling transforms is `handlePerspective`, a method patched into `IBakedModel`. It supersedes the `getItemCameraTransforms` method. Additionally, the class `PerspectiveMapWrapper` is a simple implementation of an `IBakedModel` with the method; it is a wrapper around other `IBakedModel`s, augmenting them with a `Map` to handle perspective. 34 | 35 | #### `IBakedModel::handlePerspective` 36 | 37 | Given a `TransformType`, this method produces an `IBakedModel` and `Matrix4f`. The model is what will be rendered, and the (nullable) matrix is the transform to use. Because the returned `IBakedModel` can be a totally new model, this method is more flexible than the vanilla method (e.g. a piece of paper that looks flat in hand but crumpled on the ground). 38 | 39 | ### `PerspectiveMapWrapper` 40 | 41 | A wrapper around other `IBakedModel`s, this class delegates to the wrapped model for all `IBakedModel` methods except `handlePerspective`, and utilizes a simple `Map` for `handlePerspective`. However, the more interesting parts of this class are the static helper methods. 42 | 43 | #### `getTransforms` 44 | 45 | Given an `ItemCameraTransforms` or an `IModelState`, this method will extract an `ImmutableMap` from it. To extract this information from an `IModelState`, each `TransformType` is passed to `apply`. 46 | 47 | This is how models should support custom perspective transforms through `IModelState`. `IModel`s should use `getTransforms` in `bake` and store the passed in perspective transforms in the `IBakedModel`. Then the `IBakedModel` can use these custom transforms in `handlePerspective`, composing them on top of its own. 48 | 49 | #### `handlePerspective` 50 | 51 | Given either a map of transforms or an `IModelState`, an `IBakedModel`, and a `TransformType`, this finds the `Matrix4f` for the transform from the map or the `IModelState`, and then pairs it with the given model. To extract the transform from an `IModelState`, the `TransformType` is passed to `apply`. This method is meant to be a simple implementation of `IBakedModel::handlePerspective`. 52 | 53 | [state perspective]: #gettransforms 54 | [IBakedModel]: ibakedmodel.md 55 | [IModelState]: imodelstate+part.md 56 | -------------------------------------------------------------------------------- /docs/models/blockstates/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Krasjet/Forge-Documentation-CN/a4ea75c05a400af6417d9778c994dd065b26e789/docs/models/blockstates/example.png -------------------------------------------------------------------------------- /docs/models/blockstates/forgeBlockstates.md: -------------------------------------------------------------------------------- 1 | Forge的方块状态 2 | =================== 3 | 4 | 为适应mod开发者的需要,Forge有自己的方块状态JSON格式。它包括子模型,可以让你从不同的部分构建最终的方块状态。 5 | 6 | !!! Attention "注意" 7 | 8 | 注意,所有的模型和材质都指的是原版`minecraft`的,要用你自己mod的,必须要用完整的路径!如`"mymod:blocks/blockTexture"` 9 | 10 | 你不必使用Forge的方块状态格式,可以使用原版的格式! 11 | 12 | 普通格式结构 13 | ------------------------------- 14 | 15 | ```json 16 | { 17 | "forge_marker": 1, 18 | "defaults": { 19 | "textures": { 20 | "all": "blocks/dirt" 21 | }, 22 | "model": "cube_all", 23 | "uvlock": true 24 | }, 25 | "variants": { 26 | "normal": [{}] 27 | } 28 | } 29 | ``` 30 | 31 | 这个json描述了一个每个面都是泥土的简单方块状态。让我们一点一点来看: 32 | 33 | ```json 34 | "forge_marker": 1, 35 | ``` 36 | 37 | 它告诉游戏方块状态json是来自于Forge,而不是原版Minecraft。1是格式的版本,以确保老版本的在更新后也支持。目前只有这一个版本。 38 | 39 | ```json 40 | "defaults": { 41 | "textures": { 42 | "all": "blocks/dirt" 43 | }, 44 | "model": "cube_all", 45 | "uvlock": true 46 | } 47 | ``` 48 | 49 | defaults部分包含默认值变体。它们会被变体覆盖。这一部分是__可选__的你不必定义默认值,方块可以完全被省略。 50 | 51 | ```json 52 | "variants": { 53 | "normal": [{}] 54 | } 55 | ``` 56 | 57 | 这定义了方块的变体。简单的泥土块只有默认和_normal_变体。这种情况下,它不包含任何附加信息。在默认值里定义的所有东西也可以在这定义。例如: 58 | 59 | ```json 60 | "normal": [{ 61 | "textures": { 62 | "side": "blocks/cobblestone", 63 | "end": "blocks/dirt" 64 | }, 65 | "model": "cube_column" 66 | }] 67 | ``` 68 | 69 | normal变体用_立方住_模型,四周是圆石顶部和底部是泥土。 70 | 71 | `variants`部分中每个条目要么定义一个[方块状态][blockstate]属性,要么定义一个变体。属性以这种形式定义: 72 | 73 | ```json 74 | "variants": { 75 | "property_name": { 76 | "value0": {}, 77 | "value1": {}, 78 | "__comment": "Etc." 79 | } 80 | } 81 | ``` 82 | 83 | 给定的方块状态可以有任意数量的属性。当方块状态被加载时,属性的值用于创建方块所有可能的变体。上面的例子会创建2个变体,`property_name=value0` 和`property_name=value1`。如果有两个属性,它会创建变体 `prop1=value11,prop2=value21`, `prop1=value12,prop2=value21`, `prop1=value11,prop2=value22`等(以字母表顺序排序)。每个这样的变体进入它的所有变体的集合。例如: 84 | 85 | ```json 86 | { 87 | "forge_marker": 1, 88 | "variants": { 89 | "shiny": { 90 | "true": { "textures": { "all": "some:shiny_texture" } }, 91 | "false": { "textures": { "all": "some:flat_texture" } } 92 | }, 93 | "broken": { 94 | "true": { "model": "some:broken_model" }, 95 | "false": { "model": "some:intact_model" } 96 | } 97 | } 98 | } 99 | ``` 100 | 101 | 变体 `broken=false,shiny=true` 从`variants.broken.true.model`采用`some:intact_model` ,从 `variants.shiny.true.textures`采用 `some:shiny_texture` 。 102 | 103 | 条目也可以是普通的变体 104 | 105 | ```json 106 | "variants": { 107 | "normal": { "model": "some:model" } 108 | } 109 | ``` 110 | 111 | 这种方式的定义直接定义"normal"变体,不与那些列出来的键值形成组合。它仍然继承自“defaults”方块(如果存在),并且如果属性的变体生成具有相同名称的变体,则直接定义的变量覆盖原有的值。如果变体定义成一个列表,那么每个元素都是变体的定义,那么会随机使用一个: 112 | 113 | ```json 114 | "defaults": { "model": "some:model" } 115 | "variants": { 116 | "__comment": "当被使用时,模型有75%的可能性会被旋转.", 117 | "normal": [{ "y": 0 }, { "y": 90 }, { "y": 180 }, { "y": 270 }] 118 | } 119 | ``` 120 | 121 | 通过第一个条目的类型消除直接变体和属性定义的歧义。 如果`variants.`的第一个条目是一个对象,那么它是一个属性定义。 如果它是其他的东西,它是一个直的变体。 为了避免混淆,建议使用一个元素将直接变体包装在列表中: 122 | 123 | ```json 124 | "variants": { 125 | "simple": [{ 126 | "custom": {}, 127 | "model": "some:model", 128 | "__comment": "没有列表的话,定义的{}会使Forge认为这是一个属性定义。" 129 | }] 130 | } 131 | ``` 132 | 133 | 子模型 134 | ---------- 135 | 136 | 为了展示子模型的使用,我们将创建一个具有不同变体的模型。 每个变体都将使用子模型来创建不同的模型。 137 | 138 | 该模型将是一个压力板,根据其状态,它将添加不同部件。 139 | 140 | ```json 141 | { 142 | "forge_marker": 1, 143 | "defaults": { 144 | "textures": { 145 | "texture": "blocks/planks_oak", 146 | "wall": "blocks/planks_oak" 147 | }, 148 | "model": "pressure_plate_up", 149 | "uvlock": true 150 | }, 151 | "variants": { 152 | "__comment": "mossy是一个布尔值.", 153 | "mossy": { 154 | "true": { 155 | "__comment": "如果为true,它会将压力板从橡木板改为苔石。", 156 | "textures": { 157 | "texture": "blocks/cobblestone_mossy" 158 | } 159 | }, 160 | "false": { 161 | "__comment": "什么也没变.这一条必须在,这样Forge方块状态加载器才会生成这种变体." 162 | } 163 | }, 164 | "__comment": "pillarcount 决定模型有多少柱子. 从0到2.", 165 | "pillarcount": { 166 | "0": { 167 | "__comment": "没有柱子.记住,空的定义必须要在." 168 | }, 169 | "1": { 170 | "__comment": "它将添加墙模型并将其与压力板结合.", 171 | "submodel": "wall_n" 172 | }, 173 | "2": { 174 | "textures": { 175 | "wall": "blocks/cobblestone" 176 | }, 177 | "submodel": { 178 | "pillar1": { "model": "wall_n" }, 179 | "pillar2": { "model": "wall_n", "y": 90 } 180 | } 181 | } 182 | } 183 | } 184 | } 185 | ``` 186 | 187 | 这些注释已经解释了各个部分的细节,但是这里的整体工作原理如下:代码中的块定义有两个属性。 一个名为`mossy`的布尔属性和一个名为`pillarCount`的整数属性。 188 | 189 | !!! note "提示" 190 | 191 | 注意这里json里的字符串是用**小写**的.它必须是小写的,不然它就找不到. 192 | 193 | 我们不是定义“这种属性的组合给出模型X”,而是说“**这个属性的**值对模型有什么影响”。 在这个例子中很容易看出来: 194 | 195 | * 如果`mossy`为真,压力板会用苔石材质. 196 | * 如果`pillarCount`为`1`,它会加上朝北的墙.墙的默认材质是橡木板. 197 | * 如果`pillarCount`是`2`,它将增加两面墙,两面都朝北。 然而,第二个墙将旋转90度。 这表明您不需要与Forge系统分开的模型。 您只需要一次并绕Y轴的旋转。 此外,墙壁的纹理改为鹅卵石. 198 | * 如果`pillarCount`是`0`,不会增加墙. 199 | 200 | 这是结果: 201 | 202 | ![The model in different variations](example.png) 203 | 204 | [blockstate]: ../../blocks/states.md 205 | -------------------------------------------------------------------------------- /docs/models/blockstates/introduction.md: -------------------------------------------------------------------------------- 1 | 方块状态JSON概述 2 | ================================ 3 | 4 | 方块状态JSON是Minecraft将“变体字符串”映射到模型的方式。 变体字符串可以是任何东西,如“inventory”,“power= 5”或“I am your father”。 它们代表一个实际模型,其中方块状态只是它们的容器。 在代码中,方块状态JSON中的变体字符串由`ModelResourceLocation`表示。 5 | 6 | 当游戏搜索对应于世界中某个方块的模型时,它会对该位置采取[方块状态][blockstate],然后使用`IStateMapper`为它找到相应的`ModelResourceLocation`,然后引用它 实际模型。 默认的`IStateMapper`使用方块的注册表名称作为方块状态JSON的位置。 (例如,方块`examplemod:testblock`对应的`ResourceLocation`是`examplemod:testblock`)变体字符串把方块状态的属性拼凑在一起。 更多信息可以在[这里][statemapper]找到。 7 | 8 | 举个例子,我们来看看原版的`oak_log.json`: 9 | 10 | ```json 11 | { 12 | "variants": { 13 | "axis=y": { "model": "oak_log" }, 14 | "axis=z": { "model": "oak_log_side" }, 15 | "axis=x": { "model": "oak_log_side", "y": 90 }, 16 | "axis=none": { "model": "oak_bark" } 17 | } 18 | } 19 | ``` 20 | 21 | 在这里我们定义了4个变体字符串,每个我们使用一个特定的模型,直立原木,侧立原木(旋转或不旋转)和全树皮模型(这种模型通常不会在原版中看到;你必须使用` /setblock`来创建它)。由于原木使用默认的`IStateMapper`,因此这些变体将根据属性`axis`定义原木的外观。 22 | 23 | 必须始终为所有可能的变体字符串定义块状态。 如果有许多属性,则会产生许多可能的变体,因为必须定义每个属性组合。 在Minecraft 1.8的方块状态格式中,您必须明确定义每个字符串,这会导致长而复杂的文件。 它也不支持子模型的概念,也不支持同一个方块状态中的多个模型。 为了实现这一目标,Forge推出了[自己的blockstate格式][Forge blockstate],可以在Minecraft 1.8及更高版本中使用。 24 | 25 | 从Minecraft 1.9开始,Mojang还推出了“multipart”格式。 您可以在[wiki][]上找到其格式的定义。 Forge的格式和multipart格式并没有谁比谁更好; 它们各自涵盖不同的用例,您可以选择使用哪种用例。 26 | 27 | !!! note "提示" 28 | 29 | Forge格式更像是语法糖,用于在后台自动计算所有可能变体的集合。 这允许您使用生成的`ModelResourceLocation`来处理方块之外的其他内容。(例如[item][item blockstates]。1.8格式也是如此,但几乎没有理由使用那种格式。)1.9格式是一个更复杂的系统,依赖于`IBlockState` 选择模型。 如果没有代码,它将不会直接在其他环境中工作。 30 | 31 | 作为参考,这里是1.8栅栏的方块状态`fence.json`的摘录: 32 | 33 | ```json 34 | "east=true,north=false,south=false,west=false": { "model": "oak_fence_n", "y": 90, "uvlock": true } 35 | ``` 36 | 37 | 这只是16个中的一个变体。更糟糕的是,有6个围栏模型,一个用于无连接,一个连接,两个直线连接,两个垂直连接,三个连接,以及一个用于所有四个连接。 38 | 39 | 这是1.9中同样的例子,但用的是multipart格式: 40 | 41 | ```json 42 | { "when": { "east": "true" }, 43 | "apply": { "model": "oak_fence_side", "y": 90, "uvlock": true } 44 | } 45 | ``` 46 | 47 | 48 | 这是一个5的情况。您可以将其读作“当east = true时,使用模型oak_fence_side旋转90度”。 这允许最终模型由5个较小的部分构成,其中4个(连接)是有条件的,第5个是无条件的中心柱。 这仅使用两个模型,一个用于中心柱,一个用于侧面连接。 49 | 50 | [blockstate]: ../../blocks/states.md 51 | [statemapper]: ../using.md#block-models 52 | [Forge blockstate]: forgeBlockstates.md 53 | [wiki]: https://minecraft.gamepedia.com/Model#Block_states 54 | [item blockstates]: ../using.md#blockstate-jsons-for-items 55 | -------------------------------------------------------------------------------- /docs/models/color.md: -------------------------------------------------------------------------------- 1 | 彩色纹理 2 | ================= 3 | 4 | 原版中的许多方块和物品(如草方块)会根据它们的位置改变其纹理颜色。 模型支持在面上指定“色调指数”,这些数字可以由`IBlockColor`和`IItemColor`处理。 有关如何在原版模型中定义色调指数的信息,请参阅[wiki][]。 5 | 6 | ### `IBlockColor`/`IItemColor` 7 | 8 | 这两个都是单方法接口。 `IBlockColor`需要一个`IBlockState`,一个(可为null)的`IBlockAccess`和一个(可为null)的`BlockPos`。 `IItemColor`需要一个`ItemStack`。它们都需要参数`tintindex`,这是被着色的面的色调指数。它们都返回一个`int`,一个颜色乘数。这个`int`按顺序被视为4个无符号字节,透明度,红,绿和蓝,从最高的字节到最低字节。对于着色面中的每个像素,每个颜色通道的值是`(int)((float)base * multiplier / 255)`,其中`base`是通道的原始值,`multiplier`是关联的来自颜色乘数的字节。请注意,方块不使用alpha通道。例如,草纹理,未着色,看起来是白色和灰色的。用于草的`IBlockColor`和`IItemColor`返回颜色乘数,具有低红色和蓝色成分,但是高透明度和绿色成分,因此当执行乘法时,绿色被带出并且红色/蓝色减少了。 9 | 10 | 如果物品继承自`builtin/generated`模型,则每个图层(“layer0”,“layer1”等)都具有与其图层索引对应的色调指数。 11 | 12 | ### 创建颜色控制器 13 | 14 | `IBlockColors`需要注册到游戏的`BlockColors`实例。 `BlockColors`可以通过`Minecraft.getMinecraft().getBlockColors()`获得,`IBlockColor`可以通过`BlockColors::registerBlockColorHandler`注册。 请注意,这不会导致给定方块的`ItemBlock`被着色。 `ItemBlock`是物品,需要用`IItemColor`着色。 15 | 16 | `IItemColors`需要注册到游戏的`ItemColors`实例。 `ItemColors`可以通过`Minecraft.getMinecraft().getItemColors()`获得,`IItemColor`可以通过`ItemColors::registerItemColorHandler`注册。 这个方法被重载也需要`Block`,它只是为物品的`Item.getItemFromBlock(block)`注册颜色控制器(即方块的`ItemBlock`)。 17 | 18 | 注册只能在初始化阶段在客户端完成. 19 | 20 | [wiki]: https://minecraft.gamepedia.com/Model#Block_models 21 | -------------------------------------------------------------------------------- /docs/models/files.md: -------------------------------------------------------------------------------- 1 | 模型文件 2 | =========== 3 | 4 | 一个“模型”可以是一个简单的形状,可以是几个长方体,可以是大斜方截半二十面体,或者其它形状。你见到的绝大多数模型是以原版的JSON的格式的。其它格式的模型是运行时由`ICustomModelLoader`加载到`IModel`中。Forge默认提供WaveFront OBJ和Blitz3D的实现。大多数时候不用担心模型是什么格式的,因为它们在代码中都实现了`IModel`接口。 5 | 6 | 当`ResourceLocation`指的是一个模型时,路径会关联到`models`下(例如 :`examplemod:block/block` → `assets/examplemod/models/block/block`)。常见错误是在[方块状态JSON][blockstate JSON](共3种格式)中模型文件会关联到`models/block` (例如: `examplemod:block` → `assets/examplemod/models/block/block`)。 7 | 8 | 方块和物品的模型有一点不同,最主要在[物品属性概述][overrides]。 9 | 10 | 材质 11 | -------- 12 | 13 | 材质,和模型一样,包括在材质包中,用`ResourceLocation`表示。当`ResourceLocation`在模型中表示的是材质,路径会关联到`textures/`(例如: `examplemod:blocks/test` → `assets/examplemod/textures/blocks/test.png`)。另外,在Minecraft中,[UV坐标][UV]中,(0,0)代表__左上__角。UV_总是_在0到16之间。如果材质更大或更小,坐标要适当缩放,材质必须是正方形的,且边长最好是2的幂,因为不这样的话会破坏纹理映射。(例如: 1x1, 2x2, 8x8, 16x16, 和128x128是最好的, 5x5和30x30不推荐因为边长不是2的幂,5x10和4x8会完全损坏因为不是正方形)。如果存在与纹理相关联的`mcmeta`文件,并且定义了动画,则图像可以是矩形的,会被解释为从上到下的正方形区域的垂直序列,其中每个正方形是动画的帧。 14 | 15 | JSON 模型 16 | ----------- 17 | 18 | Minecraft原版的JSON模型格式非常简单。它定义了长方体(矩形棱柱)元素,并为其面指定材质。在[wiki][JSON model format]中有它格式的定义 19 | 20 | !!! note "提示" 21 | 22 | JSON模型只支持立方体元素;没法表示三棱柱或其它这样的东西。要有更复杂的东西,必须用其它格式。 23 | 24 | 当一个`ResourceLocation`只JSON模型的位置,它没有后缀`.json`,不像OBJ和B3D模型(例如: `minecraft:block/cube_all`, 不是`minecraft:block/cube_all.json`) 25 | 26 | WaveFront OBJ 模型 27 | -------------------- 28 | 29 | Forge增加了`.obj`格式文件的加载器。要用这些模型,资源的命名空间必须用 `OBJLoader.addDomain`注册。加载器接受注册过的命名空间下的以`.obj`结尾的任何文件。`.mtl`文件应放在`.obj`文件旁,并会在使用`.obj`是自动使用。可能必须要手动编辑`.mtl`文件把其纹理指向Minecraft的`ResourceLocation`。另外,用外部软件创建的材质的V轴可能被翻转了(例如,V=0可能是底部,而不是顶部)。 这可以在建模程序本身中纠正,或者在Forge方块状态JSON中完成,如下所示: 30 | 31 | ```json 32 | { 33 | "__comment": "在与“模型”声明相同的级别上添加以下行。", 34 | "custom": { "flip-v": true }, 35 | "model": "examplemod:model.obj" 36 | } 37 | ``` 38 | 39 | Blitz3D 模型 40 | -------------- 41 | 42 | Forge增加了`.b3d`格式文件的加载器。要用这些模型,资源的命名空间必须用 `B3DLoader.addDomain`注册。加载器接受注册过的命名空间下的以`.b3d`结尾的任何文件。 43 | 44 | [JSON model format]: https://minecraft.gamepedia.com/Model#Block_models 45 | [overrides]: overrides.md 46 | [blockstate JSON]: blockstates/introduction.md 47 | [UV]: https://en.wikipedia.org/wiki/UV_mapping 48 | -------------------------------------------------------------------------------- /docs/models/introduction.md: -------------------------------------------------------------------------------- 1 | 模型概述 2 | =============== 3 | 4 | 模型系统是Minecraft给方块和物品设置形状的方式。通过模型系统,方块和物品可以对应到它们的模型。模型系统的主要目的之一是为了资源包可以不仅仅换材质,还可以换方块/物品的整个模型。事实上,每个mod添加的物品和方块都有一个小型的材质包。 5 | 6 | `ResourceLocation`类可以把代码连接到文件中的模型和材质。这个类可以从注册系统中识别出模型和材质,但它们的初衷是为了识别文件,它们还可以作为唯一标识符使用。`ResourceLocation`是一个由两个`String`组成的一个简单对象——命名空间和路径。`ResourceLocation`可以表示为`namespace:path`。若创建`ResourceLocation`没有给出明确的命名空间,命名空间默认是`minecraft`。尽管这样,最好还是包含命名空间。 7 | 8 | 模型系统中,`ResourceLocation`的命名空间直接代表了 `assets/`下的一个文件夹。通常,命名空间和modid一致(例如,原版Minecraft的命名空间是`minecraft`)。`ResourceLocation`的路径部分代表了命名空间下上下文敏感的文件路径。路径意味着什么,确切的路径在哪,要看在哪使用它。例如,如果要一个模型,路径会理解为在`model`下的路径,但如果要一个材质,路径会理解为在`textures`下的路径。因此,`mod:file`前者的语境下是`assets/mod/models/file`,而后者是`assets/mod/textures/file`。如果有东西需要用`ResourceLocation`描述时,它会确切的定义位置在哪。 9 | 10 | 与模型系统相关的字符串都应用蛇形命名法(尤其是 `ResourceLocation`) (如: meaning_all_lowercase_and_underscore_separated_words_like_this). Minecraft 1.11之后强制这样使用。 11 | -------------------------------------------------------------------------------- /docs/models/overrides.md: -------------------------------------------------------------------------------- 1 | 物品属性概述 2 | ======================= 3 | 4 | 物品属性是一种通过“属性”设定模型的方式。弓就是一个例子,它最重要的一个属性是它拉了多远。这个信息用于决定它用哪个模型,做出拉动的动画。与直接通过`ModelLoader.setCustomModelResourceLocation` 或 `ModelLoader.setCustomMeshDefinition`给物品分配`ModelResourceLocation`不同。这些方法固定了可能的模型集。例如弓,这些方法会固定拉动动画中的帧数为4,然而,属性是可变的。 5 | 6 | 物品属性给每个注册的`ItemStack`分配一个确定的`float`,且原版模型的定义可以用这些值来“覆盖”默认的模型,如果覆盖匹配,它会覆盖并使用另一个。物品模型的格式和覆盖可以在[wiki][format]上找到。这很有用因为它是连续的。例如,弓用物品属性来定义它的拉动动画。由于属性的值是一个`float`,它在0到1上连续增加。这可以让材质包根据想要的增幅增加许多模型,而不是限制在默认动画中的4个模型。指南针和钟也同样如此。 7 | 8 | 给物品添加属性 9 | -------------------------- 10 | 11 | 用`Item::addPropertyOverride`给物品添加属性。`ResourceLocation`参数是给属性的名字(例如`new ResourceLocation("pull")`)。`IItemPropertyGetter`是一个回调函数,其中参数有拿着的`ItemStack`,所在的`World`,拿着它的实体生物`EntityLivingBase`,该回调函数返回`float`类型,即物品的属性。一些列子是`ItemBow`里的"`pulling`"和"`pull`"属性,和`Item`里的几个`static final`的属性。对于mod里的属性,推荐用modid作为命名空间(例如`examplemod:property`而不仅仅是`property`,因为那样实际上是`minecraft:property`)。 12 | 13 | Using Overrides 14 | --------------- 15 | 16 | 覆盖的格式可以参考[wiki][format],`model/item/bow.json`是一个很好的例子。作为参考,这是一个假想的例子,有一个有`examplemod:power`属性的物品。如果它的值不匹配,则是默认模型。 17 | 18 | !!! important "重要" 19 | 20 | predicate匹配 *大于等于* 给定值的值。 21 | 22 | ```json 23 | { 24 | "parent": "item/generated", 25 | "textures": { 26 | "__comment": "默认", 27 | "layer0": "examplemod:items/examplePartial" 28 | }, 29 | "overrides": [ 30 | { 31 | "__comment": "power >= .75 时", 32 | "predicate": { 33 | "examplemod:power": 0.75 34 | }, 35 | "model": "examplemod:item/examplePowered" 36 | } 37 | ] 38 | } 39 | ``` 40 | 41 | 这是配合它的一段代码。(它不一定要仅客户端(client-only);它也可以在服务端工作。在原版里,属性在物品的构造器中注册。) 42 | 43 | ```java 44 | item.addPropertyOverride(new IItemPropertyGetter() { 45 | @SideOnly(Side.CLIENT) 46 | @Override 47 | public float apply(ItemStack stack, @Nullable World world, @Nullable EntityLivingBase entity) { 48 | return (float)getPowerLevel(stack) / (float)getMaxPower(stack); // 一些其它代码 49 | } 50 | } 51 | ``` 52 | 53 | [format]: https://minecraft-zh.gamepedia.com/%E6%A8%A1%E5%9E%8B#%E7%89%A9%E5%93%81%E6%A8%A1%E5%9E%8B 54 | -------------------------------------------------------------------------------- /docs/models/using.md: -------------------------------------------------------------------------------- 1 | 绑定模型到方块和物品 2 | ===================================== 3 | 4 | 方块模型 5 | ------------ 6 | 7 | 方块不直接连接到模型,而是方块*状态*映射到`ModelResourceLocation`,它指向模型(“模型”包括方块状态JSON)。 `IBlockState`由`IStateMapper`映射到`ModelResourceLocation`。 默认情况下,所有方块的默认状态映射器的工作原理如下: 8 | 9 | 1. 获得方块状态的方块的注册名 10 | 2. 将所述名称设置为`ModelResourceLocation`的`ResourceLocation` 11 | 3. 在方块状态中获取所有属性及其值. 12 | 4. 使用`IProperty#getNam`获取每个属性的名称 13 | 5. 使用`IProperty#getName(T)`获取每个值的名称 14 | 6. 对属性名称仅*按字母顺序*对对进行排序 15 | 7. 生成以逗号分隔的键值对字符串(例如`a=b,c=d,e=f`) 16 | 8. 将其设置为`ModelResourceLocation`的变体部分。 17 | 9. 如果变量字符串为空(即未定义属性),则将变体默认为`normal` 18 | 19 | 变体字符串是自动转成 __小写__ 的,所以如果状态映射器返回`mod:model#VARIANT`,游戏将查询JSON中的字符串“variant”,而不是“VARIANT”。 20 | 21 | ### 自定义`IStateMapper` 22 | 23 | 使用自定义的`IStateMapper`很简单。 获取实例后,可以通过调用`ModelLoader.setCustomStateMapper`来注册它。 `IStateMapper`是按每个方块单独注册的,所以这个方法接收`IStateMapper`和它所处理的方块。其它常见的用例是一个构建器`StateMap.Builder`。 24 | 25 | #### `StateMap.Builder` 26 | 27 | 构建器`StateMap.Builder`可以创建一些最常见的`IStateMapper`。 一旦实例化,可以调用方法来设置其参数,并调用`build`来使用这些参数生成`IStateMapper`。 28 | 29 | ##### `withName` 30 | 31 | `withName`以一个属性作为参数,并将为返回的`ModelResourceLocation`设置“名称”(实际上是路径)。 当生成的`IStateMapper`应用于方块状态时,它用给定属性的值,找到该值的名称,并将其用作资源路径。 看一个例子更清楚: 32 | 33 | ```java 34 | PropertyDirection PROP_FACING = PropertyDirection.create("facing"); // 创建属性 35 | IStateMapper mapper = new StateMap.Builder().withName(PROP_FACING).build(); // 使用构造器 36 | ``` 37 | 38 | 现在,如果调用 `mapper` 找方块状态`examplemod:block1[facing=east]`的 `ModelResourceLocation`, 它会把它映射到 `examplemod:east#normal`. 给定`examplemod:block2[color=red,facing=north]`,它会映射到`examplemod:north#color=red`. 39 | 40 | ##### `withSuffix` 41 | 42 | 后缀是一个普通字符串,它被添加到资源路径的末尾。 例如,如果后缀设置为`_suff`,则生成的`IStateMapper`会将方块状态`examplemod:block[facing=east]`映射到`examplemod:block_suff#facing=east`。 43 | 44 | ##### `ignore` 45 | 46 | 这导致`IStateMapper`在映射块状态时会简单地忽略给定的属性。 调用两次时,两个列表会合并。 一个例子: 47 | 48 | ```java 49 | PropertyDirection PROP_OUT = PropertyDirection.create("out"); 50 | PropertyDirection PROP_IN = PropertyDirection.create("in"); 51 | // 这两个是等价的 52 | IStateMapper together = new StateMap.Builder().ignore(PROP_OUT, PROP_IN).build(); 53 | IStateMapper merged = new StateMap.Builder().ignore(PROP_OUT).ignore(PROP_IN).build(); 54 | ``` 55 | 56 | 当要求`together`或`merged`映射方块状态`examplemod:block1[in=north,out=south]`时,它们将给出`ModelResourceLocation` `examplemod:block1#normal`。 给定`examplemod:block2[in=north,out=south,color=blue]`,它将产生`examplemod:block2#color=blue`。 最后,给定`examplemod:block3[color=white,out=east]`(没有`in`),它将产生`examplemod:block3#color=white`。 57 | 58 | 物品模型 59 | ----------- 60 | 61 | 不像方块可以不需注册自动有一个`IStateMapper`,物品必须手动注册他们的模型,通过调用`ModelLoader.setCustomModelResourceLocation`.这个方法要一个物品,matedate值,和`ModelResourceLocation`, 62 | 此方法需要传入一个物品,元数据值和`ModelResourceLocation`,并注册映射,以便所有带有物品和元数据的ItemStack使用给定的模型的`ModelResourceLocation`。 游戏搜索模型的方式如下: 63 | 64 | 1. 对于 `ModelResourceLocation` `:#` 65 | 2. 尝试寻找主动加载该模型的自定义模型加载器 66 | 1. 如果成功,用找到的加载器加载该模型,并跳出该指导. 67 | 3. 如果失败,尝试从方块状态加载器加载. 68 | 4. 如果失败,尝试从原版JSON加载器加载(从`assets//models/item/.json`加载) 69 | 70 | JSON `models/item`的物品模型可以参考[概述][overrides]. 71 | 72 | !!! note "提示" 73 | `ModelLoader.setCustomModelResourceLocation`也要通过给定的物品和`ModelResourceLocation` 调用 `ModelLoader.registerItemVariants`.这为之后的渲染做准备. 74 | 75 | ### `ItemMeshDefinition` 76 | 77 | `ItemMeshDefinition`是一个函数,它接受`ItemStack`并将它们映射到`ModelResourceLocation`。 它们是按物品注册的,这可以通过`ModelLoader.setCustomMeshDefinition`来完成,它接受一个物品和`ItemMeshDefinition`用于它的`ItemStack`。 78 | 79 | !!! important "重要" 80 | `ModelLoader.setCustomMeshDefinition` **不会**调用 `ModelLoader.registerItemVariants`. 因此, 每个`ModelResourceLocation`都必须传递`ModelLoader.registerItemVariants`方法,`ItemMeshDefinition`可以返回它以使其工作。 81 | 82 | ### 物品的方块状态JSON 83 | 84 | 请注意,*物品* 可以使用 *方块*状态JSON。 这可以通过简单地将指向块状态JSON的`ModelResourceLocation`传递给`ModelLoader.setCustomModelResourceLocation`或从`ItemMeshDefinition`返回它来实现。 这样做可以让模型利用子模型和组合变体等优点。 两个主要用例是与方块共享模型的物品(特别是`ItemBlock`)和默认的项层模型(组合变量定义中的`textures`块可用于构建模型的层,其中一个属性设置`layer0`,另一个设置`layer1`等)。 85 | 86 | !!! note "提示" 87 | 88 | 1.9多部分方块状态不能作为开箱即用的物品模型工作,因为它们需要`IBlockState`来选择模型。 89 | 90 | !!! important "重要" 91 | 92 | 有一个重要的警告。方块状态JSON只能解析`models/block`下模型的路径; 他们找不到`models/item`下的模型(即使使用`../item`也会导致错误)。 这意味着`minecraft:item/generated`模型(设置项的默认变换)不能在方块状态JSON中使用。 一种解决方法是,使用`minecraft:builtin/generated`模型,并使用方块状态JSON中的`transform`标签设置转换。(继承自`minecraft:block/block`的方块模型已经设置了变换,因此这对他们来说不是必需的。)这是一个例子: 93 | 94 | ```json 95 | "defaults": { 96 | "model": "builtin/generated", 97 | "__comment": "让Forge为玩家手中,地面上等物品设置默认旋转和比例", 98 | "transform": "forge:default-item" 99 | } 100 | ``` 101 | 102 | 103 | 104 | [blockstate JSONs]: blockstates/introduction.md 105 | [overrides]: overrides.md 106 | -------------------------------------------------------------------------------- /docs/networking/entities.md: -------------------------------------------------------------------------------- 1 | 实体 2 | ======== 3 | 4 | 除了常规网络消息之外,还提供了各种其他系统来处理同步实体数据。 5 | 6 | Spawn数据 7 | ---------- 8 | 9 | 一般来说,修改实体的产生由Forge单独处理。 10 | 11 | !!! note "提示" 12 | 13 | 这意味着简单地继承一个原版实体类可能不会在这里继承它的所有行为。 您可能需要自己实现某些原版行为。 14 | 15 | 您可以通过实现以下接口向Forge发送的spawn数据包添加额外数据。 16 | 17 | ### IEntityAdditionalSpawnData 18 | 19 | 如果您的实体具有客户端所需的数据,但不会随时间发生变化,则可以使用此接口将其添加到实体spawn数据包中。 `writeSpawnData()`和`readSpawnData()`,以类似于`IMessage`中`toBytes()`/`fromBytes()`方法的方式控制数据如何与网络缓冲区进行/解码。 20 | 21 | ### IThrowableEntity 22 | 23 | 这适用于“抛射物”类型的实体。 实现此接口将导致“源”实体的ID以及初始速度与spawn数据包一起发送到客户端。 24 | 25 | 静态数据 26 | ------------ 27 | 28 | ### 数据参数 29 | 30 | 这是用于将实体数据从服务器同步到客户端的主要原版系统。 因此,可以参考许多原版例子。 31 | 32 | 首先,您需要一个`DataParameter`来保存您希望保持同步的数据。 这应该存储为实体类中的静态final字段,通过调用`EntityDataManager.createKey()`并为该类型的数据传递实体类和序列化器来获得。 可以在`DataSerializers`类中找到可用的序列化器实现作为静态常量。 33 | 34 | !!! warning "警告" 35 | 36 | 您应该 **仅** 为您自己的实体创建数据参数,*在该实体的class*中。 37 | 向无法控制的实体添加参数会导致用于通过网络发送该数据的ID变得不同步,从而导致难以调试崩溃。 38 | 39 | 然后,覆盖`entityInit()`并为每个数据参数调用`this.dataManager.register()`,传递参数和要使用的初始值。 记得要先调用`super.entityInit()`! 40 | 41 | 然后,您可以通过实体的`dataManager`实例获取并设置这些值。 所做的更改将自动同步到客户端。 -------------------------------------------------------------------------------- /docs/networking/index.md: -------------------------------------------------------------------------------- 1 | 网络 2 | ==== 3 | 4 | 服务器与客户端之间的交流是一个成功的mod实现的关键。 5 | 6 | 阅读[概述][overview]部分来了解为什么服务端与客户端通信至关重要以及一些思考网络(Networking)的基本技巧。 7 | 8 | Forge提供了很多技巧来辅助通信 - 大部分都是基于[Netty]实现的。 9 | 10 | 对于一个新的mod,最简单的方式就是使用[SimpleImpl],这个系统使Netty复杂的系统通过抽象层简化了。它使用的是Message与Handler风格的系统。 11 | 12 | [Netty]: http://netty.io "Netty主页" 13 | [SimpleImpl]: simpleimpl.md "SimpleImpl详细介绍" 14 | [overview]: overview.md "网络概述" 15 | -------------------------------------------------------------------------------- /docs/networking/overview.md: -------------------------------------------------------------------------------- 1 | 概述 2 | ==== 3 | 4 | 网络通信有两个主要目标: 5 | 6 | 1. 保证客户端与服务端是同步的 7 | - 在坐标X,Y,Z的花长大了 8 | 2. 使得客户端能够告诉服务器玩家的变化 9 | - 玩家按了一个键 10 | 11 | 完成这两个目标最常见的方式就是在客户端和服务端中传输信息(Message)。这些信息通常是结构化的: 以一种特定的序列存储数据,从而方便发送与接收。 -------------------------------------------------------------------------------- /docs/networking/simpleimpl.md: -------------------------------------------------------------------------------- 1 | SimpleImpl 2 | ========== 3 | 4 | SimpleImpl是一个围绕着 `SimpleNetworkWrapper` 类的数据包(Packet)系统。使用这个系统是至今为止在客户端与服务端之间发送自定义数据最简单的方法了。 5 | 6 | 入门 7 | ---- 8 | 9 | 首先,你需要创建你自己的 `SimpleNetworkWrapper` 对象。我们推荐您把它放到一个单独的类里面,比如说像是 `ModidPacketHandler`。在这个类里面将你的 `SimpleNetworkWrapper` 创建为一个静态字段(Static Field): 10 | 11 | ```java 12 | public static final SimpleNetworkWrapper INSTANCE = NetworkRegistry.INSTANCE.newSimpleChannel("mymodid"); 13 | ``` 14 | 15 | 其中 `mymodid` 是你的数据包管道(Packet Channel)的标识符,一般来说是你的mod ID,除非你的mod ID太长了。 16 | 17 | 制作数据包 18 | ----------------- 19 | 20 | ### IMessage 21 | 22 | 数据包(Packet)是一个使用 `IMessage` 接口(Interface)的类。这个接口定义了2个方法,`toBytes` 和 `fromBytes`。这些方法分别向 `ByteBuf` 的对象中写入(`toBytes`)与读取(`fromBytes`)你数据包的数据,`ByteBuf` 是一个用来保存发送的字节流(数组)的对象。 23 | 24 | 举个例子,我们定义一个小的数据包,它能够传输一个整数(int)对象: 25 | 26 | ```java 27 | public class MyMessage implements IMessage { 28 | // 默认的构造器(Constructor)是必须的 29 | public MyMessage(){} 30 | 31 | private int toSend; 32 | public MyMessage(int toSend) { 33 | this.toSend = toSend; 34 | } 35 | 36 | @Override public void toBytes(ByteBuf buf) { 37 | // 写入int到buf对象 38 | buf.writeInt(toSend); 39 | } 40 | 41 | @Override public void fromBytes(ByteBuf buf) { 42 | // 从buf对象里读取int。注意如果你写入了多个值,读取的时候要按照写入的顺序读取. 43 | toSend = buf.readInt(); 44 | } 45 | } 46 | ``` 47 | 48 | ### IMessageHandler 49 | 50 | 现在,我们该如何使用这个数据包呢?首先,我们需要有一个能够**处理**(Handle)这个数据包的类。这个类需要实现 `IMessageHandler` 接口。比如说我们想要把我们之前传输的那个整数在服务端当做给一个玩家钻石的数量。这个处理器(Handler)应该是这样的: 51 | 52 | ```java 53 | // IMessageHandler的参数是 54 | // 第一个参数是你接收的包,第二个是你返回的包 55 | // 返回的包可以被用作发送包的回应(Response) 56 | public class MyMessageHandler implements IMessageHandler { 57 | // 注意,默认的构造器是需要的,但是在这里是隐式定义的 58 | 59 | @Override public IMessage onMessage(MyMessage message, MessageContext ctx) { 60 | // 这是发送到服务器的数据包发送到的玩家 61 | EntityPlayerMP serverPlayer = ctx.getServerHandler().playerEntity; 62 | // 发送的值 63 | int amount = message.toSend; 64 | // 添加为一个计划任务(Scheduled Task),在主服务器线程上执行操作 65 | serverPlayer.getServerWorld().addScheduledTask(() -> { 66 | serverPlayer.inventory.addItemStackToInventory(new ItemStack(Items.DIAMOND, amount)); 67 | }); 68 | // 没有回应数据包 69 | return null; 70 | } 71 | } 72 | ``` 73 | 74 | 为了管理的方便,我们建议(但不要求)您将这个类设置为 `MyMessage` 类的内部类(Inner Class)。如果你这样做了,注意这个类也应该被声明为静态类(`static`)。 75 | 76 | !!! warning "警告" 77 | 78 | 在Minecraft 1.8+中,数据包默认由网络线程(Network Thread)来处理(而不是主线程)。 79 | 80 | 这意味着你的 `IMessageHandler` **不能**直接操作游戏中的大部分对象。Minecraft提供了一个简单的方法让你的代码执行在主线程上: 81 | 82 | 使用 `IThreadListener.addScheduledTask`。 83 | 84 | 你可以使用 `Minecraft` 实例(客户端)或者是 `WorldServer` 实例来获取`IThreadListener`。上面的例子中,我们从`EntityPlayerMP`中获取一个`WorldServer`实例 85 | 86 | (译注: `Minecraft` 实例(客户端)与 `WorldServer`实例(服务端)都实现了 `IThreadListener`。你可以将它们获取并创建为一个 `mainThread` 对象来使用。) 87 | 88 | !!! warning "警告" 89 | 90 | 在服务端处理数据包时请采取防御性(Defensive)的方式。客户端是可以通过发送意外的数据从而利用数据包处理的。 91 | 92 | 很常见的一个问题是**任意区块生成**(Arbitrary Chunk Generation)的漏洞。这通常会在服务端使用了客户端发送来的方块位置来访问方块或者TileEntity时发生。当在世界中未加载的区域中访问方块或者TileEntity时,服务器将会生成或者从硬盘中读取这块区域,并立刻将其写回硬盘。这一点可以被轻松利用,对服务器的性能和储存空间造成**灾难性伤害**,而且不会留下什么痕迹。 93 | 94 | 要想避免这个问题,通常常用的方法是仅仅在 `world.isBlockLoaded(pos)` 为 `true` 的时候访问方块或者TileEntity。 95 | 96 | 注册数据包 97 | --------- 98 | 99 | 现在我们有了数据包,有了数据包的处理器。但是 `SimpleNetworkWrapper` 还需要一个步骤才能工作!为了让它能够使用数据包,这个数据包必须要注册一个**识别码**(Discriminator),识别码是一个用来在客户端和服务端之间映射数据包类型的一个整数。要想注册一个数据包,我们调用: 100 | 101 | ```java 102 | INSTANCE.registerMessage(MyMessageHandler.class, MyMessage.class, 0, Side.Server); 103 | ``` 104 | 105 | 这里面的 `INSTANCE` 使我们之前定义的 `SimpleNetworkWrapper`。 106 | 107 | 这是一个比较复杂的方法,我们来看一看每一个参数的用处。 108 | 109 | - 第一个参数是 `messageHandler`,也就是处理你数据包的类。这个类必须要有一个默认构造器,并且绑定的REQ类要和第二个参数一样。 110 | - 第二个参数是 `requestMessageType`,它是真正的数据包类。这个类必须有一个默认构造器,并且需要和前一个参数绑定的REQ类一样。 111 | - 第三个参数是这个数据包的识别码,对单独一个通道(Channel)来说它是唯一的,我们推荐您使用一个静态变量来保存ID,调用 `registerMessage` 时使用 `id++`。这100%保证了ID是唯一的。 112 | - 第四个也是最后一个参数是是你数据包的**接收端**,如果你想同时向客户端和服务端发送这个数据包,它必须被用**不同的**识别码注册两次。 113 | 114 | 使用数据包 115 | ------------- 116 | 117 | 当发送数据包的时候,确保在**接收端**注册了该数据包的处理器。如果没有处理器,数据包会在网络中传输之后被丢弃,变成一个“泄漏”数据包(译注: 即一直占据内存不被释放,直到程序结束。曾经wiki上一个教程导致很多mod都遭受这个问题)。这虽然这在不必要的网络带宽之外没什么影响,我们建议您还是要修复的。 118 | 119 | ### 发送到服务端 120 | 121 | 发送数据包到服务端只有一种方式。因为只存在**一个**服务端,当然只有**一种**方式发送到服务端。想要实现这个目标,我们需要再次使用我们之前定义的 `SimpleNetworkWrapper`。调用: 122 | 123 | ```java 124 | INSTANCE.sendToServer(new MyMessage(toSend)) 125 | ``` 126 | 127 | 这个消息将会发送到注册为 `Side.SERVER` 的 `IMessageHandler`,如果它存在的话。 128 | 129 | ### 发送到客户端 130 | 131 | 发送数据包到客户端一共有4种方式。 132 | 133 | 1. `sendToAll` - 调用 `INSTANCE.sendToAll` 将会发送数据包到服务器上的所有玩家,不管他们在什么地方,在什么维度(Dimension) 134 | 2. `sendToDimension` - `INSTANCE.sendToDimension` 有两个参数,一个 `IMessage` 和一个整数。整数是将要发送的维度的ID,它可以由 `world.provider.dimensionID` 获得。这个数据包将会被发送到在指定维度所有的玩家 135 | 3. `sendToAllAround` - `INSTANCE.sendToAllAround` 需要 `IMessage` 和一个 `NetworkRegistry.TargetPoint` 对象。在 `TargetPoint` 之内所有玩家都将收到这个数据包。创建一个 `TargetPoint`对象需要维度(见第2条), x/y/z坐标,范围。它代表了在世界里的一个立方体 136 | 4. `sendTo` - 最后,发送到单独的一个客户端可以使用 `INSTANCE.sendTo`。它需求 `IMessage` 和一个 `EntityPlayerMP`,就是要发送到的玩家。注意,尽管这并不是更加泛化的 `EntityPlayer`,只要你在服务端你就能转换任何 `EntityPlayer` 到 `EntityPlayerMP` -------------------------------------------------------------------------------- /docs/rendering/teisr.md: -------------------------------------------------------------------------------- 1 | TileEntityItemStackRenderer 2 | ======================= 3 | !!! note "提示" 4 | 该功能仅存在于forge版本>= 14.23.2.2638. 5 | 6 | TileEntityItemStackRenderer是一个用OpenGL渲染物品的方法. 该系统比原本的TESRItemStack系统要简单, 它需要一个 TileEntity, 并且不需要获得ItemStack. 7 | 8 | 使用TileEntityItemStackRenderer 9 | -------------------------- 10 | 11 | TileEntityItemStackRenderer 允许你用 `public void renderByItem(ItemStack itemStackIn)`渲染物品. 12 | 有一个重载会将partialTicks作为参数,但它永远没在原版中调用。 13 | 14 | 要使用TEISR,物品首先必须其模型的`IBakedModel#isBuiltInRenderer`返回true 15 | 一旦返回true,将访问Item的TEISR进行渲染。 如果它没有,它将使用默认的`TileEntityItemStackRenderer.instance`。 16 | 17 | Item设置TEISR,请使用`Item#setTileEntityItemStackRenderer`。 每个Item只能提供一个TEISR,而getter是final的,因此mods不会每帧返回新的实例。 18 | 19 | 就是这样,使用TEISR不需要额外的设置。 20 | 21 | 如果需要访问TransformType进行渲染,可以储存通过`IBakedModel#handlePerspective`传递的TransformType,并在渲染过程中使用它。 始终在`TileEntityItemStackRenderer#renderByItem`之前调用此方法。 -------------------------------------------------------------------------------- /docs/styleguide.md: -------------------------------------------------------------------------------- 1 | 样式指南 2 | ======== 3 | 4 | 向此文档贡献的指南 5 | ---------------- 6 | 7 | 这个文档需要是说明性的。贡献者需要解释**如何**做一件事,并且分成合理的逻辑块来讨论。 8 | 9 | 我们也有一个[wiki](http://www.minecraftforge.net/wiki)页,你可以在那找到更全面的代码示例。 10 | 11 | 我们的读者是任何想要理解如何使用Forge来制作一个mod的人。 12 | 13 | 请不要将这个文档变成一个Java开发教程——该文档的目标读者是那些理解Java类(Class)是怎么工作的,并且知道Java的一些其他基础结构的人。 14 | 15 | 格式 16 | ---- 17 | 18 | !!! important "重要" 19 | 20 | 请使用**两个空格**来缩进,而不是Tab。 (Please use TWO SPACES to indent, not tabs.) 21 | 22 | 标题应该根据标准标题大写格式进行大写。比如, 23 | 24 | * Guide For Contributing to This Documentation 25 | * Building and Testing Your Mod 26 | 27 | 换句话说,大写除了不重要单词以外的所有的单词 28 | 29 | 请使用等号(=)和横线(-)下划线来显示标题,而不是 `#` 和 `##`。对于h3和更低等级的标题,使用 `###` 等是可以的。这个文件的源码包含了等号和横线标题的用法示例。等号创建h1文字,横线创建h2文字。 -------------------------------------------------------------------------------- /docs/tileentities/tesr.md: -------------------------------------------------------------------------------- 1 | TileEntity特殊渲染器 2 | ========================= 3 | 4 | `TileEntitySpecialRenderer`(TESR)是用来渲染不能被一个静态的烘焙模型(Static Baked Model,即JSON、OBJ、B3D等)所表示的方块的。TESR需要方块拥有一个TileEntity。 5 | 6 | 默认情况下OpenGL(`GlStateManager`)是在TESR中处理渲染的。想了解更多的话请看OpenGL的文档。我们推荐尽可能的情况都是使用FastTESR。 7 | 8 | 创建一个TESR 9 | ----------- 10 | 11 | 如果想要创建一个TESR的话,创建一个继承自 `TileEntitySpecialRenderer` 的类。它会需求一个泛型(Generic)的参数用来指定这个方块的TileEntity类。这个泛型参数将会使用在TESR的 `renderTileEntityAt` 方法中。 12 | 13 | 对一个特定的TileEntity只存在有一个TESR。所以,世界中存在于单独实例中的值应该存储在传入TESR的TileEntity中,而不是TESR本身。比如说,有一个整数在每一帧都会递增,如果它储存在TESR中的话,它在每一帧、对世界中每一个该类型的TileEntity都会递增。 14 | 15 | ### `renderTileEntityAt` 16 | 17 | 这个方法在每一帧都会调用以渲染TileEntity。 18 | 19 | #### 参数 20 | 21 | - `tileentity`:这是被渲染TileEntity的实例 22 | - `x`, `y`, `z`:TileEntity需要被渲染的位置 23 | - `partialTicks`: 从上一个整刻(Full Tick)之后经过的时间 24 | - `destroyStage`:方块被破坏的破坏阶段 25 | 26 | 注册TESR 27 | -------- 28 | 29 | 如果想要注册一个TESR的话,可以调用 `ClientRegistry#bindTileEntitySpecialRenderer`,并传入这个TESR所对应的TileEntity类以及TESR的实例。 30 | 31 | `FastTESR` 32 | ---------- 33 | 34 | 一个TESR可以通过选择继承 `FastTESR` 而不是 `TileEntitySpecialRenderer` 并在 `TileEntity#hasFastRenderer` 中返回 true 来变为一个 FastTESR。使用FastTESR的话需要实现的是 `renderTileEntityFast` 而不是 `renderTileEntityAt`。 35 | 36 | FastTESR相比于传统的TESR性能会更好一点,在可能的情况下都应该使用它。这是因为所有的FastTESR实例都被安排到了一起,并且在每一帧只会发送**一个**合并了所有FastTESR的绘制命令到GPU。这个优点的代价是使直接的OpenGL访问(通过 `GlStateManager` 或 `GLXX`)无效。取而代之的是FastTESR必须将顶点添加到所提供的 `VertexBuffer` 中,它表示所有FastTESR合并的顶点数据。这样就可以渲染 `IBakedModel` 了。例子可以在 Forge的 `AnimationTESR` 中找到。 -------------------------------------------------------------------------------- /docs/tileentities/tileentity.md: -------------------------------------------------------------------------------- 1 | # TileEntities 2 | 3 | Tile Entities就像简化的实体一样,绑定到Block上。 4 | 它们用于存储动态数据,执行基于tick的任务以及动态渲染。 5 | 原本 Minecraft的一些例子是:处理库存(箱子),熔炉上的冶炼逻辑或信标的区域效应。 6 | mod中存在更高级的示例,例如采石场,分拣机,管道和显示器。 7 | 8 | !!! note "提示" 9 | 10 | `TileEntities`不是一切的解决方案,如果使用不当会导致卡顿。 11 | 请尽可能不要使用它们。 12 | 13 | ## 创建 `TileEntity` 14 | 15 | 为了创建`TileEntity`,你需要继承`TileEntity`类。 16 | 重要的是你的`TileEntity`有一个默认的构造函数,以便Minecraft可以正确加载它。 17 | 创建类后,需要注册`TileEntity`。 为此你需要调用: 18 | 19 | ```JAVA 20 | GameRegistry#registerTileEntity(Class tileEntityClass, ResourceLocation key) 21 | ``` 22 | 23 | 第一个参数是你的TileEntity类,第二个参数是你的TileEntity的注册表名称。 24 | 此时,您可以选择在`FMLPreInitializationEvent`期间或在`RegistryEvent.Register `事件期间执行此操作,因为`TileEntities`还没有自己的注册表事件。 25 | 26 | !!! note "提示" 27 | 28 | 在Forge版本`14.23.3.2694`之前注册`TileEntity`的方法是使用`String`而不是`ResourceLocation`。 29 | 此方法从String创建`ResourceLocation`。 一定要使用ResourceLocation格式modid:tile_entity来避免它被改为minecraft:tile_entity。 30 | 31 | ## `TileEntity` 连接到方块上 32 | 33 | 要将新的`TileEntity`附加到`Block`,您需要覆盖Block类中的2个方法。 34 | 35 | ```JAVA 36 | Block#hasTileEntity(IBlockstate state) 37 | 38 | Block#createTileEntity(World world, IBlockState state) 39 | ``` 40 | 使用这些参数,您可以选择块是否应该具有`TileEntity`。 41 | 通常,您将在第一个方法中返回`true`,在第二个方法中返回`TileEntity`的新实例。 42 | 43 | ## 用`TileEntity`储存数据 44 | 45 | 可以重写这两个方法来储存数据: 46 | ```JAVA 47 | TileEntity#writeToNBT(NBTTagCompound nbt) 48 | 49 | TileEntity#readFromNBT(NBTTagCompound nbt) 50 | ``` 51 | 每当包含`TileEntity`的区块加载/保存NBT数据时,就会调用这些方法。使用它们来读取和写入TileEntity类中的字段。 52 | 53 | !!! note "提示" 54 | 55 | 每当您的数据发生变化时,您需要调用`TileEntity#markDirty()`,否则在保存世界时可能会跳过包含您的`TileEntity`的区块。 56 | 57 | !!! important "重要" 58 | 59 | 调用父类方法很重要! 60 | 标签名称`id`,`x`,`y`,`z`,`ForgeData`和`ForgeCaps`由父类方法保留。 61 | 62 | ## 通过 `BlockStates`保存 `TileEntity` 63 | 64 | 可能存在需要更改`BlockState`的情况,例如使用原本熔炉,当燃料和内部物品燃烧时,原本熔炉将其状态从`lit=false`改为`lit=true`。 65 | 通过覆盖以下方法实,现这一点非常简单: 66 | 67 | ```JAVA 68 | TileEntity#shouldRefresh(World world, BlockPos pos, IBlockState oldState, IBlockState newSate) 69 | ``` 70 | 71 | !!! important "重要" 72 | 73 | 你应该检查`BlockStates`而不仅仅是返回`false`,而要以防止不必要的行为和错误。 特别是当你的`Block(State)`被另一个取代时,比如说`Air`,也会调用这个方法。 74 | 75 | ## 刷新`TileEntities` 76 | 77 | 如果您需要刷新`TileEntity`,例如为了刷新冶炼过程中的进度,您的`TileEntity`需要实现`net.minecraft.util.ITickable`接口。 78 | 然后,您可以在其中实现所有计算: 79 | 80 | ```JAVA 81 | ITickable#update() 82 | ``` 83 | 84 | !!! note "提示" 85 | 86 | 每个tick都会调用此方法,因此您应该避免在此处进行复杂的计算。 87 | 如果可能的话,您应该在每个X tick处进行更复杂的计算。 88 | (一秒钟内的tick数可能低于20但不会更高) 89 | 90 | ## 将数据同步到客户端 91 | 92 | 有三种方法可以将数据同步到客户端。 93 | 同步区块加载,同步区块更新并与自定义网络消息同步。 94 | 95 | ### 同步区块加载 96 | 97 | 首先你需要重写: 98 | ```JAVA 99 | TileEntity#getUpdateTag() 100 | 101 | TileEntity#handleUpdateTag(NBTTagCompound nbt) 102 | ``` 103 | 104 | 同样,这很简单,第一种方法收集应该发送给客户端的数据,而第二个处理该数据。 如果您的`TileEntity`不包含太多数据,您可以使用[在TileEntity中存储数据][Storing]部分中的方法。 105 | 106 | !!! important "重要" 107 | 108 | 为TileEntities同步过多或无用的数据可能导致网络拥塞。 您应该通过仅在客户端需要时发送客户端所需的信息来优化您的网络使用。 例如,通常不需要在更新标签中发送TileEntity,因为这可以通过GUI同步。 109 | 110 | ### 同步方块更新 111 | 112 | 这个方法有点复杂,但你只需要重写2个方法。 113 | 这是一个很小的示例: 114 | 115 | ```JAVA 116 | @Override 117 | public SPacketUpdateTileEntity getUpdatePacket(){ 118 | NBTTagCompound nbtTag = new NBTTagCompound(); 119 | //在这里写入NBT数据 120 | return new SPacketUpdateTileEntity(getPos(), 1, nbtTag); 121 | } 122 | 123 | @Override 124 | public void onDataPacket(NetworkManager net, SPacketUpdateTileEntity pkt){ 125 | NBTTagCompound tag = pkt.getNbtCompound(); 126 | //处理你的数据 127 | } 128 | ``` 129 | `SPacketUpdateTileEntity`的构造函数需要: 130 | 131 | * `TileEntity`的位置. 132 | * 一个ID,虽然除了原本之外它并没有真正用过,因此你可以在那里放一个1。 133 | * 一个包含你的数据的`NBTTagCompound` 134 | 135 | 除此之外,您现在需要在客户端上生成"BlockUpdate”。 136 | 137 | ```JAVA 138 | World#notifyBlockUpdate(BlockPos pos, IBlockState oldState, IBlockState newState, int flags) 139 | ``` 140 | `pos`是你的TileEntitiy的位置。 对于`oldState`和`newState`,您可以传递当前的BlockState。 141 | 142 | `flags`是一个位掩码,应该包含2,它将把更改同步到客户端。 143 | 144 | ### 使用自定义网络消息进行同步 145 | 146 | 这种同步方式可能是最复杂的方式,但通常也是最优化的方式,因为您可以确保只同步你需要数据。 147 | 在尝试此操作之前,您应首先查看[网络][networking]部分,特别是[SimpleImpl][simple_impl]。 148 | 创建自定义网络消息后,您可以将其发送给加载了“TileEntity”的所有用户: 149 | 150 | ```JAVA 151 | SimpleNetworkWrapper#sendToAllTracking(IMessage, NetworkRegistry.TargetPoint) 152 | ``` 153 | 154 | !!! warning "警告" 155 | 156 | 重要的是你进行安全检查,当消息到达玩家时,TileEntity可能已被销毁/替换! 157 | 你还应该检查是否加载了区块(`World#isBlockLoaded(BlockPos)`) 158 | 159 | [networking]: ../networking/index.md 160 | [simple_impl]: ../networking/simpleimpl.md 161 | [Storing]: #store 162 | 163 | -------------------------------------------------------------------------------- /docs/utilities/oredictionary.md: -------------------------------------------------------------------------------- 1 | 矿物词典 2 | ======= 3 | 4 | 矿物词典(OreDictionary)主要是为了Mod间兼容而存在。 5 | 6 | 已注册到矿物词典的物品将能够代替其它拥有相同矿物词典名的物品。这样就可以使用以上任一物品合成相同的结果。 7 | 8 | 虽然名字是“矿物(Ore)”词典,但是它也可以使用在非矿物的物品上。只要一个物品与另一个物品(比如染料)相似,就都可以注册进矿物词典,并通过矿物词典调用。 9 | 10 | 矿物词典名称规范 11 | --------------- 12 | 13 | !!! note "提示" 14 | 15 | 由于矿物词典名称需要在不同Mod间共享,它们应该是比较统一的。请使用其它Mod可能会使用的名称。 16 | 17 | Forge并没有规定名称需要有某种特定的格式,但下面这些规则现在已经是比较流行的标准了: 18 | 19 | 整个矿物词典名称通常使用驼峰命名法(camelCase,使用小写字母开头,接下来的单词首字母大写的复合词),并且不要使用空格以及下划线。 20 | 21 | 矿物词典名称的第一个单词应该指明物品的类型。对于那些特殊的物品(比如`record`,`dirt`,`egg`,`vine`),一个单词就足够了,不用指明物品类型。 22 | 23 | 名称的最后一部分应当指明物品的材料。这将区分开`ingotIron`与`ingotGold`。 24 | 25 | 如果两个单词还是不够详细,你也可以加上第三个单词。比如说,花岗岩被注册为`blockGranite`,而磨制花岗岩被注册为`blockGranitePolished`。 26 | 27 | 如果想要一份通用前缀以及后缀的列表,见[通用矿物词典名称](#_5)。 28 | 29 | WILDCARD_VALUE 30 | -------------- 31 | 32 | 这个值表示 `ItemStack` 的metadata不重要。使用范例[见下](#_3)。 33 | 34 | 在合成配方中使用矿物词典 35 | --------------------- 36 | 37 | 使用矿物词典的配方在创建与注册上与普通的合成配方差别不大。主要的区别就是逆序要使用一个矿物词典名称而不是一个特定的 `Item` 或 `ItemStack`。 38 | 39 | 如果想要让一个配方使用矿物词典条目,创建一个 `ShapelessOreRecipe` 条目或者 `ShapedOreRecipe` 实例,并且调用 `GameRegistry.addRecipe(IRecipe recipe)` 注册它。 40 | 41 | !!! note "提示" 42 | 43 | 你可以通过调用 `OreDictionary.doesOreNameExist(String name)`,检测返回值是否为一个有效的 `ItemStack` 来验证矿物词典名称的有效性。 44 | 45 | 在合成中矿物词典的另一种用途就是 `[WILDCARD_VALUE]()`,你可以将 `OreDictionary.WILDCARD_VALUE` 传入一个 `ItemStack` 的构造器。 46 | 47 | !!! note "提示" 48 | 49 | `OreDictionary.WILDCARD_VALUE` 应该只被用在配方的输入中,在配方输出中使用 `WILDCARD_VALUE` 只会改变输出 `ItemStack` 的损害值(Damage)。 50 | 51 | 注册物品至矿物词典 52 | ---------------- 53 | 54 | 你应该在 `FMLPreInitializationEvent` 阶段,在初始化需要注册的方块以及物品之后添加条目到矿物词典。 55 | 56 | 只需要调用 `OreDictionary.registerOre(ItemStack stack, String name)`,并传入包含物品或方块以及其metadata值,就能够将其注册至矿物词典。 57 | 58 | 你也可以调用 `OreDictionary.registerOre` 的重载函数,直接传递 `Block` 或 `Item` 即可,不需要自己手动创建一个 `ItemStack` 了。 59 | 60 | 如果需要一个 `ItemStack` 矿物词典命名的帮助,见[矿物词典名称规范](#_2)。 61 | 62 | 通用矿物词典名称 63 | -------------- 64 | 65 | 原版Minecraft物品以及方块的矿物词典名称可以在 `net.minecraftforge.oredict.OreDictionary` 中找到。完整的列表将不会包含在这里。 66 | 67 | 在矿物词典中已经使用的通用前缀包括 `ore`,`ingot`,`nugget`,`dust`,`gem`,`dye`,`block`,`stone`,`crop`,`slab`,`stair`,和 `pane`。 68 | 69 | 在Mod物品中的通用前缀包括 `gear`,`rod`,`stick`,`plate`,`dustTiny`,和 `cover`。 70 | 71 | 在矿物词典中已经使用的通用后缀包括 `Wood`,`Glass`,`Iron`,`Gold`,`Leaves`,和 `Brick`。 72 | 73 | 在Mod物品中的通用前缀包括金属的名称(`Copper`,`Aluminum`,`Lead`,`Steel` 等)以及其它材料。 -------------------------------------------------------------------------------- /docs/utilities/permissionapi.md: -------------------------------------------------------------------------------- 1 | 权限API 2 | ============= 3 | 4 | 权限API是权限系统的一个基础的实现。 5 | 6 | 它默认的实现不是增加一个先进的权限管理(例如我们知道的PEX),但它有3个权限级别,(ALL = 所有玩家, OP = 操作者, NONE = 未知是普通玩家还是操作者)。 7 | 8 | 这些行为可以由mod实现自己的`PermissionHandler`改变 9 | 10 | 如何使用权限API 11 | ----------------------------- 12 | 13 | 要基础的支持只需要调用 `PermissionAPI.hasPermission(EntityPlayer player, String node)`,但这默认总是返回false,因为默认的实现使用权限级别`NONE`。所以我们要想要全部玩家,或只有OP才能使用,要注册自己的权限节点。这和检查权限一样简单`PermissionAPI.registerNode(String node, DefaultPermissionLevel level, String description)`,尽管它会在之后完成。 14 | 15 | !!! note "提示" 16 | 17 | 权限API不限于用于命令,你可以用它干其它事情,如限制对GUI的访问。 18 | 另外,你将它与命令结合,你需要检查`ICommandSender`是否为玩家! 19 | 20 | `DefaultPermissionLevel` 21 | -------------- 22 | 23 | `DefaultPermissionLevel` 有3个值: 24 | 25 | * ALL =所有玩家都有此权限 26 | * OP = 只有操作者有此权限 27 | * NONE = 不管是普通玩家还是操作者都有此权限 28 | 29 | 权限节点 30 | --------------------------------------- 31 | 32 | 虽然技术上没有权限节点的规则,但最佳做法是用`modid.subgroup.permission_id`形式。 33 | 建议使用此命名方案,因为其他实现可能具有更严格的规则。 34 | 35 | 自己实现 `PermissionHandler` 36 | -------------------------------------- 37 | 38 | 默认`PermissionHandler `是非常基础的,但对绝大多数用户已经足够了。但你想在一个大型服务器里控制更多权限。可以通过自定义`PermissionHandler`来实现。 39 | 40 | 它如何工作以及它的功能完全有你决定,例如你可以做一个简单的实现,只需为每个玩家保存一个文件。 41 | 或者你可以使它像PEX一样先进,有数据库支持和许多其他功能。 42 | 43 | !!! note "提示" 44 | 45 | 并非每个想要使用PermissionAPI的mod都应该更改PermissionHandler,因为同时只能有1个! 46 | 47 | 首先,您如何实现自己的PermissionHandler完全取决于你,你可以使用文件,数据库或其它任何方式。你所要做的只是实现接口`IPermissionHandler`。在这之后,你需要这样注册它:`PermissionAPI.setPermissionHandler(IPermissionHandler handler)` 48 | 49 | !!! note "提示" 50 | 51 | 你必须在PreInit阶段设置Handle! 52 | 建议你检查它是否已被其他mod替换。 -------------------------------------------------------------------------------- /docs/utilities/recipes.md: -------------------------------------------------------------------------------- 1 | 合成 2 | ======= 3 | 4 | 随着Minecraft 1.12的更新,Mojang引入了一种基于JSON文件的新型数据驱动的合成系统。在那之后Forge也改成了这个,Minecraft 1.13中拓展成了数据包。 5 | 6 | 加载配方 7 | --------------- 8 | Forge会加载在`./assets//recipes/`目录下的所有配方。你可以随意调用这些文件,考虑到原版约定是在输出物品之后命名它们。 此名称也用作注册密钥,但不影响配方的操作。 9 | 10 | !!! note "提示" 11 | 12 | 配方文件不能以下划线开头,因为这是为静态文件保留的。 JSON文件扩展名是必需的。 13 | 14 | 配方文件 15 | --------------- 16 | 17 | 一个基本的配方文件应该如下: 18 | 19 | ```json 20 | { 21 | "type": "minecraft:crafting_shaped", 22 | "pattern": 23 | [ 24 | "xxa", 25 | "x x", 26 | "xxx" 27 | ], 28 | "key": 29 | { 30 | "x": 31 | { 32 | "type": "forge:ore_dict", 33 | "ore": "gemDiamond" 34 | }, 35 | "a": 36 | { 37 | "item": "mymod:myfirstitem", 38 | "data": 1 39 | } 40 | }, 41 | "result": 42 | { 43 | "item": "mymod:myitem", 44 | "count": 9, 45 | "data": 2 46 | } 47 | } 48 | ``` 49 | 50 | !!! note "提示" 51 | 52 | 当您首次获得原版合成的配方时,它将自动解锁合成书中的配方。 要达到同样的效果,您必须使用[成就(未完成)][Advancements]系统并为每种合成创建新的成就。 53 | 54 | 如果一个成就存在,并不意味着它可在成就树中被看见。 55 | 56 | ### 类型 (Type) 57 | 58 | 合成的类型。你可以把它当作定义用哪一种合成布局,例如 `minecraft:crafting_shaped` (有序合成)或 `minecraft:crafting_shapeless`(无序合成). 59 | 60 | 你也可以定义你自己的合成类型,只需要使用[`_factories.json`][Factories]文件。 61 | 62 | ### 组 (Groups) 63 | 64 | 你还可以把你的配方增加到一个组里,这样可以在合成帮助接口里一起显示。group字符串相同的配方会放在同一个组里。例如,这可以用于所有的门的合成配方,这样即使有很多种不同的门,在合成帮助里只会有一个条目。 65 | 66 | 配方的类型 67 | ----------------------------- 68 | 这样的话,我们可以仔细看一下有序合成和无序合成定义上的区别。 69 | 70 | ### 有序合成 71 | 72 | 有序合成需要`pattern`和`key`两个关键字。pattern定义物品的排序,它必须以占位符的形式。每一个物品你都可以选择任意的一个字符作为占位符。而key定义了占位符对应的物品。可以添加附加属性`forge:ore_dict` ,它可以定义物品是[矿物词典][OreDictionary]的一部分。例如,无论什么铜矿都可以生成铜锭。要这样的效果的话,要用`org`标签定义物品,而不是用`item`定义那个物品。默认有[很多][Wiki]这样的类型,你也可以自己定义。`data`是一个可选标签,用于定义方块或物品的metadata。 73 | 74 | !!! important "重要" 75 | 76 | 用了`setHasSubtypes(true)`的物品必须要`data`字段。如果不用,那么意味着有着任何metadata的该物品都可以用于合成。例如,不定义剑的数据意味着用了一半的剑也可以用于合成! 77 | 78 | 79 | ### 无序合成 80 | 81 | 无序合成不需要`pattern`和`key`关键字。 82 | 83 | 要定义一个无序合成,你要使用`ingredients`列表。它定义了合成中要用的物品,也可以用`forge:ore_dict`它函数式的声明在上方。默认有[很多][Wiki]这样的类型,你也可以自己定义。它甚至可以一个对象定义多个实例,意味着合成时必须要在合成台里放多个这样的物品。 84 | 85 | !!! note "提示" 86 | 87 | 你的配方里有多少ingredients没有限制,原版合成台没有只允许一个合成里放9个物品。 88 | 89 | 下面这个例子展示了ingredients在JSON里是什么样的: 90 | 91 | ```json 92 | "ingredients": [ 93 | { 94 | "type": "forge:ore_dict", 95 | "ore": "gemDiamond" 96 | }, 97 | { 98 | "item": "minecraft:nether_star" 99 | } 100 | ], 101 | ``` 102 | 103 | ### 烧炼 104 | 要定义一个熔炉的烧炼,你要用 `GameRegistry.addSmelting(input, output, exp);`,因为烧炼现在不是基于JSON的。 105 | 106 | 合成元素 107 | --------------- 108 | 109 | ### Patterns 110 | 111 | pattern用`pattern`列表定义。每一个字符串代表合成网格中的一行,每个占位符代表一列。在上面的例子中可以看到空格表示那个位置不需要物品。 112 | 113 | ### Keys 114 | 115 | Key集合用于绑定pattern,其键为列表里的占位符。一个key可以定义多个物品,例如木制按钮。这意味着玩家可以列表中的任意一个合成,例如不同种的木头。 116 | 117 | ```json 118 | "key": { 119 | "#": [ 120 | { 121 | "item": "minecraft:planks", 122 | "data": 0 123 | }, 124 | { 125 | "item": "minecraft:planks", 126 | "data": 1 127 | } 128 | ] 129 | } 130 | ``` 131 | 132 | ### Results 133 | 134 | 每个合成必须有result标签用于定义输出。 135 | 136 | 有时当合成东西时,你可以得到不止1个物品。这是由定义`count`数值实现的。如果忽略了它,意味着它没有输出方块,它默认为1.不能是负数因为Itemstack不能小于0.除result外,其它地方不能使用`count`。 137 | 138 | `data`字段是可选的,用于定义方块或物品的metadata。不存在是默认为0. 139 | 140 | !!! note "提示" 141 | 142 | 使用`setHasSubtypes(true)`的物品必须要data字段,这种情况下它不是可选的。 143 | 144 | 工厂 145 | --------- 146 | 工厂可以定义配方和自定义类型的原料。要创建一个工厂,先创建一个`_factories.json`,在这个文件中定义合成的类型,例如 `recipes`, `ingredients` 和 `conditions`这些类型各自代表了`IRecipeFactory `, `IIngredientFactory`, 和`IConditionFactory`,主键是可以之后用于配方的`name,`值是全类名,这个类必须实现上面的一个接口。该类必须有空的构造方法。例如: 147 | 148 | ```json 149 | { 150 | "": { 151 | "": "" 152 | } 153 | } 154 | ``` 155 | 156 | !!! note "提示" 157 | 158 | 没必要每种类型都创建一个新的`_factories.json`,它们可以定义在一个文件中。 159 | 160 | ### 条件配方 161 | 162 | 条件配方可以通过上述工厂系统创建,为此你可以用`conditions`类型和 上面的`IConditionFactory` 一起用,然后你可以把`conditions`添加到你的配方中: 163 | 164 | ```json 165 | { 166 | "conditions": [ 167 | { 168 | "type": ":" 169 | } 170 | ] 171 | } 172 | ``` 173 | 这些条件仅适用于整个配方,而不是一种原料。例如,你想用已有的条件`forge:mod_loaded`, 和`"modid": ""`检查一个mod是否加载。 174 | 175 | !!! note "提示" 176 | 177 | 条件只会在启动时检查。 178 | 179 | 常量 180 | --------- 181 | 它可以为你的配方定义常量。这些值定义在`_constants.json`中,只需要在你mod的配方里写`#`。对装满的桶,你应该使用 `fluid`而不是`data`。例如,这个常量定义了`#SADDLE为原版的马鞍。 182 | 183 | ```json 184 | [ 185 | { 186 | "name": "SADDLE", 187 | "ingredient": { 188 | "item": "minecraft:saddle", 189 | "data": 0 190 | } 191 | } 192 | ] 193 | ``` 194 | 195 | [Wikipedia]: https://en.wikipedia.org/wiki/Factory_(object-oriented_programming) 196 | [OreDictionary]: ../utilities/oredictionary.md 197 | [Advancements]: # 198 | [Wiki]: https://minecraft.gamepedia.com/Recipe 199 | [Factories]: #factories 200 | -------------------------------------------------------------------------------- /forge_theme/404.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |

页面未找到

5 |

很遗憾,请求的页面不存在。

6 | {% endblock %} 7 | 8 | {% block next_prev %} 9 | {% endblock %} -------------------------------------------------------------------------------- /forge_theme/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {% block title_prefix %}{% endblock %}{% if not page.is_homepage %}{{ page.title }} - {% endif %}{{ config.site_name }} 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | {% for path in extra_css %} 25 | 26 | {% endfor %} 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | {% for path in extra_javascript %} 38 | 39 | {% endfor %} 40 | 41 | 42 | 43 | 47 | 48 | 49 |
50 |
51 | 55 | 60 | {% block search_form %} 61 | 67 | {% endblock %} 68 |
69 |
70 |
71 | 93 | 94 | 113 |
114 |
115 |
116 | 基于 MkDocs 使用自定义主题构建. 托管于 Read the Docs. 117 |
118 | 启用夜间模式 119 | 123 |
124 |
125 |
126 | 127 | 128 | -------------------------------------------------------------------------------- /forge_theme/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | #ff0000 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /forge_theme/images/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Krasjet/Forge-Documentation-CN/a4ea75c05a400af6417d9778c994dd065b26e789/forge_theme/images/apple-touch-icon.png -------------------------------------------------------------------------------- /forge_theme/images/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Krasjet/Forge-Documentation-CN/a4ea75c05a400af6417d9778c994dd065b26e789/forge_theme/images/favicon-16x16.png -------------------------------------------------------------------------------- /forge_theme/images/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Krasjet/Forge-Documentation-CN/a4ea75c05a400af6417d9778c994dd065b26e789/forge_theme/images/favicon-32x32.png -------------------------------------------------------------------------------- /forge_theme/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Krasjet/Forge-Documentation-CN/a4ea75c05a400af6417d9778c994dd065b26e789/forge_theme/images/favicon.ico -------------------------------------------------------------------------------- /forge_theme/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 13 | 14 | 15 | 17 | 24 | 28 | 34 | 36 | 37 | 42 | 43 | 44 | 45 | 47 | 54 | 58 | 64 | 66 | 67 | 72 | 73 | 75 | 77 | 79 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /forge_theme/images/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /forge_theme/js/theme-switch.js: -------------------------------------------------------------------------------- 1 | window.forge={THEME_LIGHT:"light",THEME_DARK:"dark",swapThemeCSS:function(d){var c=document.styleSheets,e=c.length,a;for(a=0;a 3 | {% if nav_item.active %} 4 | {{ nav_item.title }} 5 | {% else %} 6 | {{ nav_item.title }} 7 | {% endif %} 8 | {% if nav_item.active %} 9 |
    10 | {% for toc_item in page.toc %} 11 | {% if toc_item != (page.toc|first) %} 12 |
  • {{ toc_item.title }}
  • 13 | {% endif %} 14 | {% for toc_item in toc_item.children %} 15 |
  • {{ toc_item.title }}
  • 16 | {% endfor %} 17 | {% endfor %} 18 |
19 | {% endif %} 20 | 21 | {% else %} 22 |
  • 23 | {% if nav_item.children.active %} 24 | {{ nav_item.title }} 25 | {% else %} 26 | {{ nav_item.title }} 27 | {% endif %} 28 | 33 |
  • 34 | {% endif %} 35 | -------------------------------------------------------------------------------- /forge_theme/search.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title_prefix %}搜索结果{% endblock %} 4 | 5 | {% block search_form %} 6 | 14 | {% endblock %} 15 | 16 | {% block content %} 17 |

    搜索结果

    18 | 19 |
    20 | 正在搜索... 21 |
    22 | {% endblock %} 23 | 24 | {% block next_prev %} 25 | {% endblock %} 26 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | pages: 2 | - 主页: 'index.md' 3 | - 样式指南: 'styleguide.md' 4 | - 入门: 5 | - 概述: 'gettingstarted/index.md' 6 | - Mod的结构: 'gettingstarted/structuring.md' 7 | - Forge更新检查器: 'gettingstarted/autoupdate.md' 8 | - 依赖管理: 'gettingstarted/dependencymanagement.md' 9 | - 调试分析器: 'gettingstarted/debugprofiler.md' 10 | - 概念: 11 | - Sides: 'concepts/sides.md' 12 | - 资源: 'concepts/resources.md' 13 | - 注册表: 'concepts/registries.md' 14 | - Jar签名: 'concepts/jarsigning.md' 15 | - 国际化和本地化: 'concepts/internationalization.md' 16 | - 方块: 17 | - 概述: 'blocks/blocks.md' 18 | - 介绍方块状态: 'blocks/states.md' 19 | - 方块互动: 'blocks/interaction.md' 20 | - 动画 API: 21 | - 概述: 'animation/intro.md' 22 | - 骨骼: 'animation/armature.md' 23 | - 动画状态机: 'animation/asm.md' 24 | - 使用API: 'animation/implementing.md' 25 | - TileEntity: 26 | - 概述: 'tileentities/tileentity.md' 27 | - 特殊渲染器: 'tileentities/tesr.md' 28 | - 物品: 29 | - 主页: 'items/items.md' 30 | - 战利品表: 'items/loot_tables.md' 31 | - 模型: 32 | - 模型概述: 'models/introduction.md' 33 | - 模型文件: 'models/files.md' 34 | - 方块状态: 35 | - 方块状态JSON概述: 'models/blockstates/introduction.md' 36 | - Forge方块状态JSON: 'models/blockstates/forgeBlockstates.md' 37 | - 绑定模型到方块和物品: 'models/using.md' 38 | - 彩色纹理: 'models/color.md' 39 | - 物品属性概述: 'models/overrides.md' 40 | - 高级模型(未翻译): 41 | - 高级模型介绍: 'models/advanced/introduction.md' 42 | - IModel: 'models/advanced/imodel.md' 43 | - IModelState and IModelPart: 'models/advanced/imodelstate+part.md' 44 | - IBakedModel: 'models/advanced/ibakedmodel.md' 45 | - Extended Blockstates: 'models/advanced/extended-blockstates.md' 46 | - Perspective: 'models/advanced/perspective.md' 47 | - ItemOverrideList: 'models/advanced/itemoverridelist.md' 48 | - ICustomModelLoader: 'models/advanced/icustommodelloader.md' 49 | - 渲染: 50 | - TileEntityItemStackRenderer: 'rendering/teisr.md' 51 | - 事件: 52 | - 基本用法: 'events/intro.md' 53 | - 网络: 54 | - 主页: 'networking/index.md' 55 | - 概述: 'networking/overview.md' 56 | - SimpleImpl: 'networking/simpleimpl.md' 57 | - 实体: 'networking/entities.md' 58 | - 数据储存: 59 | - 能力系统: 'datastorage/capabilities.md' 60 | - World Saved Data: 'datastorage/worldsaveddata.md' 61 | - 拓展实体属性: 'datastorage/extendedentityproperties.md' 62 | - Config注解: 'config/annotations.md' 63 | - 工具: 64 | - 合成: 'utilities/recipes.md' 65 | - 矿物词典: 'utilities/oredictionary.md' 66 | - 权限API: 'utilities/permissionapi.md' 67 | - 效果: 68 | - 音效: 'effects/sounds.md' 69 | - 惯例: 70 | - 版本命名: 'conventions/versioning.md' 71 | - 文件位置: 'conventions/locations.md' 72 | - 加载阶段: 'conventions/loadstages.md' 73 | - 参与Forge开发: 74 | - 入门: 'forgedev/index.md' 75 | - PR指南: 'forgedev/prguidelines.md' 76 | 77 | # Do not edit in PRs below here 78 | site_name: Forge文档 79 | 80 | markdown_extensions: 81 | - admonition 82 | - smarty 83 | - sane_lists 84 | - pymdownx.superfences 85 | - pymdownx.highlight: 86 | use_pygments: false 87 | - toc: 88 | permalink: ' ' 89 | 90 | theme: 91 | name: null 92 | custom_dir: forge_theme 93 | # Technically deprecated, but RTD overrides theme.custom_dir with their default style 94 | # They still seem to respect the old option, though 95 | theme_dir: forge_theme 96 | 97 | extra: 98 | current_version: 1.12.x 99 | versions: 100 | 1.12.x: latest -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pymdown-extensions 2 | --------------------------------------------------------------------------------