Custom items, weapons, armor, blocks and furniture from plain YAML — with the resource pack built, hosted and sent automatically.
No manual resource pack work. No texture editor required. Just YAML.
Server owners define custom content as YAML files. On every reload, Andreaitemmaker:
- Parses all content files — bad entries are reported and skipped, never break the rest.
- Generates textures (procedural PNGs — no image files needed), model JSONs, item definitions, block states and armor assets.
- Wires everything for the running server version (1.20.5 → 26.2+, pack formats 32 → 88).
- Zips it into
pack.zip+ an unzippedpack/folder, computes the SHA-1. - Distributes it — built-in HTTP server, fixed public URL, or upload to a CDN.
- Sends it to joining players with hash verification and retry messaging.
No resource pack experience required. The plugin does the whole pipeline for you.
Real in-game captures from testing the plugin:
![]() |
![]() |
![]() |
![]() |
# Build (Maven wrapper downloads Maven itself — no local install needed)
./mvnw package # Windows: mvnw.cmd package- Drop
target/Andreaitemmaker-1.0.0.jarinto your server'splugins/folder. - Restart. The plugin creates
plugins/Andreaitemmaker/withconfig.ymland example content. - Give yourself something:
/aitem give example_sword - Players get the pack automatically on join (
pack.send-on-joinis on by default).
Port 8163 is opened by default for the built-in pack server. Firewalled? Host the generated
pack/folder anywhere and setpack.public-url— see Hosting the pack.
Full documentation lives in docs/ — getting started, configuration,
items/weapons/armor/food, blocks, furniture, mechanics, resource
packs, commands & permissions, the developer API, compatibility,
security and an FAQ. A friendly version is also maintained on the
project wiki.
| Doc | Covers |
|---|---|
| getting-started.md | install, first reload, folder layout |
| items.md | items, weapons, armor, food YAML |
| armor.md | 3D helmets + 2D worn layers |
| blocks.md | custom blocks, persistent identity, explosions, pistons |
| furniture.md | furniture lifecycle & protection |
| mechanics.md | built-in + custom mechanics |
| resource-packs.md | hosting, delivery, HTTP server |
| commands.md | every command & permission |
| api.md | developer API with examples |
| compatibility.md | version support table |
| security.md | path traversal, HTTP safety, secrets |
| manual-testing.md | the QA checklist |
| faq.md | common questions |
| Requirement | Minimum |
|---|---|
| Java | 17+ (servers on 1.20.5+ already run Java 21) |
| Server | Spigot or Paper 1.20.5 or newer (tested through with 1.21.5) |
Content lives in plugins/Andreaitemmaker/ — one YAML file per item, block or furniture piece:
items/ → type: ITEM | WEAPON | ARMOR | FOOD
blocks/ → type: BLOCK (base-block required)
furniture/ → type: FURNITURE
assets/textures/ → your own .png files (optional, .png.mcmeta animations supported)
assets/models/ → your own model .json files (optional, e.g. Blockbench exports)
# items/my_sword.yml
type: WEAPON
material: diamond_sword
display-name: "&bStorm Blade"
attributes:
attack_damage: 9.0
texture:
pattern: gradient # solid | gradient | diagonal | checker
color: "#3f9bff"
color2: "#0f2a6b"
mechanics:
lightning:
damage: 4.0# furniture/my_lamp.yml
material: stick
small: true
consumable: trueRight-click places an invisible armor stand rendering the item model; right-click again to pick it up.
# blocks/my_block.yml
material: stick
base-block: white_wool # vanilla block used as the hitbox (one base = one custom block)
drops-item: trueDrop a Blockbench export into assets/models/ and reference it:
model: "assets/models/my_statue.json"Drag-and-drop: Blockbench Bedrock Edition exports work as-is. The plugin detects them
(format_version, groups, numeric texture keys) and converts them to the Java format
automatically — ordering inverted from/to boxes, renaming 0/1 → layer0/layer1,
rounding rotation angles to 22.5° steps and stripping Bedrock-only fields. Java exports pass
through untouched.
Any PNG in assets/textures/ is copied into the pack (with its .mcmeta if it's animated).
The model's texture path should point at <namespace>:item/<name> (default namespace: itemmaker).
On 1.21.2+ there are two native worn-rendering paths, chosen automatically per item.
3D helmets. A HEAD-slot armor piece whose model: file exists renders as its actual
3D model on the player's head — the same vanilla mechanism the carved pumpkin uses: the
client draws the equipped item's own model in head display context. The visual and the
gameplay state are literally the same stack, so they can't desync: what you equip is exactly
what renders, in survival, for every player. No per-tick work, no invisible entities, no
ModelEngine.
# items/my_helmet.yml
type: ARMOR
material: diamond_helmet
model: "assets/models/my_helmet.json" # rendered 3D on the head when equipped- Build the model in 16-unit "head space" — a 16×16×16 box from
0,0,0to16,16,16covers the head exactly, like the pumpkin — or add adisplay.headentry to fine-tune the pose. armor-texture:is ignored for a 3D helmet: the model is the worn look.
2D layers (chest / legs / feet, and flat helmets). The client has no native 3D worn path for body slots, so those pieces render from a flat 2D layer, never from the 3D model. Set the worn texture explicitly with a dedicated 64×32 humanoid armor texture:
# items/my_chestplate.yml
type: ARMOR
material: diamond_chestplate
armor-texture: "assets/textures/my_layer_1.png" # 64x32 look when worntexture:is the item icon;armor-texture:is the worn layer — two different images.- Without
armor-texture:, the plugin auto-detects by convention:assets/textures/<id>_layer_1.png/_layer_2.png,<id>_armor_layer_1/2.png, or a shared set file likeeternal_armor_layer_1.png/_2.pngfor ids likeeternal_helmet(layer 1 = helmet/ chestplate/boots, layer 2 = leggings). - A square UV atlas (16/32/64) is never squashed into the worn layer — only flat 64×32 textures are used, so the worn piece can't look like garbled atlas regions.
Attach behaviors to any item — all config-driven:
| Mechanic | When | Options |
|---|---|---|
heal |
right-click | amount, cooldown |
feed |
right-click | amount, saturation, cooldown |
effect |
right-click | effects: [{type, duration, amplifier, ambient, particles}], cooldown |
launch |
right-click | power, cooldown |
sound |
right-click | sound, volume, pitch |
lightning |
hit entity | damage (0 = visual only) |
ignite |
hit entity | seconds |
knockback |
hit entity | power |
armor-effects |
while worn (armor) | effects: same format as effect |
Unknown mechanics are reported at load so typos are caught immediately. Other plugins can register their own mechanics through the API.
/aitem give <id> [amount] [player]
/aitem list [items|weapons|armor|food|blocks|furniture]
/aitem info <id>
/aitem pack send [player|all] | url | regenerate
/aitem reload
Aliases: andreaitemmaker, aitem, itemmaker.
| Permission | Default | Purpose |
|---|---|---|
andreaitemmaker.admin |
op | All commands |
andreaitemmaker.give |
op | Give items |
andreaitemmaker.bypass |
false | Never receive the pack prompt |
Every generation writes both pack.zip and an unzipped pack/ folder, so you always
have a way to distribute the pack — even when a firewall blocks the built-in server.
| Strategy | Setup |
|---|---|
| Built-in server (default) | Port 8163 must be reachable from players' clients |
| Fixed URL | Host pack/ or pack.zip anywhere → set pack.public-url |
| Upload to a file host | pack.upload.enabled: true + URL/method/headers → players download from your CDN |
/aitem pack url shows the current download URL and folder path.
Other plugins can use Andreaitemmaker through AndreaitemmakerAPI:
import com.andreaitemmaker.api.AndreaitemmakerAPI;
import com.andreaitemmaker.api.CustomItem;
import org.bukkit.inventory.ItemStack;
AndreaitemmakerAPI api = AndreaitemmakerAPI.get();
CustomItem sword = api.getCustomItem("my_sword"); // by id
CustomItem held = api.getCustomItem(playerStack); // by ItemStack (PDC tag)
ItemStack stack = api.createItemStack("my_sword", 1); // build a stack
api.getCustomBlocks(); // all blocks
api.getCustomFurnitures(); // all furniture
api.getResourcePack(); // generate(), getUrl(), getSha1(), sendTo(player), ...
api.getMechanicRegistry(); // register(new MyMechanic()); — add your own mechanics
api.reload();Cancellable events: CustomItemUseEvent, CustomItemHitEvent, CustomItemConsumeEvent,
CustomBlockPlaceEvent, CustomBlockBreakEvent, CustomFurniturePlaceEvent,
CustomFurnitureBreakEvent.
With PlaceholderAPI installed, the
plugin registers placeholders under the andreaitemmaker identifier — without PAPI the
plugin works exactly the same, the placeholders are simply unavailable.
| Placeholder | Returns |
|---|---|
%andreaitemmaker_has_item_<id>% |
yes/no — the player has the item in their inventory |
%andreaitemmaker_amount_<id>% |
total count of that item in the player's inventory |
%andreaitemmaker_holding_<id>% |
yes/no — the item is in the player's main hand |
%andreaitemmaker_cooldown_<id>_<mechanic>% |
remaining cooldown seconds for that mechanic (0 = ready) |
%andreaitemmaker_content_count% |
total content entries |
%andreaitemmaker_item_count% / weapon_count / armor_count / food_count / block_count / furniture_count |
per-type counts |
Example line for a scoreboard/TAB plugin:
Cooldown: %andreaitemmaker_cooldown_storm_blade_lightning%s
80 unit tests cover PNG encode/decode round-trips, JSON validity, texture generation, version → pack-format mapping, full modern/legacy pack generation, plus:
- path-traversal protection (absolute paths,
../, Windows drives, backslashes, symlink escapes) - registry consistency (replacement removes stale indexes, base-block conflicts, immutability)
- the embedded HTTP server over a real socket (headers, ETag/304, path security, method rejection)
- async generation coordinator (overlapping requests coalesce to the latest snapshot)
- imported model/texture collisions and invalid-model rejection
| Server | Pack format | Item wiring |
|---|---|---|
| 1.20.5 – 1.20.6 | 32 | CustomModelData + patched vanilla models |
| 1.21 – 1.21.1 | 34 | CustomModelData + patched vanilla models |
| 1.21.2 – 1.21.3 | 42 | item_model component + item definitions |
| 1.21.4 – 1.21.8 | 46 / 55 / 63 / 64 | item_model + equipment assets for armor |
| 1.21.9 – 1.21.10 | 69 | min/max format in pack.mcmeta |
| 1.21.11 | 75 | equipment textures under textures/entity/equipment/ |
| 26.1 / 26.2+ | 84 / 88 | latest |
pack.format in config.yml overrides detection if you ever need to pin it.
- Custom blocks have a persistent identity. Placing a custom block stores its id in the chunk's persistent data, keyed by block coordinates — not by material. A normal vanilla STONE or WOOL block is never treated as a custom block, and the identity survives server restarts, plugin reloads and chunk unload/reload in every world. The tag is removed when the block is broken or replaced.
- Pack generation never blocks the server. On reload the plugin snapshots the content and builds textures, models, the zip and the SHA-1 on a background thread; the finished pack is swapped in atomically, so the previously generated pack keeps being served until the new one is fully ready. Overlapping reloads/generations coalesce into a single run.
- Reload is transactional. Config and content are loaded and validated first; the old state stays active until the new one is complete, so a broken config never leaves the plugin half-loaded.
- Config asset paths are validated.
model:andtexture:entries must be relative paths insideassets/— traversal attempts (../, absolute paths, Windows drives, symlink escapes) are rejected at load time with a clear error. - The built-in pack server is minimal and safe. It serves only
/pack.zip(never maps request paths to files), streams the pack in chunks, uses a bounded thread pool, and answers withContent-Length,Content-Type,Cache-ControlandETag/304 so clients don't re-download an unchanged pack. - Armor mechanics only scan players who actually wear custom armor. The tracked set is kept fresh by inventory/interact/death events plus a slow reconciliation, instead of checking every player's four armor slots on every tick.
- Custom blocks: one base block per custom block; the base block's vanilla appearance is replaced for everyone with the pack.
- Worn armor renders from the equipment asset on 1.21.2+ (the
equippablecomponent withasset_id/model wired by Paper). On 1.20.5 – 1.21.1 the piece is wearable but shows the vanilla worn texture. Worn rendering is always the 2D layer texture — the client renderer has no 3D-on-body path, so a 3D model shows in hand/inventory/on the ground while the worn piece uses the layer texture. Pointarmor-texture:at a 64×32 armor texture to control the worn look (see above); without it the plugin auto-detects layer files or uses the model's own texture when it is already a flat 64×32 layer. - Items/blocks placed through the API bypass protection plugins (WorldGuard, etc.) — add protections in your own listeners if needed.
- Async generation:
/aitem reloadand/aitem pack regeneratereport that generation started — the pack swap happens in the background and players receive the new pack automatically once it's ready. - External world editors: replacing a custom block with a different material is detected and the stale tag cleaned up; replacing it with the same base material is indistinguishable and breaking it will drop the custom item.
- Armor tracking covers all vanilla ways to change armor (equip clicks, right-click equip, death/respawn, join/quit) plus a 30-second safety recheck; direct
setItemcalls from other plugins are picked up within that window.
All options live in config.yml with comments. Highlights:
Scan report · 2026-09-17
- ✓ Prohibited terms or links
- ✓ Repository eligibility
- ✓ slopscore.md paperwork
- ✓ Content policy
- ✓ Risk review — +25 binaries at repo root (mvnw.cmd)




0 comments
log in to comment.