新增按钮 +10 Red Bones:在当前选中骨骼下用编辑器自己的 New Bone 建 10 根,位置随机(±200),颜色写 red(Gp 的 x/y 走 names.tsv 反查、color 按类型找字段)
This commit is contained in:
1 parent
2e325bc1b9
commit
f696c15671
1 file changed
+137
-11
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user