356 lines
8.9 KiB
Markdown
356 lines
8.9 KiB
Markdown
# TC KubeJS Bridge (tckubejsbridge)
|
||
|
||
> **English** | [中文](README.md)
|
||
|
||
Bridges the **Thaumcraft 4 (port)** recipe and research systems to **KubeJS**.
|
||
|
||
Modpack authors can use scripts in `kubejs/server_scripts/` to register:
|
||
|
||
- Infusion recipes
|
||
- Crucible recipes
|
||
- Shaped / shapeless arcane recipes
|
||
- Item aspect tags
|
||
- Custom research categories / research nodes
|
||
- Replacing existing TC recipes
|
||
|
||
Author: AlkSur | License: GPL-3.0
|
||
|
||
---
|
||
|
||
## Requirements
|
||
|
||
| Mod | Version | Relation |
|
||
|---|---|---|
|
||
| NeoForge | 21.1.234 | Required |
|
||
| Minecraft | 1.21.1 | Required |
|
||
| Thaumcraft (TC4 port) | 0.2.2.34-port.1 | Required |
|
||
| KubeJS (NeoForge) | 2101.7.x | Required (plugin depends on its API) |
|
||
|
||
> KubeJS is declared **optional** in `neoforge.mods.toml`: without KubeJS the plugin part
|
||
> simply does not load, and the game still starts.
|
||
|
||
---
|
||
|
||
## Building & Installation
|
||
|
||
The project lives at `modules/tc-kubejs-bridge` inside a multi-module Gradle build.
|
||
|
||
```bash
|
||
# Run from the repository root
|
||
gradle :tckubejsbridge:build
|
||
```
|
||
|
||
Artifact:
|
||
|
||
```text
|
||
modules/tc-kubejs-bridge/build/libs/KubeJS Thaumcraft-<mod_version>+<neoforge_version>.jar
|
||
```
|
||
|
||
For example:
|
||
|
||
```text
|
||
KubeJS Thaumcraft-1.0.0+21.1.234.jar
|
||
```
|
||
|
||
Put the jar into your `mods/` folder.
|
||
|
||
---
|
||
|
||
## Quick Start
|
||
|
||
1. Create a `.js` file under `kubejs/server_scripts/`.
|
||
2. Use `ServerEvents.recipes(event => { ... })`.
|
||
3. Run `/reload` or restart the game.
|
||
|
||
All `thaumcraft.xxx` calls are **top-level bindings**; do not use `event.thaumcraft.xxx`.
|
||
|
||
---
|
||
|
||
## Recipe Registration
|
||
|
||
### 1. Infusion Recipe
|
||
|
||
```js
|
||
ServerEvents.recipes(event => {
|
||
event.recipes.thaumcraft.infusion(
|
||
'MY_INFUSION', // research key (globally unique)
|
||
'minecraft:diamond', // result
|
||
3, // instability
|
||
['ignis 64', 'potentia 32'], // required aspects
|
||
'minecraft:golden_apple', // central item
|
||
'minecraft:blaze_rod', // component
|
||
'minecraft:ender_pearl'
|
||
);
|
||
});
|
||
```
|
||
|
||
### 2. Crucible Recipe
|
||
|
||
```js
|
||
ServerEvents.recipes(event => {
|
||
event.recipes.thaumcraft.crucible(
|
||
'MY_CRUCIBLE',
|
||
'minecraft:diamond',
|
||
'minecraft:coal', // catalyst
|
||
{ aer: 16, ignis: 8 } // object syntax also works
|
||
);
|
||
});
|
||
```
|
||
|
||
### 3. Shaped Arcane Recipe
|
||
|
||
```js
|
||
ServerEvents.recipes(event => {
|
||
event.recipes.thaumcraft.arcane(
|
||
'MY_ARCANE',
|
||
'minecraft:diamond_sword',
|
||
['ignis 16', 'ordo 8'],
|
||
[' I ', ' I ', ' S '], // pattern: up to 3 rows
|
||
{ I: 'minecraft:iron_ingot', S: 'minecraft:stick' }
|
||
);
|
||
});
|
||
```
|
||
|
||
### 4. Shapeless Arcane Recipe
|
||
|
||
> Note: `arcane_shapeless` ingredients must be provided as **one array**.
|
||
|
||
```js
|
||
ServerEvents.recipes(event => {
|
||
event.recipes.thaumcraft.arcane_shapeless(
|
||
'MY_SHAPELESS',
|
||
'minecraft:diamond_helmet',
|
||
['aer 8', 'aqua 8'],
|
||
[
|
||
'minecraft:leather_helmet',
|
||
'minecraft:diamond',
|
||
'minecraft:diamond',
|
||
'minecraft:diamond'
|
||
]
|
||
);
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
## Item Aspect Tags
|
||
|
||
```js
|
||
ServerEvents.recipes(event => {
|
||
// add
|
||
thaumcraft.addObjectTag('minecraft:stick', { aer: 1, ignis: 2 });
|
||
|
||
// remove
|
||
thaumcraft.removeObjectTag('minecraft:stick');
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
## Replacing Existing Recipes
|
||
|
||
```js
|
||
ServerEvents.recipes(event => {
|
||
// remove first
|
||
thaumcraft.removeInfusion('INFUSIONPROVIDER'); // infusion
|
||
// thaumcraft.removeArcane('RESEARCH_KEY'); // arcane
|
||
// thaumcraft.removeCrucible('RESEARCH_KEY'); // crucible
|
||
|
||
// then register a new recipe
|
||
event.recipes.thaumcraft.infusion(
|
||
'INFUSIONPROVIDER',
|
||
'thaumicenergistics:infusion_provider',
|
||
0,
|
||
['auram 10'],
|
||
'ae2:interface',
|
||
'minecraft:oak_planks'
|
||
);
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
## Custom Research Categories / Nodes
|
||
|
||
### Add a Research Category
|
||
|
||
```js
|
||
ServerEvents.recipes(event => {
|
||
thaumcraft.addResearchCategory(
|
||
'MY_CATEGORY',
|
||
'textures/research/cat_icon.png',
|
||
'textures/research/cat_bg.png'
|
||
);
|
||
});
|
||
```
|
||
|
||
- A two-argument form is also available: `addResearchCategory('MY_CATEGORY', 'textures/research/cat_icon.png')`
|
||
- Paths without a namespace default to the `kubejs:` namespace.
|
||
|
||
### Add a Research Node
|
||
|
||
```js
|
||
ServerEvents.recipes(event => {
|
||
thaumcraft.addResearchNode({
|
||
key: 'MY_RESEARCH',
|
||
category: 'MY_CATEGORY',
|
||
icon: 'textures/research/my_research.png',
|
||
parents: ['PARENT_KEY'],
|
||
autoUnlock: true, // unlocked by default
|
||
virtual: false, // visible in the Thaumonomicon; true hides it
|
||
hidden: false,
|
||
col: 0,
|
||
row: 0,
|
||
complexity: 1,
|
||
aspects: { aer: 1, ignis: 1 }
|
||
});
|
||
});
|
||
```
|
||
|
||
Supported fields:
|
||
|
||
| Field | Description |
|
||
|---|---|
|
||
| `key` | Unique research node ID |
|
||
| `category` | Existing research category |
|
||
| `icon` | Icon path |
|
||
| `parents` / `parentsHidden` / `siblings` | Research dependencies |
|
||
| `autoUnlock` | Unlocked by default |
|
||
| `virtual` | Hidden from the Thaumonomicon when `true` |
|
||
| `hidden` / `concealed` / `lost` | Visibility flags |
|
||
| `special` / `stub` / `secondary` / `round` | Research type flags |
|
||
| `col` / `row` / `complexity` | Position and complexity on the research map |
|
||
| `aspects` | Associated aspects |
|
||
|
||
### Icon File Location
|
||
|
||
If you write:
|
||
|
||
```js
|
||
icon: 'textures/research/my_research.png'
|
||
```
|
||
|
||
The file should be at:
|
||
|
||
```text
|
||
kubejs/assets/kubejs/textures/research/my_research.png
|
||
```
|
||
|
||
You can also use a full path:
|
||
|
||
```js
|
||
icon: 'kubejs:textures/research/my_research.png'
|
||
```
|
||
|
||
### Research Names / Descriptions
|
||
|
||
TC research names use language files.
|
||
Add this to `kubejs/assets/kubejs/lang/en_us.json`:
|
||
|
||
```json
|
||
{
|
||
"tc.research_category.MY_CATEGORY": "My Category",
|
||
"tc.research_name.MY_RESEARCH": "My Research",
|
||
"tc.research_text.MY_RESEARCH": "This is a research description."
|
||
}
|
||
```
|
||
|
||
For Chinese, add the same keys to `kubejs/assets/kubejs/lang/zh_cn.json`.
|
||
|
||
---
|
||
## Aspect Tag Reference (actual values in the TC4 port)
|
||
|
||
**Important: the port uses the original Latin tags, not the TC4 English/localized names.**
|
||
|
||
| Aspect | Tag | Aspect | Tag |
|
||
|---|---|---|---|
|
||
| Air | `aer` | Earth | `terra` |
|
||
| Fire | `ignis` | Water | `aqua` |
|
||
| Order | `ordo` | Entropy | `perditio` |
|
||
| Void | `vacuos` | Light | `lux` |
|
||
| Weather | `tempestas` | Motion | `motus` |
|
||
| Crystal | `vitreus` | Life | `victus` |
|
||
| Energy | `potentia` | Exchange | `permutatio` |
|
||
| Death | `mortuus` | Darkness | `tenebrae` |
|
||
| Soul | `spiritus` | Eldritch | `alienis` |
|
||
| Magic | `praecantatio` | Aura | `auram` |
|
||
| Slime | `limus` | Plant | `herba` |
|
||
| Tree | `arbor` | Beast | `bestia` |
|
||
| Flesh | `corpus` | Undead | `exanimis` |
|
||
| Mind | `cognitio` | Senses | `sensus` |
|
||
| Man | `humanus` | Tool | `instrumentum` |
|
||
| Greed | `lucrum` | Craft | `fabrico` |
|
||
| Cloth | `pannus` | Mechanism | `machina` |
|
||
|
||
## Hot Reload
|
||
|
||
| Content | `/reload` | Notes |
|
||
|---|---|---|
|
||
| Infusion / Crucible / Arcane recipes | ✅ | Can be hot-reloaded |
|
||
| Item aspect tags | ✅ | Can be hot-reloaded |
|
||
| Research categories / nodes | ❌ | Requires a game restart |
|
||
|
||
### Research key notes
|
||
|
||
- If `research` is **not** an existing TC research, the bridge registers a default-unlocked virtual research node and treats the recipe as having **no research requirement**.
|
||
- If `research` **is** an existing TC research, the recipe still requires that research to be completed.
|
||
- Research keys are globally unique; duplicate keys are skipped.
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
## Troubleshooting
|
||
|
||
| Symptom | Cause |
|
||
|---|---|
|
||
| `Constructor for thaumcraft:arcane_shapeless with N arguments not found` | Ingredients were not placed in an array |
|
||
| `Unknown aspect 'xxx'` | Wrong aspect tag |
|
||
| `Item with ID xxx does not exist` | Wrong item id |
|
||
| Research category name shows `tc.research_category.XXX` | Missing language file |
|
||
| Research icon / background is purple-black | Texture missing, wrong path, not a PNG, or KubeJS assets not loaded |
|
||
| `event.thaumcraft is undefined` | Use top-level `thaumcraft.xxx`, not `event.thaumcraft.xxx` |
|
||
|
||
## Project Layout
|
||
|
||
```text
|
||
src/main/java/tckubejsbridge/
|
||
├── TCKubeJSBridge.java // @Mod main class
|
||
└── kubejs/
|
||
├── TCKubeJSPlugin.java // KubeJS plugin: schemas + bindings
|
||
├── TCRecipeKJS.java // Custom KubeRecipe base
|
||
├── TCRecipeBridge.java // TC API calls / dedup / removal / research registration
|
||
├── AspectListComponent.java // Aspect list component
|
||
├── InfusionRecipeKJS.java // Infusion recipe
|
||
├── CrucibleRecipeKJS.java // Crucible recipe
|
||
├── ShapedArcaneRecipeKJS.java // Shaped arcane recipe
|
||
└── ShapelessArcaneRecipeKJS.java // Shapeless arcane recipe
|
||
|
||
src/main/resources/
|
||
├── kubejs.plugins.txt // Plugin discovery file
|
||
└── META-INF/neoforge.mods.toml
|
||
|
||
example/infusion_example.js // Example script
|
||
```
|