├── .github └── workflows │ ├── Build.yml │ ├── Document.yml │ ├── Release.yml │ └── ReleaseMessage.md ├── .gitignore ├── GFX ├── Discord.png ├── Statistics.gif ├── compound-eye.ase ├── eye.ase └── recipe-drawer.ase ├── LICENSE ├── README.md ├── build.gradle.kts ├── buildSrc ├── build.gradle.kts └── src │ └── net │ └── liplum │ └── DistributeInjection.kt ├── docs ├── api │ ├── drawer.md │ └── multicrafter.md ├── assets │ ├── customizedIcon-alphaaaa.png │ ├── customizedIcon-mono.png │ ├── draw-recipe.png │ ├── draw-recipes.gif │ ├── menu │ │ ├── detailed-1.png │ │ ├── detailed-2.png │ │ ├── number.png │ │ ├── simple.png │ │ ├── transform-1.png │ │ └── transform-2.png │ └── test-drawer │ │ ├── mine-crafter-1.png │ │ ├── mine-crafter-2.png │ │ └── mine-crafter-3.png ├── customize │ ├── builtin.md │ ├── drawer.md │ └── menu.md ├── favicon.png ├── icon.png ├── index.md ├── migration.md ├── troubleshooting.md └── usage │ ├── java.md │ └── json.md ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── icon.png ├── inject.bat ├── inject.sh ├── injection ├── assets │ └── scripts │ │ └── multi-crafter │ │ └── lib.js ├── build.gradle.kts └── src │ └── MultiCrafterAdapter.java ├── java ├── assets │ └── content │ │ └── blocks │ │ └── test.hjson ├── build.gradle.kts └── src │ └── WithJsonMod.java ├── js ├── .gitignore ├── assets │ ├── content │ │ └── blocks │ │ │ ├── crafter │ │ │ ├── json.hjson │ │ │ ├── no-hjson.json │ │ │ ├── no-io.json │ │ │ ├── overpower.hjson │ │ │ └── zero-power.hjson │ │ │ ├── in-hjson.hjson │ │ │ └── test-drawer.hjson │ ├── scripts │ │ └── main.js │ └── sprites │ │ ├── test-drawer-1.png │ │ ├── test-drawer-2.png │ │ └── test-drawer-3.png ├── build.gradle.kts └── mod.hjson ├── lib ├── .gitignore ├── assets │ └── scripts │ │ └── multi-crafter │ │ └── lib.js ├── build.gradle.kts ├── src │ └── multicraft │ │ ├── ConsumeFluidDynamic.java │ │ ├── ContentResolver.java │ │ ├── CustomConsumePayloadDynamic.java │ │ ├── DrawHeatRegion.java │ │ ├── DrawRecipe.java │ │ ├── IOEntry.java │ │ ├── MultiCrafter.java │ │ ├── MultiCrafterParser.java │ │ ├── ParserUtils.java │ │ ├── Recipe.java │ │ ├── RecipeParserException.java │ │ ├── RecipeSwitchStyle.java │ │ └── ui │ │ ├── FluidImage.java │ │ ├── HeatImage.java │ │ ├── PayloadImage.java │ │ └── PowerImage.java └── test │ └── TestParser.java ├── main ├── .gitignore ├── assets │ └── scripts │ │ ├── lib.js │ │ └── main.js ├── build.gradle.kts └── src │ └── net │ └── liplum │ ├── MultiCrafterMod.java │ ├── MultiJavaAdapter.java │ └── TestBlocks.java ├── mkdocs.yml ├── mod.hjson ├── overrides └── main.html ├── settings.gradle.kts └── standalone ├── assets ├── content │ └── blocks │ │ └── json.hjson └── scripts │ └── main.js ├── build.gradle.kts └── mod.hjson /.github/workflows/Build.yml: -------------------------------------------------------------------------------- 1 | name: Build Mod 2 | on: [push, pull_request, workflow_dispatch] 3 | 4 | jobs: 5 | build: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v4 9 | 10 | - name: Set Android SDK tools 11 | run: echo "$ANDROID_HOME/build-tools/34.0.0" >> $GITHUB_PATH 12 | 13 | - name: Set up JDK 17 14 | uses: actions/setup-java@v4 15 | with: 16 | java-version: '17' 17 | distribution: 'temurin' 18 | cache: 'gradle' 19 | 20 | - name: Setup gradle 21 | run: | 22 | chmod +x gradlew 23 | 24 | - name: MultiCrafter unit test 25 | run: | 26 | ./gradlew test 27 | 28 | - name: Build MultiCrafter 29 | run: | 30 | ./gradlew :main:deploy 31 | ./gradlew distInjection 32 | 33 | - uses: actions/upload-artifact@v3 34 | with: 35 | name: MultiCrafterMod 36 | path: main/build/tmp/deploy/*.jar 37 | - uses: actions/upload-artifact@v3 38 | with: 39 | name: MultiCrafterLibDist 40 | path: build/tmp/distInjection/*.zip 41 | -------------------------------------------------------------------------------- /.github/workflows/Document.yml: -------------------------------------------------------------------------------- 1 | name: Deploy MkDocs to gh-pages 2 | on: workflow_dispatch 3 | 4 | jobs: 5 | deploy: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v4 9 | 10 | - uses: actions/setup-python@v4 11 | with: 12 | python-version: 3.x 13 | 14 | - run: pip install mkdocs-material 15 | - run: mkdocs gh-deploy --force -------------------------------------------------------------------------------- /.github/workflows/Release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: workflow_dispatch 3 | 4 | jobs: 5 | build: 6 | runs-on: ubuntu-latest 7 | env: 8 | GH_REPO: ${{ github.repository }} 9 | steps: 10 | - uses: actions/checkout@v4 11 | 12 | - name: Set Android SDK tools 13 | run: echo "$ANDROID_HOME/build-tools/34.0.0" >> $GITHUB_PATH 14 | 15 | - name: Set up JDK 17 16 | uses: actions/setup-java@v4 17 | with: 18 | java-version: '17' 19 | distribution: 'temurin' 20 | cache: 'gradle' 21 | 22 | - name: Build MultiCrafter 23 | run: | 24 | chmod +x gradlew 25 | ./gradlew :main:deploy 26 | ./gradlew distInjection 27 | 28 | - name: Move building 29 | run: | 30 | mkdir out 31 | cp main/build/tmp/deploy/*.jar out 32 | cp build/tmp/distInjection/*.zip out 33 | 34 | - name: Upload building 35 | uses: actions/upload-artifact@v3 36 | with: 37 | name: MultiCrafter 38 | path: out/* 39 | 40 | - name: Retrieve meta 41 | id: retrieveMeta 42 | run: | 43 | chmod +x gradlew 44 | ./gradlew retrieveMeta 45 | 46 | - name: Create release 47 | if: ${{ steps.retrieveMeta.outputs.tag_exist == 'false' }} 48 | env: 49 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 50 | HEADER: ${{ steps.retrieveMeta.outputs.header }} 51 | VERSION: ${{ steps.retrieveMeta.outputs.version }} 52 | TAG_EXIST: ${{ steps.retrieveMeta.outputs.tag_exist }} 53 | run: | 54 | gh release create "$VERSION" --title "$HEADER" --notes-file "$GITHUB_WORKSPACE/.github/workflows/ReleaseMessage.md" out/* 55 | 56 | - name: Update release file if possible 57 | if: always() 58 | env: 59 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 60 | VERSION: ${{ steps.retrieveMeta.outputs.version }} 61 | run: | 62 | gh release upload "$VERSION" out/* --clobber -------------------------------------------------------------------------------- /.github/workflows/ReleaseMessage.md: -------------------------------------------------------------------------------- 1 | ## How to use MultiCrafter Lib 2 | You can learn how to use this mod on [this instruction](https://liplum.github.io/MultiCrafterLib/). 3 | 4 | The mod file is `MultiCrafterLib-.jar`. 5 | 6 | For `Injection` use case, please download and unzip `MultiCrafterLib-injection.zip`. -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .idea 3 | build 4 | /local.properties 5 | standalone/injection 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /GFX/Discord.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liplum/MultiCrafterLib/5068f7184487e5157711260619a25e8dbca5320f/GFX/Discord.png -------------------------------------------------------------------------------- /GFX/Statistics.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liplum/MultiCrafterLib/5068f7184487e5157711260619a25e8dbca5320f/GFX/Statistics.gif -------------------------------------------------------------------------------- /GFX/compound-eye.ase: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liplum/MultiCrafterLib/5068f7184487e5157711260619a25e8dbca5320f/GFX/compound-eye.ase -------------------------------------------------------------------------------- /GFX/eye.ase: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liplum/MultiCrafterLib/5068f7184487e5157711260619a25e8dbca5320f/GFX/eye.ase -------------------------------------------------------------------------------- /GFX/recipe-drawer.ase: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liplum/MultiCrafterLib/5068f7184487e5157711260619a25e8dbca5320f/GFX/recipe-drawer.ase -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # [MultiCrafter Lib](https://liplum.github.io/MultiCrafterLib/) [![Discord](GFX/Discord.png)](https://discord.gg/PDwyxM3waw) 4 | 5 | [![Discord](https://img.shields.io/discord/937228972041842718?color=%23529b69&label=Discord&logo=Discord&style=for-the-badge)](https://discord.gg/PDwyxM3waw) 6 | [![TotalDownloads](https://img.shields.io/github/downloads/liplum/MultiCrafterLib/total?color=674ea7&label=Download&logo=docusign&logoColor=white&style=for-the-badge)](https://github.com/liplum/MultiCrafterLib/releases) 7 | [![](https://jitpack.io/v/liplum/MultiCrafterLib.svg)](https://jitpack.io/#liplum/MultiCrafterLib) 8 | 9 | A Mindustry MultiCrafter lib-mod for Json and JavaScript mods. 10 | Please check the [instruction](https://liplum.github.io/MultiCrafterLib/). 11 | ___ 12 |
13 | 14 | ## How to Use 15 | 16 | Please check the [instruction](https://liplum.github.io/MultiCrafterLib/) to learn MultiCrafter. 17 | 18 | ## Showcase 19 | ![Statistics](GFX/Statistics.gif) 20 | 21 | ## Awesome 22 | 23 | Some mods using *MultiCrafter Lib* are listed here, you may learn from them. 24 | 25 | - [Java] [Omaloon](https://github.com/xStaBUx/Omaloon-mod-public) by `xStaBUx` 26 | 27 | - [Hjson/Json] [Polgranium Works](https://github.com/Stieno3000/Polgranium-Works) by [`Stieno3000`] 28 | 29 | - [Json] [Z.P.G.M._Mod](https://github.com/r-omnom/Z.P.G.M._Mod) by `r-omnom` 30 | 31 | - [Json] [Sapphirium](https://github.com/3Snake3/Sapphirium) by `3Snake3` 32 | 33 | - [Java] [Fictional Octo System](https://github.com/TeamOct/fictional-octo-system) by `Team Oct` 34 | 35 | - [Java] [Stuasut](https://github.com/Nezerit6/Stuasut) by Nezerit6 36 | 37 | - [Java] [Axthrix Java](https://github.com/Otamamori917/Axthrix-Modded-Java) by `Otamamori` 38 | 39 | - [Json] [Pyra Erekir](https://github.com/valigaming15/pyra-erekir) By `Valigaming15` & `Otamamori` 40 | 41 | - [Json] [Nucleardustry](https://github.com/Chrono-Heritage/Nucleardustry) by `Chrono Heritage` 42 | 43 | - [Java] [Unlimited Armament Works](https://github.com/Eschatologue/Unlimited-Armament-Works) by `Eschatologue` 44 | 45 | - [Java] [Aeyama: The New World](https://github.com/Aeyama-Mod/aeyama) by `FredyJabe` & `Jojo` 46 | 47 | Make a pull request to tell us if your mod is using MultiCrafter Lib. :rocket::rocket::rocket: 48 | 49 | ## Licence 50 | 51 | GNU General Public License v3.0 52 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import io.github.liplum.mindustry.minGameVersion 2 | 3 | plugins { 4 | `maven-publish` 5 | id("io.github.liplum.mgpp") version "1.3.2" 6 | } 7 | buildscript { 8 | repositories { 9 | mavenCentral() 10 | gradlePluginPortal() 11 | maven { url = uri("https://raw.githubusercontent.com/Zelaux/MindustryRepo/master/repository") } 12 | maven { url = uri("https://www.jitpack.io") } 13 | } 14 | } 15 | allprojects { 16 | group = "net.liplum" 17 | version = "2.0.0" 18 | buildscript { 19 | repositories { 20 | maven { url = uri("https://raw.githubusercontent.com/Zelaux/MindustryRepo/master/repository") } 21 | maven { url = uri("https://www.jitpack.io") } 22 | } 23 | } 24 | repositories { 25 | mavenCentral() 26 | maven { url = uri("https://raw.githubusercontent.com/Zelaux/MindustryRepo/master/repository") } 27 | maven { url = uri("https://www.jitpack.io") } 28 | } 29 | 30 | //force arc version 31 | configurations.all { 32 | resolutionStrategy { 33 | eachDependency { 34 | if(this.requested.group == "com.github.Anuken.Arc") { 35 | this.useVersion("v146") 36 | } 37 | } 38 | } 39 | } 40 | 41 | tasks.withType().configureEach { 42 | useJUnitPlatform() 43 | testLogging { 44 | exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL 45 | showStandardStreams = true 46 | } 47 | } 48 | } 49 | mindustry { 50 | dependency { 51 | mindustry on "v146" 52 | arc on "v146" 53 | } 54 | client { 55 | mindustry official "v146" 56 | } 57 | server { 58 | mindustry official "v146" 59 | } 60 | run { 61 | clearOtherMods 62 | } 63 | } 64 | 65 | tasks.register("distInjection") { 66 | group = "build" 67 | dependsOn(":injection:deploy") 68 | jar.from(tasks.getByPath(":injection:deploy")) 69 | name.set("MultiCrafterLib-injection.zip") 70 | excludeFiles.add(File("icon.png")) 71 | excludeFiles.add(File("mod.hjson")) 72 | excludeFolders.add(File("META-INF")) 73 | } 74 | 75 | tasks.register("retrieveMeta") { 76 | doLast { 77 | println("::set-output name=header::${rootProject.name} v$version on Mindustry v${mindustry.meta.minGameVersion}") 78 | println("::set-output name=version::v$version") 79 | try { 80 | val releases = java.net.URL("https://api.github.com/repos/liplum/MultiCrafterLib/releases").readText() 81 | val gson = com.google.gson.Gson() 82 | val info = gson.fromJson>>(releases, List::class.java) 83 | val tagExisted = info.any { 84 | it["tag_name"] == "v$version" 85 | } 86 | println("::set-output name=tag_exist::$tagExisted") 87 | } catch (e: Exception) { 88 | println("::set-output name=tag_exist::false") 89 | logger.warn("Can't fetch the releases", e) 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "1.9.21" 3 | groovy 4 | java 5 | } 6 | buildscript{ 7 | dependencies{ 8 | classpath(kotlin("gradle-plugin", version = "1.9.21")) 9 | } 10 | } 11 | repositories { 12 | mavenCentral() 13 | maven { url = uri("https://raw.githubusercontent.com/Zelaux/MindustryRepo/master/repository") } 14 | maven { url = uri("https://www.jitpack.io") } 15 | } 16 | sourceSets { 17 | main { 18 | java.srcDir("src") 19 | } 20 | } 21 | dependencies { 22 | implementation(gradleApi()) 23 | implementation("net.lingala.zip4j:zip4j:2.11.1") 24 | implementation("com.google.code.gson:gson:2.9.0") 25 | } -------------------------------------------------------------------------------- /buildSrc/src/net/liplum/DistributeInjection.kt: -------------------------------------------------------------------------------- 1 | package net.liplum 2 | 3 | import net.lingala.zip4j.ZipFile 4 | import net.lingala.zip4j.model.ZipParameters 5 | import org.gradle.api.DefaultTask 6 | import org.gradle.api.GradleException 7 | import org.gradle.api.file.ConfigurableFileCollection 8 | import org.gradle.api.provider.Property 9 | import org.gradle.api.provider.SetProperty 10 | import org.gradle.api.tasks.Input 11 | import org.gradle.api.tasks.InputFiles 12 | import org.gradle.api.tasks.TaskAction 13 | import java.io.File 14 | 15 | open class DistributeInjection : DefaultTask() { 16 | val jar: ConfigurableFileCollection = project.files() 17 | @InputFiles get 18 | val excludeFolders: SetProperty = project.objects.setProperty(File::class.java) 19 | @Input get 20 | val excludeFiles: SetProperty = project.objects.setProperty(File::class.java) 21 | @Input get 22 | val name: Property = project.objects.property(String::class.java) 23 | @Input get 24 | @TaskAction 25 | fun dist() { 26 | val dest = temporaryDir.resolve(name.get()) 27 | val first = jar.files.first() ?: throw GradleException("No input") 28 | val excludedFiles = excludeFiles.get() 29 | val excludedFolders = excludeFolders.get() 30 | val unziped = temporaryDir.resolve("unziped") 31 | dest.delete() 32 | unziped.deleteRecursively() 33 | ZipFile(first).extractAll(unziped.absolutePath) 34 | val para = ZipParameters().apply { 35 | setExcludeFileFilter { 36 | val relative = it.relativeTo(unziped) 37 | excludedFiles.contains(relative) 38 | } 39 | } 40 | val zip = ZipFile(dest) 41 | unziped.listFiles()?.forEach { 42 | if (it.isFile && it.relativeTo(unziped) !in excludedFiles) 43 | zip.addFile(it, para) 44 | else if (it.isDirectory && it.relativeTo(unziped) !in excludedFolders) 45 | zip.addFolder(it, para) 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /docs/api/drawer.md: -------------------------------------------------------------------------------- 1 | # Drawer 2 | 3 | | Drawer | Replacement | 4 | |:-----------------:|:-------------------------:| 5 | | DrawHeatRegion | multicraft.DrawHeatRegion | 6 | | DrawLiquidOutputs | No replacement | -------------------------------------------------------------------------------- /docs/api/multicrafter.md: -------------------------------------------------------------------------------- 1 | # MultiCrafter 2 | 3 | The `MultiCrafter` works like a 4 | normal [`GenericCrafter`](https://mindustrygame.github.io/wiki/modding/5-types/#genericcrafter), 5 | so `MultiCrafter` has most of `GenericCrafter`'s API. 6 | 7 | | Field | Type | Default | Note | 8 | |:-----------------------:|:---------:|:---------------------------------:|:---------------------------------------------------------------------------------------------------------------:| 9 | | itemCapacityMultiplier | float | 1f | | 10 | | fluidCapacityMultiplier | float | 1f | | 11 | | powerCapacityMultiplier | float | 1f | ️ | 12 | | recipes | Object | null | ️ | 13 | | menu | String | transform | ️ | 14 | | craftEffect | Effect | none | ️ | 15 | | updateEffect | Effect | none | ️ | 16 | | changeRecipeEffect | Effect | upgradeCore | when recipe is changed. ️ | 17 | | fluidOutputDirections | int[] | {-1} | substitute for vanilla `liquidOutputDirections` ️ | 18 | | updateEffectChance | float | 0.04f | ️ | 19 | | warmupSpeed | float | 0.019f | ️ | 20 | | powerCapacity | float | 0f | ️ | 21 | | dumpExtraFluid | boolean | true | ️ | 22 | | heatColor | Color | new Color(1f, 0.22f, 0.22f, 0.8f) | What color of heat for recipe selector. ️ | 23 | | drawer | DrawBlock | new DrawDefault() | ️ | 24 | -------------------------------------------------------------------------------- /docs/assets/customizedIcon-alphaaaa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liplum/MultiCrafterLib/5068f7184487e5157711260619a25e8dbca5320f/docs/assets/customizedIcon-alphaaaa.png -------------------------------------------------------------------------------- /docs/assets/customizedIcon-mono.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liplum/MultiCrafterLib/5068f7184487e5157711260619a25e8dbca5320f/docs/assets/customizedIcon-mono.png -------------------------------------------------------------------------------- /docs/assets/draw-recipe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liplum/MultiCrafterLib/5068f7184487e5157711260619a25e8dbca5320f/docs/assets/draw-recipe.png -------------------------------------------------------------------------------- /docs/assets/draw-recipes.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liplum/MultiCrafterLib/5068f7184487e5157711260619a25e8dbca5320f/docs/assets/draw-recipes.gif -------------------------------------------------------------------------------- /docs/assets/menu/detailed-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liplum/MultiCrafterLib/5068f7184487e5157711260619a25e8dbca5320f/docs/assets/menu/detailed-1.png -------------------------------------------------------------------------------- /docs/assets/menu/detailed-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liplum/MultiCrafterLib/5068f7184487e5157711260619a25e8dbca5320f/docs/assets/menu/detailed-2.png -------------------------------------------------------------------------------- /docs/assets/menu/number.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liplum/MultiCrafterLib/5068f7184487e5157711260619a25e8dbca5320f/docs/assets/menu/number.png -------------------------------------------------------------------------------- /docs/assets/menu/simple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liplum/MultiCrafterLib/5068f7184487e5157711260619a25e8dbca5320f/docs/assets/menu/simple.png -------------------------------------------------------------------------------- /docs/assets/menu/transform-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liplum/MultiCrafterLib/5068f7184487e5157711260619a25e8dbca5320f/docs/assets/menu/transform-1.png -------------------------------------------------------------------------------- /docs/assets/menu/transform-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liplum/MultiCrafterLib/5068f7184487e5157711260619a25e8dbca5320f/docs/assets/menu/transform-2.png -------------------------------------------------------------------------------- /docs/assets/test-drawer/mine-crafter-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liplum/MultiCrafterLib/5068f7184487e5157711260619a25e8dbca5320f/docs/assets/test-drawer/mine-crafter-1.png -------------------------------------------------------------------------------- /docs/assets/test-drawer/mine-crafter-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liplum/MultiCrafterLib/5068f7184487e5157711260619a25e8dbca5320f/docs/assets/test-drawer/mine-crafter-2.png -------------------------------------------------------------------------------- /docs/assets/test-drawer/mine-crafter-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liplum/MultiCrafterLib/5068f7184487e5157711260619a25e8dbca5320f/docs/assets/test-drawer/mine-crafter-3.png -------------------------------------------------------------------------------- /docs/customize/builtin.md: -------------------------------------------------------------------------------- 1 | # Built-in menu styles 2 | 3 | ## Transform 4 | 5 | ![Transform 1](../assets/menu/transform-1.png){ loading=lazy width="280" } 6 | ![Transform 2](../assets/menu/transform-2.png){ loading=lazy width="280" } 7 | 8 | ## Simple 9 | 10 | ![Simple](../assets/menu/simple.png){ loading=lazy width="280" } 11 | 12 | ## Number 13 | 14 | ![Number](../assets/menu/number.png){ loading=lazy width="280" } 15 | 16 | ## Detailed 17 | 18 | ![Detailed 1](../assets/menu/detailed-1.png){ loading=lazy width="280" } 19 | ![Detailed 2](../assets/menu/detailed-2.png){ loading=lazy width="280" } 20 | -------------------------------------------------------------------------------- /docs/customize/drawer.md: -------------------------------------------------------------------------------- 1 | # DrawRecipe 2 | 3 | `DrawRecipe` drawer let you draw different images for each recipe. 4 | 5 | **Type:** multicraft.DrawRecipe 6 | 7 | It looks like a `DrawMulti`, but the drawer will be changed once another recipe is selected. 8 | 9 | | Field | Type | Default | Note | 10 | |:-------------:|:-----------:|:-------:|:---------------------------------------------------------:| 11 | | drawers | DrawBlock[] | {} | ordered drawers for each recipe | 12 | | defaultDrawer | int | 0 | the default drawer index in `drawers` for icon generation | 13 | 14 | Suppose you have those sprites with a MultiCrafter, named `mine-crafter`. 15 | 16 | ``` 17 | sprites/ 18 | ├─ blocks/ 19 | │ ├─ mine-crafter-1.png 20 | │ ├─ mine-crafter-2.png 21 | │ ├─ mine-crafter-3.png 22 | ``` 23 | 24 | | Sprite | File Name | 25 | |:---------------------------------------------------------------:|:------------------:| 26 | | ![mine-crafter-1.png](../assets/test-drawer/mine-crafter-1.png) | mine-crafter-1.png | 27 | | ![mine-crafter-2.png](../assets/test-drawer/mine-crafter-2.png) | mine-crafter-2.png | 28 | | ![mine-crafter-3.png](../assets/test-drawer/mine-crafter-3.png) | mine-crafter-3.png | 29 | 30 | ![DrawRecipe example](../assets/draw-recipes.gif){ loading=lazy width="280" } 31 | 32 | === "HJSON" 33 | 34 | ```hjson 35 | drawer: { 36 | type: multicraft.DrawRecipe 37 | defaultDrawer: 0 // an index used for generating the icon of this crafter. 38 | drawers: [ 39 | // for recipe 0 40 | { 41 | type: DrawMulti 42 | drawers: [ 43 | { 44 | type: DrawRegion 45 | suffix: -1 46 | } 47 | { 48 | type: DrawArcSmelt 49 | } 50 | ] 51 | } 52 | // for recipe 1 53 | { 54 | type: DrawRegion 55 | suffix: -2 56 | } 57 | // for recipe 2 58 | { 59 | type: DrawRegion 60 | suffix: -3 61 | } 62 | ] 63 | } 64 | ``` 65 | 66 | === "JSON" 67 | 68 | ```json 69 | "drawer": { 70 | "type": "multicraft.DrawRecipe", 71 | "drawers": [ 72 | { 73 | "type": "DrawMulti", 74 | "drawers": [ 75 | { 76 | "type": "DrawRegion", 77 | "suffix": "-1" 78 | }, 79 | { 80 | "type": "DrawArcSmelt" 81 | }, 82 | ] 83 | }, 84 | { 85 | "type": "DrawRegion", 86 | "suffix":"-2" 87 | }, 88 | { 89 | "type": "DrawRegion", 90 | "suffix": "-3" 91 | } 92 | ] 93 | } 94 | ``` -------------------------------------------------------------------------------- /docs/customize/menu.md: -------------------------------------------------------------------------------- 1 | # Menu Style 2 | 3 | You can select which menu style detailed-described blow you want with a case-insensitive name. 4 | The default menu style is `Transform`. 5 | 6 | === "HJSON" 7 | 8 | Suppose you have such structure with a MultiCrafter, named `mine-crafter` 9 | ``` 10 | content/ 11 | ├─ blocks/ 12 | │ ├─ mine-crafter.hjson 13 | ``` 14 | You can configure its menu style. 15 | ```hjson 16 | menu: Transform 17 | ``` 18 | 19 | === "JSON" 20 | 21 | Suppose you have such structure with a MultiCrafter, named `mine-crafter` 22 | ``` 23 | content/ 24 | ├─ blocks/ 25 | │ ├─ mine-crafter.json 26 | ``` 27 | You can configure its menu style. 28 | ```json 29 | "menu": "Transform" 30 | ``` 31 | 32 | === "JavaScript" 33 | 34 | Suppose you have a MultiCrafter, named `mine-crafter` 35 | ```javascript 36 | const multi = require("multi-crafter/lib") 37 | const mineCrafter = multi.MultiCrafter("mine-crafter") 38 | ``` 39 | You can configure its menu style. 40 | ```javascript 41 | mineCrafter.menu= "Transform" 42 | ``` 43 | 44 | -------------------------------------------------------------------------------- /docs/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liplum/MultiCrafterLib/5068f7184487e5157711260619a25e8dbca5320f/docs/favicon.png -------------------------------------------------------------------------------- /docs/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liplum/MultiCrafterLib/5068f7184487e5157711260619a25e8dbca5320f/docs/icon.png -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | ## Install 4 | 5 | You can get the latest release on [here](https://github.com/liplum/MultiCrafterLib/releases/latest) 6 | or search it on the Mod Browser with its name, `MultiCrafter Lib`. 7 | 8 | ## Supports 9 | 10 | | Method | Json | JavaScript | Java | Note | 11 | |:----------:|:----:|:----------:|:-------:|:------------------------------:| 12 | | Dependency | ✔️ | ✔️ | ✔️ | Players need download this mod | 13 | | Injection | ✔️ | ✔️ | No Need | Keep your mod standalone | 14 | | Jitpack | ❌ | ❌ | ✔️ | Full sources code support | 15 | 16 | === "Dependency" 17 | 18 | **For a JSON, JavaScript or Java mod.** 19 | 20 | If you want your mod to depend on MultiCrafter and only focus on your contents, it's for your use case. 21 | 22 | You should add MultiCrafter as a dependency in the `mod.[h]json` file: 23 | 24 | === "mod.json" 25 | 26 | ```json 27 | "dependencies": ["multi-crafter"] 28 | ``` 29 | 30 | === "mod.hjson" 31 | 32 | ```hjson 33 | dependencies: ["multi-crafter"] 34 | ``` 35 | 36 | === "Injection" 37 | 38 | **For a JSON or JavaScript mod.** 39 | 40 | *Injection makes your mod an actual Java mod, so it must fit the structure of Java mod.* 41 | 42 | ##### Step 1 43 | You will have to download a zip, named `MultiCrafter-injection.zip`, in [here](https://github.com/liplum/MultiCrafterLib/releases/latest). 44 | 45 | ##### Step 2 46 | Unzip the downloaded zip and copy its contents into the root directory of your mod. 47 | 48 |
49 | 50 | Unzip will add essential files into the root directory. 51 |
52 | Please pay attention to your structure and avoid secondary directory in your mod zip. 53 |
54 | Suppose your have this structure: 55 | 56 | - Before unzip: 57 | ``` 58 | your-mod/ 59 | ├─ content/ 60 | | ├─ crafter.hjson 61 | ├─ mod.hjson 62 | ├─ icon.png 63 | ``` 64 | 65 | - After unzip: 66 | ``` 67 | your-mod/ 68 | ├─ multicrafter/ 69 | ├─ scripts/ 70 | | ├─ multi-crafter/ 71 | | | ├─ lib.js 72 | ├─ content/ 73 | | ├─ crafter.hjson 74 | ├─ mod.hjson 75 | ├─ icon.png 76 | ├─ classes.dex 77 | ``` 78 | 79 | MultiCrafter injection doesn't work when you zip your mod folder in a wrong way 80 | where have created a secondary directory. 81 | 82 | Suppose you had a mod zip, named `your-mod.zip`. 83 | 84 | - This will work. 85 | In your-mod.zip, there are... 86 | ``` 87 | multicrafter/ 88 | scripts/ 89 | content/ 90 | mod.hjson 91 | icon.png 92 | ``` 93 | 94 | - But this will not. 95 | In your-mod.zip, there are... 96 | ``` 97 | your-mod/ 98 | ├─ multicrafter/ 99 | ├─ scripts/ 100 | ├─ content/ 101 | ├─ mod.hjson 102 | ├─ icon.png 103 | ``` 104 | 105 | If you're using `ZArchiver` app, you could multi-select the each file inside your mod folder 106 | and zip them all into one. 107 |
108 | 109 | ##### Step 3 110 | Then add this line in your `mod.[h]json`: 111 | 112 | === "mod.json" 113 | 114 | ```json 115 | "main": "MultiCrafterAdapter" 116 | ``` 117 | 118 | === "mod.hjson" 119 | 120 | ```hjson 121 | main: MultiCrafterAdapter 122 | ``` 123 | 124 | Then you can create your own multicrafter after checking this instrution. 125 | 126 | - Root Directory: A folder which always has `icon.png` and `mod.[h]json`. 127 | 128 |
129 | You may face a warning about overwriting. 130 | 131 | Your device may warn you that would overwrite something. 132 | It's always safe, but you'd better to back-up your mod workspace before copy. 133 |
134 | 135 | ### For GitHub 136 | If you've uploaded your mod onto GitHub, please be careful about your mod type. 137 | 138 | Because, a non-Java mod is always downloaded as zip with a secondary directory ingame, 139 | which doesn't work like what's mentioned above. 140 | 141 | you have to add this line in your `mod.[h]json`: 142 | 143 | === "mod.json" 144 | 145 | ```json 146 | "java": "true" 147 | ``` 148 | 149 | === "mod.hjson" 150 | 151 | ```hjson 152 | java: true 153 | ``` 154 | 155 | Thus, you need create a `release` with your mod file on your GitHub page manually. 156 | 157 | Remember to change its extension directly from `*.zip` to `*.jar`, for example: 158 | 159 | If your mod file is `my-first-json-mod.zip`, it should be `my-first-json-mod.jar`. 160 | 161 | ### Upgrade MultiCrafter Lib 162 | With Injection, you have to upgrade `MultiCrafter Lib` manually. 163 | 164 | It's easy that you just need repeat the step above and handle with overwritten. 165 | 166 | === "Jitpack" 167 | 168 | **For a Java mod.** 169 | 170 | You can click here [![](https://jitpack.io/v/liplum/MultiCrafterLib.svg)](https://jitpack.io/#liplum/MultiCrafterLib) 171 | to fetch the latest version of MultiCrafter Lib. 172 | 173 | === "Groovy" 174 | 175 | 1. Add the JitPack repository to your build.gradle 176 | 177 | ```groovy 178 | repositories { maven { url 'https://jitpack.io' } } 179 | ``` 180 | 2. Add the dependency 181 | 182 | ```groovy 183 | dependencies { 184 | implementation 'com.github.liplum:MultiCrafterLib:' 185 | } 186 | ``` 187 | === "Kotlin" 188 | 189 | 1. Add the JitPack repository to your build.gradle.kts 190 | 191 | ```kotlin 192 | repositories { 193 | maven { url = uri("https://www.jitpack.io") } 194 | } 195 | ``` 196 | 2. Add the dependency 197 | 198 | ```kotlin 199 | dependencies { 200 | implementation("com.github.liplum:MultiCrafterLib:") 201 | } 202 | ``` 203 | ## More Info 204 | 205 | You can access the [repository](https://github.com/liplum/MultiCrafterLib) on GitHub to obtain more information. 206 | 207 | If you face any issue with MultiCrafter, please contact us 208 | on [Issue Report](https://github.com/liplum/MultiCrafterLib/issues) page. 209 | 210 | Join our [Discord server](https://discord.gg/PDwyxM3waw) to send us feedback or get help immediately. 211 | 212 | Welcome to contribute MultiCrafter! 213 | -------------------------------------------------------------------------------- /docs/migration.md: -------------------------------------------------------------------------------- 1 | # Migration Guide 2 | 3 | This is a guide to help users migrate from the `v1.y.z` to `v2.y.z`. 4 | ???+ info 5 | The `v2.y.z` is not yet officially out and is still in the devlopment phase! 6 | 7 | ## Nullary Constructor (Java) 8 | 9 | The class `Recipe` and `IOEntry` now use nullary constructor. 10 | 11 | ???+ example "Example: Before" 12 | ```java 13 | new Recipe( 14 | new IOEntry( 15 | Seq.with( 16 | ItemStack.with(Items.copper, 1) 17 | ) 18 | ), 19 | new IOEntry( 20 | Seq.with(), 21 | Seq.with( 22 | LiquidStack.with(Liquids.water, 1f) 23 | ) 24 | ), 25 | 120f 26 | ) 27 | ``` 28 | ???+ example "Example: After" 29 | ```java 30 | new Recipe() {{ 31 | input = new IOEntry() {{ 32 | items = Seq.with( 33 | ItemStack.with(Items.copper, 1) 34 | ); 35 | }}; 36 | output = new IOEntry() {{ 37 | liquids = Seq.with( 38 | LiquidStack.with(Liquids.water, 1f) 39 | ); 40 | }}; 41 | craftTime = 120f; 42 | }} 43 | ``` 44 | 45 | ## Using `Stack[]` (Java) 46 | 47 | Instead of using `Seq` we now simply use `Stack[]`. It only impacts the variables in the `IOEntry` class (`items`, `fluids` and `payloads`). 48 | 49 | ???+ example "Example: Before" 50 | ```java 51 | input = new IOEntry() {{ 52 | items = Seq.with( 53 | ItemStack.with(Items.copper, 1) 54 | ); 55 | }}; 56 | output = new IOEntry() {{ 57 | liquids = Seq.with( 58 | LiquidStack.with(Liquids.water, 1f) 59 | ); 60 | }}; 61 | ``` 62 | ???+ example "Example: After" 63 | ```java 64 | input = new IOEntry() {{ 65 | items = ItemStack.with(Items.copper, 1); 66 | }}; 67 | output = new IOEntry() {{ 68 | liquids = LiquidStack.with(Liquids.water, 1f); 69 | }}; 70 | ``` 71 | -------------------------------------------------------------------------------- /docs/troubleshooting.md: -------------------------------------------------------------------------------- 1 | # Troubleshooting -------------------------------------------------------------------------------- /docs/usage/java.md: -------------------------------------------------------------------------------- 1 | # Java 2 | 3 | In your Java file containing all your blocks (create one if you don't already have one) imports the MultiCrater library. 4 | 5 | ```java 6 | import multicraft.* 7 | ``` 8 | 9 | Then create a new block with the type `MultiCrafter` 10 | 11 | ```java 12 | Block mine-crafter = new MultiCrafter("mine-crafter") {{ 13 | 14 | }}; 15 | ``` 16 | 17 | You can add recipes like this: 18 | 19 | ```java 20 | resolvedRecipes = Seq.with( 21 | new Recipe() {{ 22 | input = new IOEntry() {{ 23 | items = ItemStack.with( 24 | Items.copper, 1, 25 | Items.lead, 1 26 | ); 27 | }}; 28 | output = new IOEntry() {{ 29 | items = ItemStack.with( 30 | Items.surgeAlloy, 1, 31 | Items.thorium, 1 32 | ); 33 | }}; 34 | craftTime = 120f; 35 | }}, 36 | new Recipe() {{ 37 | input = new IOEntry() {{ 38 | items = ItemStack.with( 39 | Items.copper, 1 40 | ); 41 | }}; 42 | output = new IOEntry() {{ 43 | items = ItemStack.with( 44 | Items.copper, 1, 45 | Items.beryllium, 1 46 | ); 47 | }}; 48 | craftTime = 160f; 49 | }} 50 | ); 51 | ``` 52 | 53 | ### Recipe 54 | 55 | A recipe has several fields: 56 | 57 | | Field | Type | Note | 58 | |-------------|------------------------------------|---------------------------------------------| 59 | | input | IOEntry | | 60 | | output | IOEntry | | 61 | | crafterTime | Float | how long to do a synthesis, can be 0. | 62 | | icon | Prov | such as `Icon.lock-open`. See [Icon](#icon) | 63 | | iconColor | Color (RGB, RGBA, rgba8888 or Hex) | a color for icon | 64 | 65 | 66 | 67 | ### Input and Output 68 | 69 | The `input` or `output` are `IOEntry`. 70 | With this style, its power is unlimited. 71 | 72 | | Key | Type | Note | 73 | |-------------|------------------------------------|-------------------------------------------------| 74 | | items | ItemStack[] | how much item for input/output, default: empty | 75 | | fluids | LiquidStack[] | how much fluid for input/output, default: empty | 76 | | power | Float | unit: power/tick | how much power for input/output, default: 0f | 77 | | heat | Float | how much heat for input/output, default: 0f | 78 | | icon | Icon | such as `Icon.lock-open`. See [Icon](#icon) | 79 | | iconColor | Color (RGB, RGBA, rgba8888 or Hex) | a color for icon | 80 | | craftEffect | Effect | an independent craft effect for each recipe | 81 | 82 | ### Icon 83 | 84 | You can customize which icon is used for your recipe selector menu. 85 | 86 | If you don't set a dedicated icon, it will find the first one from the recipe. 87 | 88 | For example: 89 | 90 | === "alphaaaa" 91 | 92 | ![Alphaaaa](../assets/customizedIcon-alphaaaa.png){ loading=lazy } 93 | 94 |
95 | 96 | icon = Icon.alphaaaa 97 |
98 | iconColor: F30000 99 |
100 | ``` 101 | switchStyle = RecipeSwitchStyle.simple; 102 | resolvedRecipes = Seq.with( 103 | new Recipe() {{ 104 | input = new IOEntry(){{ 105 | fluids = Seq.with( 106 | Liquids.ozone, 1.5f 107 | ); 108 | }}; 109 | output = new IOEntry() {{ 110 | items = Seq.with( 111 | Items.coal, 1 112 | ) 113 | power = 2f; 114 | icon: alphaaaa 115 | iconColor = Color.valueOf("#F30000"); 116 | }}; 117 | craftTime = 250f; 118 | }}, 119 | new Recipe() {{ 120 | input = new IOEntry(){{ 121 | items = Seq.with( 122 | Items.copper, 1 123 | ); 124 | }}; 125 | output = new IOEntry() {{ 126 | items = Seq.with( 127 | Items.coal, 1 128 | ) 129 | icon = () -> Icon.lock.uiIcon; 130 | }}; 131 | craftTime = 120f; 132 | }} 133 | ); 134 | ``` 135 |
136 | 137 | === "mono" 138 | 139 | ![Mono](../assets/customizedIcon-mono.png){ loading=lazy width="250" } 140 | 141 |
142 | 143 | icon: mono 144 | 145 | ```java 146 | switchStyle = RecipeSwitchStyle.simple; 147 | resolvedRecipes = Seq.with( 148 | new Recipe() {{ 149 | input = new IOEntry(){{ 150 | items = Seq.with( 151 | Items.copper, 1 152 | ); 153 | }}; 154 | output = new IOEntry() {{ 155 | items = Seq.with( 156 | Items.coal, 1 157 | ) 158 | }}; 159 | craftTime = 60f; 160 | icon = () -> UnitTypes.mono.uiIcon; 161 | }}, 162 | new Recipe() {{ 163 | input = new IOEntry(){{ 164 | items = Seq.with( 165 | Items.copper, 1 166 | ); 167 | }}; 168 | output = new IOEntry() {{ 169 | fluids = Seq.with( 170 | Liquid.ozone, 1f 171 | ) 172 | }}; 173 | craftTime = 60f; 174 | }} 175 | ); 176 | ``` 177 |
178 | 179 | - The `icon` variable as to be always defined by `icon = () -> ...;` 180 | - For a built-in icon, it should start with `Icon.`, such as `Icon.lock-open` or `Icon.trash`. 181 | - For an icon from item, fluid, unit or block, it should be the content `uiIcon`, such as `Units.mono.uiIcon`,`phase-heat.uiIcon`. 182 | - For any texture, it should be its name, such as `your-mod-icon` or `alphaaaa`. 183 | 184 | -------------------------------------------------------------------------------- /docs/usage/json.md: -------------------------------------------------------------------------------- 1 | # JSON & Javascript 2 | 3 | === "HJSON" 4 | 5 | Create a file, for example, a `mine-crafter.hjson`, in the `content/blocks` folder. 6 | ``` 7 | content/ 8 | ├─ blocks/ 9 | │ ├─ mine-crafter.hjson 10 | 11 | ``` 12 | Then set its type to `multicraft.MultiCrafter`. 13 | ```hjson 14 | type: multicraft.MultiCrafter 15 | ``` 16 | 17 | You can add recipes like this: 18 | 19 | ```hjson 20 | recipes: 21 | [ 22 | { 23 | input: ozone/1.5 24 | output: { 25 | items: [ 26 | copper/1 27 | graphite/2 28 | ] 29 | power: 2.5 30 | } 31 | craftTime: 250.0 32 | } 33 | { 34 | input: { 35 | items: [ 36 | cyber-io-ic/1 // You can add moded items or fluids 37 | lead // the same as "lead/1" 38 | ] 39 | } 40 | output: { 41 | fluids: [ 42 | cyber-io-cyberion/1.2 43 | ] 44 | } 45 | craftTime: 210.0 46 | } 47 | ] 48 | ``` 49 | 50 | === "JSON" 51 | 52 | Create a file, for example, a `mine-crafter.json`, in the `content/blocks` folder. 53 | ``` 54 | content/ 55 | ├─ blocks/ 56 | │ ├─ mine-crafter.json 57 | ``` 58 | Then set its type to `multicraft.MultiCrafter`. 59 | ```json 60 | "type": "multicraft.MultiCrafter" 61 | ``` 62 | 63 | You can add recipes like this: 64 | 65 | ```json 66 | "recipes": [ 67 | { 68 | "input": "ozone/1.5", 69 | "output": { 70 | "items": [ 71 | "copper/1", 72 | "graphite/2" 73 | ], 74 | "power": 2.5 75 | }, 76 | "craftTime": 250.0 77 | }, 78 | { 79 | "input": { 80 | "items": [ 81 | "cyber-io-ic/1", 82 | "lead" 83 | ] 84 | }, 85 | "output": { 86 | "fluids": [ 87 | "cyber-io-cyberion/1.2" 88 | ] 89 | }, 90 | "craftTime": 210.0 91 | } 92 | ] 93 | ``` 94 | 95 | === "JavaScript" 96 | 97 | In a JavaScript file, you should import the `MultiCrafter` class from `multi-cafter` 98 | ```javascript 99 | const multi = require("multi-crafter/lib") 100 | const mineCrafter = multi.MultiCrafter("mine-crafter") 101 | ``` 102 | You can add recipes like this: 103 | ```javascript 104 | mineCrafter.recipes= [ 105 | { 106 | input: "ozone/1.5", 107 | output: { 108 | items: ["copper/1","graphite/2"] 109 | power: 2.5 110 | }, 111 | craftTime: 250.0 112 | },{ 113 | input: { 114 | items: ["cyber-io-ic/1", "lead"] 115 | }, 116 | output: { 117 | fluids: ["cyber-io-cyberion/1.2"] 118 | }, 119 | craftTime: 210.0 120 | }] 121 | ``` 122 | 123 | 124 | ### Recipe 125 | 126 | A recipe has several fields: 127 | 128 | | Field | Type | Note | 129 | |-------------|--------------------------|---------------------------------------------| 130 | | input | Object, String or List | alias: [`in`,`i`] | 131 | | output | Object, String or List | alias: [`out`,`o`] | 132 | | crafterTime | Number | unit: tick | how long to do a synthesis, can be 0. | 133 | | icon | String | such as `Icon.lock-open`. See [Icon](#icon) | 134 | | iconColor | String | a hex color for icon | 135 | 136 | ### Input and Output 137 | 138 | #### String 139 | 140 | The `input` or `output` can be a `String`. 141 | If so, it will be considered as an item or fluid. 142 | 143 | If there is no amount given, `1` will be the amount as default. 144 | === "HJSON" 145 | 146 | ```hjson 147 | input: copper/2 148 | output: water/1.2 149 | ``` 150 | 151 | === "JSON" 152 | 153 | ```json 154 | "input": "copper/2", 155 | "output": "water/1.2" 156 | ``` 157 | 158 | === "JavaScript" 159 | 160 | ```javascript 161 | input: "copper/2", 162 | output: "water/1.2" 163 | ``` 164 | 165 | #### List 166 | 167 | The `input` or `output` can be a `List`. 168 | If so, every element will be treated as an item or fluid. 169 | 170 | === "HJSON" 171 | 172 | ```hjson 173 | input: [copper/2,lead/3] 174 | output: slag/2.5 175 | ``` 176 | 177 | === "JSON" 178 | 179 | ```json 180 | "input": ["copper/2","lead/3"], 181 | "output": "slag/2.5" 182 | ``` 183 | 184 | === "JavaScript" 185 | 186 | ```javascript 187 | input: ["copper/2","lead/3"], 188 | output: "slag/2.5" 189 | ``` 190 | 191 | #### Object 192 | 193 | The `input` or `output` can be an `Object`. 194 | With this style, its power is unlimited. 195 | 196 | | Key | Type | Note | 197 | |-------------|--------------------------------|-------------------------------------------------| 198 | | items | String or List | how much item for input/output, default: empty | 199 | | fluids | String or List | how much fluid for input/output, default: empty | 200 | | power | Number | unit: power/tick | how much power for input/output, default: 0 | 201 | | heat | Number | how much heat for input/output, default: 0 | 202 | | icon | String | such as `Icon.lock-open`. See [Icon](#icon) | 203 | | iconColor | String | a hex color for icon | 204 | | craftEffect | String | an independent craft effect for each recipe | 205 | 206 | === "HJSON" 207 | 208 | ```hjson 209 | input: { 210 | items: copper/10 211 | heat: 5 212 | } 213 | output: { 214 | items: moded-item/1 215 | fluids: [ 216 | water/1.5, ozone/3 217 | ] 218 | power: 1.5 219 | } 220 | ``` 221 | 222 | === "JSON" 223 | 224 | ```json 225 | "input": { 226 | "items": "copper/10", 227 | "heat": 5 228 | } 229 | "output": { 230 | "items": "moded-item/1", 231 | "fluids": [ 232 | "water/1.5", "ozone/3" 233 | ], 234 | "power": 1.5 235 | } 236 | ``` 237 | 238 | === "JavaScript" 239 | 240 | ```javascript 241 | input: { 242 | items: "copper/10", 243 | heat: 5 244 | }, 245 | output: { 246 | items: "moded-item/1", 247 | fluids: [ 248 | "water/1.5", "ozone/3" 249 | ], 250 | power: 1.5 251 | } 252 | ``` 253 | 254 | ### Icon 255 | 256 | You can customize which icon is used for your recipe selector menu. 257 | 258 | If you don't set a dedicated icon, it will find the first one from the recipe. 259 | 260 | For example: 261 | 262 | === "alphaaaa" 263 | 264 | ![Alphaaaa](../assets/customizedIcon-alphaaaa.png){ loading=lazy } 265 | 266 |
267 | 268 | icon: alphaaaa 269 |
270 | iconColor: F30000 271 |
272 | ```hjson 273 | recipes: [ 274 | { 275 | input: ozone/1.5 276 | output:{ 277 | items : copper 278 | power : 2 279 | icon: alphaaaa 280 | iconColor: "F30000" 281 | } 282 | craftTime : 250.0 283 | } 284 | { 285 | input: copper 286 | output:{ 287 | items : coal 288 | icon: lock-open 289 | } 290 | craftTime : 120 291 | } 292 | ] 293 | ``` 294 |
295 | 296 | === "mono" 297 | 298 | ![Mono](../assets/customizedIcon-mono.png){ loading=lazy width="250" } 299 | 300 |
301 | 302 | icon = mono 303 | 304 | ```hjson 305 | menu: Simple 306 | recipes: 307 | [ 308 | { 309 | input: copper 310 | output: coal 311 | craftTime : 60 312 | icon: mono 313 | } 314 | { 315 | input: copper 316 | output: ozone 317 | craftTime : 60 318 | } 319 | ] 320 | ``` 321 |
322 | 323 | You can set it to a `String`, it will find the proper icon automatically. 324 | 325 | - For a built-in icon, it should start with `Icon.`, such as `Icon.lock-open` or `Icon.trash`. 326 | - For an icon from item, fluid, unit or block, it should be its name, such as `mono`,`phase-heat`. 327 | - For any texture, it should be its name, such as `your-mod-icon` or `alphaaaa`. 328 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liplum/MultiCrafterLib/5068f7184487e5157711260619a25e8dbca5320f/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 87 | APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit 88 | 89 | # Use the maximum available, or set MAX_FD != -1 to use that value. 90 | MAX_FD=maximum 91 | 92 | warn () { 93 | echo "$*" 94 | } >&2 95 | 96 | die () { 97 | echo 98 | echo "$*" 99 | echo 100 | exit 1 101 | } >&2 102 | 103 | # OS specific support (must be 'true' or 'false'). 104 | cygwin=false 105 | msys=false 106 | darwin=false 107 | nonstop=false 108 | case "$( uname )" in #( 109 | CYGWIN* ) cygwin=true ;; #( 110 | Darwin* ) darwin=true ;; #( 111 | MSYS* | MINGW* ) msys=true ;; #( 112 | NONSTOP* ) nonstop=true ;; 113 | esac 114 | 115 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 116 | 117 | 118 | # Determine the Java command to use to start the JVM. 119 | if [ -n "$JAVA_HOME" ] ; then 120 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 121 | # IBM's JDK on AIX uses strange locations for the executables 122 | JAVACMD=$JAVA_HOME/jre/sh/java 123 | else 124 | JAVACMD=$JAVA_HOME/bin/java 125 | fi 126 | if [ ! -x "$JAVACMD" ] ; then 127 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 128 | 129 | Please set the JAVA_HOME variable in your environment to match the 130 | location of your Java installation." 131 | fi 132 | else 133 | JAVACMD=java 134 | if ! command -v java >/dev/null 2>&1 135 | then 136 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | fi 142 | 143 | # Increase the maximum file descriptors if we can. 144 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 145 | case $MAX_FD in #( 146 | max*) 147 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 148 | # shellcheck disable=SC2039,SC3045 149 | MAX_FD=$( ulimit -H -n ) || 150 | warn "Could not query maximum file descriptor limit" 151 | esac 152 | case $MAX_FD in #( 153 | '' | soft) :;; #( 154 | *) 155 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 156 | # shellcheck disable=SC2039,SC3045 157 | ulimit -n "$MAX_FD" || 158 | warn "Could not set maximum file descriptor limit to $MAX_FD" 159 | esac 160 | fi 161 | 162 | # Collect all arguments for the java command, stacking in reverse order: 163 | # * args from the command line 164 | # * the main class name 165 | # * -classpath 166 | # * -D...appname settings 167 | # * --module-path (only if needed) 168 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 169 | 170 | # For Cygwin or MSYS, switch paths to Windows format before running java 171 | if "$cygwin" || "$msys" ; then 172 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 173 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 174 | 175 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 176 | 177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 178 | for arg do 179 | if 180 | case $arg in #( 181 | -*) false ;; # don't mess with options #( 182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 183 | [ -e "$t" ] ;; #( 184 | *) false ;; 185 | esac 186 | then 187 | arg=$( cygpath --path --ignore --mixed "$arg" ) 188 | fi 189 | # Roll the args list around exactly as many times as the number of 190 | # args, so each arg winds up back in the position where it started, but 191 | # possibly modified. 192 | # 193 | # NB: a `for` loop captures its iteration list before it begins, so 194 | # changing the positional parameters here affects neither the number of 195 | # iterations, nor the values presented in `arg`. 196 | shift # remove old arg 197 | set -- "$@" "$arg" # push replacement arg 198 | done 199 | fi 200 | 201 | 202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 204 | 205 | # Collect all arguments for the java command: 206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 207 | # and any embedded shellness will be escaped. 208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 209 | # treated as '${Hostname}' itself on the command line. 210 | 211 | set -- \ 212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 213 | -classpath "$CLASSPATH" \ 214 | org.gradle.wrapper.GradleWrapperMain \ 215 | "$@" 216 | 217 | # Stop when "xargs" is not available. 218 | if ! command -v xargs >/dev/null 2>&1 219 | then 220 | die "xargs is not available" 221 | fi 222 | 223 | # Use "xargs" to parse quoted args. 224 | # 225 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 226 | # 227 | # In Bash we could simply go: 228 | # 229 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 230 | # set -- "${ARGS[@]}" "$@" 231 | # 232 | # but POSIX shell has neither arrays nor command substitution, so instead we 233 | # post-process each arg (as a line of input to sed) to backslash-escape any 234 | # character that might be a shell metacharacter, then use eval to reverse 235 | # that process (while maintaining the separation between arguments), and wrap 236 | # the whole thing up as a single "set" statement. 237 | # 238 | # This will of course break if any of these variables contains a newline or 239 | # an unmatched quote. 240 | # 241 | 242 | eval "set -- $( 243 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 244 | xargs -n1 | 245 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 246 | tr '\n' ' ' 247 | )" '"$@"' 248 | 249 | exec "$JAVACMD" "$@" 250 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liplum/MultiCrafterLib/5068f7184487e5157711260619a25e8dbca5320f/icon.png -------------------------------------------------------------------------------- /inject.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal enabledelayedexpansion 3 | 4 | @REM Check if 2 or 3 arguments are provided 5 | if "%~2"=="" ( 6 | echo Usage: %0 ^ ^ [new_folder_name] 7 | exit /b 1 8 | ) 9 | 10 | set "folder_path=%~1" 11 | set "zip_file_path=%~2" 12 | set "new_folder_name=%~3" 13 | 14 | @REM Set default new folder name if not provided 15 | if "%new_folder_name%"=="" ( 16 | set "new_folder_name=%~n1-injected" 17 | ) 18 | 19 | @REM Step 1: Copy the folder recursively to a new folder 20 | xcopy /s /e /i "%folder_path%" "%new_folder_name%" 21 | 22 | @REM Step 2: Decompress the zip file into the new folder, overwriting existing files 23 | powershell Expand-Archive -Path "%zip_file_path%" -DestinationPath "%new_folder_name%" -Force 24 | 25 | @REM Step 3: Compress the files under the new folder to a new zip file 26 | @REM powershell Compress-Archive -Path "%new_folder_name%\*" -DestinationPath "%new_folder_name%.zip" -Force 27 | 28 | @REM Step 4: Remove the temporary folder 29 | @REM rmdir /s /q "%new_folder_name%" 30 | 31 | echo Task completed successfully. 32 | 33 | exit /b 0 -------------------------------------------------------------------------------- /inject.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Function to check if a command is available 3 | check_command() { 4 | command -v "$1" >/dev/null 2>&1 || { echo >&2 "Error: $1 is not installed. Aborting."; exit 1; } 5 | } 6 | 7 | # Check required commands 8 | check_command "cp" 9 | check_command "unzip" 10 | check_command "zip" 11 | 12 | if [ "$#" -lt 2 ]; then 13 | echo "Usage: $0 [dest_folder]" 14 | exit 1 15 | fi 16 | 17 | folder_path="$1" 18 | zip_file_path="$2" 19 | # Generated from folder_path by default 20 | dest="${3:-${folder_path##*/}-injected}" 21 | 22 | # Step 1: Copy the folder recursively to a new folder 23 | cp -r "$folder_path" "$dest" 24 | 25 | # Step 2: Decompress the zip file into the new folder, overwriting existing files 26 | unzip -o "$zip_file_path" -d "$dest" 27 | 28 | # Step 3: Compress the files under the new folder to a new zip file 29 | cd "$dest" && zip -r "../${dest}.zip" * && cd .. 30 | 31 | # Step 4: Remove the temporary folder 32 | rm -r "$dest" 33 | 34 | echo "Task completed successfully." 35 | -------------------------------------------------------------------------------- /injection/assets/scripts/multi-crafter/lib.js: -------------------------------------------------------------------------------- 1 | const loader = Vars.mods.mainLoader(); 2 | const scripts = Vars.mods.scripts; 3 | const NativeJavaClass = Packages.rhino.NativeJavaClass; 4 | function getClass(name){ 5 | return NativeJavaClass(scripts.scope, loader.loadClass(name)); 6 | }; 7 | 8 | const multiCrafterClz = getClass("multicraft.MultiCrafter") 9 | module.exports = { 10 | MultiCrafter : multiCrafterClz 11 | } -------------------------------------------------------------------------------- /injection/build.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("SpellCheckingInspection") 2 | 3 | import io.github.liplum.mindustry.importMindustry 4 | import io.github.liplum.mindustry.mindustryAssets 5 | 6 | plugins { 7 | java 8 | id("io.github.liplum.mgpp") 9 | } 10 | sourceSets { 11 | main { 12 | java.srcDirs("src") 13 | } 14 | test { 15 | java.srcDir("test") 16 | } 17 | } 18 | java { 19 | sourceCompatibility = JavaVersion.VERSION_1_8 20 | targetCompatibility = JavaVersion.VERSION_1_8 21 | } 22 | mindustry { 23 | isLib = true 24 | } 25 | mindustryAssets { 26 | root at "$projectDir/assets" 27 | } 28 | tasks.jar { 29 | from(projectDir.resolve("assets")) { 30 | include("scripts/lib.js") 31 | } 32 | } 33 | dependencies { 34 | implementation(project(":lib")) 35 | importMindustry() 36 | testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.2") 37 | testImplementation("com.github.liplum:TestUtils:v0.1") 38 | } 39 | -------------------------------------------------------------------------------- /injection/src/MultiCrafterAdapter.java: -------------------------------------------------------------------------------- 1 | import mindustry.mod.Mod; 2 | 3 | public class MultiCrafterAdapter extends Mod { 4 | } 5 | -------------------------------------------------------------------------------- /java/assets/content/blocks/test.hjson: -------------------------------------------------------------------------------- 1 | type: ItemTurret 2 | targetAir: true 3 | targetGround: true 4 | health: 4500 5 | size: 4 6 | reload: 90 7 | recoil: 2 8 | ammoUseEffect: casing3 9 | shootEffect: hitLancer 10 | shootSound: railgun 11 | shootCone: 25 12 | rotateSpeed: 3.8 13 | maxAmmo: 36 14 | ammoPerShot: 8 15 | range: 720 16 | requirements: [ 17 | copper/1000 18 | lead/770 19 | silicon/600 20 | titanium/100 21 | thorium/200 22 | surge-alloy/450 23 | ] 24 | category: turret 25 | research: spectre -------------------------------------------------------------------------------- /java/build.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("SpellCheckingInspection") 2 | 3 | import io.github.liplum.mindustry.* 4 | 5 | plugins { 6 | java 7 | id("io.github.liplum.mgpp") 8 | } 9 | sourceSets { 10 | main { 11 | java.srcDirs("src") 12 | resources.srcDir("resources") 13 | } 14 | test { 15 | java.srcDir("test") 16 | resources.srcDir("resources") 17 | } 18 | } 19 | 20 | java { 21 | sourceCompatibility = JavaVersion.VERSION_1_8 22 | targetCompatibility = JavaVersion.VERSION_1_8 23 | } 24 | mindustry { 25 | meta = ModMeta( 26 | name = "java", 27 | displayName = "Java", 28 | main = "WithJsonMod", 29 | minGameVersion = "136", 30 | version = "0.1", 31 | java = true, 32 | ) 33 | } 34 | mindustryAssets { 35 | root at "$projectDir/assets" 36 | } 37 | dependencies { 38 | implementation(project(":lib")) 39 | importMindustry() 40 | testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.2") 41 | testImplementation("com.github.liplum:TestUtils:v0.1") 42 | } 43 | -------------------------------------------------------------------------------- /java/src/WithJsonMod.java: -------------------------------------------------------------------------------- 1 | import arc.struct.Seq; 2 | import arc.util.Log; 3 | import mindustry.content.*; 4 | import mindustry.graphics.Pal; 5 | import mindustry.mod.Mod; 6 | import mindustry.type.Category; 7 | import mindustry.type.ItemStack; 8 | import mindustry.type.LiquidStack; 9 | import multicraft.*; 10 | 11 | public class WithJsonMod extends Mod { 12 | public WithJsonMod() { 13 | Log.info("WithJson Mod "); 14 | } 15 | 16 | @Override 17 | public void init() { 18 | Log.info("WithJson Mod init()"); 19 | } 20 | 21 | @Override 22 | public void loadContent() { 23 | Log.info("WithJson Mod loadContent()"); 24 | new MultiCrafter("java-crafter") {{ 25 | requirements(Category.crafting, ItemStack.with( 26 | Items.copper, 10 27 | )); 28 | switchStyle = RecipeSwitchStyle.detailed; 29 | resolvedRecipes = Seq.with( 30 | new Recipe() {{ 31 | input = new IOEntry() {{ 32 | items = ItemStack.with(Items.copper, 1); 33 | }}; 34 | output = new IOEntry() {{ 35 | items = ItemStack.with(Items.copper, 1); 36 | power = 0.5f; 37 | }}; 38 | craftTime = 160f; 39 | icon = () -> Items.copper.uiIcon; 40 | craftEffect = Fx.unitCapKill; 41 | iconColor = Pal.accent; 42 | }}, 43 | new Recipe() {{ 44 | input = new IOEntry() {{ 45 | fluids = LiquidStack.with(Liquids.oil, 0.15f); 46 | }}; 47 | output = new IOEntry() {{ 48 | items = ItemStack.with(Items.coal, 2); 49 | power = 2.5f; 50 | }}; 51 | craftTime = 60f; 52 | craftEffect = Fx.smoke; 53 | }} 54 | ); 55 | }}; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /js/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .idea 3 | build -------------------------------------------------------------------------------- /js/assets/content/blocks/crafter/json.hjson: -------------------------------------------------------------------------------- 1 | type: multicraft.MultiCrafter 2 | size : 10 3 | requirements: [ 4 | graphite/100 5 | surge-alloy/50 6 | phase-fabric/70 7 | ] 8 | category: crafting 9 | recipes : 10 | [ 11 | { 12 | input: ozone/1.5 13 | output:{ 14 | items : [ 15 | coal/1 16 | copper/1 17 | lead/1 18 | graphite/2 19 | ] 20 | power : 2 21 | icon: alphaaaa 22 | iconColor: "F30000" 23 | } 24 | craftTime : 250.0 25 | } 26 | { 27 | input:{ 28 | items : [ 29 | coal/1 30 | copper/1 31 | lead/1 32 | graphite/2 33 | ] 34 | fluids:[ 35 | ozone/1.5 36 | water/10.0 37 | ] 38 | power : 2 39 | heat: 3 40 | icon: Icon.lock-open 41 | } 42 | output:{ 43 | items : [ 44 | coal/1 45 | copper/1 46 | lead/1 47 | graphite/2 48 | ] 49 | fluids:[ 50 | ozone/1.5 51 | water/10.0 52 | ] 53 | power : 2 54 | heat: 5 55 | } 56 | craftTime : 1260.0 57 | } 58 | ] -------------------------------------------------------------------------------- /js/assets/content/blocks/crafter/no-hjson.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "multicraft.MultiCrafter", 3 | "size": 5, 4 | "requirements": [ 5 | "graphite/100", 6 | "surge-alloy/50", 7 | "phase-fabric/70" 8 | ], 9 | "category": "crafting", 10 | "recipes": [ 11 | { 12 | "input": "ozone/1.5", 13 | "output": { 14 | "items": [ 15 | "copper/1", 16 | "graphite/2" 17 | ], 18 | "power": 2.5 19 | }, 20 | "craftTime": 250.0 21 | }, 22 | { 23 | "input": { 24 | "items": [ 25 | "cyber-io-ic/1", 26 | "lead" 27 | ] 28 | }, 29 | "output": { 30 | "fluids": [ 31 | "cyber-io-cyberion/1.2" 32 | ] 33 | }, 34 | "craftTime": 210.0 35 | } 36 | ] 37 | } -------------------------------------------------------------------------------- /js/assets/content/blocks/crafter/no-io.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "multicraft.MultiCrafter", 3 | "size": 2, 4 | "requirements": [ 5 | "graphite/10" 6 | ], 7 | "category": "crafting", 8 | "recipes": [ 9 | { 10 | "input": "ozone/1.5", 11 | "craftTime": 60.0 12 | }, 13 | { 14 | "output": { 15 | "items": [ 16 | "copper/1", 17 | "graphite/2" 18 | ], 19 | "power": 2.5 20 | }, 21 | "craftTime": 120.0 22 | }, 23 | { 24 | "craftTime": 250.0 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /js/assets/content/blocks/crafter/overpower.hjson: -------------------------------------------------------------------------------- 1 | type: multicraft.MultiCrafter 2 | size : 2 3 | requirements: [ 4 | graphite/100 5 | ] 6 | menu: Simple 7 | category: crafting 8 | changeRecipeEffect: placeBlock 9 | recipes : 10 | [ 11 | { 12 | input: { 13 | items: copper 14 | power: 2.5 15 | } 16 | output: { 17 | items: lead 18 | power: 5 19 | } 20 | icon: alphaaaa 21 | iconColor: ABCDEF 22 | } 23 | { 24 | input: { 25 | items: copper 26 | power: 2.5 27 | } 28 | output: { 29 | items: lead 30 | power: 5 31 | } 32 | craftTime: 60 33 | icon: Icon.right 34 | iconColor: ABCDEF 35 | } 36 | { 37 | input: { 38 | fluids: ozone 39 | power: 2.5 40 | } 41 | output:{ 42 | fluids: neoplasm 43 | power: 5 44 | } 45 | icon: neoplasm 46 | iconColor: FF00FF 47 | } 48 | ] -------------------------------------------------------------------------------- /js/assets/content/blocks/crafter/zero-power.hjson: -------------------------------------------------------------------------------- 1 | type: multicraft.MultiCrafter 2 | size : 4 3 | category: crafting 4 | requirements: [ 5 | graphite/100 6 | ] 7 | menu: Detailed 8 | recipes : [ 9 | { 10 | input: ozone/1.5 11 | output: { 12 | items: [ 13 | coal/1 14 | copper/1 15 | lead/1 16 | graphite/2 17 | ] 18 | icon: poly 19 | } 20 | craftTime : 250.0, 21 | icon: mono 22 | } 23 | { 24 | input: { 25 | items: lead/1 26 | } 27 | output: ozone/1.5 28 | craftTime : 210.0 29 | } 30 | ] -------------------------------------------------------------------------------- /js/assets/content/blocks/in-hjson.hjson: -------------------------------------------------------------------------------- 1 | craftEffect: smeltsmoke 2 | hasPower: true 3 | hasItems: true 4 | hasLiquids: true 5 | category: crafting 6 | menu: transform 7 | 8 | recipes: [ 9 | { 10 | input: water/0.05 11 | power: 0.3 12 | craftTime: 30 13 | output: { 14 | items: [ 15 | ice-cube/1 16 | ] 17 | } 18 | } 19 | { 20 | input: cryofluid/0.05 21 | power: 0.5 22 | craftTime: 40 23 | output: { 24 | items: [ 25 | cryocube/1 26 | ] 27 | } 28 | } 29 | { 30 | input: slag/0.05 31 | power: 0.7 32 | craftTime: 50 33 | output: { 34 | items: [ 35 | stone/1 36 | ] 37 | } 38 | } 39 | { 40 | input: oil/0.15 41 | power: 0.5 42 | craftTime: 60 43 | output: { 44 | items: [ 45 | coal/2 46 | ] 47 | power: 2.5 48 | } 49 | } 50 | { 51 | input: surge-mass/0.05 52 | power: 1.5 53 | craftTime: 80 54 | output: { 55 | items: [ 56 | surge-stone/1 57 | ] 58 | power: 4 59 | } 60 | } 61 | ] 62 | size: 2 63 | liquidCapacity: 10 64 | itemCapacity: 10 -------------------------------------------------------------------------------- /js/assets/content/blocks/test-drawer.hjson: -------------------------------------------------------------------------------- 1 | type: multicraft.MultiCrafter 2 | size : 2 3 | requirements: [ 4 | graphite/100 5 | ] 6 | menu: Simple 7 | category: crafting 8 | changeRecipeEffect: placeBlock 9 | recipes : 10 | [ 11 | { 12 | input: copper 13 | output: copper 14 | icon: alphaaaa 15 | iconColor: F30000 16 | craftEffect: unitCapKill 17 | } 18 | { 19 | input: lead 20 | output: lead 21 | icon: Icon.lock 22 | craftEffect: explosion 23 | } 24 | { 25 | input: phase-fabric 26 | output: phase-fabric 27 | icon: poly 28 | craftEffect: [smeltsmoke, lightningShoot] 29 | } 30 | ] 31 | 32 | drawer: { 33 | type: multicraft.DrawRecipe 34 | drawers: [ 35 | { 36 | type: DrawMulti 37 | drawers: [ 38 | { 39 | type: DrawRegion 40 | suffix: -1 41 | } 42 | { 43 | type: DrawArcSmelt 44 | } 45 | ] 46 | } 47 | { 48 | type: DrawRegion 49 | suffix: -2 50 | } 51 | { 52 | type: DrawRegion 53 | suffix: -3 54 | } 55 | ] 56 | } -------------------------------------------------------------------------------- /js/assets/scripts/main.js: -------------------------------------------------------------------------------- 1 | function setup(block){ 2 | block.requirements = ItemStack.with( 3 | Items.graphite, 5, 4 | Items.silicon, 3 5 | ) 6 | block.health = 110; 7 | block.buildVisibility = BuildVisibility.shown 8 | block.category = Category.crafting 9 | } 10 | 11 | const multi = require("multi-crafter/lib") 12 | const c = multi.MultiCrafter("js") 13 | setup(c) 14 | c.recipes = [{ 15 | input:{ 16 | items : [ 17 | "copper/1",{ 18 | item:"surge-alloy", 19 | amount: 2 20 | }] 21 | }, 22 | output:{ 23 | items : ["coal/1"], 24 | power : 2 25 | }, 26 | craftTime : 120.0 27 | },{ 28 | input:{ 29 | items : [ 30 | "cyber-io-ic/1", 31 | "titanium/1" 32 | ], 33 | power : 2 34 | }, 35 | output:{ 36 | items : "graphite/1", 37 | fluids : "cyber-io-cyberion/1.2" 38 | }, 39 | craftTime : 240.0 40 | },{ 41 | input:{ 42 | fluids : "water/1" 43 | }, 44 | output:{ 45 | fluids:{ 46 | fluid : "slag", 47 | amount : 1.5 48 | } 49 | }, 50 | craftTime : 240.0 51 | },{ 52 | input:{ 53 | fluids : "water/1" 54 | }, 55 | output:{ 56 | heat : 5 57 | }, 58 | craftTime : 120.0 59 | },{ 60 | input:{ 61 | heat : 8 62 | }, 63 | output: "sand/1", 64 | craftTime : 120.0 65 | }] 66 | 67 | print(">>>>>MultiCrafter Test JavaScript loaded.") 68 | 69 | const inHjson = multi.MultiCrafter("in-hjson") 70 | setup(inHjson) 71 | -------------------------------------------------------------------------------- /js/assets/sprites/test-drawer-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liplum/MultiCrafterLib/5068f7184487e5157711260619a25e8dbca5320f/js/assets/sprites/test-drawer-1.png -------------------------------------------------------------------------------- /js/assets/sprites/test-drawer-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liplum/MultiCrafterLib/5068f7184487e5157711260619a25e8dbca5320f/js/assets/sprites/test-drawer-2.png -------------------------------------------------------------------------------- /js/assets/sprites/test-drawer-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liplum/MultiCrafterLib/5068f7184487e5157711260619a25e8dbca5320f/js/assets/sprites/test-drawer-3.png -------------------------------------------------------------------------------- /js/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | java 3 | id("io.github.liplum.mgpp") 4 | } 5 | mindustryAssets { 6 | root at "$projectDir/assets" 7 | } -------------------------------------------------------------------------------- /js/mod.hjson: -------------------------------------------------------------------------------- 1 | displayName: "MultiCrafter Test Js" 2 | name: "test" 3 | author: "[#42a5f5[]]Liplum[[]]" 4 | description: "MultiCrafter Test Js" 5 | version: 1.0 6 | minGameVersion: 136 7 | java: false 8 | dependencies:["multi-crafter"] -------------------------------------------------------------------------------- /lib/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .idea 3 | build -------------------------------------------------------------------------------- /lib/assets/scripts/multi-crafter/lib.js: -------------------------------------------------------------------------------- 1 | const urlLoader = Packages.java.net.URLClassLoader([ 2 | Vars.mods.getMod(modName).file.file().toURI().toURL() 3 | ], Vars.mods.mainLoader()); 4 | function getClass(name){ 5 | return Packages.rhino.NativeJavaClass(Vars.mods.scripts.scope, urlLoader.loadClass(name)); 6 | }; 7 | const multiCrafterClz = getClass("multicraft.MultiCrafter") 8 | module.exports = { 9 | MultiCrafter : multiCrafterClz 10 | } -------------------------------------------------------------------------------- /lib/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import io.github.liplum.mindustry.genModHjson 2 | import io.github.liplum.mindustry.importMindustry 3 | 4 | plugins { 5 | java 6 | `maven-publish` 7 | id("io.github.liplum.mgpp") 8 | } 9 | sourceSets { 10 | main { 11 | java.srcDir("src") 12 | resources.srcDir("resources") 13 | } 14 | test { 15 | java.srcDir("test") 16 | resources.srcDir("resources") 17 | } 18 | } 19 | 20 | java { 21 | sourceCompatibility = JavaVersion.VERSION_1_8 22 | targetCompatibility = JavaVersion.VERSION_1_8 23 | } 24 | dependencies { 25 | importMindustry() 26 | testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.2") 27 | testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") 28 | testImplementation("com.github.liplum:TestUtils:v0.1") 29 | } 30 | 31 | java { 32 | withSourcesJar() 33 | withJavadocJar() 34 | } 35 | mindustryAssets{ 36 | // no icon 37 | icon at "$projectDir/icon.png" 38 | } 39 | tasks.genModHjson { 40 | // no mod.hjson 41 | enabled = false 42 | } 43 | publishing { 44 | publications { 45 | create("maven") { 46 | from(components["java"]) 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /lib/src/multicraft/ConsumeFluidDynamic.java: -------------------------------------------------------------------------------- 1 | package multicraft; 2 | 3 | import arc.func.*; 4 | import arc.scene.ui.layout.*; 5 | import mindustry.gen.*; 6 | import mindustry.type.*; 7 | import mindustry.ui.*; 8 | import mindustry.world.*; 9 | import mindustry.world.consumers.*; 10 | import mindustry.world.modules.*; 11 | import multicraft.ui.*; 12 | 13 | public class ConsumeFluidDynamic extends Consume { 14 | public final Func fluids; 15 | 16 | @SuppressWarnings("unchecked") 17 | public ConsumeFluidDynamic(Func fluids) { 18 | this.fluids = (Func) fluids; 19 | } 20 | 21 | @Override 22 | public void apply(Block block) { 23 | block.hasLiquids = true; 24 | } 25 | 26 | @Override 27 | public void update(Building build) { 28 | LiquidStack[] fluids = this.fluids.get(build); 29 | remove(build.liquids, fluids, build.edelta()); 30 | } 31 | 32 | @Override 33 | public void build(Building build, Table table) { 34 | final LiquidStack[][] current = {fluids.get(build)}; 35 | 36 | table.table(cont -> { 37 | table.update(() -> { 38 | LiquidStack[] newFluids = fluids.get(build); 39 | if (current[0] != newFluids) { 40 | rebuild(build, cont); 41 | current[0] = newFluids; 42 | } 43 | }); 44 | 45 | rebuild(build, cont); 46 | }); 47 | } 48 | 49 | private void rebuild(Building tile, Table table) { 50 | table.clear(); 51 | int i = 0; 52 | 53 | LiquidStack[] fluids = this.fluids.get(tile); 54 | for (LiquidStack stack : fluids) { 55 | table.add(new ReqImage(new FluidImage(stack.liquid.uiIcon), 56 | () -> tile.liquids != null && tile.liquids.get(stack.liquid) >= stack.amount)).padRight(8).left(); 57 | if (++i % 4 == 0) table.row(); 58 | } 59 | } 60 | 61 | @Override 62 | public float efficiency(Building build) { 63 | LiquidStack[] fluids = this.fluids.get(build); 64 | return build.consumeTriggerValid() || has(build.liquids, fluids) ? 1f : 0f; 65 | } 66 | public static boolean has(LiquidModule fluids, LiquidStack[] reqs) { 67 | for (LiquidStack req : reqs) { 68 | if (fluids.get(req.liquid) < req.amount) 69 | return false; 70 | } 71 | return true; 72 | } 73 | 74 | public static void remove(LiquidModule fluids, LiquidStack[] reqs, float multiplier) { 75 | for (LiquidStack req : reqs) { 76 | fluids.remove(req.liquid, req.amount * multiplier); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /lib/src/multicraft/ContentResolver.java: -------------------------------------------------------------------------------- 1 | package multicraft; 2 | 3 | import arc.*; 4 | import arc.func.*; 5 | import arc.graphics.g2d.*; 6 | import arc.scene.style.*; 7 | import arc.util.*; 8 | import mindustry.*; 9 | import mindustry.content.*; 10 | import mindustry.ctype.*; 11 | import mindustry.entities.*; 12 | import mindustry.gen.*; 13 | import mindustry.type.*; 14 | import mindustry.world.*; 15 | 16 | import java.lang.reflect.*; 17 | 18 | import static multicraft.ParserUtils.*; 19 | 20 | public class ContentResolver { 21 | 22 | @Nullable 23 | public static Effect findFx(String name) { 24 | Object effect = field(Fx.class, name); 25 | if (effect instanceof Effect) return (Effect) effect; 26 | else return null; 27 | } 28 | 29 | 30 | @Nullable 31 | public static Item findItem(String id) { 32 | for (Item item : Vars.content.items()) 33 | if (id.equals(item.name)) return item;// prevent null pointer 34 | return null; 35 | } 36 | 37 | @Nullable 38 | public static Liquid findFluid(String id) { 39 | for (Liquid fluid : Vars.content.liquids()) 40 | if (id.equals(fluid.name)) return fluid;// prevent null pointer 41 | return null; 42 | } 43 | 44 | @Nullable 45 | public static Block findBlock(String id) { 46 | for (Block block : Vars.content.blocks()) 47 | if (id.equals(block.name)) return block; // prevent null pointer 48 | return null; 49 | } 50 | 51 | @Nullable 52 | public static UnitType findUnit(String id) { 53 | for (UnitType unit : Vars.content.units()) 54 | if (id.equals(unit.name)) return unit; // prevent null pointer 55 | return null; 56 | } 57 | 58 | @Nullable 59 | public static UnlockableContent findPayload(String id) { 60 | UnitType unit = findUnit(id); 61 | if (unit != null) return unit; 62 | return findBlock(id); 63 | } 64 | 65 | 66 | /** 67 | * Supported name pattern: 68 | *
    69 | *
  • "Icon.xxx" from {@link Icon} 70 | *
  • "copper", "water", "router" or "mono" 71 | *
72 | */ 73 | @Nullable 74 | public static Prov findIcon(String name) { 75 | if (name.startsWith("Icon.") && name.length() > 5) { 76 | try { 77 | String fieldName = name.substring(5); 78 | Field field = Icon.class.getField(fieldName.contains("-") ? kebab2camel(fieldName) : fieldName); 79 | Object icon = field.get(null); 80 | TextureRegion tr = ((TextureRegionDrawable) icon).getRegion(); 81 | return () -> tr; 82 | } catch (NoSuchFieldException | IllegalAccessException e) { 83 | return null; 84 | } 85 | } else { 86 | Item item = findItem(name); 87 | if (item != null) return () -> item.uiIcon; 88 | Liquid fluid = findFluid(name); 89 | if (fluid != null) return () -> fluid.uiIcon; 90 | UnlockableContent payload = findPayload(name); 91 | if (payload != null) return () -> payload.uiIcon; 92 | TextureRegion tr = Core.atlas.find(name); 93 | if (tr.found()) return () -> tr; 94 | } 95 | return null; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /lib/src/multicraft/CustomConsumePayloadDynamic.java: -------------------------------------------------------------------------------- 1 | package multicraft; 2 | 3 | import arc.func.*; 4 | import arc.scene.ui.layout.*; 5 | import mindustry.gen.*; 6 | import mindustry.type.*; 7 | import mindustry.ui.*; 8 | import mindustry.world.consumers.*; 9 | import mindustry.world.meta.*; 10 | 11 | /** Copy of {@linkplain ConsumePayloadDynamic} that takes a PayloadStack[] */ 12 | public class CustomConsumePayloadDynamic extends Consume { 13 | public final Func payloads; 14 | 15 | @SuppressWarnings("unchecked") 16 | public CustomConsumePayloadDynamic(Func payloads) { 17 | this.payloads = (Func)payloads; 18 | } 19 | 20 | @Override 21 | public float efficiency(Building build){ 22 | float mult = multiplier.get(build); 23 | for(PayloadStack stack : payloads.get(build)){ 24 | if(!build.getPayloads().contains(stack.item, Math.round(stack.amount * mult))){ 25 | return 0f; 26 | } 27 | } 28 | return 1f; 29 | } 30 | 31 | @Override 32 | public void trigger(Building build){ 33 | float mult = multiplier.get(build); 34 | for(PayloadStack stack : payloads.get(build)){ 35 | build.getPayloads().remove(stack.item, Math.round(stack.amount * mult)); 36 | } 37 | } 38 | 39 | @Override 40 | public void display(Stats stats){ 41 | //needs to be implemented by the block itself, not enough info to display here 42 | } 43 | 44 | @Override 45 | public void build(Building build, Table table){ 46 | PayloadStack[][] current = {payloads.get(build)}; 47 | 48 | table.table(cont -> { 49 | table.update(() -> { 50 | if(current[0] != payloads.get(build)){ 51 | rebuild(build, cont); 52 | current[0] = payloads.get(build); 53 | } 54 | }); 55 | 56 | rebuild(build, cont); 57 | }); 58 | } 59 | 60 | private void rebuild(Building build, Table table){ 61 | PayloadSeq inv = build.getPayloads(); 62 | PayloadStack[] pay = payloads.get(build); 63 | 64 | table.table(c -> { 65 | int i = 0; 66 | for(PayloadStack stack : pay){ 67 | c.add(new ReqImage(new ItemImage(stack.item.uiIcon, Math.round(stack.amount * multiplier.get(build))), 68 | () -> inv.contains(stack.item, Math.round(stack.amount * multiplier.get(build))))).padRight(8); 69 | if(++i % 4 == 0) c.row(); 70 | } 71 | }).left(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/src/multicraft/DrawHeatRegion.java: -------------------------------------------------------------------------------- 1 | package multicraft; 2 | 3 | import arc.*; 4 | import arc.graphics.*; 5 | import arc.graphics.g2d.*; 6 | import arc.math.*; 7 | import mindustry.gen.*; 8 | import mindustry.graphics.*; 9 | import mindustry.world.*; 10 | import mindustry.world.blocks.heat.*; 11 | import mindustry.world.draw.*; 12 | 13 | /** 14 | * {@link mindustry.world.draw.DrawHeatRegion} 15 | */ 16 | public class DrawHeatRegion extends DrawBlock { 17 | public Color color = new Color(1f, 0.22f, 0.22f, 0.8f); 18 | public float pulse = 0.3f, pulseScl = 10f; 19 | 20 | public TextureRegion heat; 21 | public String suffix = "-glow"; 22 | 23 | public DrawHeatRegion(String suffix) { 24 | this.suffix = suffix; 25 | } 26 | 27 | public DrawHeatRegion() { 28 | } 29 | 30 | @Override 31 | public void draw(Building build) { 32 | Draw.z(Layer.blockAdditive); 33 | if (build instanceof HeatBlock && build instanceof HeatConsumer) { 34 | HeatBlock hb = (HeatBlock) build; 35 | HeatConsumer hc = (HeatConsumer) build; 36 | if (hb.heat() > 0) { 37 | Draw.blend(Blending.additive); 38 | Draw.color(color, Mathf.clamp(hb.heat() / hc.heatRequirement()) * (color.a * (1f - pulse + Mathf.absin(pulseScl, pulse)))); 39 | Draw.rect(heat, build.x, build.y); 40 | Draw.blend(); 41 | Draw.color(); 42 | } 43 | } 44 | Draw.z(Layer.block); 45 | } 46 | 47 | @Override 48 | public void load(Block block) { 49 | heat = Core.atlas.find(block.name + suffix); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/src/multicraft/DrawRecipe.java: -------------------------------------------------------------------------------- 1 | package multicraft; 2 | 3 | import arc.graphics.g2d.*; 4 | import arc.struct.*; 5 | import arc.util.*; 6 | import mindustry.entities.units.*; 7 | import mindustry.gen.*; 8 | import mindustry.world.*; 9 | import mindustry.world.draw.*; 10 | import multicraft.MultiCrafter.*; 11 | 12 | public class DrawRecipe extends DrawBlock { 13 | public int defaultDrawer = 0; 14 | public DrawBlock[] drawers = {}; 15 | 16 | public DrawRecipe() { 17 | super(); 18 | } 19 | 20 | @Override 21 | public void getRegionsToOutline(Block block, Seq out) { 22 | if (0 <= defaultDrawer && defaultDrawer < drawers.length) 23 | drawers[defaultDrawer].getRegionsToOutline(block, out); 24 | } 25 | 26 | @Override 27 | public void draw(Building build) { 28 | if (build instanceof MultiCrafterBuild) { 29 | MultiCrafterBuild crafter = (MultiCrafterBuild) build; 30 | int i = crafter.curRecipeIndex; 31 | if (0 <= i && i < drawers.length) 32 | drawers[i].draw(build); 33 | } else { 34 | Draw.rect(build.block.region, build.x, build.y, build.drawrot()); 35 | } 36 | } 37 | 38 | @Override 39 | public void drawLight(Building build) { 40 | if (build instanceof MultiCrafterBuild) { 41 | MultiCrafterBuild crafter = (MultiCrafterBuild) build; 42 | int i = crafter.curRecipeIndex; 43 | if (0 < i && i < drawers.length) 44 | drawers[i].drawLight(build); 45 | } 46 | } 47 | 48 | @Override 49 | public void drawPlan(Block block, BuildPlan plan, Eachable list) { 50 | if (0 <= defaultDrawer && defaultDrawer < drawers.length) 51 | drawers[defaultDrawer].drawPlan(block, plan, list); 52 | else 53 | block.drawDefaultPlanRegion(plan, list); 54 | } 55 | 56 | @Override 57 | public void load(Block block) { 58 | for (DrawBlock drawer : drawers) { 59 | drawer.load(block); 60 | } 61 | } 62 | 63 | @Override 64 | public TextureRegion[] icons(Block block) { 65 | if (0 <= defaultDrawer && defaultDrawer < drawers.length) 66 | return drawers[defaultDrawer].icons(block); 67 | return new TextureRegion[]{block.region}; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/src/multicraft/IOEntry.java: -------------------------------------------------------------------------------- 1 | package multicraft; 2 | 3 | import arc.func.*; 4 | import arc.graphics.*; 5 | import arc.graphics.g2d.*; 6 | import arc.struct.*; 7 | import arc.util.*; 8 | 9 | import mindustry.ctype.*; 10 | import mindustry.type.*; 11 | 12 | public class IOEntry { 13 | public ItemStack[] items = ItemStack.empty; 14 | public LiquidStack[] fluids = LiquidStack.empty; 15 | public float power = 0f; 16 | public float heat = 0f; 17 | public PayloadStack[] payloads = {}; // Equivalent of empty 18 | 19 | public ObjectSet itemsUnique = new ObjectSet<>(); 20 | public ObjectSet fluidsUnique = new ObjectSet<>(); 21 | public ObjectSet payloadsUnique = new ObjectSet<>(); 22 | @Nullable 23 | public Prov icon; 24 | @Nullable 25 | public Color iconColor; 26 | 27 | public IOEntry() {} 28 | 29 | public void cacheUnique() { 30 | for (ItemStack item : items) { 31 | itemsUnique.add(item.item); 32 | } 33 | for (LiquidStack fluid : fluids) { 34 | fluidsUnique.add(fluid.liquid); 35 | } 36 | for (PayloadStack payload : payloads) { 37 | // "item" can be any UnlockableContent 38 | payloadsUnique.add(payload.item); 39 | } 40 | } 41 | 42 | public boolean isEmpty() { 43 | return items.length == 0 && fluids.length == 0 44 | && power <= 0f && heat <= 0f && payloads.length == 0; 45 | } 46 | 47 | public int maxItemAmount() { 48 | int max = 0; 49 | for (ItemStack item : items) { 50 | max = Math.max(item.amount, max); 51 | } 52 | return max; 53 | } 54 | 55 | public float maxFluidAmount() { 56 | float max = 0; 57 | for (LiquidStack fluid : fluids) { 58 | max = Math.max(fluid.amount, max); 59 | } 60 | return max; 61 | } 62 | 63 | public int maxPayloadAmount() { 64 | int max = 0; 65 | for (PayloadStack payload : payloads) { 66 | max = Math.max(payload.amount, max); 67 | } 68 | return max; 69 | } 70 | 71 | @Override 72 | public String toString() { 73 | return "IOEntry{" + 74 | "items=" + items + 75 | "fluids=" + fluids + 76 | "power=" + power + 77 | "heat=" + heat + 78 | "payloads=" + payloads + 79 | "}"; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /lib/src/multicraft/MultiCrafter.java: -------------------------------------------------------------------------------- 1 | package multicraft; 2 | 3 | import arc.*; 4 | import arc.graphics.*; 5 | import arc.graphics.g2d.*; 6 | import arc.math.*; 7 | import arc.math.geom.*; 8 | import arc.scene.ui.layout.*; 9 | import arc.struct.*; 10 | import arc.util.*; 11 | import arc.util.io.*; 12 | import mindustry.*; 13 | import mindustry.content.*; 14 | import mindustry.entities.*; 15 | import mindustry.entities.units.*; 16 | import mindustry.gen.*; 17 | import mindustry.graphics.*; 18 | import mindustry.io.*; 19 | import mindustry.logic.*; 20 | import mindustry.type.*; 21 | import mindustry.ui.*; 22 | import mindustry.world.*; 23 | import mindustry.world.blocks.heat.*; 24 | import mindustry.world.blocks.payloads.*; 25 | import mindustry.world.consumers.*; 26 | import mindustry.world.draw.*; 27 | import mindustry.world.meta.*; 28 | import multicraft.ui.*; 29 | 30 | import static mindustry.Vars.*; 31 | 32 | public class MultiCrafter extends PayloadBlock { 33 | public boolean hasHeat = false; 34 | public boolean hasPayloads = false; 35 | 36 | public float powerCapacity = 0f; 37 | /** maximum payloads this block can carry */ 38 | public int payloadCapacity = 1; 39 | 40 | public float itemCapacityMultiplier = 1f; 41 | public float fluidCapacityMultiplier = 1f; 42 | public float powerCapacityMultiplier = 1f; 43 | public float payloadCapacityMultiplier = 2f; 44 | /* 45 | [ ==> Seq 46 | { ==> ObjectMap 47 | // String ==> Any --- Value may be a Seq, Map or String 48 | input:{ 49 | // String ==> Any --- Value may be a Seq, Seq, String or Map 50 | items:["mod-id-item/1","mod-id-item2/1"], 51 | fluids:["mod-id-liquid/10.5","mod-id-gas/10"] 52 | power: 3 pre tick 53 | }, 54 | output:{ 55 | items:["mod-id-item/1","mod-id-item2/1"], 56 | fluids:["mod-id-liquid/10.5","mod-id-gas/10"] 57 | heat: 10 58 | }, 59 | craftTime: 120 60 | } 61 | ] 62 | */ 63 | /** 64 | * For Json and Javascript to configure. 65 | */ 66 | public Object recipes; 67 | /** 68 | * The resolved recipes. 69 | */ 70 | @Nullable 71 | public Seq resolvedRecipes = null; 72 | /** 73 | * For Json and Javascript to configure. 74 | */ 75 | public String menu = "transform"; 76 | /** 77 | * The resolved menu. 78 | */ 79 | @Nullable 80 | public RecipeSwitchStyle switchStyle = null; 81 | public Effect craftEffect = Fx.none; 82 | public Effect updateEffect = Fx.none; 83 | public Effect changeRecipeEffect = Fx.rotateBlock; 84 | public int[] fluidOutputDirections = {-1}; 85 | public float updateEffectChance = 0.04f; 86 | public float warmupSpeed = 0.019f; 87 | /** 88 | * Whether stop production when the fluid is full. 89 | * Turn off this to ignore fluid output, for instance, the fluid is only by-product. 90 | */ 91 | public boolean ignoreLiquidFullness = false; 92 | 93 | /** 94 | * If true, the crafter with multiple fluid outputs will dump excess, 95 | * when there's still space for at least one fluid type. 96 | */ 97 | public boolean dumpExtraFluid = true; 98 | public DrawBlock drawer = new DrawDefault(); 99 | 100 | protected boolean isOutputItem = false; 101 | protected boolean isConsumeItem = false; 102 | protected boolean isOutputFluid = false; 103 | protected boolean isConsumeFluid = false; 104 | protected boolean isOutputPower = false; 105 | protected boolean isConsumePower = false; 106 | protected boolean isOutputHeat = false; 107 | protected boolean isConsumeHeat = false; 108 | protected boolean isOutputPayload = false; 109 | protected boolean isConsumePayload = false; 110 | /** 111 | * What color of heat for recipe selector. 112 | */ 113 | public Color heatColor = new Color(1f, 0.22f, 0.22f, 0.8f); 114 | /** 115 | * For {@linkplain HeatConsumer}, 116 | * it's used to display something of block or initialize the recipe index. 117 | */ 118 | public int defaultRecipeIndex = 0; 119 | /** 120 | * For {@linkplain HeatConsumer}, 121 | * after heat meets this requirement, excess heat will be scaled by this number. 122 | */ 123 | public float overheatScale = 1f; 124 | /** 125 | * For {@linkplain HeatConsumer}, 126 | * maximum possible efficiency after overheat. 127 | */ 128 | public float maxEfficiency = 1f; 129 | /** 130 | * For {@linkplain HeatBlock} 131 | */ 132 | public float warmupRate = 0.15f; 133 | /** 134 | * Whether to show name tooltip in {@link MultiCrafterBuild#buildStats(Table)} 135 | */ 136 | protected boolean showNameTooltip = false; 137 | 138 | public MultiCrafter(String name) { 139 | super(name); 140 | update = true; 141 | solid = true; 142 | sync = true; 143 | flags = EnumSet.of(BlockFlag.factory); 144 | ambientSound = Sounds.machine; 145 | configurable = true; 146 | saveConfig = true; 147 | ambientSoundVolume = 0.03f; 148 | config(Integer.class, MultiCrafterBuild::setCurRecipeIndexFromRemote); 149 | Log.info("MultiCrafter[" + this.name + "] loaded."); 150 | } 151 | 152 | @Override 153 | public void init() { 154 | hasItems = false; 155 | hasLiquids = false; 156 | hasPower = false; 157 | hasHeat = false; 158 | hasPayloads = false; 159 | outputsPower = false; 160 | outputsPayload = false; 161 | 162 | final MultiCrafterParser parser = new MultiCrafterParser(); 163 | // if the recipe is already set in another way, don't analyze it again. 164 | if (resolvedRecipes == null && recipes != null) resolvedRecipes = parser.parse(this, recipes); 165 | if (resolvedRecipes == null || resolvedRecipes.isEmpty()) 166 | throw new ArcRuntimeException(MultiCrafterParser.genName(this) + " has no recipe! It's perhaps because all recipes didn't find items, fluids or payloads they need. Check your `last_log.txt` to obtain more information."); 167 | if (switchStyle == null) switchStyle = RecipeSwitchStyle.get(menu); 168 | decorateRecipes(); 169 | setupBlockByRecipes(); 170 | defaultRecipeIndex = Mathf.clamp(defaultRecipeIndex, 0, resolvedRecipes.size - 1); 171 | recipes = null; // free the recipe Seq, it's useless now. 172 | setupConsumers(); 173 | super.init(); 174 | } 175 | 176 | @Nullable 177 | protected static Table hoveredInfo; 178 | 179 | public class MultiCrafterBuild extends PayloadBlockBuild implements HeatBlock, HeatConsumer { 180 | /** 181 | * For {@linkplain HeatConsumer}, only enabled when the multicrafter requires heat input 182 | */ 183 | public float[] sideHeat = new float[4]; 184 | /** 185 | * For {@linkplain HeatConsumer} and {@linkplain HeatBlock}, 186 | * only enabled when the multicrafter requires heat as input or can output heat. 187 | * Serialized 188 | */ 189 | public float heat = 0f; 190 | /** 191 | * Serialized 192 | */ 193 | public float craftingTime; 194 | public float totalProgress; 195 | /** 196 | * Serialized 197 | */ 198 | public float warmup; 199 | /** 200 | * Serialized 201 | */ 202 | public int curRecipeIndex = defaultRecipeIndex; 203 | 204 | public PayloadSeq payloads = new PayloadSeq(); 205 | public @Nullable Vec2 commandPos; 206 | 207 | public void setCurRecipeIndexFromRemote(int index) { 208 | int newIndex = Mathf.clamp(index, 0, resolvedRecipes.size - 1); 209 | if (newIndex != curRecipeIndex) { 210 | curRecipeIndex = newIndex; 211 | createEffect(changeRecipeEffect); 212 | craftingTime = 0f; 213 | if (!Vars.headless) rebuildHoveredInfo(); 214 | } 215 | } 216 | 217 | public Recipe getCurRecipe() { 218 | // Prevent out of bound 219 | curRecipeIndex = Mathf.clamp(curRecipeIndex, 0, resolvedRecipes.size - 1); 220 | return resolvedRecipes.get(curRecipeIndex); 221 | } 222 | 223 | @Override 224 | public boolean acceptItem(Building source, Item item) { 225 | return hasItems && 226 | getCurRecipe().input.itemsUnique.contains(item) && 227 | items.get(item) < getMaximumAccepted(item); 228 | } 229 | 230 | @Override 231 | public boolean acceptLiquid(Building source, Liquid liquid) { 232 | return hasLiquids && 233 | getCurRecipe().input.fluidsUnique.contains(liquid) && 234 | liquids.get(liquid) < liquidCapacity; 235 | } 236 | 237 | @Override 238 | public boolean acceptPayload(Building source, Payload payload) { 239 | return hasPayloads && this.payload == null && 240 | getCurRecipe().input.payloadsUnique.contains(payload.content()) && 241 | payloads.get(payload.content()) < payloadCapacity; 242 | } 243 | 244 | @Override 245 | public PayloadSeq getPayloads() { 246 | return this.payloads; 247 | } 248 | 249 | public void yeetPayload(Payload payload) { 250 | payloads.add(payload.content(), 1); 251 | } 252 | 253 | @Override 254 | public Vec2 getCommandPosition() { 255 | if (getCurRecipe().isOutputPayload()) 256 | return this.commandPos; 257 | else return null; 258 | } 259 | 260 | @Override 261 | public void onCommand(Vec2 target) { 262 | if (getCurRecipe().isOutputPayload()) 263 | this.commandPos = target; 264 | } 265 | 266 | @Override 267 | public float edelta() { 268 | Recipe cur = getCurRecipe(); 269 | if (cur.input.power > 0f) return this.efficiency * 270 | Mathf.clamp(getCurPowerStore() / cur.input.power) * 271 | this.delta(); 272 | else return this.efficiency * this.delta(); 273 | } 274 | 275 | @Override 276 | public void updateTile() { 277 | Recipe cur = getCurRecipe(); 278 | float craftTimeNeed = cur.craftTime; 279 | // As HeatConsumer 280 | if (cur.isConsumeHeat()) heat = calculateHeat(sideHeat); 281 | if (cur.isOutputHeat()) { 282 | float heatOutput = cur.output.heat; 283 | heat = Mathf.approachDelta(heat, heatOutput * efficiency, warmupRate * edelta()); 284 | } 285 | // cool down 286 | if (efficiency > 0 && (!hasPower || getCurPowerStore() >= cur.input.power)) { 287 | // if <= 0, instantly produced 288 | if (craftTimeNeed > 0f) craftingTime += edelta(); 289 | warmup = Mathf.approachDelta(warmup, warmupTarget(), warmupSpeed); 290 | if (hasPower) { 291 | float powerChange = (cur.output.power - cur.input.power) * delta(); 292 | if (!Mathf.zero(powerChange)) 293 | setCurPowerStore((getCurPowerStore() + powerChange)); 294 | } 295 | 296 | //continuously output fluid based on efficiency 297 | if (cur.isOutputFluid()) { 298 | float increment = getProgressIncrease(1f); 299 | for (LiquidStack output : cur.output.fluids) { 300 | Liquid fluid = output.liquid; 301 | handleLiquid(this, fluid, Math.min(output.amount * increment, liquidCapacity - liquids.get(fluid))); 302 | } 303 | } 304 | // particle fx 305 | if (wasVisible && Mathf.chanceDelta(updateEffectChance)) 306 | updateEffect.at(x + Mathf.range(size * 4f), y + Mathf.range(size * 4)); 307 | } else warmup = Mathf.approachDelta(warmup, 0f, warmupSpeed); 308 | totalProgress += warmup * Time.delta; 309 | 310 | if (moveInPayload()) { 311 | yeetPayload(payload); 312 | payload = null; 313 | } 314 | 315 | if (craftTimeNeed <= 0f) { 316 | if (efficiency > 0f) 317 | craft(); 318 | } else if (craftingTime >= craftTimeNeed) 319 | craft(); 320 | 321 | updateBars(); 322 | dumpOutputs(); 323 | } 324 | 325 | public void updateBars() { 326 | barMap.clear(); 327 | setBars(); 328 | } 329 | 330 | @Override 331 | public boolean shouldConsume() { 332 | Recipe cur = getCurRecipe(); 333 | if (hasItems) for (ItemStack output : cur.output.items) 334 | if (items.get(output.item) + output.amount > itemCapacity) 335 | return false; 336 | 337 | if (hasLiquids) if (cur.isOutputFluid() && !ignoreLiquidFullness) { 338 | boolean allFull = true; 339 | for (LiquidStack output : cur.output.fluids) 340 | if (liquids.get(output.liquid) >= liquidCapacity - 0.001f) { 341 | if (!dumpExtraFluid) return false; 342 | } else 343 | allFull = false; //if there's still space left, it's not full for all fluids 344 | 345 | //if there is no space left for any fluid, it can't reproduce 346 | if (allFull) return false; 347 | } 348 | if (hasPayloads) for (PayloadStack output : cur.output.payloads) 349 | if (payloads.get(output.item) + output.amount > payloadCapacity) 350 | return false; 351 | return enabled; 352 | } 353 | 354 | public void craft() { 355 | consume(); 356 | Recipe cur = getCurRecipe(); 357 | if (cur.isOutputItem()) 358 | for (ItemStack output : cur.output.items) for (int i = 0; i < output.amount; i++) offload(output.item); 359 | 360 | if (wasVisible) createCraftEffect(); 361 | if (cur.craftTime > 0f) 362 | craftingTime %= cur.craftTime; 363 | else 364 | craftingTime = 0f; 365 | } 366 | 367 | public void createCraftEffect() { 368 | Recipe cur = getCurRecipe(); 369 | Effect curFx = cur.craftEffect; 370 | Effect fx = curFx != Fx.none ? curFx : craftEffect; 371 | createEffect(fx); 372 | } 373 | 374 | public void dumpOutputs() { 375 | Recipe cur = getCurRecipe(); 376 | if (timer(timerDump, dumpTime / timeScale)) { 377 | if (cur.isOutputItem()) 378 | for (ItemStack output : cur.output.items) dump(output.item); 379 | 380 | //TODO fix infinite output 381 | if (cur.isOutputPayload()) { 382 | for (PayloadStack output : cur.output.payloads) { 383 | Payload payloadOutput = null; 384 | if (output.item instanceof Block) 385 | payloadOutput = new BuildPayload((Block) output.item, this.team); 386 | else if (output.item instanceof UnitType) 387 | payloadOutput = new UnitPayload(((UnitType) output.item).create(this.team)); 388 | 389 | if (payloadOutput != null) 390 | dumpPayload(payloadOutput); 391 | } 392 | } 393 | } 394 | 395 | if (cur.isOutputFluid()) { 396 | LiquidStack[] fluids = cur.output.fluids; 397 | for (int i = 0; i < fluids.length; i++) { 398 | int dir = fluidOutputDirections.length > i ? fluidOutputDirections[i] : -1; 399 | dumpLiquid(fluids[i].liquid, 2f, dir); 400 | } 401 | } 402 | } 403 | 404 | /** 405 | * As {@linkplain HeatBlock} 406 | */ 407 | @Override 408 | public float heat() { 409 | return heat; 410 | } 411 | 412 | /** 413 | * As {@linkplain HeatBlock} 414 | */ 415 | @Override 416 | public float heatFrac() { 417 | Recipe cur = getCurRecipe(); 418 | if (isOutputHeat && cur.isOutputHeat()) return heat / cur.output.heat; 419 | else if (isConsumeHeat && cur.isConsumeHeat()) return heat / cur.input.heat; 420 | return 0f; 421 | } 422 | 423 | /** 424 | * As {@linkplain HeatConsumer} 425 | * Only for visual effects 426 | */ 427 | @Override 428 | public float[] sideHeat() { 429 | return sideHeat; 430 | } 431 | 432 | /** 433 | * As {@linkplain HeatConsumer} 434 | * Only for visual effects 435 | */ 436 | @Override 437 | public float heatRequirement() { 438 | Recipe cur = getCurRecipe(); 439 | // When As HeatConsumer 440 | if (isConsumeHeat && cur.isConsumeHeat()) return cur.input.heat; 441 | return 0f; 442 | } 443 | 444 | @Override 445 | public float calculateHeat(float[] sideHeat) { 446 | Point2[] edges = this.block.getEdges(); 447 | int length = edges.length; 448 | for (int i = 0; i < length; ++i) { 449 | Point2 edge = edges[i]; 450 | Building build = this.nearby(edge.x, edge.y); 451 | if (build != null && build.team == this.team && build instanceof HeatBlock) { 452 | HeatBlock heater = (HeatBlock) build; 453 | // Only calculate heat if the block is a heater or a multicrafter heat output 454 | if (heater instanceof MultiCrafterBuild) { 455 | MultiCrafterBuild multi = (MultiCrafterBuild) heater; 456 | if (multi.getCurRecipe().isOutputHeat()) 457 | return this.calculateHeat(sideHeat, (IntSet) null); 458 | } else return this.calculateHeat(sideHeat, (IntSet) null); 459 | } 460 | } 461 | 462 | return 0.0f; 463 | } 464 | 465 | @Override 466 | public float getPowerProduction() { 467 | Recipe cur = getCurRecipe(); 468 | 469 | if (isOutputPower && cur.isOutputPower()) return cur.output.power * efficiency; 470 | else return 0f; 471 | } 472 | 473 | @Override 474 | public void buildConfiguration(Table table) { 475 | switchStyle.build(MultiCrafter.this, this, table); 476 | } 477 | 478 | public float getCurPowerStore() { 479 | if (power == null) return 0f; 480 | return power.status * powerCapacity; 481 | } 482 | 483 | public void setCurPowerStore(float powerStore) { 484 | if (power == null) return; 485 | power.status = Mathf.clamp(powerStore / powerCapacity); 486 | } 487 | 488 | @Override 489 | public void draw() { 490 | drawer.draw(this); 491 | } 492 | 493 | @Override 494 | public void drawLight() { 495 | super.drawLight(); 496 | drawer.drawLight(this); 497 | } 498 | 499 | @Override 500 | public Object config() { 501 | return curRecipeIndex; 502 | } 503 | 504 | @Override 505 | public boolean shouldAmbientSound() { 506 | return efficiency > 0; 507 | } 508 | 509 | @Override 510 | public double sense(LAccess sensor) { 511 | if (sensor == LAccess.progress) return progress(); 512 | if (sensor == LAccess.heat) return warmup(); 513 | //attempt to prevent wild total fluid fluctuation, at least for crafter 514 | //if(sensor == LAccess.totalLiquids && outputLiquid != null) return liquids.get(outputLiquid.liquid); 515 | return super.sense(sensor); 516 | } 517 | 518 | @Override 519 | public void write(Writes write) { 520 | super.write(write); 521 | write.f(craftingTime); 522 | write.f(warmup); 523 | write.i(curRecipeIndex); 524 | write.f(heat); 525 | 526 | //TODO Fix save corruption 527 | if(getCurRecipe().isConsumePayload()) 528 | payloads.write(write); 529 | if (getCurRecipe().isOutputPayload()) 530 | TypeIO.writeVecNullable(write, commandPos); 531 | } 532 | 533 | @Override 534 | public void read(Reads read, byte revision) { 535 | super.read(read, revision); 536 | craftingTime = read.f(); 537 | warmup = read.f(); 538 | curRecipeIndex = Mathf.clamp(read.i(), 0, resolvedRecipes.size - 1); 539 | heat = read.f(); 540 | 541 | //TODO Fix save corruption 542 | if(getCurRecipe().isConsumePayload()) 543 | payloads.read(read); 544 | if (revision >= 1 && getCurRecipe().isOutputPayload()) 545 | commandPos = TypeIO.readVecNullable(read); 546 | } 547 | 548 | public float warmupTarget() { 549 | Recipe cur = getCurRecipe(); 550 | // When As HeatConsumer 551 | if (isConsumeHeat && cur.isConsumeHeat()) return Mathf.clamp(heat / cur.input.heat); 552 | else return 1f; 553 | } 554 | 555 | @Override 556 | public void updateEfficiencyMultiplier() { 557 | Recipe cur = getCurRecipe(); 558 | // When As HeatConsumer 559 | if (isConsumeHeat && cur.isConsumeHeat()) { 560 | efficiency *= efficiencyScale(); 561 | potentialEfficiency *= efficiencyScale(); 562 | } 563 | } 564 | 565 | public float efficiencyScale() { 566 | Recipe cur = getCurRecipe(); 567 | // When As HeatConsumer 568 | if (isConsumeHeat && cur.isConsumeHeat()) { 569 | float heatRequirement = cur.input.heat; 570 | float over = Math.max(heat - heatRequirement, 0f); 571 | return Math.min(Mathf.clamp(heat / heatRequirement) + over / heatRequirement * overheatScale, maxEfficiency); 572 | } else return 1f; 573 | } 574 | 575 | @Override 576 | public float warmup() { 577 | return warmup; 578 | } 579 | 580 | @Override 581 | public float progress() { 582 | Recipe cur = getCurRecipe(); 583 | return Mathf.clamp(cur.craftTime > 0f ? craftingTime / cur.craftTime : 1f); 584 | } 585 | 586 | @Override 587 | public float totalProgress(){ 588 | return totalProgress; 589 | } 590 | 591 | @Override 592 | public void display(Table table) { 593 | super.display(table); 594 | hoveredInfo = table; 595 | } 596 | 597 | public void rebuildHoveredInfo() { 598 | try { 599 | Table info = hoveredInfo; 600 | if (info != null) { 601 | info.clear(); 602 | display(info); 603 | } 604 | } catch (Exception ignored) { 605 | // Maybe null pointer or cast exception 606 | } 607 | } 608 | 609 | public void createEffect(Effect effect) { 610 | if (effect == Fx.none) return; 611 | if (effect == Fx.placeBlock) effect.at(x, y, block.size); 612 | else if (effect == Fx.coreBuildBlock) effect.at(x, y, 0f, block); 613 | else if (effect == Fx.upgradeCore) effect.at(x, y, 0f, block); 614 | else if (effect == Fx.upgradeCoreBloom) effect.at(x, y, block.size); 615 | else if (effect == Fx.rotateBlock) effect.at(x, y, block.size); 616 | else effect.at(x, y, 0, this); 617 | } 618 | } 619 | 620 | @Override 621 | public void setStats() { 622 | super.setStats(); 623 | stats.add(Stat.output, t -> { 624 | showNameTooltip = true; 625 | buildStats(t); 626 | showNameTooltip = false; 627 | }); 628 | } 629 | 630 | public void buildStats(Table stat) { 631 | stat.row(); 632 | for (Recipe recipe : resolvedRecipes) { 633 | Table t = new Table(); 634 | t.background(Tex.whiteui); 635 | t.setColor(Pal.darkestGray); 636 | // Input 637 | buildIOEntry(t, recipe, true); 638 | // Time 639 | Table time = new Table(); 640 | final float[] duration = {0f}; 641 | float visualCraftTime = recipe.craftTime; 642 | time.update(() -> { 643 | duration[0] += Time.delta; 644 | if (duration[0] > visualCraftTime) duration[0] = 0f; 645 | }); 646 | String craftTime = recipe.craftTime == 0 ? "0" : String.format("%.2f", recipe.craftTime / 60f); 647 | Cell barCell = time.add(new Bar(() -> craftTime, 648 | () -> Pal.accent, 649 | () -> Interp.smooth.apply(duration[0] / visualCraftTime))) 650 | .height(45f); 651 | barCell.width(Vars.mobile ? 220f : 250f); 652 | Cell timeCell = t.add(time).pad(12f); 653 | if (showNameTooltip) 654 | timeCell.tooltip(Stat.productionTime.localized() + ": " + craftTime + " " + StatUnit.seconds.localized()); 655 | // Output 656 | buildIOEntry(t, recipe, false); 657 | stat.add(t).pad(10f).grow(); 658 | stat.row(); 659 | } 660 | stat.row(); 661 | stat.defaults().grow(); 662 | } 663 | 664 | protected void buildIOEntry(Table table, Recipe recipe, boolean isInput) { 665 | Table t = new Table(); 666 | if (isInput) t.left(); 667 | else t.right(); 668 | Table mat = new Table(); 669 | IOEntry entry = isInput ? recipe.input : recipe.output; 670 | int i = 0; 671 | for (ItemStack stack : entry.items) { 672 | Cell iconCell = mat.add(new ItemImage(stack.item.uiIcon, stack.amount)) 673 | .pad(2f); 674 | if (isInput) iconCell.left(); 675 | else iconCell.right(); 676 | if (showNameTooltip) 677 | iconCell.tooltip(stack.item.localizedName); 678 | if (i != 0 && i % 2 == 0) mat.row(); 679 | i++; 680 | } 681 | for (LiquidStack stack : entry.fluids) { 682 | Cell iconCell = mat.add(new FluidImage(stack.liquid.uiIcon, stack.amount * 60f)) 683 | .pad(2f); 684 | if (isInput) iconCell.left(); 685 | else iconCell.right(); 686 | if (showNameTooltip) 687 | iconCell.tooltip(stack.liquid.localizedName); 688 | if (i != 0 && i % 2 == 0) mat.row(); 689 | i++; 690 | } 691 | // No redundant ui 692 | // Power 693 | if (entry.power > 0f) { 694 | Cell iconCell = mat.add(new PowerImage(entry.power * 60f)) 695 | .pad(2f); 696 | if (isInput) iconCell.left(); 697 | else iconCell.right(); 698 | if (showNameTooltip) 699 | iconCell.tooltip(entry.power + " " + StatUnit.powerSecond.localized()); 700 | i++; 701 | if (i != 0 && i % 2 == 0) mat.row(); 702 | } 703 | //Heat 704 | if (entry.heat > 0f) { 705 | Cell iconCell = mat.add(new HeatImage(entry.heat)) 706 | .pad(2f); 707 | if (isInput) iconCell.left(); 708 | else iconCell.right(); 709 | if (showNameTooltip) 710 | iconCell.tooltip(entry.heat + " " + StatUnit.heatUnits.localized()); 711 | i++; 712 | if (i != 0 && i % 2 == 0) mat.row(); 713 | } 714 | for (PayloadStack stack : entry.payloads) { 715 | Cell iconCell = mat.add(new PayloadImage(stack.item.uiIcon, stack.amount)) 716 | .pad(2f); 717 | if (showNameTooltip) 718 | iconCell.tooltip(stack.item.localizedName); 719 | if (isInput) iconCell.left(); 720 | else iconCell.right(); 721 | if (i != 0 && i % 2 == 0) mat.row(); 722 | i++; 723 | } 724 | Cell
matCell = t.add(mat); 725 | if (isInput) matCell.left(); 726 | else matCell.right(); 727 | Cell
tCell = table.add(t).pad(12f).fill(); 728 | tCell.width(Vars.mobile ? 90f : 120f); 729 | } 730 | 731 | @Override 732 | public void setBars() { 733 | super.setBars(); 734 | 735 | if (hasPower) 736 | addBar("power", (MultiCrafterBuild b) -> new Bar( 737 | b.getCurRecipe().isOutputPower() ? Core.bundle.format("bar.poweroutput", Strings.fixed(b.getPowerProduction() * 60f * b.timeScale(), 1)) : "bar.power", 738 | Pal.powerBar, 739 | () -> b.efficiency 740 | )); 741 | if (isConsumeHeat || isOutputHeat) 742 | addBar("heat", (MultiCrafterBuild b) -> new Bar( 743 | b.getCurRecipe().isConsumeHeat() ? Core.bundle.format("bar.heatpercent", (int) (b.heat + 0.01f), (int) (b.efficiencyScale() * 100 + 0.01f)) : "bar.heat", 744 | Pal.lightOrange, 745 | b::heatFrac 746 | )); 747 | addBar("progress", (MultiCrafterBuild b) -> new Bar( 748 | "bar.loadprogress", 749 | Pal.accent, 750 | b::progress 751 | )); 752 | } 753 | 754 | @Override 755 | public boolean rotatedOutput(int x, int y) { 756 | return false; 757 | } 758 | 759 | @Override 760 | public void load() { 761 | super.load(); 762 | drawer.load(this); 763 | } 764 | 765 | @Override 766 | public void drawPlanRegion(BuildPlan plan, Eachable list) { 767 | drawer.drawPlan(this, plan, list); 768 | } 769 | 770 | @Override 771 | public TextureRegion[] icons() { 772 | return drawer.finalIcons(this); 773 | } 774 | 775 | @Override 776 | public void getRegionsToOutline(Seq out) { 777 | drawer.getRegionsToOutline(this, out); 778 | } 779 | 780 | @Override 781 | public boolean outputsItems() { 782 | return isOutputItem; 783 | } 784 | 785 | @Override 786 | public void drawOverlay(float x, float y, int rotation) { 787 | Recipe firstRecipe = resolvedRecipes.get(defaultRecipeIndex); 788 | LiquidStack[] fluids = firstRecipe.output.fluids; 789 | for (int i = 0; i < fluids.length; i++) { 790 | int dir = fluidOutputDirections.length > i ? fluidOutputDirections[i] : -1; 791 | 792 | if (dir != -1) Draw.rect( 793 | fluids[i].liquid.fullIcon, 794 | x + Geometry.d4x(dir + rotation) * (size * tilesize / 2f + 4), 795 | y + Geometry.d4y(dir + rotation) * (size * tilesize / 2f + 4), 796 | 8f, 8f 797 | ); 798 | } 799 | } 800 | 801 | protected void decorateRecipes() { 802 | resolvedRecipes.shrink(); 803 | for (Recipe recipe : resolvedRecipes) 804 | recipe.cacheUnique(); 805 | } 806 | 807 | protected void setupBlockByRecipes() { 808 | int maxItemAmount = 0; 809 | float maxFluidAmount = 0f; 810 | float maxPower = 0f; 811 | float maxHeat = 0f; 812 | int maxPayloadAmount = 0; 813 | 814 | for (Recipe recipe : resolvedRecipes) { 815 | hasItems |= recipe.hasItems(); 816 | hasLiquids |= recipe.hasFluids(); 817 | conductivePower = hasPower |= recipe.hasPower(); 818 | hasHeat |= recipe.hasHeat(); 819 | hasPayloads |= recipe.hasPayloads(); 820 | 821 | maxItemAmount = Math.max(recipe.maxItemAmount(), maxItemAmount); 822 | maxFluidAmount = Math.max(recipe.maxFluidAmount(), maxFluidAmount); 823 | maxPower = Math.max(recipe.maxPower(), maxPower); 824 | maxHeat = Math.max(recipe.maxHeat(), maxHeat); 825 | maxPayloadAmount = Math.max(recipe.maxPayloadAmount(), maxPayloadAmount); 826 | 827 | isOutputItem |= recipe.isOutputItem(); 828 | acceptsItems = isConsumeItem |= recipe.isConsumeItem(); 829 | outputsLiquid = isOutputFluid |= recipe.isOutputFluid(); 830 | isConsumeFluid |= recipe.isConsumeFluid(); 831 | outputsPower = isOutputPower |= recipe.isOutputPower(); 832 | consumesPower = isConsumePower |= recipe.isConsumePower(); 833 | isOutputHeat |= recipe.isOutputHeat(); 834 | isConsumeHeat |= recipe.isConsumeHeat(); 835 | outputsPayload = isOutputPayload |= recipe.isOutputPayload(); 836 | acceptsPayload = isConsumePayload |= recipe.isConsumePayload(); 837 | } 838 | 839 | itemCapacity = Math.max((int) (maxItemAmount * itemCapacityMultiplier), itemCapacity); 840 | liquidCapacity = Math.max((int) (maxFluidAmount * 60f * fluidCapacityMultiplier), liquidCapacity); 841 | powerCapacity = Math.max(maxPower * 60f * powerCapacityMultiplier, powerCapacity); 842 | payloadCapacity = Math.max((int) (maxPayloadAmount * payloadCapacityMultiplier), payloadCapacity); 843 | if (isOutputHeat) { 844 | rotate = true; 845 | rotateDraw = false; 846 | canOverdrive = false; 847 | drawArrow = true; 848 | } 849 | } 850 | 851 | protected void setupConsumers() { 852 | if (isConsumeItem) consume(new ConsumeItemDynamic( 853 | (MultiCrafterBuild b) -> b.getCurRecipe().input.items 854 | )); 855 | if (isConsumeFluid) consume(new ConsumeFluidDynamic( 856 | (MultiCrafterBuild b) -> b.getCurRecipe().input.fluids 857 | )); 858 | if (isConsumePower) consume(new ConsumePowerDynamic(b -> 859 | ((MultiCrafterBuild) b).getCurRecipe().input.power 860 | )); 861 | if (isConsumePayload) consume(new CustomConsumePayloadDynamic( 862 | (MultiCrafterBuild b) -> b.getCurRecipe().input.payloads 863 | )); 864 | } 865 | } 866 | -------------------------------------------------------------------------------- /lib/src/multicraft/MultiCrafterParser.java: -------------------------------------------------------------------------------- 1 | package multicraft; 2 | 3 | import arc.func.*; 4 | import arc.graphics.*; 5 | import arc.graphics.g2d.*; 6 | import arc.struct.*; 7 | import arc.util.*; 8 | import mindustry.content.*; 9 | import mindustry.ctype.*; 10 | import mindustry.entities.*; 11 | import mindustry.entities.effect.*; 12 | import mindustry.gen.*; 13 | import mindustry.type.*; 14 | import mindustry.world.*; 15 | 16 | import java.util.*; 17 | 18 | import static multicraft.ContentResolver.*; 19 | import static multicraft.ParserUtils.*; 20 | 21 | public class MultiCrafterParser { 22 | private static final String[] inputAlias = {"input", "in", "i"}; 23 | private static final String[] outputAlias = {"output", "out", "o"}; 24 | 25 | private String curBlock = ""; 26 | private int recipeIndex = 0; 27 | 28 | private final Seq errors = new Seq<>(); 29 | private final Seq warnings = new Seq<>(); 30 | 31 | @SuppressWarnings({"rawtypes"}) 32 | public Seq parse(Block meta, Object o) { 33 | curBlock = genName(meta); 34 | try { 35 | o = parseJsonToObject(o); 36 | } catch (Exception e) { 37 | error("Can't convert Seq in preprocess " + o, e); 38 | o = Collections.emptyList(); 39 | } 40 | Seq recipes = new Seq<>(Recipe.class); 41 | recipeIndex = 0; 42 | if (o instanceof List) { // A list of recipe 43 | List all = (List) o; 44 | for (Object recipeMapObj : all) { 45 | Map recipeMap = (Map) recipeMapObj; 46 | parseRecipe(recipeMap, recipes); 47 | recipeIndex++; 48 | } 49 | } else if (o instanceof Map) { // Only one recipe 50 | Map recipeMap = (Map) o; 51 | parseRecipe(recipeMap, recipes); 52 | } else { 53 | throw new RecipeParserException("Unsupported recipe list from <" + o + ">"); 54 | } 55 | return recipes; 56 | } 57 | 58 | private static final Prov NotFound = () -> Icon.cancel.getRegion(); 59 | 60 | @SuppressWarnings("rawtypes") 61 | private void parseRecipe(Map recipeMap, Seq to) { 62 | try { 63 | Recipe recipe = new Recipe(); 64 | // parse input 65 | Object inputsRaw = findValueByAlias(recipeMap, inputAlias); 66 | if (inputsRaw == null) { 67 | warn("Recipe has no input, please ensure it's expected."); 68 | } 69 | recipe.input = parseIOEntry("input", inputsRaw); 70 | // parse output 71 | Object outputsRaw = findValueByAlias(recipeMap, outputAlias); 72 | if (outputsRaw == null) { 73 | warn("Recipe has no output, please ensure it's expected"); 74 | } 75 | recipe.output = parseIOEntry("output", outputsRaw); 76 | // parse craft time 77 | Object craftTimeObj = recipeMap.get("craftTime"); 78 | recipe.craftTime = parseFloat(craftTimeObj); 79 | // parse icon 80 | Object iconObj = recipeMap.get("icon"); 81 | if (iconObj instanceof String) { 82 | recipe.icon = findIcon((String) iconObj); 83 | } 84 | Object iconColorObj = recipeMap.get("iconColor"); 85 | if (iconColorObj instanceof String) { 86 | recipe.iconColor = Color.valueOf((String) iconColorObj); 87 | } 88 | // parse fx 89 | Object fxObj = recipeMap.get("craftEffect"); 90 | Effect fx = parseFx(fxObj); 91 | if (fx != null) { 92 | recipe.craftEffect = fx; 93 | } 94 | // Check empty 95 | if (recipe.input.isEmpty() && recipe.output.isEmpty()) { 96 | warn("Recipe is completely empty."); 97 | } 98 | to.add(recipe); 99 | } catch (Exception e) { 100 | error("Can't load a recipe", e); 101 | } 102 | } 103 | 104 | @SuppressWarnings({"rawtypes"}) 105 | private IOEntry parseIOEntry(String meta, @Nullable Object ioEntry) { 106 | IOEntry res = new IOEntry(); 107 | if (ioEntry == null) { 108 | return res; 109 | } else if (ioEntry instanceof Map) { 110 | /* 111 | input/output:{ 112 | items:[], 113 | fluids:[], 114 | power:0, 115 | heat:0, 116 | payloads:[], 117 | icon: Icon.power, 118 | iconColor: "#FFFFFF" 119 | } 120 | */ 121 | Map ioRawMap = (Map) ioEntry; 122 | // Items 123 | Object items = ioRawMap.get("items"); 124 | if (items != null) { 125 | if (items instanceof List) { // ["mod-id-item/1","mod-id-item2"] 126 | parseItems((List) items, res); 127 | } else if (items instanceof String) { 128 | parseItemPair((String) items, res); 129 | } else if (items instanceof Map) { 130 | parseItemMap((Map) items, res); 131 | } else 132 | throw new RecipeParserException("Unsupported type of items at " + meta + " from <" + items + ">"); 133 | } 134 | // Fluids 135 | Object fluids = ioRawMap.get("fluids"); 136 | if (fluids != null) { 137 | if (fluids instanceof List) { // ["mod-id-fluid/1","mod-id-fluid2"] 138 | parseFluids((List) fluids, res); 139 | } else if (fluids instanceof String) { 140 | parseFluidPair((String) fluids, res); 141 | } else if (fluids instanceof Map) { 142 | parseFluidMap((Map) fluids, res); 143 | } else 144 | throw new RecipeParserException("Unsupported type of fluids at " + meta + " from <" + fluids + ">"); 145 | } 146 | // Power 147 | Object powerObj = ioRawMap.get("power"); 148 | res.power = parseFloat(powerObj); 149 | // Heat 150 | Object heatObj = ioRawMap.get("heat"); 151 | res.heat = parseFloat(heatObj); 152 | // Payloads 153 | Object payloads = ioRawMap.get("payloads"); 154 | if (payloads != null) { 155 | if (payloads instanceof List) { // ["mod-id-payload/1","mod-id-payload2"] 156 | parsePayloads((List) payloads, res); 157 | } else if (payloads instanceof String) { 158 | parsePayloadPair((String) payloads, res); 159 | } else if (payloads instanceof Map) { 160 | parsePayloadMap((Map) payloads, res); 161 | } else 162 | throw new RecipeParserException("Unsupported type of payloads at " + meta + " from <" + payloads + ">"); 163 | } 164 | 165 | // Icon 166 | Object iconObj = ioRawMap.get("icon"); 167 | if (iconObj instanceof String) { 168 | res.icon = findIcon((String) iconObj); 169 | } 170 | Object iconColorObj = ioRawMap.get("iconColor"); 171 | if (iconColorObj instanceof String) { 172 | res.iconColor = Color.valueOf((String) iconColorObj); 173 | } 174 | } else if (ioEntry instanceof List) { 175 | /* 176 | input/output: [] 177 | */ 178 | for (Object content : (List) ioEntry) { 179 | if (content instanceof String) { 180 | parseAnyPair((String) content, res); 181 | } else if (content instanceof Map) { 182 | parseAnyMap((Map) content, res); 183 | } else { 184 | throw new RecipeParserException("Unsupported type of content at " + meta + " from <" + content + ">"); 185 | } 186 | } 187 | } else if (ioEntry instanceof String) { 188 | /* 189 | input/output : "item/1" 190 | */ 191 | parseAnyPair((String) ioEntry, res); 192 | } else { 193 | throw new RecipeParserException("Unsupported type of " + meta + " <" + ioEntry + ">"); 194 | } 195 | return res; 196 | } 197 | 198 | @SuppressWarnings("rawtypes") 199 | private void parseItems(List items, IOEntry res) { 200 | for (Object entryRaw : items) { 201 | if (entryRaw instanceof String) { // if the input is String as "mod-id-item/1" 202 | parseItemPair((String) entryRaw, res); 203 | } else if (entryRaw instanceof Map) { 204 | // if the input is Map as { item : "copper", amount : 1 } 205 | parseItemMap((Map) entryRaw, res); 206 | } else { 207 | error("Unsupported type of items <" + entryRaw + ">, so skip them"); 208 | } 209 | } 210 | } 211 | 212 | private void parseItemPair(String pair, IOEntry res) { 213 | try { 214 | String[] id2Amount = pair.split("/"); 215 | if (id2Amount.length != 1 && id2Amount.length != 2) { 216 | error("<" + Arrays.toString(id2Amount) + "> doesn't contain 1 or 2 parts, so skip this"); 217 | return; 218 | } 219 | String itemID = id2Amount[0]; 220 | Item item = findItem(itemID); 221 | if (item == null) { 222 | error("<" + itemID + "> doesn't exist in all items, so skip this"); 223 | return; 224 | } 225 | ItemStack entry = new ItemStack(); 226 | entry.item = item; 227 | if (id2Amount.length == 2) { 228 | String amountStr = id2Amount[1]; 229 | entry.amount = Integer.parseInt(amountStr);// throw NumberFormatException 230 | } else { 231 | entry.amount = 1; 232 | } 233 | res.items = addItemStack(res.items, entry); 234 | } catch (Exception e) { 235 | error("Can't parse an item from <" + pair + ">, so skip it", e); 236 | } 237 | } 238 | 239 | @SuppressWarnings("rawtypes") 240 | private void parseFluids(List fluids, IOEntry res) { 241 | for (Object entryRaw : fluids) { 242 | if (entryRaw instanceof String) { // if the input is String as "mod-id-fluid/1" 243 | parseFluidPair((String) entryRaw, res); 244 | } else if (entryRaw instanceof Map) { 245 | // if the input is Map as { fluid : "water", amount : 1.2 } 246 | parseFluidMap((Map) entryRaw, res); 247 | } else { 248 | error("Unsupported type of fluids <" + entryRaw + ">, so skip them"); 249 | } 250 | } 251 | } 252 | 253 | private void parseFluidPair(String pair, IOEntry res) { 254 | try { 255 | String[] id2Amount = pair.split("/"); 256 | if (id2Amount.length != 1 && id2Amount.length != 2) { 257 | error("<" + Arrays.toString(id2Amount) + "> doesn't contain 1 or 2 parts, so skip this"); 258 | return; 259 | } 260 | String fluidID = id2Amount[0]; 261 | Liquid fluid = findFluid(fluidID); 262 | if (fluid == null) { 263 | error("<" + fluidID + "> doesn't exist in all fluids, so skip this"); 264 | return; 265 | } 266 | LiquidStack entry = new LiquidStack(Liquids.water, 0f); 267 | entry.liquid = fluid; 268 | if (id2Amount.length == 2) { 269 | String amountStr = id2Amount[1]; 270 | entry.amount = Float.parseFloat(amountStr);// throw NumberFormatException 271 | } else { 272 | entry.amount = 1f; 273 | } 274 | res.fluids = addLiquidStack(res.fluids, entry); 275 | } catch (Exception e) { 276 | error("Can't parse a fluid from <" + pair + ">, so skip it", e); 277 | } 278 | } 279 | 280 | @SuppressWarnings("rawtypes") 281 | private void parsePayloads(List payloads, IOEntry res) { 282 | for (Object entryRaw : payloads) { 283 | if (entryRaw instanceof String) { // if the input is String as "mod-id-payload/1" 284 | parsePayloadPair((String) entryRaw, res); 285 | } else if (entryRaw instanceof Map) { 286 | // if the input is Map as { payload : "router", amount : 1 } 287 | parsePayloadMap((Map) entryRaw, res); 288 | } else { 289 | error("Unsupported type of items <" + entryRaw + ">, so skip them"); 290 | } 291 | } 292 | } 293 | 294 | private void parsePayloadPair(String pair, IOEntry res) { 295 | try { 296 | String[] id2Amount = pair.split("/"); 297 | if (id2Amount.length != 1 && id2Amount.length != 2) { 298 | error("<" + Arrays.toString(id2Amount) + "> doesn't contain 1 or 2 parts, so skip this"); 299 | return; 300 | } 301 | String payloadID = id2Amount[0]; 302 | UnlockableContent payload = findPayload(payloadID); 303 | if (payload == null) { 304 | error("<" + payloadID + "> doesn't exist in all payloads, so skip this"); 305 | return; 306 | } 307 | PayloadStack entry = new PayloadStack(); 308 | entry.item = payload; 309 | if (id2Amount.length == 2) { 310 | String amountStr = id2Amount[1]; 311 | entry.amount = Integer.parseInt(amountStr);// throw NumberFormatException 312 | } else { 313 | entry.amount = 1; 314 | } 315 | res.payloads = addPayloadStack(res.payloads, entry); 316 | } catch (Exception e) { 317 | error("Can't parse an item from <" + pair + ">, so skip it", e); 318 | } 319 | } 320 | 321 | /** 322 | * @param pair "mod-id-item/1" or "mod-id-gas" 323 | */ 324 | private void parseAnyPair(String pair, IOEntry res) { 325 | try { 326 | String[] id2Amount = pair.split("/"); 327 | if (id2Amount.length != 1 && id2Amount.length != 2) { 328 | error("<" + Arrays.toString(id2Amount) + "> doesn't contain 1 or 2 parts, so skip this"); 329 | return; 330 | } 331 | String id = id2Amount[0]; 332 | // Find in item 333 | Item item = findItem(id); 334 | if (item != null) { 335 | ItemStack entry = new ItemStack(Items.copper, 1); 336 | entry.item = item; 337 | if (id2Amount.length == 2) { 338 | String amountStr = id2Amount[1]; 339 | entry.amount = Integer.parseInt(amountStr);// throw NumberFormatException 340 | } 341 | res.items = addItemStack(res.items, entry); 342 | return; 343 | } 344 | // Find in fluid 345 | Liquid fluid = findFluid(id); 346 | if (fluid != null) { 347 | LiquidStack entry = new LiquidStack(Liquids.water, 1f); 348 | entry.liquid = fluid; 349 | if (id2Amount.length == 2) { 350 | String amountStr = id2Amount[1]; 351 | entry.amount = Float.parseFloat(amountStr);// throw NumberFormatException 352 | } 353 | res.fluids = addLiquidStack(res.fluids, entry); 354 | return; 355 | } 356 | // Find in payload 357 | UnlockableContent payload = findPayload(id); 358 | if (payload != null) { 359 | PayloadStack entry = new PayloadStack(Blocks.router, 1); 360 | entry.item = payload; 361 | if (id2Amount.length == 2) { 362 | String amountStr = id2Amount[1]; 363 | entry.amount = Integer.parseInt(amountStr);// throw NumberFormatException 364 | } 365 | res.payloads = addPayloadStack(res.payloads, entry); 366 | return; 367 | } 368 | error("Can't find the corresponding item, fluid or payload from this <" + pair + ">, so skip it"); 369 | } catch (Exception e) { 370 | error("Can't parse this uncertain <" + pair + ">, so skip it", e); 371 | } 372 | } 373 | 374 | @SuppressWarnings("rawtypes") 375 | private void parseAnyMap(Map map, IOEntry res) { 376 | try { 377 | Object itemRaw = map.get("item"); 378 | if (itemRaw instanceof String) { 379 | Item item = findItem((String) itemRaw); 380 | if (item != null) { 381 | ItemStack entry = new ItemStack(); 382 | entry.item = item; 383 | Object amountRaw = map.get("amount"); 384 | entry.amount = parseInt(amountRaw); 385 | res.items = addItemStack(res.items, entry); 386 | return; 387 | } 388 | } 389 | Object fluidRaw = map.get("fluid"); 390 | if (fluidRaw instanceof String) { 391 | Liquid fluid = findFluid((String) fluidRaw); 392 | if (fluid != null) { 393 | LiquidStack entry = new LiquidStack(Liquids.water, 0f); 394 | entry.liquid = fluid; 395 | Object amountRaw = map.get("amount"); 396 | entry.amount = parseFloat(amountRaw); 397 | res.fluids = addLiquidStack(res.fluids, entry); 398 | return; 399 | } 400 | } 401 | Object payloadRaw = map.get("payload"); 402 | if (payloadRaw instanceof String) { 403 | UnlockableContent payload = findPayload((String) payloadRaw); 404 | if (payload != null) { 405 | PayloadStack entry = new PayloadStack(); 406 | entry.item = payload; 407 | Object amountRaw = map.get("amount"); 408 | entry.amount = parseInt(amountRaw); 409 | res.payloads = addPayloadStack(res.payloads, entry); 410 | return; 411 | } 412 | } 413 | error("Can't find the corresponding item, fluid or payload from <" + map + ">, so skip it"); 414 | } catch (Exception e) { 415 | error("Can't parse this uncertain <" + map + ">, so skip it", e); 416 | } 417 | } 418 | 419 | @SuppressWarnings("rawtypes") 420 | private void parseItemMap(Map map, IOEntry res) { 421 | try { 422 | ItemStack entry = new ItemStack(); 423 | Object itemID = map.get("item"); 424 | if (itemID instanceof String) { 425 | Item item = findItem((String) itemID); 426 | if (item == null) { 427 | error("<" + itemID + "> doesn't exist in all items, so skip this"); 428 | return; 429 | } 430 | entry.item = item; 431 | } else { 432 | error("Can't recognize a fluid from <" + map + ">"); 433 | return; 434 | } 435 | int amount = parseInt(map.get("amount")); 436 | entry.amount = amount; 437 | if (amount <= 0) { 438 | error("Item amount is +" + amount + " <= 0, so reset as 1"); 439 | entry.amount = 1; 440 | } 441 | res.items = addItemStack(res.items, entry); 442 | } catch (Exception e) { 443 | error("Can't parse an item <" + map + ">, so skip it", e); 444 | } 445 | } 446 | 447 | @SuppressWarnings("rawtypes") 448 | private void parseFluidMap(Map map, IOEntry res) { 449 | try { 450 | LiquidStack entry = new LiquidStack(Liquids.water, 0f); 451 | Object fluidID = map.get("fluid"); 452 | if (fluidID instanceof String) { 453 | Liquid fluid = findFluid((String) fluidID); 454 | if (fluid == null) { 455 | error(fluidID + " doesn't exist in all fluids, so skip this"); 456 | return; 457 | } 458 | entry.liquid = fluid; 459 | } else { 460 | error("Can't recognize an item from <" + map + ">"); 461 | return; 462 | } 463 | float amount = parseFloat(map.get("amount")); 464 | entry.amount = amount; 465 | if (amount <= 0f) { 466 | error("Fluids amount is +" + amount + " <= 0, so reset as 1.0f"); 467 | entry.amount = 1f; 468 | } 469 | res.fluids = addLiquidStack(res.fluids, entry); 470 | } catch (Exception e) { 471 | error("Can't parse <" + map + ">, so skip it", e); 472 | } 473 | } 474 | 475 | @SuppressWarnings("rawtypes") 476 | private void parsePayloadMap(Map map, IOEntry res) { 477 | try { 478 | PayloadStack entry = new PayloadStack(); 479 | Object payloadID = map.get("payload"); 480 | if (payloadID instanceof String) { 481 | UnlockableContent payload = findPayload((String) payloadID); 482 | if (payload == null) { 483 | error("<" + payloadID + "> doesn't exist in all payloads, so skip this"); 484 | return; 485 | } 486 | entry.item = payload; 487 | } else { 488 | error("Can't recognize a fluid from <" + map + ">"); 489 | return; 490 | } 491 | int amount = parseInt(map.get("amount")); 492 | entry.amount = amount; 493 | if (amount <= 0) { 494 | error("Payload amount is +" + amount + " <= 0, so reset as 1"); 495 | entry.amount = 1; 496 | } 497 | res.payloads = addPayloadStack(res.payloads, entry); 498 | } catch (Exception e) { 499 | error("Can't parse an item <" + map + ">, so skip it", e); 500 | } 501 | } 502 | 503 | 504 | private void error(String content) { 505 | error(content, null); 506 | } 507 | 508 | private void error(String content, @Nullable Throwable e) { 509 | String message = buildRecipeIndexInfo() + content; 510 | errors.add(message); 511 | if (e == null) { 512 | Log.err(message); 513 | } else { 514 | Log.err(message, e); 515 | } 516 | } 517 | 518 | private void warn(String content) { 519 | String message = buildRecipeIndexInfo() + content; 520 | warnings.add(message); 521 | Log.warn(message); 522 | } 523 | 524 | private String buildRecipeIndexInfo() { 525 | return "[" + curBlock + "](at recipe " + recipeIndex + ")\n"; 526 | } 527 | 528 | public static String genName(Block meta) { 529 | if (meta.localizedName.equals(meta.name)) { 530 | return meta.name; 531 | } else { 532 | return meta.localizedName + "(" + meta.name + ")"; 533 | } 534 | } 535 | 536 | private Prov findIcon(String name) { 537 | Prov icon = ContentResolver.findIcon(name); 538 | if (icon == null) { 539 | error("Icon <" + name + "> not found, so use a cross instead."); 540 | icon = NotFound; 541 | } 542 | return icon; 543 | } 544 | 545 | private static final Effect[] EffectType = new Effect[0]; 546 | 547 | private static Effect composeMultiFx(List names) { 548 | ArrayList all = new ArrayList<>(); 549 | for (String name : names) { 550 | Effect fx = findFx(name); 551 | if (fx != null) all.add(fx); 552 | } 553 | return new MultiEffect(all.toArray(EffectType)); 554 | } 555 | 556 | @SuppressWarnings("unchecked") 557 | @Nullable 558 | private static Effect parseFx(Object obj) { 559 | if (obj instanceof String) return findFx((String) obj); 560 | else if (obj instanceof List) { 561 | return composeMultiFx((List) obj); 562 | } else { 563 | return null; 564 | } 565 | } 566 | 567 | @SuppressWarnings("rawtypes") 568 | @Nullable 569 | private static Object findValueByAlias(Map map, String... aliases) { 570 | for (String alias : aliases) { 571 | Object tried = map.get(alias); 572 | if (tried != null) { 573 | return tried; 574 | } 575 | } 576 | return null; 577 | } 578 | } 579 | -------------------------------------------------------------------------------- /lib/src/multicraft/ParserUtils.java: -------------------------------------------------------------------------------- 1 | package multicraft; 2 | 3 | import arc.struct.*; 4 | import arc.util.*; 5 | import arc.util.serialization.*; 6 | import mindustry.type.*; 7 | 8 | import java.util.*; 9 | 10 | public class ParserUtils { 11 | 12 | public static float parseFloat(@Nullable Object floatObj) { 13 | if (floatObj == null) return 0f; 14 | if (floatObj instanceof Number) { 15 | return ((Number) floatObj).floatValue(); 16 | } 17 | try { 18 | return Float.parseFloat((String) floatObj); 19 | } catch (Exception e) { 20 | return 0f; 21 | } 22 | } 23 | 24 | public static int parseInt(@Nullable Object intObj) { 25 | if (intObj == null) return 0; 26 | if (intObj instanceof Number) { 27 | return ((Number) intObj).intValue(); 28 | } 29 | try { 30 | return Integer.parseInt((String) intObj); 31 | } catch (Exception e) { 32 | return 0; 33 | } 34 | } 35 | 36 | @SuppressWarnings({"rawtypes", "unchecked"}) 37 | public static Object parseJsonToObject(Object o) { 38 | if (o instanceof Seq) { 39 | Seq seq = (Seq) o; 40 | ArrayList list = new ArrayList(seq.size); 41 | for (Object e : new Seq.SeqIterable<>(seq)) { 42 | list.add(parseJsonToObject(e)); 43 | } 44 | return list; 45 | } else if (o instanceof ObjectMap) { 46 | ObjectMap objMap = (ObjectMap) o; 47 | HashMap map = new HashMap(); 48 | for (ObjectMap.Entry entry : new ObjectMap.Entries(objMap)) { 49 | map.put(entry.key, parseJsonToObject(entry.value)); 50 | } 51 | return map; 52 | } else if (o instanceof JsonValue) { 53 | return convert((JsonValue) o); 54 | } 55 | return o; 56 | } 57 | 58 | @SuppressWarnings({"rawtypes", "unchecked"}) 59 | @Nullable 60 | private static Object convert(JsonValue j) { 61 | JsonValue.ValueType type = j.type(); 62 | switch (type) { 63 | case object: { 64 | HashMap map = new HashMap(); 65 | for (JsonValue cur = j.child; cur != null; cur = cur.next) { 66 | map.put(cur.name, convert(cur)); 67 | } 68 | return map; 69 | } 70 | case array: { 71 | ArrayList list = new ArrayList(); 72 | for (JsonValue cur = j.child; cur != null; cur = cur.next) { 73 | list.add(convert(cur)); 74 | } 75 | return list; 76 | } 77 | case stringValue: 78 | return j.asString(); 79 | case doubleValue: 80 | return j.asDouble(); 81 | case longValue: 82 | return j.asLong(); 83 | case booleanValue: 84 | return j.asBoolean(); 85 | case nullValue: 86 | return null; 87 | } 88 | return Collections.emptyMap(); 89 | } 90 | 91 | public static String kebab2camel(String kebab) { 92 | StringBuilder sb = new StringBuilder(); 93 | boolean hyphen = false; 94 | for (int i = 0; i < kebab.length(); i++) { 95 | char c = kebab.charAt(i); 96 | if (c == '-') { 97 | hyphen = true; 98 | } else { 99 | if (hyphen) { 100 | sb.append(Character.toUpperCase(c)); 101 | hyphen = false; 102 | } else { 103 | if (i == 0) sb.append(Character.toLowerCase(c)); 104 | else sb.append(c); 105 | } 106 | } 107 | } 108 | return sb.toString(); 109 | } 110 | 111 | /** 112 | * Gets a field from a static class by name, throwing a descriptive exception if not found. 113 | */ 114 | public static Object field(Class type, String name) { 115 | try { 116 | Object b = type.getField(name).get(null); 117 | if (b == null) throw new IllegalArgumentException(type.getSimpleName() + ": not found: '" + name + "'"); 118 | return b; 119 | } catch (Exception e) { 120 | throw new RuntimeException(e); 121 | } 122 | } 123 | 124 | public static ItemStack[] addItemStack(ItemStack[] stackArray, ItemStack stack) { 125 | ArrayList newItemStack = new ArrayList(Arrays.asList(stackArray)); 126 | newItemStack.add(stack); 127 | return newItemStack.toArray(stackArray); 128 | } 129 | 130 | public static LiquidStack[] addLiquidStack(LiquidStack[] stackArray, LiquidStack stack) { 131 | ArrayList newLiquidStack = new ArrayList(Arrays.asList(stackArray)); 132 | newLiquidStack.add(stack); 133 | return newLiquidStack.toArray(stackArray); 134 | } 135 | 136 | public static PayloadStack[] addPayloadStack(PayloadStack[] stackArray, PayloadStack stack) { 137 | ArrayList newPayloadStack = new ArrayList(Arrays.asList(stackArray)); 138 | newPayloadStack.add(stack); 139 | return newPayloadStack.toArray(stackArray); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /lib/src/multicraft/Recipe.java: -------------------------------------------------------------------------------- 1 | package multicraft; 2 | 3 | import arc.func.*; 4 | import arc.graphics.*; 5 | import arc.graphics.g2d.*; 6 | import arc.util.*; 7 | import mindustry.content.*; 8 | import mindustry.entities.*; 9 | 10 | public class Recipe { 11 | public IOEntry input; 12 | public IOEntry output; 13 | public float craftTime = 0f; 14 | @Nullable 15 | public Prov icon; 16 | @Nullable 17 | public Color iconColor; 18 | 19 | public Effect craftEffect = Fx.none; 20 | 21 | public Recipe() {} 22 | 23 | public void cacheUnique() { 24 | input.cacheUnique(); 25 | output.cacheUnique(); 26 | } 27 | 28 | public boolean isConsumeItem() { 29 | return input.items.length > 0; 30 | } 31 | 32 | public boolean isOutputItem() { 33 | return output.items.length > 0; 34 | } 35 | 36 | public boolean isConsumeFluid() { 37 | return input.fluids.length > 0; 38 | } 39 | 40 | public boolean isOutputFluid() { 41 | return output.fluids.length > 0; 42 | } 43 | 44 | public boolean isConsumePower() { 45 | return input.power > 0f; 46 | } 47 | 48 | public boolean isOutputPower() { 49 | return output.power > 0f; 50 | } 51 | 52 | public boolean isConsumeHeat() { 53 | return input.heat > 0f; 54 | } 55 | 56 | public boolean isOutputHeat() { 57 | return output.heat > 0f; 58 | } 59 | 60 | public boolean isConsumePayload() { 61 | return input.payloads.length > 0; 62 | } 63 | 64 | public boolean isOutputPayload() { 65 | return output.payloads.length > 0; 66 | } 67 | 68 | public boolean hasItems() { 69 | return isConsumeItem() || isOutputItem(); 70 | } 71 | 72 | public boolean hasFluids() { 73 | return isConsumeFluid() || isOutputFluid(); 74 | } 75 | 76 | public boolean hasPower() { 77 | return isConsumePower() || isOutputPower(); 78 | } 79 | 80 | public boolean hasHeat() { 81 | return isConsumeHeat() || isOutputHeat(); 82 | } 83 | 84 | public boolean hasPayloads() { 85 | return isConsumePayload() || isOutputPayload(); 86 | } 87 | 88 | public int maxItemAmount() { 89 | return Math.max(input.maxItemAmount(), output.maxItemAmount()); 90 | } 91 | 92 | public float maxFluidAmount() { 93 | return Math.max(input.maxFluidAmount(), output.maxFluidAmount()); 94 | } 95 | 96 | public float maxPower() { 97 | return Math.max(input.power, output.power); 98 | } 99 | 100 | public float maxHeat() { 101 | return Math.max(input.heat, output.heat); 102 | } 103 | 104 | public int maxPayloadAmount() { 105 | return Math.max(input.maxPayloadAmount(), output.maxPayloadAmount()); 106 | } 107 | 108 | @Override 109 | public String toString() { 110 | return "Recipe{" + 111 | "input=" + input + 112 | "output=" + output + 113 | "craftTime" + craftTime + 114 | "}"; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /lib/src/multicraft/RecipeParserException.java: -------------------------------------------------------------------------------- 1 | package multicraft; 2 | 3 | public class RecipeParserException extends RuntimeException{ 4 | public RecipeParserException() { 5 | super(); 6 | } 7 | 8 | public RecipeParserException(String message) { 9 | super(message); 10 | } 11 | 12 | public RecipeParserException(String message, Throwable cause) { 13 | super(message, cause); 14 | } 15 | 16 | public RecipeParserException(Throwable cause) { 17 | super(cause); 18 | } 19 | 20 | protected RecipeParserException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 21 | super(message, cause, enableSuppression, writableStackTrace); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/src/multicraft/RecipeSwitchStyle.java: -------------------------------------------------------------------------------- 1 | package multicraft; 2 | 3 | import arc.scene.ui.*; 4 | import arc.scene.ui.layout.*; 5 | import arc.scene.utils.*; 6 | import arc.util.*; 7 | import mindustry.*; 8 | import mindustry.gen.*; 9 | import mindustry.graphics.*; 10 | import mindustry.type.*; 11 | import mindustry.ui.*; 12 | import multicraft.MultiCrafter.*; 13 | 14 | import java.util.*; 15 | 16 | public abstract class RecipeSwitchStyle { 17 | public static HashMap all = new HashMap<>(); 18 | 19 | public static RecipeSwitchStyle get(@Nullable String name) { 20 | if (name == null) return transform; 21 | RecipeSwitchStyle inMap = all.get(name.toLowerCase()); 22 | if (inMap == null) return transform; 23 | else return inMap; 24 | } 25 | 26 | public RecipeSwitchStyle(String name) { 27 | all.put(name.toLowerCase(), this); 28 | } 29 | 30 | public abstract void build(MultiCrafter b, MultiCrafterBuild c, Table table); 31 | 32 | public static Image getDefaultIcon(MultiCrafter b, MultiCrafterBuild c, IOEntry entry) { 33 | if (entry.icon != null) { 34 | Image img = new Image(entry.icon.get()); 35 | if (entry.iconColor != null) 36 | img.setColor(entry.iconColor); 37 | return img; 38 | } 39 | ItemStack[] items = entry.items; 40 | LiquidStack[] fluids = entry.fluids; 41 | boolean outputPower = entry.power > 0f; 42 | boolean outputHeat = entry.heat > 0f; 43 | PayloadStack[] paylods = entry.payloads; 44 | if (items.length > 0) { 45 | return new Image(items[0].item.uiIcon); 46 | } else if (fluids.length > 0) { 47 | return new Image(fluids[0].liquid.uiIcon); 48 | } else if (outputPower) { 49 | Image img = new Image(Icon.power.getRegion()); 50 | img.setColor(Pal.power); 51 | return img; 52 | } else if (outputHeat) { 53 | Image img = new Image(Icon.waves.getRegion()); 54 | img.setColor(b.heatColor); 55 | return img; 56 | } else if (paylods.length > 0) { 57 | return new Image(paylods[0].item.uiIcon); 58 | } 59 | return new Image(Icon.cancel.getRegion()); 60 | } 61 | 62 | public static RecipeSwitchStyle simple = new RecipeSwitchStyle("simple") { 63 | 64 | @Override 65 | public void build(MultiCrafter b, MultiCrafterBuild c, Table table) { 66 | Table t = new Table(); 67 | t.background(Tex.whiteui); 68 | t.setColor(Pal.darkerGray); 69 | for (int i = 0; i < b.resolvedRecipes.size; i++) { 70 | Recipe recipe = b.resolvedRecipes.get(i); 71 | int finalI = i; 72 | ImageButton button = new ImageButton(Styles.clearTogglei); 73 | Image img; 74 | if (recipe.icon != null) { 75 | img = new Image(recipe.icon.get()); 76 | if (recipe.iconColor != null) 77 | img.setColor(recipe.iconColor); 78 | } else { 79 | img = getDefaultIcon(b, c, recipe.output); 80 | } 81 | button.replaceImage(img); 82 | button.getImageCell().scaling(Scaling.fit).size(Vars.iconLarge); 83 | button.changed(() -> c.configure(finalI)); 84 | button.update(() -> button.setChecked(c.curRecipeIndex == finalI)); 85 | t.add(button).grow().margin(10f); 86 | if (i != 0 && i % 3 == 0) { 87 | t.row(); 88 | } 89 | } 90 | table.add(t).grow(); 91 | } 92 | }; 93 | 94 | public static RecipeSwitchStyle number = new RecipeSwitchStyle("number") { 95 | @Override 96 | public void build(MultiCrafter b, MultiCrafterBuild c, Table table) { 97 | Table t = new Table(); 98 | for (int i = 0; i < b.resolvedRecipes.size; i++) { 99 | Recipe recipe = b.resolvedRecipes.get(i); 100 | int finalI = i; 101 | TextButton button = Elem.newButton("" + i, Styles.togglet, 102 | () -> c.configure(finalI)); 103 | if (recipe.iconColor != null) 104 | button.setColor(recipe.iconColor); 105 | button.update(() -> button.setChecked(c.curRecipeIndex == finalI)); 106 | t.add(button).size(50f); 107 | if (i != 0 && i % 3 == 0) { 108 | t.row(); 109 | } 110 | } 111 | table.add(t).grow(); 112 | } 113 | }; 114 | public static RecipeSwitchStyle transform = new RecipeSwitchStyle("transform") { 115 | @Override 116 | public void build(MultiCrafter b, MultiCrafterBuild c, Table table) { 117 | Table t = new Table(); 118 | for (int i = 0; i < b.resolvedRecipes.size; i++) { 119 | if (i != 0 && i % 2 == 0) { 120 | t.row(); 121 | } 122 | Recipe recipe = b.resolvedRecipes.get(i); 123 | int finalI = i; 124 | ImageButton button = new ImageButton(Styles.clearTogglei); 125 | Table bt = new Table(); 126 | Image in = getDefaultIcon(b, c, recipe.input); 127 | bt.add(in).pad(6f); 128 | bt.image(Icon.right).pad(6f); 129 | Image out = getDefaultIcon(b, c, recipe.output); 130 | bt.add(out).pad(6f); 131 | button.replaceImage(bt); 132 | button.changed(() -> c.configure(finalI)); 133 | button.update(() -> button.setChecked(c.curRecipeIndex == finalI)); 134 | t.add(button).grow().pad(8f).margin(10f); 135 | } 136 | table.add(t).grow(); 137 | } 138 | }; 139 | 140 | public static RecipeSwitchStyle detailed = new RecipeSwitchStyle("detailed") { 141 | 142 | @Override 143 | public void build(MultiCrafter b, MultiCrafterBuild c, Table table) { 144 | for (int i = 0; i < b.resolvedRecipes.size; i++) { 145 | Recipe recipe = b.resolvedRecipes.get(i); 146 | Table t = new Table(); 147 | t.background(Tex.whiteui); 148 | t.setColor(Pal.darkestGray); 149 | b.buildIOEntry(t, recipe, true); 150 | t.image(Icon.right); 151 | b.buildIOEntry(t, recipe, false); 152 | int finalI = i; 153 | ImageButton button = new ImageButton(Styles.clearTogglei); 154 | button.changed(() -> c.configure(finalI)); 155 | button.update(() -> button.setChecked(c.curRecipeIndex == finalI)); 156 | button.replaceImage(t); 157 | table.add(button).pad(5f).margin(10f).grow(); 158 | table.row(); 159 | } 160 | } 161 | }; 162 | } 163 | -------------------------------------------------------------------------------- /lib/src/multicraft/ui/FluidImage.java: -------------------------------------------------------------------------------- 1 | package multicraft.ui; 2 | 3 | import arc.graphics.g2d.*; 4 | import arc.math.*; 5 | import arc.scene.ui.*; 6 | import arc.scene.ui.layout.*; 7 | 8 | import mindustry.core.*; 9 | import mindustry.type.*; 10 | import mindustry.ui.*; 11 | 12 | public class FluidImage extends Stack { 13 | /** 14 | * No amount text 15 | * @param region the fluid icon 16 | */ 17 | public FluidImage(TextureRegion region) { 18 | 19 | add(new Table(o -> { 20 | o.left(); 21 | o.add(new Image(region)).size(32f); 22 | })); 23 | } 24 | 25 | public FluidImage(TextureRegion region, float amount) { 26 | 27 | add(new Table(o -> { 28 | o.left(); 29 | o.add(new Image(region)).size(32f); 30 | })); 31 | 32 | if (amount != 0) { 33 | add(new Table(t -> { 34 | t.left().bottom(); 35 | t.add(amount >= 1000 ? 36 | UI.formatAmount((long) amount) : 37 | Mathf.round(amount) + "").fontScale(0.9f) 38 | .style(Styles.outlineLabel); 39 | t.pack(); 40 | })); 41 | } 42 | } 43 | 44 | public FluidImage(LiquidStack stack) { 45 | this(stack.liquid.uiIcon, stack.amount); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/src/multicraft/ui/HeatImage.java: -------------------------------------------------------------------------------- 1 | package multicraft.ui; 2 | 3 | import arc.graphics.*; 4 | import arc.scene.ui.*; 5 | import arc.scene.ui.layout.*; 6 | import arc.util.*; 7 | 8 | import mindustry.core.*; 9 | import mindustry.gen.*; 10 | import mindustry.ui.*; 11 | 12 | /** An ItemDisplay, but for heat. */ 13 | public class HeatImage extends Table { 14 | public final float amount; 15 | 16 | public HeatImage(float amount) { 17 | this.amount = amount; 18 | 19 | add(new Stack() {{ 20 | add(new Table(o -> { 21 | o.left(); 22 | o.add(new Image(Icon.waves)).size(32f).scaling(Scaling.fit).color(new Color(1f, 0.22f, 0.22f, 0.8f)); 23 | })); 24 | 25 | if(amount != 0) { 26 | add(new Table(t -> { 27 | t.left().bottom(); 28 | t.add(amount >= 1000f ? UI.formatAmount((int) amount) : (int) amount + "").style(Styles.outlineLabel); 29 | t.pack(); 30 | })); 31 | } 32 | }}); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/src/multicraft/ui/PayloadImage.java: -------------------------------------------------------------------------------- 1 | package multicraft.ui; 2 | 3 | import arc.graphics.g2d.*; 4 | import arc.math.*; 5 | import arc.scene.ui.*; 6 | import arc.scene.ui.layout.*; 7 | 8 | import mindustry.core.*; 9 | import mindustry.type.*; 10 | import mindustry.ui.*; 11 | 12 | public class PayloadImage extends Stack { 13 | 14 | public PayloadImage(TextureRegion region) { 15 | add(new Table(o -> { 16 | o.left(); 17 | o.add(new Image(region)).size(32f); 18 | })); 19 | } 20 | 21 | public PayloadImage(TextureRegion region, float amount) { 22 | add(new Table(o -> { 23 | o.left(); 24 | o.add(new Image(region)).size(32f); 25 | })); 26 | 27 | if (amount != 0) { 28 | add(new Table(t -> { 29 | t.left().bottom(); 30 | t.add(amount >= 1000 ? 31 | UI.formatAmount((long) amount) : 32 | Mathf.round(amount) + "").fontScale(0.9f) 33 | .style(Styles.outlineLabel); 34 | t.pack(); 35 | })); 36 | } 37 | } 38 | 39 | public PayloadImage(PayloadStack stack) { 40 | this(stack.item.uiIcon, stack.amount); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/src/multicraft/ui/PowerImage.java: -------------------------------------------------------------------------------- 1 | package multicraft.ui; 2 | 3 | import arc.scene.ui.*; 4 | import arc.scene.ui.layout.*; 5 | import arc.util.*; 6 | 7 | import mindustry.core.*; 8 | import mindustry.gen.*; 9 | import mindustry.graphics.*; 10 | import mindustry.ui.*; 11 | 12 | /** An ItemDisplay, but for power. */ 13 | public class PowerImage extends Table { 14 | public final float amount; 15 | 16 | public PowerImage(float amount) { 17 | this.amount = amount; 18 | 19 | add(new Stack() {{ 20 | add(new Table(o -> { 21 | o.left(); 22 | o.add(new Image(Icon.power)).size(32f).scaling(Scaling.fit).color(Pal.power); 23 | })); 24 | 25 | if(amount != 0) { 26 | add(new Table(t -> { 27 | t.left().bottom(); 28 | t.add(amount >= 1000f ? UI.formatAmount((int) amount) : (int) amount + "").style(Styles.outlineLabel); 29 | t.pack(); 30 | })); 31 | } 32 | }}); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/test/TestParser.java: -------------------------------------------------------------------------------- 1 | import mindustry.Vars; 2 | import mindustry.core.ContentLoader; 3 | import mindustry.type.Item; 4 | import org.junit.jupiter.api.Test; 5 | 6 | public class TestParser { 7 | @SuppressWarnings("unused") 8 | @Test 9 | public void input(){ 10 | Vars.content = new ContentLoader(); 11 | final Item item = new Item("test"); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /main/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .idea 3 | build -------------------------------------------------------------------------------- /main/assets/scripts/lib.js: -------------------------------------------------------------------------------- 1 | const loader = Vars.mods.mainLoader(); 2 | const scripts = Vars.mods.scripts; 3 | const NativeJavaClass = Packages.rhino.NativeJavaClass; 4 | function getClass(name){ 5 | return NativeJavaClass(scripts.scope, loader.loadClass(name)); 6 | }; 7 | 8 | const multiCrafterClz = getClass("multicraft.MultiCrafter") 9 | module.exports = { 10 | MultiCrafter : multiCrafterClz 11 | } -------------------------------------------------------------------------------- /main/assets/scripts/main.js: -------------------------------------------------------------------------------- 1 | require("lib") 2 | print(">>>>>MultiCrafter JavaScript loaded.") 3 | -------------------------------------------------------------------------------- /main/build.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("SpellCheckingInspection") 2 | 3 | import io.github.liplum.mindustry.importMindustry 4 | import io.github.liplum.mindustry.mindustry 5 | import io.github.liplum.mindustry.mindustryAssets 6 | 7 | plugins { 8 | java 9 | id("io.github.liplum.mgpp") 10 | } 11 | sourceSets { 12 | main { 13 | java.srcDirs("src") 14 | resources.srcDir("resources") 15 | } 16 | test { 17 | java.srcDir("test") 18 | resources.srcDir("resources") 19 | } 20 | } 21 | java { 22 | sourceCompatibility = JavaVersion.VERSION_1_8 23 | targetCompatibility = JavaVersion.VERSION_1_8 24 | } 25 | mindustry { 26 | mods { 27 | worksWith { 28 | add fromTask ":js:jar" 29 | // add java "3Snake3/Sapphirium" 30 | // add local "$buildDir/sapphirium-erekir.zip" 31 | // add local "$buildDir/units-mod.zip" 32 | } 33 | } 34 | run { 35 | clearOtherMods 36 | } 37 | deploy { 38 | baseName = "MultiCrafterLib" 39 | } 40 | } 41 | mindustryAssets { 42 | root at "$projectDir/assets" 43 | } 44 | dependencies { 45 | implementation(project(":lib")) 46 | importMindustry() 47 | testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.2") 48 | testImplementation("com.github.liplum:TestUtils:v0.1") 49 | } 50 | -------------------------------------------------------------------------------- /main/src/net/liplum/MultiCrafterMod.java: -------------------------------------------------------------------------------- 1 | package net.liplum; 2 | 3 | import mindustry.mod.Mod; 4 | 5 | public class MultiCrafterMod extends Mod { 6 | public static final boolean DebugMode = false; 7 | 8 | public MultiCrafterMod() { 9 | 10 | } 11 | 12 | @Override 13 | public void loadContent() { 14 | if (DebugMode) { 15 | TestBlocks.load(); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /main/src/net/liplum/TestBlocks.java: -------------------------------------------------------------------------------- 1 | package net.liplum; 2 | 3 | import arc.struct.*; 4 | import mindustry.content.*; 5 | import mindustry.type.*; 6 | import mindustry.world.meta.*; 7 | import multicraft.*; 8 | 9 | public class TestBlocks { 10 | public static void load() { 11 | @SuppressWarnings("unused") 12 | MultiCrafter test = new MultiCrafter("multi-crafter") {{ 13 | buildVisibility = BuildVisibility.shown; 14 | category = Category.crafting; 15 | size = 5; 16 | resolvedRecipes = Seq.with( 17 | new Recipe() {{ 18 | input = new IOEntry() {{ 19 | items = ItemStack.with( 20 | Items.copper, 1, 21 | Items.lead, 1 22 | ); 23 | payloads = PayloadStack.with( 24 | Blocks.thoriumWall, 2 25 | ); 26 | }}; 27 | output = new IOEntry() {{ 28 | items = ItemStack.with( 29 | Items.surgeAlloy, 1, 30 | Items.thorium, 1 31 | ); 32 | }}; 33 | craftTime = 120f; 34 | }}, 35 | new Recipe() {{ 36 | input = new IOEntry() {{ 37 | items = ItemStack.with( 38 | Items.plastanium, 1, 39 | Items.pyratite, 1 40 | ); 41 | fluids = LiquidStack.with( 42 | Liquids.slag, 0.5f 43 | ); 44 | }}; 45 | output = new IOEntry() {{ 46 | items = ItemStack.with( 47 | Items.coal, 1, 48 | Items.sand, 1 49 | ); 50 | fluids = LiquidStack.with( 51 | Liquids.oil, 0.2f 52 | ); 53 | payloads = PayloadStack.with( 54 | UnitTypes.dagger, 1 55 | ); 56 | }}; 57 | craftTime = 150f; 58 | }} 59 | ); 60 | }}; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: MultiCrafter Lib 2 | repo_url: https://github.com/liplum/MultiCrafterLib 3 | repo_name: MultiCrafter 4 | theme: 5 | name: material 6 | logo: icon.png 7 | custom_dir: overrides 8 | favicon: favicon.png 9 | icon: 10 | repo: fontawesome/brands/github 11 | shortcuts: 12 | help: 191 # ? 13 | next: 78 # n 14 | previous: 66 # p 15 | search: 83 # s 16 | features: 17 | - navigation.tabs 18 | - content.tabs.link 19 | palette: 20 | # Palette toggle for light mode 21 | - media: "(prefers-color-scheme: light)" 22 | scheme: default 23 | toggle: 24 | icon: material/brightness-7 25 | name: Switch to dark mode 26 | 27 | # Palette toggle for dark mode 28 | - media: "(prefers-color-scheme: dark)" 29 | scheme: slate 30 | toggle: 31 | icon: material/weather-night 32 | name: Switch to light mode 33 | 34 | #page tree 35 | nav: 36 | - Home: 37 | - Overview: index.md 38 | - Troubleshooting: troubleshooting.md 39 | - Migration Guide: migration.md 40 | - Usage: 41 | - JSON & Javascript: usage/json.md 42 | - Java: usage/java.md 43 | - Customize: 44 | - Menu Style: customize/menu.md 45 | - Built-in: customize/builtin.md 46 | - Drawer: customize/drawer.md 47 | - API: 48 | - MultiCrafter: api/multicrafter.md 49 | - Drawer: api/drawer.md 50 | 51 | markdown_extensions: 52 | - pymdownx.tabbed: 53 | alternate_style: true 54 | - pymdownx.superfences 55 | - pymdownx.tilde 56 | - pymdownx.snippets 57 | - pymdownx.details 58 | - attr_list 59 | - md_in_html 60 | - meta 61 | - toc: 62 | permalink: "#" 63 | -------------------------------------------------------------------------------- /mod.hjson: -------------------------------------------------------------------------------- 1 | displayName: "MultiCrafter Lib" 2 | name: "multi-crafter" 3 | author: "[#42a5f5[]]Liplum[[]] & Jojo" 4 | subtitle: "For JSON and Javascript mods." 5 | repo: "liplum/MultiCrafterLib" 6 | main: "net.liplum.MultiCrafterMod" 7 | description: 8 | ''' 9 | A Mindustry mod library for (H)JSON, JavaScript and Java mods, 10 | including a crafter supporting multiple recipes for items, fluids, power, heat and payloads production. 11 | Please check the repository below to learn more. 12 | Work on v136 to v146. 13 | ''' 14 | version: "2.0.0" 15 | minGameVersion: "136" 16 | java: true 17 | hidden: true -------------------------------------------------------------------------------- /overrides/main.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block extrahead %} 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | {% endblock %} -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "MultiCrafter" 2 | pluginManagement { 3 | repositories { 4 | gradlePluginPortal() 5 | mavenCentral() 6 | } 7 | } 8 | include( 9 | "main", 10 | "js", 11 | "java", 12 | "injection", 13 | "lib", 14 | "standalone" 15 | ) -------------------------------------------------------------------------------- /standalone/assets/content/blocks/json.hjson: -------------------------------------------------------------------------------- 1 | type: multicraft.MultiCrafter 2 | size : 3 3 | requirements: [ 4 | graphite/100 5 | surge-alloy/50 6 | phase-fabric/70 7 | ] 8 | category: crafting 9 | recipes : 10 | [ 11 | { 12 | input: ozone/1.5 13 | output:{ 14 | items : [ 15 | coal/1 16 | copper/1 17 | lead/1 18 | graphite/2 19 | ] 20 | power : 2, 21 | icon: alphaaaa, 22 | iconColor: "F30000" 23 | } 24 | craftTime : 250.0 25 | } 26 | { 27 | input:{ 28 | items : [ 29 | coal/1 30 | copper/1 31 | lead/1 32 | graphite/2 33 | ] 34 | fluids:[ 35 | ozone/1.5 36 | water/10.0 37 | ] 38 | power : 2 39 | heat: 3, 40 | icon: Icon.lock-open 41 | } 42 | output:{ 43 | items : [ 44 | coal/1 45 | copper/1 46 | lead/1 47 | graphite/2 48 | ] 49 | fluids:[ 50 | ozone/1.5 51 | water/10.0 52 | ] 53 | power : 2 54 | heat: 5 55 | } 56 | craftTime : 1260.0 57 | } 58 | ] -------------------------------------------------------------------------------- /standalone/assets/scripts/main.js: -------------------------------------------------------------------------------- 1 | print(">>>>>MultiCrafter Standalone JavaScript loaded.") 2 | const multi = require("multi-crafter/lib") 3 | const c = multi.MultiCrafter("standalone") 4 | c.requirements = ItemStack.with( 5 | Items.graphite, 5, 6 | Items.silicon, 3 7 | ) 8 | c.health = 110; 9 | c.buildVisibility = BuildVisibility.shown 10 | c.category = Category.crafting 11 | c.recipes = [{ 12 | input:{ 13 | items : [ 14 | "titanium/1" 15 | ], 16 | power : 2 17 | }, 18 | output:{ 19 | items : "graphite/1", 20 | }, 21 | craftTime : 240.0 22 | },{ 23 | input:{ 24 | fluids : "water/1" 25 | }, 26 | output:{ 27 | fluids:{ 28 | fluid : "slag", 29 | amount : 1.5 30 | } 31 | }, 32 | craftTime : 240.0 33 | },{ 34 | input:{ 35 | fluids : "water/1" 36 | }, 37 | output:{ 38 | heat : 5 39 | }, 40 | craftTime : 120.0 41 | }] -------------------------------------------------------------------------------- /standalone/build.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("SpellCheckingInspection") 2 | 3 | import io.github.liplum.mindustry.mindustry 4 | import io.github.liplum.mindustry.mindustryAssets 5 | 6 | plugins { 7 | java 8 | id("io.github.liplum.mgpp") 9 | } 10 | sourceSets { 11 | main { 12 | java.srcDirs("src") 13 | resources.srcDir("resources") 14 | } 15 | test { 16 | java.srcDir("test") 17 | resources.srcDir("resources") 18 | } 19 | } 20 | 21 | mindustry { 22 | deploy { 23 | baseName = "TestInjection" 24 | } 25 | } 26 | 27 | mindustryAssets { 28 | root at "$projectDir/assets" 29 | } 30 | 31 | tasks.jar { 32 | from(projectDir.resolve("injection")) { 33 | include("**/**") 34 | } 35 | } -------------------------------------------------------------------------------- /standalone/mod.hjson: -------------------------------------------------------------------------------- 1 | displayName: "Test Standalone" 2 | name: "standalone" 3 | author: "[#42a5f5[]]Liplum[[]]" 4 | description: "A standalone without MultiCrafter lib." 5 | version: 1.0 6 | minGameVersion: 136 7 | java: false 8 | main: MultiCrafterAdapter --------------------------------------------------------------------------------