Skip to content

Commit 45e0be6

Browse files
committed
feat: implement dynamic class generation for custom blocks, items, and entities using ByteBuddy
1 parent 0b2141c commit 45e0be6

17 files changed

Lines changed: 1536 additions & 8 deletions

adapter-pm1e/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,7 @@ dependencies {
2020
} else {
2121
logger.lifecycle("未提供 PM1E patched.jar(可通过 -PPM1E_PATCHED_JAR 或放置到 adapter-pm1e/lib/patched.jar)")
2222
}
23+
24+
// ByteBuddy for runtime class generation (支持无限数量的自定义物品和实体)
25+
implementation("net.bytebuddy:byte-buddy:1.14.10")
2326
}
Lines changed: 248 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,265 @@
11
package net.easecation.bridge.adapter.pm1e;
22

3+
import cn.nukkit.block.custom.CustomBlockManager;
4+
import cn.nukkit.entity.custom.EntityDefinition;
5+
import cn.nukkit.entity.custom.EntityManager;
6+
import cn.nukkit.item.custom.CustomItemManager;
7+
import cn.nukkit.item.custom.ItemDefinition;
8+
import net.easecation.bridge.adapter.pm1e.block.BlockDataDriven;
9+
import net.easecation.bridge.adapter.pm1e.block.BlockIdAllocator;
10+
import net.easecation.bridge.adapter.pm1e.block.DynamicBlockClassGenerator;
11+
import net.easecation.bridge.adapter.pm1e.entity.DynamicEntityClassGenerator;
12+
import net.easecation.bridge.adapter.pm1e.entity.EntityDataDriven;
13+
import net.easecation.bridge.adapter.pm1e.entity.EntityDefinitionBuilder;
14+
import net.easecation.bridge.adapter.pm1e.item.DynamicItemClassGenerator;
15+
import net.easecation.bridge.adapter.pm1e.item.ItemDataDriven;
16+
import net.easecation.bridge.adapter.pm1e.item.ItemDefinitionBuilder;
17+
import net.easecation.bridge.adapter.pm1e.item.ItemIdAllocator;
318
import net.easecation.bridge.core.*;
419

520
import java.util.List;
621

22+
/**
23+
* PM1E Nukkit adapter for registering custom items, blocks, and entities.
24+
* Uses PM1E's CustomItemManager, CustomBlockManager, and EntityManager APIs.
25+
*/
726
public class Pm1eRegistry implements AddonRegistry {
827
private final BridgeLogger log;
928
private static final Capabilities CAPS = new Capabilities(true);
1029

11-
public Pm1eRegistry(BridgeLogger log) { this.log = log; }
30+
private final ItemIdAllocator itemIdAllocator = new ItemIdAllocator();
31+
private final BlockIdAllocator blockIdAllocator = new BlockIdAllocator();
1232

13-
@Override public void registerItems(List<ItemDef> items) { log.info("[PM1E] registerItems size=" + items.size()); }
14-
@Override public void registerBlocks(List<BlockDef> blocks) { log.info("[PM1E] registerBlocks size=" + blocks.size()); }
15-
@Override public void registerEntities(List<EntityDef> entities) { log.info("[PM1E] registerEntities size=" + entities.size()); }
16-
@Override public void registerRecipes(List<RecipeDef> recipes) { log.info("[PM1E] registerRecipes size=" + recipes.size()); }
17-
@Override public Capabilities capabilities() { return CAPS; }
33+
public Pm1eRegistry(BridgeLogger log) {
34+
this.log = log;
35+
}
36+
37+
@Override
38+
public void registerItems(List<ItemDef> items) {
39+
if (items.isEmpty()) {
40+
log.info("[PM1E] No items to register");
41+
return;
42+
}
43+
44+
log.debug("[PM1E] Starting item registration: " + items.size() + " items");
45+
46+
int successCount = 0;
47+
int failureCount = 0;
48+
49+
for (ItemDef itemDef : items) {
50+
String itemId = itemDef.id();
51+
try {
52+
log.debug("[PM1E] Processing item: " + itemId);
53+
54+
int nukkitId = itemIdAllocator.allocate(itemId);
55+
log.debug("[PM1E] - Allocated ID: " + nukkitId);
56+
57+
ItemDataDriven.registerItemDef(itemId, itemDef);
58+
59+
String textureName = extractTextureName(itemDef);
60+
if (textureName != null) {
61+
log.debug("[PM1E] - Texture name: " + textureName);
62+
}
63+
64+
Class<? extends ItemDataDriven> dynamicClass = DynamicItemClassGenerator.generateItemClass(itemDef, nukkitId);
65+
log.debug("[PM1E] - Generated class: " + dynamicClass.getSimpleName());
66+
67+
ItemDefinition definition = ItemDefinitionBuilder.build(dynamicClass, itemDef, nukkitId, textureName);
68+
ItemDataDriven.registerDefinition(itemId, definition);
69+
70+
CustomItemManager.get().registerDefinition(definition);
71+
72+
log.info("[PM1E] ✓ Successfully registered item: " + itemId + " (ID: " + nukkitId + ")");
73+
successCount++;
74+
75+
} catch (Exception e) {
76+
failureCount++;
77+
log.error("[PM1E] ✗ Failed to register item: " + itemId);
78+
log.error("[PM1E] Error: " + e.getMessage());
79+
if (log instanceof NukkitLoggerAdapter) {
80+
e.printStackTrace();
81+
}
82+
}
83+
}
84+
85+
log.info("[PM1E] Item registration completed - Success: " + successCount + ", Failed: " + failureCount);
86+
}
87+
88+
@Override
89+
public void registerBlocks(List<BlockDef> blocks) {
90+
if (blocks.isEmpty()) {
91+
log.info("[PM1E] No blocks to register");
92+
return;
93+
}
94+
95+
log.debug("[PM1E] Starting block registration: " + blocks.size() + " blocks");
96+
97+
int successCount = 0;
98+
int failureCount = 0;
99+
100+
for (BlockDef blockDef : blocks) {
101+
String blockId = blockDef.id();
102+
try {
103+
log.debug("[PM1E] Processing block: " + blockId);
104+
105+
int nukkitId = blockIdAllocator.allocate(blockId);
106+
log.debug("[PM1E] - Allocated ID: " + nukkitId);
107+
108+
BlockDataDriven.registerBlockDef(blockId, blockDef);
109+
110+
Class<? extends BlockDataDriven> dynamicClass = DynamicBlockClassGenerator.generateBlockClass(blockDef, nukkitId);
111+
log.debug("[PM1E] - Generated class: " + dynamicClass.getSimpleName());
112+
113+
CustomBlockManager.get().registerCustomBlock(blockId, nukkitId, () -> {
114+
try {
115+
return dynamicClass.getDeclaredConstructor().newInstance();
116+
} catch (Exception e) {
117+
throw new RuntimeException("Failed to create block instance: " + blockId, e);
118+
}
119+
});
120+
121+
log.info("[PM1E] ✓ Successfully registered block: " + blockId + " (ID: " + nukkitId + ")");
122+
successCount++;
123+
124+
} catch (Exception e) {
125+
failureCount++;
126+
log.error("[PM1E] ✗ Failed to register block: " + blockId);
127+
log.error("[PM1E] Error: " + e.getMessage());
128+
if (log instanceof NukkitLoggerAdapter) {
129+
e.printStackTrace();
130+
}
131+
}
132+
}
133+
134+
log.info("[PM1E] Block registration completed - Success: " + successCount + ", Failed: " + failureCount);
135+
}
136+
137+
@Override
138+
public void registerEntities(List<EntityDef> entities) {
139+
if (entities.isEmpty()) {
140+
log.info("[PM1E] No entities to register");
141+
return;
142+
}
143+
144+
log.debug("[PM1E] Starting entity registration: " + entities.size() + " entities");
145+
146+
int successCount = 0;
147+
int failureCount = 0;
148+
149+
for (EntityDef entityDef : entities) {
150+
String entityId = entityDef.id();
151+
try {
152+
log.debug("[PM1E] Processing entity: " + entityId);
153+
154+
EntityDataDriven.registerEntityDef(entityId, entityDef);
155+
156+
Class<? extends EntityDataDriven> dynamicClass = DynamicEntityClassGenerator.generateEntityClass(entityDef);
157+
log.debug("[PM1E] - Generated class: " + dynamicClass.getSimpleName());
158+
159+
EntityDefinition definition = EntityDefinitionBuilder.build(dynamicClass, entityDef);
160+
EntityDataDriven.registerDefinition(entityId, definition);
161+
162+
EntityManager.get().registerDefinition(definition);
163+
164+
log.info("[PM1E] ✓ Successfully registered entity: " + entityId);
165+
successCount++;
166+
167+
} catch (Exception e) {
168+
failureCount++;
169+
log.error("[PM1E] ✗ Failed to register entity: " + entityId);
170+
log.error("[PM1E] Error: " + e.getMessage());
171+
if (log instanceof NukkitLoggerAdapter) {
172+
e.printStackTrace();
173+
}
174+
}
175+
}
176+
177+
log.info("[PM1E] Entity registration completed - Success: " + successCount + ", Failed: " + failureCount);
178+
}
179+
180+
@Override
181+
public void registerRecipes(List<RecipeDef> recipes) {
182+
log.info("[PM1E] Recipe registration not yet implemented - skipping " + recipes.size() + " recipes");
183+
}
184+
185+
@Override
186+
public Capabilities capabilities() {
187+
return CAPS;
188+
}
18189

19190
@Override
20191
public void afterAllRegistrations() {
21-
// PM1E 平台当前不需要特殊的后处理逻辑
22-
log.info("[PM1E] Registration completed, no platform-specific post-processing needed");
192+
// PM1E Server会在enablePlugins(STARTUP)后自动调用closeRegistry()
193+
// 参考:Server.java:540-541
194+
// CustomItemManager.get().closeRegistry();
195+
// EntityManager.get().closeRegistry();
196+
//
197+
// 插件不应该手动调用closeRegistry,否则会导致:
198+
// java.lang.IllegalStateException: Item registry was already closed
199+
//
200+
// 这符合PM1E官方设计和示例代码的实践:
201+
// - reference/nk-custom-samples 中所有示例都不调用closeRegistry
202+
// - PNX和MOT适配器也不调用closeRegistry
203+
204+
log.info("[PM1E] Registration completed");
205+
log.info("[PM1E] Item and entity registries will be closed automatically by PM1E Server");
206+
}
207+
208+
@Override
209+
public void setupResourcePackPushing(List<DeployedPack> deployedPacks, BridgeConfig config, Object plugin) {
210+
if (deployedPacks == null || deployedPacks.isEmpty()) {
211+
log.info("[PM1E] No resource packs to load");
212+
return;
213+
}
214+
215+
try {
216+
// Create and register resource pack loader
217+
Pm1eResourcePackLoader loader = new Pm1eResourcePackLoader(deployedPacks, config, log);
218+
219+
// Get ResourcePackManager from server
220+
cn.nukkit.Server server = cn.nukkit.Server.getInstance();
221+
cn.nukkit.resourcepacks.ResourcePackManager packManager = server.getResourcePackManager();
222+
223+
// Register the loader
224+
packManager.registerPackLoader(loader);
225+
226+
// Reload packs to trigger loading
227+
packManager.reloadPacks();
228+
229+
log.info("[PM1E] Resource pack loader registered successfully");
230+
231+
} catch (Exception e) {
232+
log.error("[PM1E] Failed to setup resource pack pushing: " + e.getMessage());
233+
log.error("[PM1E] Stack trace:");
234+
for (StackTraceElement element : e.getStackTrace()) {
235+
log.error("[PM1E] at " + element.toString());
236+
if (element.getClassName().contains("easecation.bridge")) {
237+
break;
238+
}
239+
}
240+
}
241+
}
242+
243+
private String extractTextureName(ItemDef itemDef) {
244+
if (itemDef.isLegacy()) {
245+
return null;
246+
}
247+
248+
if (itemDef.componentComponents() != null
249+
&& itemDef.componentComponents().minecraft_icon() != null) {
250+
net.easecation.bridge.core.dto.item.v1_21_60.Icon icon =
251+
itemDef.componentComponents().minecraft_icon();
252+
253+
if (icon instanceof net.easecation.bridge.core.dto.item.v1_21_60.Icon.Icon_Variant0 variant0) {
254+
return variant0.value();
255+
} else if (icon instanceof net.easecation.bridge.core.dto.item.v1_21_60.Icon.Icon_Variant1 variant1) {
256+
if (variant1.textures() != null && variant1.textures().defaultField() != null) {
257+
return variant1.textures().defaultField();
258+
}
259+
}
260+
}
261+
262+
return itemDef.id().replace(":", "_");
23263
}
24264
}
25265

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package net.easecation.bridge.adapter.pm1e;
2+
3+
import cn.nukkit.resourcepacks.ResourcePack;
4+
import cn.nukkit.resourcepacks.ZippedResourcePack;
5+
import cn.nukkit.resourcepacks.loader.ResourcePackLoader;
6+
import net.easecation.bridge.core.BridgeConfig;
7+
import net.easecation.bridge.core.BridgeLogger;
8+
import net.easecation.bridge.core.DeployedPack;
9+
10+
import java.io.File;
11+
import java.net.URI;
12+
import java.util.ArrayList;
13+
import java.util.List;
14+
15+
/**
16+
* PM1E (PowerNukkit) resource pack loader for AddonBridge.
17+
* Loads deployed resource packs at server startup.
18+
*/
19+
public class Pm1eResourcePackLoader implements ResourcePackLoader {
20+
private final List<DeployedPack> deployedPacks;
21+
private final BridgeConfig config;
22+
private final BridgeLogger log;
23+
24+
public Pm1eResourcePackLoader(List<DeployedPack> deployedPacks, BridgeConfig config, BridgeLogger log) {
25+
this.deployedPacks = deployedPacks;
26+
this.config = config;
27+
this.log = log;
28+
}
29+
30+
@Override
31+
public List<ResourcePack> loadPacks() {
32+
List<ResourcePack> resourcePacks = new ArrayList<>();
33+
34+
log.info("[PM1E] Loading AddonBridge resource packs...");
35+
36+
for (DeployedPack pack : deployedPacks) {
37+
// Filter based on pack type and configuration
38+
if (!config.shouldPushPack(pack.packType())) {
39+
log.debug("[PM1E] Skipping pack (disabled in config): " + pack.url());
40+
continue;
41+
}
42+
43+
try {
44+
// Convert file:// URL to File
45+
String url = pack.url();
46+
if (!url.startsWith("file://")) {
47+
log.warning("[PM1E] Non-file URL not supported for pack loading: " + url);
48+
continue;
49+
}
50+
51+
URI uri = new URI(url);
52+
File packFile = new File(uri);
53+
54+
if (!packFile.exists()) {
55+
log.error("[PM1E] Pack file does not exist: " + packFile.getAbsolutePath());
56+
continue;
57+
}
58+
59+
log.debug("[PM1E] Loading pack: " + packFile.getName());
60+
61+
// Create ZippedResourcePack
62+
ZippedResourcePack resourcePack = new ZippedResourcePack(packFile);
63+
resourcePacks.add(resourcePack);
64+
65+
log.info("[PM1E] ✓ Loaded resource pack: " + packFile.getName() +
66+
" (UUID: " + resourcePack.getPackId() + ", Version: " + resourcePack.getPackVersion() + ")");
67+
68+
} catch (Exception e) {
69+
log.error("[PM1E] ✗ Failed to load pack: " + pack.url());
70+
log.error("[PM1E] Error type: " + e.getClass().getSimpleName());
71+
log.error("[PM1E] Error message: " + e.getMessage());
72+
73+
// Log stack trace
74+
log.error("[PM1E] Stack trace:");
75+
for (StackTraceElement element : e.getStackTrace()) {
76+
log.error("[PM1E] at " + element.toString());
77+
if (element.getClassName().contains("easecation.bridge")) {
78+
break;
79+
}
80+
}
81+
}
82+
}
83+
84+
log.info("[PM1E] Loaded " + resourcePacks.size() + " resource pack(s)");
85+
86+
return resourcePacks;
87+
}
88+
}

0 commit comments

Comments
 (0)