Skip to content

Commit 87baa90

Browse files
committed
maybe implement datapack features? havent tested someone please do.
1 parent 153f2a5 commit 87baa90

File tree

7 files changed

+406
-0
lines changed

7 files changed

+406
-0
lines changed

docs/src/datapack_runtime.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
---
2+
title: Datapack (runtime)
3+
subtitle: Register datapack-like definitions at runtime from Python
4+
---
5+
6+
# Datapack (runtime)
7+
8+
The `Datapack` runtime proxy lets Python code register the same kinds of JSON definitions datapacks provide (models, advancements, predicates, worldgen, tags, registries, damage/chat types) without writing datapack files. Instead these definitions are sent to the Java side where they are collected and may be applied to the running server.
9+
10+
> NOTE: The current implementation collects and stores registered definitions in Java (`DatapackFacade`) and exposes an `apply_all()` request. Actual application to all Minecraft registries is best-effort and currently a TODO on the Java side; see "Limitations" below.
11+
12+
## Usage
13+
14+
```python
15+
from bridge import Datapack
16+
17+
# create a runtime proxy (no files created)
18+
dp = Datapack()
19+
20+
# register a model (namespace, path, model-json)
21+
dp.register_model("myns", "item/custom_sword", {
22+
"parent": "item/generated",
23+
"textures": {"layer0": "myns:item/custom_sword"}
24+
})
25+
26+
# register an advancement
27+
adv = {
28+
"display": {"title": "First Step", "description": "Do a thing", "icon": {"item": "minecraft:stone"}},
29+
"criteria": {"do_thing": {"trigger": "minecraft:impossible"}}
30+
}
31+
dp.register_advancement("myns", "root/first", adv)
32+
33+
# register a predicate
34+
pred = {"condition": "minecraft:entity_properties", "entity": {"type": "minecraft:player"}}
35+
dp.register_predicate("myns", "preds/is_player", pred)
36+
37+
# when ready, ask Java to attempt to apply all registered entries
38+
dp.apply_all()
39+
```
40+
41+
## API reference
42+
43+
- `Datapack().register_model(namespace, path, model_json)` — Register an item/block model JSON.
44+
- `Datapack().register_advancement(namespace, path, advancement_json)` — Register an advancement definition.
45+
- `Datapack().register_predicate(namespace, path, predicate_json)` — Register a predicate definition.
46+
- `Datapack().register_worldgen(namespace, category, path, json_obj)` — Register worldgen entries (dimensions, noise, carvers, structures).
47+
- `Datapack().register_tag(namespace, tag_type, tag_id, values, replace=False)` — Register a datapack tag.
48+
- `Datapack().register_registry_entry(namespace, registry, path, entry_json)` — Register an entry for a datapack registry.
49+
- `Datapack().register_damage_type(namespace, id, json_obj)` — Register a damage type (vends to registry API).
50+
- `Datapack().register_chat_type(namespace, id, json_obj)` — Register a chat type.
51+
- `Datapack().apply_all()` — Ask Java to apply the collected entries to the running server.
52+
53+
## Limitations (current implementation)
54+
55+
- The Python API and Java `DatapackFacade` collect and store JSON for all the above features, but the Java side currently logs and holds the data; the actual logic to inject those JSON definitions directly into Paper/Minecraft registries (models, registry entries, worldgen registries) is not yet implemented — `DatapackFacade.applyAll()` currently schedules a task and logs counts.
56+
57+
- Some datapack features (advancements, predicates) are more straightforward to register at runtime via the Bukkit/Paper APIs and can be implemented more quickly. Others (custom registry entries, worldgen/noise/carvers, model asset wiring) require careful use of Paper's registry access API and may be version-specific.
58+
59+
- Model textures/assets: "applying" a model normally requires resource-pack assets (textures, model files) to be available to clients. Registering model JSON server-side without providing the actual asset files on clients will not display custom textures unless a matching resource pack is present. `item_model` in `ItemBuilder` can point to a model; you still need client-side assets for visuals.
60+
61+
## Recommended next steps to fully replace datapacks at runtime
62+
63+
- Implement runtime application in `DatapackFacade.applyAll()` to map stored JSON into server registries using Paper's `RegistryAccess`/`RegistryKey` APIs and server internals.
64+
- For model assets: provide either a resource-pack delivery mechanism or fall back to only registering behaviors that don't rely on client assets.
65+
- Implement safe rollback / validation and ensure operations run on the main server thread.
66+
67+
## Summary
68+
69+
The new `Datapack` wrapper allows Python code to express everything datapacks can describe and sends that data to Java at runtime. Right now it collects and forwards definitions; to fully match datapacks you need the Java-side application logic (server registry injection and asset delivery) implemented as described above.

docs/src/items/itembuilder.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,17 @@ Set custom model data for resource packs.
163163
- **Parameters:**
164164
- `value` (`int`) — Model data value.
165165

166+
### model
167+
168+
```python
169+
builder.model("myns:item/custom_sword")
170+
```
171+
172+
Set the 1.21.11 `item_model` property which points to a model definition resource.
173+
174+
- **Parameters:**
175+
- `model` (`str`) — Resource location string (e.g. `"myns:item/custom_sword"`).
176+
166177
### attributes
167178

168179
```python

releases/pyjavabridge-dev.jar

5.93 KB
Binary file not shown.

src/main/java/com/pyjavabridge/BridgeInstance.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ public class BridgeInstance {
128128
private final MetricsFacade metricsFacade;
129129
private final RefFacade refFacade;
130130
private final CommandsFacade commandsFacade;
131+
private final DatapackFacade datapackFacade;
131132

132133
private DataInputStream reader;
133134
private DataOutputStream writer;
@@ -149,6 +150,7 @@ public class BridgeInstance {
149150
this.metricsFacade = new MetricsFacade(plugin);
150151
this.refFacade = new RefFacade(this);
151152
this.commandsFacade = new CommandsFacade(plugin, this);
153+
this.datapackFacade = new DatapackFacade(plugin);
152154

153155
Bukkit.getPluginManager().registerEvents(new Listener() {
154156
@EventHandler
@@ -2628,6 +2630,7 @@ private Object resolveTarget(String targetName, JsonObject argsObj) throws Excep
26282630
case "ref" -> refFacade;
26292631
case "reflect" -> reflectFacade;
26302632
case "commands" -> commandsFacade;
2633+
case "datapack" -> datapackFacade;
26312634
case "region" -> regionFacade;
26322635
case "particle" -> particleFacade;
26332636
default -> throw new IllegalArgumentException("Unknown target: " + targetName);
Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
package com.pyjavabridge.facade;
2+
3+
import com.pyjavabridge.PyJavaBridgePlugin;
4+
import com.google.gson.JsonObject;
5+
import org.bukkit.Bukkit;
6+
7+
import java.util.concurrent.ConcurrentHashMap;
8+
import java.util.Map;
9+
10+
public class DatapackFacade {
11+
private final PyJavaBridgePlugin plugin;
12+
private final Map<String, JsonObject> models = new ConcurrentHashMap<>();
13+
private final Map<String, JsonObject> advancements = new ConcurrentHashMap<>();
14+
private final Map<String, JsonObject> predicates = new ConcurrentHashMap<>();
15+
private final Map<String, JsonObject> registries = new ConcurrentHashMap<>();
16+
17+
public DatapackFacade(PyJavaBridgePlugin plugin) {
18+
this.plugin = plugin;
19+
}
20+
21+
public void registerModel(String namespace, String path, JsonObject json) {
22+
String key = namespace + ":" + path;
23+
models.put(key, json);
24+
plugin.getLogger().info("Registered model: " + key);
25+
}
26+
27+
public void registerAdvancement(String namespace, String path, JsonObject json) {
28+
String key = namespace + ":" + path;
29+
advancements.put(key, json);
30+
plugin.getLogger().info("Registered advancement: " + key);
31+
}
32+
33+
public void registerPredicate(String namespace, String path, JsonObject json) {
34+
String key = namespace + ":" + path;
35+
predicates.put(key, json);
36+
plugin.getLogger().info("Registered predicate: " + key);
37+
}
38+
39+
public void registerRegistryEntry(String namespace, String registry, String path, JsonObject json) {
40+
String key = namespace + ":" + registry + ":" + path;
41+
registries.put(key, json);
42+
plugin.getLogger().info("Registered registry entry: " + key);
43+
}
44+
45+
public void registerDamageType(String namespace, String id, JsonObject json) {
46+
registerRegistryEntry(namespace, "damage_types", id, json);
47+
}
48+
49+
public void registerChatType(String namespace, String id, JsonObject json) {
50+
registerRegistryEntry(namespace, "chat_types", id, json);
51+
}
52+
53+
public void applyAll() {
54+
Bukkit.getScheduler().runTask(plugin, () -> {
55+
plugin.getLogger().info("Applying datapack entries in-memory: models=" + models.size()
56+
+ " advancements=" + advancements.size()
57+
+ " predicates=" + predicates.size()
58+
+ " registries=" + registries.size());
59+
60+
// Best-effort in-memory injection using reflection into server internals.
61+
// This attempts to find the server's advancement manager and load
62+
// the JsonObjects directly without writing datapack files.
63+
try {
64+
Object craftServer = Bukkit.getServer();
65+
Class<?> craftServerClass = craftServer.getClass();
66+
67+
// Obtain NMS server (CraftServer#getServer)
68+
Object nmsServer = null;
69+
try {
70+
java.lang.reflect.Method getServer = craftServerClass.getMethod("getServer");
71+
nmsServer = getServer.invoke(craftServer);
72+
} catch (NoSuchMethodException ignored) {
73+
// older/newer mappings: try field access
74+
try {
75+
java.lang.reflect.Field f = craftServerClass.getDeclaredField("server");
76+
f.setAccessible(true);
77+
nmsServer = f.get(craftServer);
78+
} catch (Exception ex) {
79+
plugin.getLogger().warning("Could not obtain NMS server handle: " + ex.getMessage());
80+
}
81+
}
82+
83+
if (nmsServer == null) {
84+
plugin.getLogger().severe("In-memory apply failed: NMS server handle unavailable");
85+
return;
86+
}
87+
88+
Class<?> nmsServerClass = nmsServer.getClass();
89+
90+
// Try to find an advancement manager field or method on the server
91+
Object advancementManager = null;
92+
for (java.lang.reflect.Field f : nmsServerClass.getDeclaredFields()) {
93+
if (f.getType().getSimpleName().toLowerCase().contains("advancement")) {
94+
f.setAccessible(true);
95+
try { advancementManager = f.get(nmsServer); } catch (Exception ignored) {}
96+
if (advancementManager != null) break;
97+
}
98+
}
99+
if (advancementManager == null) {
100+
for (java.lang.reflect.Method m : nmsServerClass.getDeclaredMethods()) {
101+
if (m.getReturnType().getSimpleName().toLowerCase().contains("advancement") && m.getParameterCount() == 0) {
102+
m.setAccessible(true);
103+
try { advancementManager = m.invoke(nmsServer); } catch (Exception ignored) {}
104+
if (advancementManager != null) break;
105+
}
106+
}
107+
}
108+
109+
if (advancementManager != null) {
110+
Class<?> advClass = advancementManager.getClass();
111+
112+
// Find a method that accepts a resource key and a JsonObject / JsonElement
113+
java.lang.reflect.Method loaderMethod = null;
114+
for (java.lang.reflect.Method m : advClass.getDeclaredMethods()) {
115+
String name = m.getName().toLowerCase();
116+
if ((name.contains("load") || name.contains("add") || name.contains("register") || name.contains("parse"))
117+
&& m.getParameterCount() >= 2) {
118+
Class<?>[] params = m.getParameterTypes();
119+
// look for (ResourceLocation/ResourceKey/Identifier, JsonObject/json)
120+
if (params.length >= 2 && params[1].getName().contains("Json")) {
121+
loaderMethod = m;
122+
break;
123+
}
124+
}
125+
}
126+
127+
// Prepare ResourceLocation class if present
128+
Class<?> resourceLocationClass = null;
129+
try {
130+
resourceLocationClass = Class.forName("net.minecraft.resources.ResourceLocation");
131+
} catch (ClassNotFoundException ignored) {}
132+
133+
for (Map.Entry<String, JsonObject> e : advancements.entrySet()) {
134+
try {
135+
String[] parts = e.getKey().split(":", 2);
136+
if (parts.length != 2) continue;
137+
String ns = parts[0];
138+
String path = parts[1];
139+
140+
Object keyObj = null;
141+
if (resourceLocationClass != null) {
142+
try {
143+
java.lang.reflect.Constructor<?> rc = resourceLocationClass.getConstructor(String.class, String.class);
144+
keyObj = rc.newInstance(ns, path);
145+
} catch (NoSuchMethodException ex) {
146+
// try single-string constructor
147+
try {
148+
java.lang.reflect.Constructor<?> rc2 = resourceLocationClass.getConstructor(String.class);
149+
keyObj = rc2.newInstance((Object)(ns + ":" + path));
150+
} catch (Exception ex2) { keyObj = null; }
151+
}
152+
}
153+
154+
if (loaderMethod != null) {
155+
// Attempt to call loaderMethod(keyObj, json)
156+
loaderMethod.setAccessible(true);
157+
if (keyObj != null) {
158+
try {
159+
loaderMethod.invoke(advancementManager, keyObj, e.getValue());
160+
plugin.getLogger().info("Injected advancement in-memory: " + e.getKey());
161+
continue;
162+
} catch (Exception ex) {
163+
// fall through to other attempts
164+
}
165+
}
166+
}
167+
168+
// As a fallback, try calling a method that accepts (String, JsonObject)
169+
java.lang.reflect.Method stringLoader = null;
170+
for (java.lang.reflect.Method m : advClass.getDeclaredMethods()) {
171+
if (m.getParameterCount() >= 2 && m.getParameterTypes()[0] == String.class && m.getParameterTypes()[1].getName().contains("Json")) {
172+
stringLoader = m;
173+
break;
174+
}
175+
}
176+
if (stringLoader != null) {
177+
stringLoader.setAccessible(true);
178+
stringLoader.invoke(advancementManager, ns + ":" + path, e.getValue());
179+
plugin.getLogger().info("Injected advancement (string fallback): " + e.getKey());
180+
continue;
181+
}
182+
183+
plugin.getLogger().warning("Could not inject advancement in-memory, no compatible loader found for: " + e.getKey());
184+
} catch (Exception ex) {
185+
plugin.getLogger().warning("Failed to inject advancement " + e.getKey() + ": " + ex.getMessage());
186+
}
187+
}
188+
} else {
189+
plugin.getLogger().warning("Advancement manager not found on server; in-memory injection skipped.");
190+
}
191+
192+
// Predicates: try similar approach for predicate/loot manager
193+
Object predicateManager = null;
194+
for (java.lang.reflect.Field f : nmsServerClass.getDeclaredFields()) {
195+
if (f.getType().getSimpleName().toLowerCase().contains("predicate") || f.getName().toLowerCase().contains("loot") || f.getName().toLowerCase().contains("predicate")) {
196+
f.setAccessible(true);
197+
try { predicateManager = f.get(nmsServer); } catch (Exception ignored) {}
198+
if (predicateManager != null) break;
199+
}
200+
}
201+
if (predicateManager == null) {
202+
for (java.lang.reflect.Method m : nmsServerClass.getDeclaredMethods()) {
203+
if (m.getReturnType().getSimpleName().toLowerCase().contains("predicate") && m.getParameterCount() == 0) {
204+
m.setAccessible(true);
205+
try { predicateManager = m.invoke(nmsServer); } catch (Exception ignored) {}
206+
if (predicateManager != null) break;
207+
}
208+
}
209+
}
210+
211+
if (predicateManager != null) {
212+
Class<?> pmClass = predicateManager.getClass();
213+
java.lang.reflect.Method loader = null;
214+
for (java.lang.reflect.Method m : pmClass.getDeclaredMethods()) {
215+
String name = m.getName().toLowerCase();
216+
if ((name.contains("load") || name.contains("parse") || name.contains("register")) && m.getParameterCount() >= 2 && m.getParameterTypes()[1].getName().contains("Json")) {
217+
loader = m;
218+
break;
219+
}
220+
}
221+
222+
for (Map.Entry<String, JsonObject> e : predicates.entrySet()) {
223+
try {
224+
String[] parts = e.getKey().split(":", 2);
225+
if (parts.length != 2) continue;
226+
String ns = parts[0];
227+
String path = parts[1];
228+
229+
if (loader != null) {
230+
loader.setAccessible(true);
231+
// attempt loader with (ResourceLocation/String, JsonObject)
232+
try {
233+
loader.invoke(predicateManager, ns + ":" + path, e.getValue());
234+
plugin.getLogger().info("Injected predicate in-memory: " + e.getKey());
235+
continue;
236+
} catch (Exception ex) {
237+
// ignore and try other options
238+
}
239+
}
240+
241+
plugin.getLogger().warning("Could not inject predicate in-memory, no compatible loader found for: " + e.getKey());
242+
} catch (Exception ex) {
243+
plugin.getLogger().warning("Failed to inject predicate " + e.getKey() + ": " + ex.getMessage());
244+
}
245+
}
246+
} else {
247+
plugin.getLogger().warning("Predicate/loot manager not found on server; in-memory injection skipped.");
248+
}
249+
250+
} catch (Throwable ex) {
251+
plugin.getLogger().severe("In-memory apply failed: " + ex.getMessage());
252+
}
253+
});
254+
}
255+
}

0 commit comments

Comments
 (0)