itemscore-helper 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +84 -0
- package/bin/cli.js +143 -0
- package/bin/mcp.js +133 -0
- package/data/itemscore-api.json +5847 -0
- package/lib/manifest.js +234 -0
- package/package.json +45 -0
- package/skill/ITEM_FORMAT.md +193 -0
- package/skill/SKILL.md +118 -0
- package/skill/examples/healers_touch.item +23 -0
- package/skill/examples/storm_blade.item +19 -0
- package/skill/mcp.json +8 -0
package/lib/manifest.js
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"use strict"
|
|
2
|
+
|
|
3
|
+
const fs = require("fs")
|
|
4
|
+
const path = require("path")
|
|
5
|
+
|
|
6
|
+
const BUNDLED = path.join(__dirname, "..", "data", "itemscore-api.json")
|
|
7
|
+
const NEED_BLOCK_VALUES = ["BOTH", "AIR", "BLOCK"]
|
|
8
|
+
|
|
9
|
+
function resolveManifestPath(explicit) {
|
|
10
|
+
const candidates = [
|
|
11
|
+
explicit,
|
|
12
|
+
process.env.ITEMSCORE_API,
|
|
13
|
+
path.resolve(process.cwd(), "plugins", "ItemsCore", "itemscore-api.json"),
|
|
14
|
+
]
|
|
15
|
+
for (const c of candidates) {
|
|
16
|
+
if (c && fs.existsSync(c)) return c
|
|
17
|
+
}
|
|
18
|
+
return BUNDLED
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function loadManifest(explicit) {
|
|
22
|
+
const file = resolveManifestPath(explicit)
|
|
23
|
+
const data = JSON.parse(fs.readFileSync(file, "utf8"))
|
|
24
|
+
return { manifest: data, source: file, isBundled: file === BUNDLED }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function buildIndex(loaded) {
|
|
28
|
+
const manifest = loaded.manifest
|
|
29
|
+
const bindings = manifest.bindings || []
|
|
30
|
+
const triggers = manifest.triggers || []
|
|
31
|
+
const events = manifest.events || []
|
|
32
|
+
const variables = manifest.variables || []
|
|
33
|
+
|
|
34
|
+
const BINDING_NAMES = bindings.map((b) => b.name)
|
|
35
|
+
const TRIGGER_NAMES = triggers.map((t) => t.name)
|
|
36
|
+
|
|
37
|
+
const KNOWN_RECEIVERS = new Set()
|
|
38
|
+
for (const b of bindings) KNOWN_RECEIVERS.add(b.name)
|
|
39
|
+
for (const v of variables) KNOWN_RECEIVERS.add(v.name)
|
|
40
|
+
for (const t of triggers) for (const v of t.variables || []) KNOWN_RECEIVERS.add(v)
|
|
41
|
+
for (const e of events) for (const v of e.variables || []) KNOWN_RECEIVERS.add(v)
|
|
42
|
+
|
|
43
|
+
return { manifest, source: loaded.source, isBundled: loaded.isBundled, bindings, triggers, events, variables, BINDING_NAMES, TRIGGER_NAMES, KNOWN_RECEIVERS }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function searchMethods(idx, opts) {
|
|
47
|
+
opts = opts || {}
|
|
48
|
+
const q = (opts.query || "").trim().toLowerCase()
|
|
49
|
+
const limit = opts.limit && opts.limit > 0 ? opts.limit : 40
|
|
50
|
+
const hits = []
|
|
51
|
+
for (const b of idx.bindings) {
|
|
52
|
+
if (opts.binding && b.name !== opts.binding) continue
|
|
53
|
+
for (const m of b.methods || []) {
|
|
54
|
+
if (m.useless && !opts.includeUseless) continue
|
|
55
|
+
if (q) {
|
|
56
|
+
const hay = (m.name + " " + m.signature + " " + m.category + " " + m.description).toLowerCase()
|
|
57
|
+
if (!hay.includes(q)) continue
|
|
58
|
+
}
|
|
59
|
+
hits.push(Object.assign({ binding: b.name }, m))
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
hits.sort((a, b) => {
|
|
63
|
+
if (q) {
|
|
64
|
+
const an = a.name.toLowerCase() === q ? 0 : a.name.toLowerCase().startsWith(q) ? 1 : 2
|
|
65
|
+
const bn = b.name.toLowerCase() === q ? 0 : b.name.toLowerCase().startsWith(q) ? 1 : 2
|
|
66
|
+
if (an !== bn) return an - bn
|
|
67
|
+
}
|
|
68
|
+
return a.name.localeCompare(b.name)
|
|
69
|
+
})
|
|
70
|
+
return hits.slice(0, limit)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function getMethod(idx, name, binding) {
|
|
74
|
+
let wantBinding = binding
|
|
75
|
+
let wantName = name
|
|
76
|
+
if (name.includes(".")) {
|
|
77
|
+
const i = name.indexOf(".")
|
|
78
|
+
wantBinding = name.slice(0, i)
|
|
79
|
+
wantName = name.slice(i + 1)
|
|
80
|
+
}
|
|
81
|
+
const out = []
|
|
82
|
+
for (const b of idx.bindings) {
|
|
83
|
+
if (wantBinding && b.name !== wantBinding) continue
|
|
84
|
+
for (const m of b.methods || []) {
|
|
85
|
+
if (m.name === wantName) out.push(Object.assign({ binding: b.name }, m))
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return out
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function validateStep(idx, step, p, errors, warnings) {
|
|
92
|
+
if (typeof step !== "object" || step === null) {
|
|
93
|
+
errors.push(p + " must be an object")
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
const call = step.call
|
|
97
|
+
if (typeof call !== "string" || call.length === 0) {
|
|
98
|
+
errors.push(p + ".call is required and must be a string")
|
|
99
|
+
return
|
|
100
|
+
}
|
|
101
|
+
if (call.includes(".")) {
|
|
102
|
+
const receiver = call.slice(0, call.indexOf("."))
|
|
103
|
+
const method = call.slice(call.indexOf(".") + 1)
|
|
104
|
+
if (idx.BINDING_NAMES.includes(receiver)) {
|
|
105
|
+
const found = getMethod(idx, method, receiver)
|
|
106
|
+
if (found.length === 0) {
|
|
107
|
+
errors.push(p + '.call "' + call + '" references unknown method "' + method + '" on binding "' + receiver + '"')
|
|
108
|
+
} else if (found[0].useless) {
|
|
109
|
+
warnings.push(p + '.call "' + call + '" is flagged useless (no real effect)')
|
|
110
|
+
}
|
|
111
|
+
} else if (!idx.KNOWN_RECEIVERS.has(receiver)) {
|
|
112
|
+
warnings.push(p + '.call receiver "' + receiver + '" is not a known variable; make sure it is defined earlier or exists at runtime')
|
|
113
|
+
}
|
|
114
|
+
} else if (!idx.KNOWN_RECEIVERS.has(call)) {
|
|
115
|
+
warnings.push(p + '.call "' + call + '" is treated as a bare variable read but is not a known variable')
|
|
116
|
+
}
|
|
117
|
+
if (step.args !== undefined && !Array.isArray(step.args)) errors.push(p + ".args must be an array when present")
|
|
118
|
+
if (step.operatorToNext !== undefined && typeof step.operatorToNext !== "string") errors.push(p + ".operatorToNext must be a string when present")
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function validateItem(idx, item) {
|
|
122
|
+
const errors = []
|
|
123
|
+
const warnings = []
|
|
124
|
+
if (typeof item !== "object" || item === null) return { valid: false, errors: ["item must be a JSON object"], warnings }
|
|
125
|
+
|
|
126
|
+
if (typeof item.name !== "string" || item.name.length === 0) errors.push("name is required and must be a non-empty string")
|
|
127
|
+
if (typeof item.material !== "string" || item.material.length === 0) errors.push("material is required and must be a non-empty string (e.g. DIAMOND_SWORD)")
|
|
128
|
+
if (item.needBlock !== undefined && (typeof item.needBlock !== "string" || !NEED_BLOCK_VALUES.includes(item.needBlock))) {
|
|
129
|
+
errors.push("needBlock must be one of " + NEED_BLOCK_VALUES.join(", "))
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (item.actions !== undefined) {
|
|
133
|
+
if (!Array.isArray(item.actions)) errors.push("actions must be an array")
|
|
134
|
+
else {
|
|
135
|
+
item.actions.forEach((action, ai) => {
|
|
136
|
+
const ap = "actions[" + ai + "]"
|
|
137
|
+
if (typeof action !== "object" || action === null) {
|
|
138
|
+
errors.push(ap + " must be an object")
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
if (typeof action.trigger !== "string" || !idx.TRIGGER_NAMES.includes(action.trigger)) {
|
|
142
|
+
errors.push(ap + '.trigger "' + String(action.trigger) + '" is not a valid trigger. Valid: ' + idx.TRIGGER_NAMES.join(", "))
|
|
143
|
+
}
|
|
144
|
+
if (action.needBlock !== undefined && (typeof action.needBlock !== "string" || !NEED_BLOCK_VALUES.includes(action.needBlock))) {
|
|
145
|
+
errors.push(ap + ".needBlock must be one of " + NEED_BLOCK_VALUES.join(", "))
|
|
146
|
+
}
|
|
147
|
+
if (!Array.isArray(action.steps)) errors.push(ap + ".steps must be an array")
|
|
148
|
+
else action.steps.forEach((s, si) => validateStep(idx, s, ap + ".steps[" + si + "]", errors, warnings))
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (item.stats !== undefined && !Array.isArray(item.stats)) errors.push("stats must be an array when present")
|
|
154
|
+
if (item.lore !== undefined && !Array.isArray(item.lore)) errors.push("lore must be an array of strings when present")
|
|
155
|
+
if (item.enchantments !== undefined && !Array.isArray(item.enchantments)) errors.push("enchantments must be an array when present")
|
|
156
|
+
|
|
157
|
+
return { valid: errors.length === 0, errors, warnings }
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function itemSchema(idx) {
|
|
161
|
+
return {
|
|
162
|
+
description:
|
|
163
|
+
"Clean item JSON consumed by the ItemsCore plugin via /ic import. The plugin converts this into both runnable code and a GUI-editable action graph, so items authored this way stay editable in-game.",
|
|
164
|
+
needBlockValues: NEED_BLOCK_VALUES,
|
|
165
|
+
fields: {
|
|
166
|
+
name: "string (required) - internal item id, no spaces",
|
|
167
|
+
fancyName: "string - display name, supports & color codes",
|
|
168
|
+
id: "string - optional explicit id, defaults to name",
|
|
169
|
+
material: "string (required) - Bukkit material, e.g. DIAMOND_SWORD",
|
|
170
|
+
needBlock: "BOTH | AIR | BLOCK - default interaction context for the whole item",
|
|
171
|
+
lore: "string[] - lore lines, support & color codes",
|
|
172
|
+
enchantments: "{ name: string, level: number }[]",
|
|
173
|
+
flags: "string[] - Bukkit ItemFlag names",
|
|
174
|
+
talisman: "boolean - whether the item works from the inventory (talisman)",
|
|
175
|
+
customModelData: "number",
|
|
176
|
+
skullOwner: "string - player name for PLAYER_HEAD skins",
|
|
177
|
+
stats: "object[] - stat modifiers (see editor)",
|
|
178
|
+
actions: "Action[] - the behavior graph",
|
|
179
|
+
events: "object[] - custom event definitions",
|
|
180
|
+
},
|
|
181
|
+
action: {
|
|
182
|
+
trigger: "string (required) - one of: " + idx.TRIGGER_NAMES.join(", "),
|
|
183
|
+
needBlock: "BOTH | AIR | BLOCK - optional per-action override",
|
|
184
|
+
steps: "Step[] - executed in order, combined by operatorToNext",
|
|
185
|
+
},
|
|
186
|
+
step: {
|
|
187
|
+
call:
|
|
188
|
+
'string (required) - "core.method", "particles.method", "values.method", "api.method", a bukkit call like "player.getLocation", or a bare variable name to read it',
|
|
189
|
+
args:
|
|
190
|
+
'Arg[] - each arg is a JSON literal (string/number/boolean), or { var: "player" } to pass a variable, or a nested { call, args } to pass a method result',
|
|
191
|
+
operatorToNext:
|
|
192
|
+
"NONE | ADD | SUBTRACT | MULTIPLY | DIVIDE | EQUALS | NOT_EQUALS | GREATER_THAN | LESS_THAN | GREATER_THAN_EQUALS | LESS_THAN_EQUALS | AND | OR | END | COMMA - how this step combines with the next one (defaults to END)",
|
|
193
|
+
},
|
|
194
|
+
bukkitObjects:
|
|
195
|
+
"player, shooter, victim, arrow, event, and any entity, block, world, location, or ItemStack returned by a method are real Bukkit/Spigot objects. You can call any standard Spigot method on them in a step (for example player.sendMessage, player.getHealth, player.getWorld, victim.setFireTicks), not only the ItemsCore methods. These are not in the method list below; see https://hub.spigotmc.org/javadocs/spigot/ for the full Spigot API. Prefer a core method when one exists.",
|
|
196
|
+
bindings: idx.bindings.map((b) => ({ name: b.name, description: b.description || "", methodCount: (b.methods || []).length })),
|
|
197
|
+
variables: idx.variables,
|
|
198
|
+
triggers: idx.triggers,
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function generateItemTemplate(kind) {
|
|
203
|
+
const k = (kind || "basic").toLowerCase()
|
|
204
|
+
if (k === "ability" || k === "spell") {
|
|
205
|
+
return {
|
|
206
|
+
name: "magic_wand",
|
|
207
|
+
fancyName: "&dMagic Wand",
|
|
208
|
+
material: "BLAZE_ROD",
|
|
209
|
+
needBlock: "BOTH",
|
|
210
|
+
lore: ["&7Right-click to cast.", "&dExample ItemsCore item."],
|
|
211
|
+
talisman: false,
|
|
212
|
+
actions: [
|
|
213
|
+
{ trigger: "rightAction", needBlock: "BOTH", steps: [{ call: "core.broadcastMessage", args: ["A wand was cast!"], operatorToNext: "END" }] },
|
|
214
|
+
],
|
|
215
|
+
events: [],
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
name: "example_sword",
|
|
220
|
+
fancyName: "&bExample Sword",
|
|
221
|
+
material: "DIAMOND_SWORD",
|
|
222
|
+
needBlock: "BOTH",
|
|
223
|
+
lore: ["&7A starter ItemsCore item.", "&7Left-click to greet the server."],
|
|
224
|
+
enchantments: [{ name: "DAMAGE_ALL", level: 1 }],
|
|
225
|
+
flags: [],
|
|
226
|
+
talisman: false,
|
|
227
|
+
actions: [
|
|
228
|
+
{ trigger: "leftAction", needBlock: "BOTH", steps: [{ call: "core.broadcastMessage", args: ["Hello from my custom sword!"], operatorToNext: "END" }] },
|
|
229
|
+
],
|
|
230
|
+
events: [],
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
module.exports = { loadManifest, buildIndex, searchMethods, getMethod, validateItem, itemSchema, generateItemTemplate, resolveManifestPath }
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "itemscore-helper",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Local MCP server + skill that lets any AI (Claude, Codex, Cursor, Gemini, and others) build and edit custom Minecraft items for the ItemsCore plugin. Runs entirely on your machine.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"itemscore-helper": "bin/cli.js",
|
|
7
|
+
"itemscore-mcp": "bin/mcp.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"lib/",
|
|
12
|
+
"data/",
|
|
13
|
+
"skill/",
|
|
14
|
+
"README.md",
|
|
15
|
+
"LICENSE"
|
|
16
|
+
],
|
|
17
|
+
"keywords": [
|
|
18
|
+
"itemscore",
|
|
19
|
+
"minecraft",
|
|
20
|
+
"spigot",
|
|
21
|
+
"bukkit",
|
|
22
|
+
"mcp",
|
|
23
|
+
"model-context-protocol",
|
|
24
|
+
"ai",
|
|
25
|
+
"skill",
|
|
26
|
+
"claude",
|
|
27
|
+
"cursor",
|
|
28
|
+
"codex",
|
|
29
|
+
"gemini"
|
|
30
|
+
],
|
|
31
|
+
"homepage": "https://www.coredevelopment.shop/docs/items-core",
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "https://github.com/Tc554/itemscore-helper.git"
|
|
35
|
+
},
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"author": "TastyCake",
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=18"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
43
|
+
"zod": "^3.25.76"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# ItemsCore clean item JSON reference
|
|
2
|
+
|
|
3
|
+
This is the offline reference for the item format ItemsCore imports. When the live API is reachable, prefer it (`get_item_schema`, `search_methods`, `get_method`), because it reflects the exact methods on the user's server. Use this file when offline.
|
|
4
|
+
|
|
5
|
+
## Top-level fields
|
|
6
|
+
|
|
7
|
+
| Field | Type | Notes |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| `name` | string, required | Internal id, no spaces (e.g. `flame_sword`) |
|
|
10
|
+
| `fancyName` | string | Display name, supports `&` color codes |
|
|
11
|
+
| `id` | string | Optional explicit id; defaults to `name` |
|
|
12
|
+
| `material` | string, required | Bukkit material (e.g. `DIAMOND_SWORD`, `BLAZE_ROD`, `PLAYER_HEAD`) |
|
|
13
|
+
| `needBlock` | `BOTH` \| `AIR` \| `BLOCK` | Default interaction context for the item |
|
|
14
|
+
| `lore` | string[] | Lore lines, support `&` color codes |
|
|
15
|
+
| `enchantments` | `{ name, level }[]` | e.g. `{ "name": "DAMAGE_ALL", "level": 3 }` |
|
|
16
|
+
| `flags` | string[] | Bukkit ItemFlag names (e.g. `HIDE_ATTRIBUTES`) |
|
|
17
|
+
| `talisman` | boolean | If true, the item works from anywhere in the inventory |
|
|
18
|
+
| `customModelData` | number | Resource-pack model id |
|
|
19
|
+
| `skullOwner` | string | Player name, for `PLAYER_HEAD` skins |
|
|
20
|
+
| `stats` | object[] | Stat modifiers (authored in the editor; preserved on re-import) |
|
|
21
|
+
| `actions` | Action[] | The behavior graph (see below) |
|
|
22
|
+
| `events` | object[] | Custom event definitions |
|
|
23
|
+
|
|
24
|
+
## Action
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{ "trigger": "rightAction", "needBlock": "BOTH", "steps": [ ... ] }
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
| Field | Type | Notes |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| `trigger` | string, required | One of the triggers below |
|
|
33
|
+
| `needBlock` | `BOTH` \| `AIR` \| `BLOCK` | Optional per-action override of the item default |
|
|
34
|
+
| `steps` | Step[] | Run in order, combined by each step's `operatorToNext` |
|
|
35
|
+
|
|
36
|
+
### Triggers
|
|
37
|
+
|
|
38
|
+
| Trigger | Fires when | Variables available |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| `leftAction` | Left-click holding the item (this is the attack swing) | player, item, event |
|
|
41
|
+
| `leftSAction` | Sneak + left-click | player, item, event |
|
|
42
|
+
| `rightAction` | Right-click holding the item | player, item, event |
|
|
43
|
+
| `rightSAction` | Sneak + right-click | player, item, event |
|
|
44
|
+
| `shiftAction` | Starts sneaking while holding or wearing it | player, item, event |
|
|
45
|
+
| `dropAction` | Drops the item (the drop is cancelled) | player, item, event |
|
|
46
|
+
| `swapAction` | Swaps hands (F) with the item | player, item, event |
|
|
47
|
+
| `shootAction` | Shoots a projectile while holding it | shooter, item, event |
|
|
48
|
+
| `pickupAction` | Picks up the item | player, item, event |
|
|
49
|
+
| `arrowLandAction` | An arrow shot while holding it lands | shooter, item, arrow, landLocation, event |
|
|
50
|
+
| `armorEquipEvent` | The item (armor) is equipped | player, item, event |
|
|
51
|
+
| `armorUnEquipEvent` | The item (armor) is unequipped | player, item, event |
|
|
52
|
+
| `playerMoveEvent` | Moves while holding or wearing it | player, item, event |
|
|
53
|
+
| `playerDamageEvent` | The holder/wearer takes damage | player, item, event |
|
|
54
|
+
| `projectileHitEntityEvent` | A custom projectile from this item hits an entity | shooter, victim, item, lastLocation, event |
|
|
55
|
+
| `projectileHitBlockEvent` | A custom projectile from this item hits a block | shooter, item, lastLocation, event |
|
|
56
|
+
|
|
57
|
+
## Step
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{ "call": "core.heal", "args": [ { "var": "player" }, 6 ], "operatorToNext": "END" }
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
| Field | Type | Notes |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| `call` | string, required | `core.method`, `particles.method`, `values.method`, `api.method`, a Bukkit call like `player.getLocation`, or a bare variable name to read it |
|
|
66
|
+
| `args` | Arg[] | The call arguments, in order |
|
|
67
|
+
| `operatorToNext` | string | How this step joins the next one (default `END`) |
|
|
68
|
+
|
|
69
|
+
### Arg forms
|
|
70
|
+
|
|
71
|
+
- A JSON literal: `"hello"`, `6`, `1.5`, `true`.
|
|
72
|
+
- A variable: `{ "var": "player" }` passes the `player` object.
|
|
73
|
+
- A nested call: `{ "call": "player.getLocation", "args": [] }` passes one call's result into another.
|
|
74
|
+
|
|
75
|
+
Example, passing results into a call:
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
{ "call": "core.playSound", "args": [
|
|
79
|
+
{ "var": "player" },
|
|
80
|
+
{ "call": "player.getLocation", "args": [] },
|
|
81
|
+
{ "call": "core.getSound", "args": ["ENTITY_BLAZE_SHOOT"] },
|
|
82
|
+
1, 1
|
|
83
|
+
] }
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Operators (`operatorToNext`)
|
|
87
|
+
|
|
88
|
+
| Operator | Meaning |
|
|
89
|
+
|---|---|
|
|
90
|
+
| `END` | End the statement (most common; use between independent steps) |
|
|
91
|
+
| `NONE` | No joining token |
|
|
92
|
+
| `ADD` `SUBTRACT` `MULTIPLY` `DIVIDE` | Arithmetic `+ - * /` between values |
|
|
93
|
+
| `EQUALS` `NOT_EQUALS` | `===` `!==` |
|
|
94
|
+
| `GREATER_THAN` `LESS_THAN` `GREATER_THAN_EQUALS` `LESS_THAN_EQUALS` | `>` `<` `>=` `<=` |
|
|
95
|
+
| `AND` `OR` | `&&` `\|\|` |
|
|
96
|
+
| `COMMA` | `,` separate values |
|
|
97
|
+
|
|
98
|
+
For most items, every step uses `END`. Operators other than `END` build a single expression across steps (for conditions and math). When you need a condition, prefer `core.doIf` / `core.doIfElse` / `core.chanceOf` rather than chaining raw operators.
|
|
99
|
+
|
|
100
|
+
## Variables
|
|
101
|
+
|
|
102
|
+
| Variable | When |
|
|
103
|
+
|---|---|
|
|
104
|
+
| `core` `particles` `values` `api` | Always available (the scripting bindings) |
|
|
105
|
+
| `item` | Always (the custom item this action belongs to) |
|
|
106
|
+
| `event` | Always (the Bukkit event; cancel it with `core.cancelEvent(event)`) |
|
|
107
|
+
| `player` | Most triggers |
|
|
108
|
+
| `shooter` | shootAction, arrowLandAction, projectile hit events |
|
|
109
|
+
| `victim` | projectileHitEntityEvent |
|
|
110
|
+
| `arrow` | arrowLandAction |
|
|
111
|
+
| `landLocation` | arrowLandAction |
|
|
112
|
+
| `lastLocation` | projectile hit events |
|
|
113
|
+
|
|
114
|
+
## Bukkit objects expose their full Spigot API
|
|
115
|
+
|
|
116
|
+
`player`, `shooter`, `victim`, `arrow`, `event`, and any entity, block, world, location, or `ItemStack` returned by a method are real Bukkit/Spigot objects. You can call any standard Spigot method on them in a step, not only the ItemsCore methods:
|
|
117
|
+
|
|
118
|
+
- `{ "call": "player.sendMessage", "args": ["&aHi"] }`
|
|
119
|
+
- `{ "call": "player.setFireTicks", "args": [60] }`
|
|
120
|
+
- `{ "call": "victim.getHealth", "args": [] }`
|
|
121
|
+
- `{ "call": "player.getWorld", "args": [] }`
|
|
122
|
+
|
|
123
|
+
These are not in the `core` / `particles` / `values` / `api` method list. For the complete set, see the Spigot API docs at https://hub.spigotmc.org/javadocs/spigot/ (match your server version). Prefer a `core` method when one exists; use raw Spigot calls for the rest.
|
|
124
|
+
|
|
125
|
+
## Bindings (summary)
|
|
126
|
+
|
|
127
|
+
- `core` - the main toolkit: messaging, teleport, damage/heal, effects, sounds, projectiles, conditions, loops, variables, commands, placeholders.
|
|
128
|
+
- `particles` - build and display particle effects (`of`, `colored`, `withLocation`, `circle`, ...).
|
|
129
|
+
- `values` - custom values registered by addons.
|
|
130
|
+
- `api` - the public plugin API.
|
|
131
|
+
|
|
132
|
+
Always confirm a method's exact name and parameters with `get_method` / `search_methods` before using it. Do not guess.
|
|
133
|
+
|
|
134
|
+
## Worked example: a healing ability wand
|
|
135
|
+
|
|
136
|
+
```json
|
|
137
|
+
{
|
|
138
|
+
"name": "healers_touch",
|
|
139
|
+
"fancyName": "&dHealer's Touch",
|
|
140
|
+
"material": "BLAZE_ROD",
|
|
141
|
+
"needBlock": "BOTH",
|
|
142
|
+
"lore": ["&7Right-click to channel healing energy."],
|
|
143
|
+
"enchantments": [{ "name": "LUCK", "level": 1 }],
|
|
144
|
+
"flags": ["HIDE_ENCHANTS"],
|
|
145
|
+
"talisman": false,
|
|
146
|
+
"actions": [
|
|
147
|
+
{
|
|
148
|
+
"trigger": "rightAction",
|
|
149
|
+
"needBlock": "BOTH",
|
|
150
|
+
"steps": [
|
|
151
|
+
{ "call": "core.sendColorMessage", "args": [ { "var": "player" }, "&aYou channel healing energy!" ], "operatorToNext": "END" },
|
|
152
|
+
{ "call": "core.heal", "args": [ { "var": "player" }, 6 ], "operatorToNext": "END" },
|
|
153
|
+
{ "call": "core.giveEffect", "args": [ { "var": "player" }, "REGENERATION", 100, 1, false, true ], "operatorToNext": "END" },
|
|
154
|
+
{ "call": "core.playSound", "args": [ { "var": "player" }, { "call": "player.getLocation", "args": [] }, { "call": "core.getSound", "args": ["ENTITY_PLAYER_LEVELUP"] }, 1, 1 ], "operatorToNext": "END" }
|
|
155
|
+
]
|
|
156
|
+
}
|
|
157
|
+
],
|
|
158
|
+
"events": []
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Worked example: a lightning sword with a chance roll
|
|
163
|
+
|
|
164
|
+
```json
|
|
165
|
+
{
|
|
166
|
+
"name": "storm_blade",
|
|
167
|
+
"fancyName": "&bStorm Blade",
|
|
168
|
+
"material": "DIAMOND_SWORD",
|
|
169
|
+
"needBlock": "BOTH",
|
|
170
|
+
"lore": ["&7Left-click: 25% chance to call lightning."],
|
|
171
|
+
"enchantments": [{ "name": "DAMAGE_ALL", "level": 4 }],
|
|
172
|
+
"talisman": false,
|
|
173
|
+
"actions": [
|
|
174
|
+
{
|
|
175
|
+
"trigger": "leftAction",
|
|
176
|
+
"needBlock": "BOTH",
|
|
177
|
+
"steps": [
|
|
178
|
+
{ "call": "core.doIf", "args": [ { "call": "core.chanceOf", "args": [25] }, "core.summonLightningByLocation(player.getLocation())" ], "operatorToNext": "END" }
|
|
179
|
+
]
|
|
180
|
+
}
|
|
181
|
+
],
|
|
182
|
+
"events": []
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Note on `core.doIf(boolean condition, String action)`: the second argument is a short string of script that runs only when the condition is true. Keep that string to a single statement and use methods you have verified exist. For multi-step conditional logic, run several `doIf` steps.
|
|
187
|
+
|
|
188
|
+
## Before you deliver
|
|
189
|
+
|
|
190
|
+
1. Every `trigger` is in the table above.
|
|
191
|
+
2. Every `core.` / `particles.` / `values.` / `api.` call is a real method (checked with `get_method`).
|
|
192
|
+
3. Argument order and count match the method signature.
|
|
193
|
+
4. Run `validate_item` and clear all errors.
|
package/skill/SKILL.md
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: itemscore
|
|
3
|
+
description: Build and edit custom Minecraft RPG items for the ItemsCore plugin. Use whenever the user wants to create a new item, change how an item behaves, add an ability or particle effect, or fix an ItemsCore item. The user runs a Minecraft server with ItemsCore installed; you produce a clean item JSON file they import in-game.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# ItemsCore item builder
|
|
7
|
+
|
|
8
|
+
ItemsCore is a Minecraft (Bukkit/Spigot) plugin that lets a server owner create fully custom RPG items without writing Java. You help the user build those items.
|
|
9
|
+
|
|
10
|
+
## The one rule that matters
|
|
11
|
+
|
|
12
|
+
Always produce a **clean item JSON** (the format described below). Never hand-write the plugin's internal `.item` YAML or its generated JavaScript `code`. When the user imports your JSON, the plugin builds **both** the runnable code **and** the in-game GUI action graph from it. That means an item you create this way still works **and** stays fully editable in the in-game editor. Hand-writing raw code produces an item the GUI cannot open, which is exactly the problem this format avoids.
|
|
13
|
+
|
|
14
|
+
## Step 1: Get the API (do this first)
|
|
15
|
+
|
|
16
|
+
Use the real API instead of guessing method names.
|
|
17
|
+
|
|
18
|
+
Preferred - the `itemscore` MCP server. It runs locally on the user's machine (installed with `npx itemscore-helper`) and exposes these tools. If it is connected, call them:
|
|
19
|
+
- `search_methods(query, binding?, includeUseless?)` - find scripting methods by name, category, or description
|
|
20
|
+
- `get_method(name, binding?)` - full signature, params, return, and example for one method (accepts `core.teleport` or `teleport`)
|
|
21
|
+
- `list_triggers()` - every trigger an item can react to, and the variables available in each
|
|
22
|
+
- `list_events()` - custom events and the global variables (player, event, core, particles, ...)
|
|
23
|
+
- `get_item_schema()` - the full field reference for the clean item JSON
|
|
24
|
+
- `validate_item(item)` - checks an item JSON and returns errors and warnings
|
|
25
|
+
- `generate_item_template(kind?)` - a valid starter item (`basic` or `ability`)
|
|
26
|
+
|
|
27
|
+
Fallback - if no MCP is connected, a hosted copy is available over plain HTTP:
|
|
28
|
+
- MCP endpoint: `https://www.coredevelopment.shop/api/mcp`
|
|
29
|
+
- API manifest: `https://www.coredevelopment.shop/api/itemscore/manifest`
|
|
30
|
+
- Item schema: `https://www.coredevelopment.shop/api/itemscore/item-schema`
|
|
31
|
+
- Quick guide: `https://www.coredevelopment.shop/llms.txt`
|
|
32
|
+
|
|
33
|
+
If nothing is reachable, use `ITEM_FORMAT.md` in this folder as the offline reference.
|
|
34
|
+
|
|
35
|
+
## Step 2: Build the item JSON
|
|
36
|
+
|
|
37
|
+
Minimal shape:
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"name": "flame_sword",
|
|
42
|
+
"fancyName": "&cFlame Sword",
|
|
43
|
+
"material": "DIAMOND_SWORD",
|
|
44
|
+
"needBlock": "BOTH",
|
|
45
|
+
"lore": ["&7Left-click to ignite the server."],
|
|
46
|
+
"talisman": false,
|
|
47
|
+
"actions": [
|
|
48
|
+
{
|
|
49
|
+
"trigger": "leftAction",
|
|
50
|
+
"needBlock": "BOTH",
|
|
51
|
+
"steps": [
|
|
52
|
+
{ "call": "core.broadcastMessage", "args": ["The flame sword roars!"], "operatorToNext": "END" }
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
],
|
|
56
|
+
"events": []
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Rules:
|
|
61
|
+
- `name` and `material` are required. `material` is a Bukkit material name (e.g. `DIAMOND_SWORD`, `BLAZE_ROD`).
|
|
62
|
+
- Color codes use `&` (e.g. `&c` red, `&a` green).
|
|
63
|
+
- `trigger` must be one of the valid triggers (call `list_triggers`). Common ones: `leftAction` (left-click, which is also the attack swing), `rightAction` (right-click, e.g. cast an ability), `shiftAction` (start sneaking), `playerDamageEvent` (the wearer takes damage), `projectileHitEntityEvent` (a custom projectile from the item hits an entity). There is no separate melee-attack trigger; left-click is the attack.
|
|
64
|
+
- Each step has a `call`, an `args` array, and `operatorToNext`. `call` is `core.method`, `particles.method`, a Bukkit call like `player.getLocation`, or a bare variable name to read it.
|
|
65
|
+
- An arg is a JSON literal, `{ "var": "player" }` to pass a variable, or a nested `{ "call": ..., "args": ... }` to pass one method's result into another.
|
|
66
|
+
- `operatorToNext` joins a step to the next one. Use `END` to end a statement. Other values (`ADD`, `EQUALS`, `AND`, ...) build expressions and conditions. See `ITEM_FORMAT.md`.
|
|
67
|
+
|
|
68
|
+
Find the exact method you need with `search_methods` / `get_method` before using it. Do not invent method names.
|
|
69
|
+
|
|
70
|
+
## Bukkit and Spigot objects
|
|
71
|
+
|
|
72
|
+
The variables `player`, `shooter`, `victim`, `arrow`, `event`, and any entity, block, world, location, or item you get back from a method are real Bukkit/Spigot objects. They expose their entire normal Spigot API, not just the ItemsCore methods, so you can call any standard Spigot method on them directly in a step.
|
|
73
|
+
|
|
74
|
+
Examples:
|
|
75
|
+
- `{ "call": "player.sendMessage", "args": ["&aHi"] }`
|
|
76
|
+
- `{ "call": "player.getHealth", "args": [] }`
|
|
77
|
+
- `{ "call": "player.getWorld", "args": [] }`
|
|
78
|
+
- `{ "call": "victim.setFireTicks", "args": [100] }`
|
|
79
|
+
- `{ "call": "player.getLocation", "args": [] }` (pass the result into another call)
|
|
80
|
+
|
|
81
|
+
These Spigot methods are not listed by `search_methods`, because the ItemsCore API (`core`, `particles`, `values`, `api`) is only one part of what you can call. For the full set of methods on `player`, `event`, entities, blocks, worlds, and locations, use the Spigot API docs at https://hub.spigotmc.org/javadocs/spigot/ and match the server's Minecraft version. Prefer a `core` method when one exists (for example `core.heal`, `core.teleport`, `core.giveEffect`), and use raw Spigot calls for anything `core` does not cover.
|
|
82
|
+
|
|
83
|
+
## Step 3: Validate
|
|
84
|
+
|
|
85
|
+
Run `validate_item` on your JSON. Fix every entry under `errors` before continuing. `warnings` are usually fine (for example a Bukkit call the API cannot introspect), but read them.
|
|
86
|
+
|
|
87
|
+
## Step 4: Hand it to the user
|
|
88
|
+
|
|
89
|
+
1. Save the JSON as `<name>.item` (or `<name>.json`).
|
|
90
|
+
2. Tell the user to put the file in `plugins/ItemsCore/imports/` on their server.
|
|
91
|
+
3. Tell them to run `/ic import <name>` in-game.
|
|
92
|
+
|
|
93
|
+
On success the plugin replies: `Imported <name> (N action(s)). It is now live and GUI-editable.` The item is immediately usable and can be opened in the editor with `/itemeditor <name>`.
|
|
94
|
+
|
|
95
|
+
## Editing an existing item
|
|
96
|
+
|
|
97
|
+
1. User runs `/ic export <name>` in-game. The plugin writes `plugins/ItemsCore/exports/<name>.json`.
|
|
98
|
+
2. User shares that file with you. Edit the clean JSON.
|
|
99
|
+
3. Validate, then re-import the same way. Importing by the same `name` updates the item and preserves its stats, recipe, and attributes.
|
|
100
|
+
|
|
101
|
+
If the item is a **legacy code-only item** (made before this format, so the editor cannot open it), have the user run `/ic adopt <name>` first. That converts its code into GUI actions; then export, edit, and re-import as above.
|
|
102
|
+
|
|
103
|
+
## Command cheat sheet
|
|
104
|
+
|
|
105
|
+
| Command | What it does |
|
|
106
|
+
|---|---|
|
|
107
|
+
| `/ic import <file>` | Import a clean JSON from `plugins/ItemsCore/imports/` (live + GUI-editable) |
|
|
108
|
+
| `/ic export <item>` | Write an existing item to `plugins/ItemsCore/exports/<item>.json` |
|
|
109
|
+
| `/ic adopt <item>` | Make a legacy code-only item GUI-editable |
|
|
110
|
+
| `/ic reload [item]` | Reload all items, or one, and report success or errors |
|
|
111
|
+
| `/ic exportapi` | Regenerate `plugins/ItemsCore/itemscore-api.json` (the API manifest) |
|
|
112
|
+
| `/itemeditor <item>` | Open the visual editor for an item |
|
|
113
|
+
|
|
114
|
+
## When in doubt
|
|
115
|
+
|
|
116
|
+
- Ask the user what the item should look like (material, name) and what it should do, and on which interaction.
|
|
117
|
+
- Keep effects simple unless asked: a message, a heal, a teleport, a particle burst, lightning, potion effects.
|
|
118
|
+
- Always validate before delivering. Never claim an item works if you did not validate it.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "healers_touch",
|
|
3
|
+
"fancyName": "&dHealer's Touch",
|
|
4
|
+
"material": "BLAZE_ROD",
|
|
5
|
+
"needBlock": "BOTH",
|
|
6
|
+
"lore": ["&7Right-click to channel healing energy."],
|
|
7
|
+
"enchantments": [{ "name": "LUCK", "level": 1 }],
|
|
8
|
+
"flags": ["HIDE_ENCHANTS"],
|
|
9
|
+
"talisman": false,
|
|
10
|
+
"actions": [
|
|
11
|
+
{
|
|
12
|
+
"trigger": "rightAction",
|
|
13
|
+
"needBlock": "BOTH",
|
|
14
|
+
"steps": [
|
|
15
|
+
{ "call": "core.sendColorMessage", "args": [ { "var": "player" }, "&aYou channel healing energy!" ], "operatorToNext": "END" },
|
|
16
|
+
{ "call": "core.heal", "args": [ { "var": "player" }, 6 ], "operatorToNext": "END" },
|
|
17
|
+
{ "call": "core.giveEffect", "args": [ { "var": "player" }, "REGENERATION", 100, 1, false, true ], "operatorToNext": "END" },
|
|
18
|
+
{ "call": "core.playSound", "args": [ { "var": "player" }, { "call": "player.getLocation", "args": [] }, { "call": "core.getSound", "args": ["ENTITY_PLAYER_LEVELUP"] }, 1, 1 ], "operatorToNext": "END" }
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"events": []
|
|
23
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "storm_blade",
|
|
3
|
+
"fancyName": "&bStorm Blade",
|
|
4
|
+
"material": "DIAMOND_SWORD",
|
|
5
|
+
"needBlock": "BOTH",
|
|
6
|
+
"lore": ["&7Left-click: 25% chance to call lightning."],
|
|
7
|
+
"enchantments": [{ "name": "DAMAGE_ALL", "level": 4 }],
|
|
8
|
+
"talisman": false,
|
|
9
|
+
"actions": [
|
|
10
|
+
{
|
|
11
|
+
"trigger": "leftAction",
|
|
12
|
+
"needBlock": "BOTH",
|
|
13
|
+
"steps": [
|
|
14
|
+
{ "call": "core.doIf", "args": [ { "call": "core.chanceOf", "args": [25] }, "core.summonLightningByLocation(player.getLocation())" ], "operatorToNext": "END" }
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"events": []
|
|
19
|
+
}
|