Skip to content

Commit 9bdce11

Browse files
committed
feat: add support for Legacy mode item definitions and components
1 parent 9c7dcb6 commit 9bdce11

30 files changed

Lines changed: 2351 additions & 423 deletions

File tree

.claude/settings.local.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@
2222
"Bash(jar -tf:*)",
2323
"Bash(javap:*)",
2424
"Bash(git log:*)",
25-
"Bash(git checkout tags/v1.20.0-0)"
25+
"Bash(git checkout tags/v1.20.0-0)",
26+
"Bash(tree:*)",
27+
"Bash(unzip:*)",
28+
"Read(//tmp/easecation_beh_temp/blocks/**)"
2629
],
2730
"deny": [],
2831
"ask": []

adapter-easecation/src/main/java/net/easecation/bridge/adapter/easecation/EasecationRegistry.java

Lines changed: 107 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import cn.nukkit.block.Blocks;
44
import cn.nukkit.entity.Entity;
55
import cn.nukkit.entity.EntityFullNames;
6+
import cn.nukkit.item.Items;
67
import cn.nukkit.nbt.tag.CompoundTag;
78
import cn.nukkit.nbt.tag.ListTag;
89
import cn.nukkit.plugin.PluginBase;
@@ -12,6 +13,7 @@
1213
import net.easecation.bridge.adapter.easecation.block.BlockStatesNBT;
1314
import net.easecation.bridge.adapter.easecation.block.BlockTraitsNBT;
1415
import net.easecation.bridge.adapter.easecation.entity.EntityDataDriven;
16+
import net.easecation.bridge.adapter.easecation.item.ItemDataDriven;
1517
import net.easecation.bridge.core.*;
1618
import org.itxtech.synapseapi.multiprotocol.utils.AvailableEntityIdentifiersPalette;
1719
import org.itxtech.synapseapi.multiprotocol.utils.CreativeItemsPalette;
@@ -62,12 +64,19 @@ public void registerItems(List<ItemDef> items) {
6264

6365
// 4. Register custom item with Nukkit
6466
log.debug("[EaseCation] - Registering custom item to Nukkit...");
65-
cn.nukkit.item.Items.registerCustomItem(
66-
itemId,
67-
customItemId,
68-
net.easecation.bridge.adapter.easecation.item.ItemDataDriven.class,
69-
(meta, count) -> new net.easecation.bridge.adapter.easecation.item.ItemDataDriven(customItemId, meta, count),
70-
nbt
67+
if (itemDef.isLegacy()) {
68+
// Legacy item
69+
log.debug("[EaseCation] - Using Legacy item");
70+
} else {
71+
// Component-based item - use 5-parameter version (with NBT)
72+
log.debug("[EaseCation] - Using Component-based item");
73+
}
74+
Items.registerCustomItem(
75+
itemId,
76+
customItemId,
77+
ItemDataDriven.class,
78+
(meta, count) -> new ItemDataDriven(customItemId, meta, count),
79+
nbt
7180
);
7281

7382
// 5. Register to creative mode inventory
@@ -93,7 +102,7 @@ public void registerItems(List<ItemDef> items) {
93102
}
94103

95104
// Log item definition details for debugging
96-
if (itemDef.components() != null) {
105+
if (itemDef.componentComponents() != null) {
97106
log.error("[EaseCation] Item has components: YES");
98107
} else {
99108
log.error("[EaseCation] Item has components: NO");
@@ -462,69 +471,112 @@ private CompoundTag buildBlockNBT(BlockDef blockDef, int runtimeId) {
462471
* Build NBT data for item registration.
463472
* The NBT contains item definition including properties and components.
464473
*
474+
* <p>For Legacy mode (v1.10):
475+
* <ul>
476+
* <li>Simple items: Returns null (no NBT needed)</li>
477+
* <li>Food items: Returns NBT with only minecraft:food and minecraft:use_duration</li>
478+
* </ul>
479+
*
480+
* <p>For Component-based mode (v1.19+):
481+
* <ul>
482+
* <li>Returns full NBT with item_properties and all components</li>
483+
* </ul>
484+
*
465485
* @param itemDef The item definition
466-
* @return CompoundTag containing the item NBT data
486+
* @return CompoundTag containing the item NBT data, or null for Legacy simple items
467487
*/
468488
private CompoundTag buildItemNBT(ItemDef itemDef) {
469489
try {
470490
log.trace("[EaseCation] Building NBT for: " + itemDef.id());
471491

472-
// Basic identifier
473-
CompoundTag nbt = new CompoundTag();
474-
nbt.putString("identifier", itemDef.id());
475-
476-
// Menu category (for creative inventory) - same as block implementation
477-
if (itemDef.menuCategory() != null) {
478-
log.trace("[EaseCation] - Adding menu category");
479-
CompoundTag menuCategory = new CompoundTag();
480-
var mc = itemDef.menuCategory();
481-
482-
menuCategory.putString("category", mc.category() != null ? mc.category() : "items");
483-
if (mc.group() != null && !mc.group().isEmpty()) {
484-
menuCategory.putString("group", mc.group());
485-
}
486-
if (mc.isHiddenInCommands() != null) {
487-
menuCategory.putBoolean("is_hidden_in_commands", mc.isHiddenInCommands());
488-
}
489-
490-
nbt.putCompound("menu_category", menuCategory);
492+
// Branch on registration mode
493+
if (itemDef.isLegacy()) {
494+
return buildLegacyItemNBT(itemDef);
491495
} else {
492-
// Default: add default menu_category (category="items")
493-
log.trace("[EaseCation] - Adding default menu category");
494-
nbt.putCompound("menu_category", new CompoundTag()
495-
.putString("category", "items")
496-
.putString("group", "")
497-
.putBoolean("is_hidden_in_commands", false)
498-
);
499-
}
500-
501-
// Add components NBT
502-
if (itemDef.components() != null) {
503-
log.trace("[EaseCation] - Adding components");
504-
try {
505-
CompoundTag componentsNBT = net.easecation.bridge.adapter.easecation.item.ItemComponentsNBT.toNBT(
506-
itemDef, // Pass the full ItemDef (includes menu_category)
507-
itemDef.id()
508-
);
509-
// Merge all component tags into the main NBT
510-
for (String key : componentsNBT.getTags().keySet()) {
511-
nbt.put(key, componentsNBT.get(key));
512-
}
513-
} catch (Exception e) {
514-
log.warning("[EaseCation] - Failed to convert components: " + e.getMessage());
515-
throw new RuntimeException("Failed to convert item components for " + itemDef.id(), e);
516-
}
496+
return buildComponentBasedItemNBT(itemDef);
517497
}
518498

519-
log.trace("[EaseCation] NBT build completed for: " + itemDef.id());
520-
return nbt;
521-
522499
} catch (Exception e) {
523500
log.error("[EaseCation] Failed to build NBT for item: " + itemDef.id());
524501
log.error("[EaseCation] Error: " + e.getClass().getSimpleName() + " - " + e.getMessage());
525502
throw new RuntimeException("Failed to build NBT for item " + itemDef.id(), e);
526503
}
527504
}
505+
506+
/**
507+
* Build NBT for Legacy mode (v1.10) items.
508+
* Returns null for simple items, food NBT for food items.
509+
*/
510+
private CompoundTag buildLegacyItemNBT(ItemDef itemDef) {
511+
log.trace("[EaseCation] - Legacy mode item");
512+
CompoundTag nbt = net.easecation.bridge.adapter.easecation.item.ItemComponentsNBT_Legacy.toNBT(itemDef);
513+
514+
if (nbt == null) {
515+
log.trace("[EaseCation] - No NBT (legacy simple item)");
516+
} else {
517+
log.trace("[EaseCation] - Legacy food item NBT built");
518+
}
519+
520+
return nbt;
521+
}
522+
523+
/**
524+
* Build NBT for Component-based mode (v1.19+) items.
525+
* Returns full NBT with item_properties and all components.
526+
*/
527+
private CompoundTag buildComponentBasedItemNBT(ItemDef itemDef) {
528+
log.trace("[EaseCation] - Component-based mode item");
529+
530+
// Basic identifier
531+
CompoundTag nbt = new CompoundTag();
532+
nbt.putString("identifier", itemDef.id());
533+
534+
// Menu category (for creative inventory) - same as block implementation
535+
if (itemDef.menuCategory() != null) {
536+
log.trace("[EaseCation] - Adding menu category");
537+
CompoundTag menuCategory = new CompoundTag();
538+
var mc = itemDef.menuCategory();
539+
540+
menuCategory.putString("category", mc.category() != null ? mc.category() : "items");
541+
if (mc.group() != null && !mc.group().isEmpty()) {
542+
menuCategory.putString("group", mc.group());
543+
}
544+
if (mc.isHiddenInCommands() != null) {
545+
menuCategory.putBoolean("is_hidden_in_commands", mc.isHiddenInCommands());
546+
}
547+
548+
nbt.putCompound("menu_category", menuCategory);
549+
} else {
550+
// Default: add default menu_category (category="items")
551+
log.trace("[EaseCation] - Adding default menu category");
552+
nbt.putCompound("menu_category", new CompoundTag()
553+
.putString("category", "items")
554+
.putString("group", "")
555+
.putBoolean("is_hidden_in_commands", false)
556+
);
557+
}
558+
559+
// Add components NBT
560+
if (itemDef.componentComponents() != null) {
561+
log.trace("[EaseCation] - Adding components");
562+
try {
563+
CompoundTag componentsNBT = net.easecation.bridge.adapter.easecation.item.ItemComponentsNBT.toNBT(
564+
itemDef, // Pass the full ItemDef (includes menu_category)
565+
itemDef.id()
566+
);
567+
// Merge all component tags into the main NBT
568+
for (String key : componentsNBT.getTags().keySet()) {
569+
nbt.put(key, componentsNBT.get(key));
570+
}
571+
} catch (Exception e) {
572+
log.warning("[EaseCation] - Failed to convert components: " + e.getMessage());
573+
throw new RuntimeException("Failed to convert item components for " + itemDef.id(), e);
574+
}
575+
}
576+
577+
log.trace("[EaseCation] NBT build completed for: " + itemDef.id());
578+
return nbt;
579+
}
528580
}
529581

530582

adapter-easecation/src/main/java/net/easecation/bridge/adapter/easecation/ResourcePackListener.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public void onRequestResourcePack(PlayerRequestResourcePackEvent event) {
8383

8484
} catch (Exception e) {
8585
log.error("[ResourcePack] ✗ Failed to push pack: " + pack.url());
86-
log.error("[ResourcePack] Error: " + e.getClass().getSimpleName() + " - " + e.getMessage());
86+
log.error("[ResourcePack] Error: " + e.getClass().getSimpleName() + " - " + e.getMessage(), e);
8787
failureCount++;
8888
}
8989
}

adapter-easecation/src/main/java/net/easecation/bridge/adapter/easecation/item/ItemComponentsNBT.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public class ItemComponentsNBT {
2222
* @return CompoundTag containing all component NBT data
2323
*/
2424
public static CompoundTag toNBT(ItemDef itemDef, String identifier) {
25-
var components = itemDef.components();
25+
var components = itemDef.componentComponents();
2626
if (components == null) {
2727
return new CompoundTag();
2828
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package net.easecation.bridge.adapter.easecation.item;
2+
3+
import cn.nukkit.nbt.tag.CompoundTag;
4+
import cn.nukkit.nbt.tag.ListTag;
5+
import net.easecation.bridge.core.ItemDef;
6+
7+
/**
8+
* Utility class for converting Legacy mode (v1.10) item components to NBT format.
9+
*
10+
* <p>Legacy mode registration differs from Component-based mode:
11+
* <ul>
12+
* <li>Legacy simple item: No NBT (returns null)</li>
13+
* <li>Legacy food item: Only minecraft:food and minecraft:use_duration components</li>
14+
* <li>Component-based item: Full item_properties with all components</li>
15+
* </ul>
16+
*/
17+
public class ItemComponentsNBT_Legacy {
18+
19+
/**
20+
* Build NBT for Legacy mode (v1.10) items.
21+
*
22+
* @param itemDef The ItemDef containing Legacy components
23+
* @return CompoundTag for food items, null for simple items
24+
*/
25+
public static CompoundTag toNBT(ItemDef itemDef) {
26+
var components = itemDef.legacyComponents();
27+
28+
if (components == null) {
29+
return null; // No NBT for minimal items
30+
}
31+
32+
// Check if this is a food item
33+
boolean hasFood = components.getFood() != null;
34+
35+
if (!hasFood) {
36+
// Legacy simple item - no NBT needed
37+
return null;
38+
}
39+
40+
// Legacy food item - build NBT with food component
41+
CompoundTag nbt = new CompoundTag();
42+
43+
// minecraft:food component
44+
var food = components.getFood();
45+
CompoundTag foodTag = new CompoundTag();
46+
47+
if (food.canAlwaysEat() != null) {
48+
foodTag.putBoolean("can_always_eat", food.canAlwaysEat());
49+
}
50+
51+
if (food.nutrition() != null) {
52+
foodTag.putInt("nutrition", food.nutrition());
53+
}
54+
55+
if (food.saturationModifier() != null) {
56+
// Convert string to float
57+
float saturation = parseSaturationModifier(food.saturationModifier());
58+
foodTag.putFloat("saturation_modifier", saturation);
59+
}
60+
61+
if (food.usingConvertsTo() != null) {
62+
foodTag.putString("using_converts_to", food.usingConvertsTo());
63+
}
64+
65+
// cooldown_type and cooldown_time
66+
if (food.cooldownType() != null) {
67+
foodTag.putString("cooldown_type", food.cooldownType());
68+
}
69+
70+
if (food.cooldownTime() != null) {
71+
foodTag.putInt("cooldown_time", food.cooldownTime());
72+
}
73+
74+
// on_use_action (String type)
75+
if (food.onUseAction() != null) {
76+
foodTag.putString("on_use_action", food.onUseAction());
77+
}
78+
79+
// on_use_range (List<Integer> type)
80+
if (food.onUseRange() != null && food.onUseRange().size() == 3) {
81+
ListTag<cn.nukkit.nbt.tag.FloatTag> rangeList = new ListTag<>();
82+
rangeList.add(new cn.nukkit.nbt.tag.FloatTag("", food.onUseRange().get(0).floatValue()));
83+
rangeList.add(new cn.nukkit.nbt.tag.FloatTag("", food.onUseRange().get(1).floatValue()));
84+
rangeList.add(new cn.nukkit.nbt.tag.FloatTag("", food.onUseRange().get(2).floatValue()));
85+
foodTag.putList("on_use_range", rangeList);
86+
}
87+
88+
nbt.putCompound("minecraft:food", foodTag);
89+
90+
// minecraft:use_duration component (if present)
91+
if (components.getUseDuration() != null) {
92+
nbt.putInt("minecraft:use_duration", components.getUseDuration());
93+
}
94+
95+
return nbt;
96+
}
97+
98+
/**
99+
* Parse saturation modifier string to float value.
100+
* Maps Bedrock's named values to numeric coefficients.
101+
*/
102+
private static float parseSaturationModifier(String modifier) {
103+
if (modifier == null) return 0.6f;
104+
return switch (modifier.toLowerCase()) {
105+
case "poor" -> 0.1f;
106+
case "low" -> 0.3f;
107+
case "normal" -> 0.6f;
108+
case "good" -> 0.8f;
109+
case "max" -> 1.0f;
110+
case "supernatural" -> 1.2f;
111+
default -> {
112+
// Try to parse as float
113+
try {
114+
yield Float.parseFloat(modifier);
115+
} catch (NumberFormatException e) {
116+
yield 0.6f;
117+
}
118+
}
119+
};
120+
}
121+
}

0 commit comments

Comments
 (0)