新增按钮 +10 Red Bones:在当前选中骨骼下用编辑器自己的 New Bone 建 10 根,位置随机(±200),颜色写 red(Gp 的 x/y 走 names.tsv 反查、color 按类型找字段)

This commit is contained in:
tianmo committed 2026-10-16 17:52:04 +08:00
1 parent 2e325bc1b9
commit f696c15671
1 file changed
+137 -11
+137 -11
View File
@@ -129,9 +129,11 @@ public final class ObjectPanel {
Object rename = button(loader, skin, "Rename");
Object inspect = button(loader, skin, "Inspect");
Object setValue = button(loader, skin, "Set Value");
Object addTen = button(loader, skin, "+10 Red Bones");
// 按钮直接放进根表格:两行按钮 + 一行滚动列表(与阶段四验证过的结构一致)
Object[] top = {refresh, newBone, newSlot, newBBox, newPath, delete, rename, inspect, setValue};
Object[] top = {refresh, newBone, newSlot, newBBox, newPath, delete, rename, inspect, setValue,
addTen};
Object buttons = tableClass.getConstructor().newInstance();
call(buttons, Obf.TABLE_TOP, new Class<?>[0], tableClass);
call(buttons, Obf.TABLE_LEFT, new Class<?>[0], tableClass); // 按钮块靠左上(不然整宽后会居中)
@@ -181,6 +183,12 @@ public final class ObjectPanel {
System.err.println("[panel] 设置中段高度失败: " + error);
}
wire(loader, addTen, new Runnable() {
@Override
public void run() {
createTenRedBones(state);
}
});
wire(loader, setValue, new Runnable() {
@Override
public void run() {
@@ -618,6 +626,109 @@ public final class ObjectPanel {
thread.start();
}
/**
* 在当前选中的骨骼下新建 10 根骨骼:位置随机、颜色红色。
* 建用编辑器自己的 New Bone(带撤销),建完立刻改该骨骼的 x/y 与 color 字段。
*/
private static void createTenRedBones(final State state) {
SceneActions.onEditorThread(state.loader, new Runnable() {
@Override
public void run() {
Object parent = state.target;
if (parent == null) {
setStatus(state, "+10 Red Bones: select a parent bone first.");
return;
}
try {
Class<?> colorClass = Class.forName("com.badlogic.gdx.graphics.Color", true, state.loader);
Object red = colorClass.getConstructor(float.class, float.class, float.class,
float.class).newInstance(Float.valueOf(1f), Float.valueOf(0f), Float.valueOf(0f),
Float.valueOf(1f));
int made = 0;
for (int i = 0; i < 10; i++) {
java.util.List<Object> before = SceneObjects.listAllNodes(state.loader);
SceneActions.create(state.loader, parent, "Bone");
Object fresh = firstNew(before, SceneObjects.listAllNodes(state.loader));
if (fresh == null) continue;
float x = (float) (Math.random() * 400.0 - 200.0);
float y = (float) (Math.random() * 400.0 - 200.0);
setBoneNumber(fresh, "x", x);
setBoneNumber(fresh, "y", y);
setBoneColor(fresh, red);
made++;
}
setStatus(state, "made " + made + " red bones under " + SceneObjects.nameOf(parent));
} catch (Throwable error) {
setStatus(state, "+10 Red Bones failed: " + error);
error.printStackTrace(System.err);
}
refreshList(state);
}
});
}
/** 前后列表对比,取第一个新出现的对象。 */
private static Object firstNew(java.util.List<Object> before, java.util.List<Object> after) {
for (Object item : after) {
boolean seen = false;
for (Object old : before) {
if (old == item) {
seen = true;
break;
}
}
if (!seen) return item;
}
return null;
}
/** 按"真名"(x/y/rotation…)改骨骼上的 float 字段:真名→混淆名走 names.tsv。 */
private static void setBoneNumber(Object bone, String realName, float value) {
try {
String obf = obfNameOf("Gp", "F", realName);
if (obf == null) return;
for (Class<?> type = bone.getClass(); type != null && type != Object.class;
type = type.getSuperclass()) {
for (java.lang.reflect.Field field : type.getDeclaredFields()) {
if (!field.getName().equals(obf) || field.getType() != float.class) continue;
field.setAccessible(true);
field.setFloat(bone, value);
return;
}
}
} catch (Throwable error) {
System.err.println("[panel] 设置 " + realName + " 失败: " + error);
}
}
/** 骨骼颜色:找类型是 libGDX Color 的字段写进去。 */
private static void setBoneColor(Object bone, Object color) {
try {
for (Class<?> type = bone.getClass(); type != null && type != Object.class;
type = type.getSuperclass()) {
for (java.lang.reflect.Field field : type.getDeclaredFields()) {
if (!field.getType().getName().equals("com.badlogic.gdx.graphics.Color")) continue;
field.setAccessible(true);
field.set(bone, color);
return;
}
}
} catch (Throwable error) {
System.err.println("[panel] 设置颜色失败: " + error);
}
}
/** 映射表反查:owner + 描述符 + 真名 → 混淆名。 */
private static String obfNameOf(String owner, String desc, String realName) {
loadNames();
for (java.util.Map.Entry<String, String> entry : NAMES.entrySet()) {
if (!entry.getValue().equals(realName)) continue;
String[] parts = entry.getKey().split("#");
if (parts.length == 3 && parts[0].equals(owner) && parts[2].equals(desc)) return parts[1];
}
return null;
}
private static int countFields(Object object) {
int count = 0;
for (Class<?> type = object.getClass(); type != null && type != Object.class;
@@ -1261,16 +1372,31 @@ public final class ObjectPanel {
StringBuilder out = new StringBuilder();
out.append("buttons: ").append(buttonLabels()).append('\n');
press(0, out); // Refresh
out.append("after Refresh: ").append(rows(loader)).append('\n');
// 列表是 Refresh 之后才建出来的,找到 "bone bone3" 那一行并按它
press(panelButton("bone bone3"), "row bone bone3", out);
out.append("after select: ").append(rows(loader)).append('\n');
press(2, out); // New Slot(会弹输入框,看面板能不能自己填+点 OK)
sleepQuiet(3000);
out.append("after New Slot: ").append(rows(loader)).append('\n');
press(3, out); // New BBox
sleepQuiet(3000);
out.append("after New BBox: ").append(rows(loader)).append('\n');
// 直接"选中 bone3 当父级"(列表行的标签取不到,就程序化选中)
press(9, out); // +10 Red Bones(未选父级时应提示)
sleepQuiet(1000);
Object parent = null;
try {
parent = SceneActions.findByName(loader, "bone3");
} catch (Throwable error) {
out.append("find bone3 failed: ").append(error).append('\n');
}
out.append("parent = ").append(parent == null ? "null" : SceneObjects.nameOf(parent)).append('\n');
if (parent != null) {
final Object target = parent;
SceneActions.onEditorThread(loader, new Runnable() {
@Override
public void run() {
SceneActions.select(loader, target);
}
});
// 让 state.target 记住它(正常是点列表行设置的)
Object st = LAST_STATE;
if (st instanceof State) ((State) st).target = parent;
}
press(9, out); // 现在应当真的建 10 根
sleepQuiet(4000);
out.append("after +10 Red Bones: ").append(rows(loader)).append('\n');
return out.toString();
}