Wire RCO texture property to the renderer; fix group parent positions

- Expose the RCO image table as resource.texturetable[label]
- Route native-object property assignment through set<Name>(BaseNativeObject)
  reflection so 'plane.texture = image' reaches PlaneObject.setTexture
- Plane/UButton/Button/Icon draw their runtime texture in preference to the
  static image, and seed the texture property from the static image
- Store the RCO entry's VSMX object so group children inherit parent position
This commit is contained in:
2026-09-17 00:37:10 -04:00
parent 80421d1cba
commit 6912439ddf
10 changed files with 113 additions and 2 deletions
+1
View File
@@ -15,3 +15,4 @@ Thumbs.db
# Disc images # Disc images
*.iso *.iso
reference/
@@ -58,6 +58,7 @@ public final class RcoFile {
public final List<Entry> subEntries = new ArrayList<>(); public final List<Entry> subEntries = new ArrayList<>();
public byte[] data; public byte[] data;
public String[] texts; public String[] texts;
public ImageObject image;
public BaseObject obj; public BaseObject obj;
public VsmxBaseObject vsmxObject; public VsmxBaseObject vsmxObject;
@@ -269,7 +270,9 @@ public final class RcoFile {
if (imgData != null && comp != COMPRESSION_NONE) { if (imgData != null && comp != COMPRESSION_NONE) {
imgData = decompressImage(imgData, comp, sizeUnpacked); imgData = decompressImage(imgData, comp, sizeUnpacked);
} }
images.put(entryOffset, new ImageObject(imgData)); ImageObject imageObject = new ImageObject(imgData);
images.put(entryOffset, imageObject);
entry.image = imageObject;
} }
} }
case TABLE_MODEL -> { case TABLE_MODEL -> {
@@ -97,6 +97,18 @@ public abstract class BaseObject extends BaseNativeObject implements IDisplay {
public VsmxBaseObject createVsmxObject(VsmxInterpreter interpreter, VsmxBaseObject parent, RcoFile.Entry entry) { public VsmxBaseObject createVsmxObject(VsmxInterpreter interpreter, VsmxBaseObject parent, RcoFile.Entry entry) {
VsmxNativeObject object = new VsmxNativeObject(interpreter, this); VsmxNativeObject object = new VsmxNativeObject(interpreter, this);
setObject(object); setObject(object);
entry.vsmxObject = object;
// Seed the "texture" property with this object's static image so the
// script can read or replace it. Assignment is routed through the
// native setTexture setter, when one exists.
Object staticImage = getImage();
if (staticImage instanceof ImageObject imageObject) {
VsmxNativeObject textureObject = new VsmxNativeObject(interpreter, imageObject);
imageObject.setObject(textureObject);
object.setPropertyValue("texture", textureObject);
}
if (entry.label != null) { if (entry.label != null) {
name = entry.label; name = entry.label;
object.setPropertyValue("name", new VsmxString(interpreter, entry.label)); object.setPropertyValue("name", new VsmxString(interpreter, entry.label));
@@ -6,6 +6,7 @@ import dev.quoije.umd.rco.type.EventType;
import dev.quoije.umd.rco.type.ImageType; import dev.quoije.umd.rco.type.ImageType;
import dev.quoije.umd.rco.type.IntType; import dev.quoije.umd.rco.type.IntType;
import dev.quoije.umd.rco.type.TextType; import dev.quoije.umd.rco.type.TextType;
import dev.quoije.umd.rco.vsmx.objects.BaseNativeObject;
/** A button object. */ /** A button object. */
public class ButtonObject extends BasePositionObject { public class ButtonObject extends BasePositionObject {
@@ -24,11 +25,25 @@ public class ButtonObject extends BasePositionObject {
@ObjectField(order = 212) public EventType onContextMenu; @ObjectField(order = 212) public EventType onContextMenu;
@ObjectField(order = 213) public IntType unknownInt40; @ObjectField(order = 213) public IntType unknownInt40;
/** Runtime texture assigned by the script; overrides the static image. */
private ImageObject texture;
@Override @Override
public Object getImage() { public Object getImage() {
if (texture != null) {
return texture;
}
return image.getImage(); return image.getImage();
} }
/** Called via the VSMX {@code texture} property setter. */
public void setTexture(BaseNativeObject texture) {
if (texture instanceof ImageObject imageObject) {
this.texture = imageObject;
}
onDisplayUpdated();
}
@Override @Override
public Object getFocusImage() { public Object getFocusImage() {
return focus.getImage(); return focus.getImage();
@@ -3,6 +3,7 @@ package dev.quoije.umd.rco.object;
import dev.quoije.umd.rco.ObjectField; import dev.quoije.umd.rco.ObjectField;
import dev.quoije.umd.rco.type.ImageType; import dev.quoije.umd.rco.type.ImageType;
import dev.quoije.umd.rco.vsmx.objects.BaseNativeObject;
/** An icon object. */ /** An icon object. */
public class IconObject extends BasePositionObject { public class IconObject extends BasePositionObject {
@@ -11,8 +12,22 @@ public class IconObject extends BasePositionObject {
@ObjectField(order = 202) public ImageType unknownImage18; @ObjectField(order = 202) public ImageType unknownImage18;
@ObjectField(order = 203) public ImageType unknownImage20; @ObjectField(order = 203) public ImageType unknownImage20;
/** Runtime texture assigned by the script; overrides the static image. */
private ImageObject texture;
@Override @Override
public Object getImage() { public Object getImage() {
if (texture != null) {
return texture;
}
return unknownImage16.getImage(); return unknownImage16.getImage();
} }
/** Called via the VSMX {@code texture} property setter. */
public void setTexture(BaseNativeObject texture) {
if (texture instanceof ImageObject imageObject) {
this.texture = imageObject;
}
onDisplayUpdated();
}
} }
@@ -1,8 +1,10 @@
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
package dev.quoije.umd.rco.object; package dev.quoije.umd.rco.object;
import dev.quoije.umd.rco.vsmx.objects.BaseNativeObject;
/** An image entry from the RCO IMG table (raw bytes until decoded by the UI). */ /** An image entry from the RCO IMG table (raw bytes until decoded by the UI). */
public final class ImageObject { public class ImageObject extends BaseNativeObject {
private final byte[] data; private final byte[] data;
@@ -4,6 +4,7 @@ package dev.quoije.umd.rco.object;
import dev.quoije.umd.rco.ObjectField; import dev.quoije.umd.rco.ObjectField;
import dev.quoije.umd.rco.type.ImageType; import dev.quoije.umd.rco.type.ImageType;
import dev.quoije.umd.rco.type.IntType; import dev.quoije.umd.rco.type.IntType;
import dev.quoije.umd.rco.vsmx.objects.BaseNativeObject;
/** A plane (textured rectangle). */ /** A plane (textured rectangle). */
public class PlaneObject extends BasePositionObject { public class PlaneObject extends BasePositionObject {
@@ -11,8 +12,22 @@ public class PlaneObject extends BasePositionObject {
@ObjectField(order = 201) public ImageType image; @ObjectField(order = 201) public ImageType image;
@ObjectField(order = 202) public IntType unknownInt18; @ObjectField(order = 202) public IntType unknownInt18;
/** Runtime texture assigned by the script; overrides the static image. */
private ImageObject texture;
@Override @Override
public Object getImage() { public Object getImage() {
if (texture != null) {
return texture;
}
return image.getImage(); return image.getImage();
} }
/** Called via the VSMX {@code texture} property setter. */
public void setTexture(BaseNativeObject texture) {
if (texture instanceof ImageObject imageObject) {
this.texture = imageObject;
}
onDisplayUpdated();
}
} }
@@ -5,6 +5,7 @@ import dev.quoije.umd.rco.ObjectField;
import dev.quoije.umd.rco.type.EventType; import dev.quoije.umd.rco.type.EventType;
import dev.quoije.umd.rco.type.ImageType; import dev.quoije.umd.rco.type.ImageType;
import dev.quoije.umd.rco.type.IntType; import dev.quoije.umd.rco.type.IntType;
import dev.quoije.umd.rco.vsmx.objects.BaseNativeObject;
/** A user button object (navigation with onLeft/onRight/onUp/onDown events). */ /** A user button object (navigation with onLeft/onRight/onUp/onDown events). */
public class UButtonObject extends BasePositionObject { public class UButtonObject extends BasePositionObject {
@@ -19,11 +20,25 @@ public class UButtonObject extends BasePositionObject {
@ObjectField(order = 208) public EventType onDown; @ObjectField(order = 208) public EventType onDown;
@ObjectField(order = 209) public IntType unknownInt32; @ObjectField(order = 209) public IntType unknownInt32;
/** Runtime texture assigned by the script; overrides the static image. */
private ImageObject texture;
@Override @Override
public Object getImage() { public Object getImage() {
if (texture != null) {
return texture;
}
return image.getImage(); return image.getImage();
} }
/** Called via the VSMX {@code texture} property setter. */
public void setTexture(BaseNativeObject texture) {
if (texture instanceof ImageObject imageObject) {
this.texture = imageObject;
}
onDisplayUpdated();
}
@Override @Override
public void setFocus() { public void setFocus() {
trigger(onFocusIn); trigger(onFocusIn);
@@ -3,6 +3,8 @@ package dev.quoije.umd.rco.vsmx.interpreter;
import dev.quoije.umd.rco.vsmx.objects.BaseNativeObject; import dev.quoije.umd.rco.vsmx.objects.BaseNativeObject;
import java.lang.reflect.Method;
/** A VSMX object wrapping a native Java object. */ /** A VSMX object wrapping a native Java object. */
public class VsmxNativeObject extends VsmxObject { public class VsmxNativeObject extends VsmxObject {
@@ -17,6 +19,27 @@ public class VsmxNativeObject extends VsmxObject {
return object; return object;
} }
/**
* Assigning a native object to a property is routed through a matching
* {@code set<Name>(BaseNativeObject)} method when the wrapped object
* declares one. This is how {@code plane.texture = image} reaches
* {@code PlaneObject.setTexture}.
*/
@Override
public void setPropertyValue(String name, VsmxBaseObject value) {
if (object != null && name != null && !name.isEmpty() && value instanceof VsmxNativeObject nativeValue) {
String setterName = "set" + Character.toUpperCase(name.charAt(0)) + name.substring(1);
try {
Method setter = object.getClass().getMethod(setterName, BaseNativeObject.class);
setter.invoke(object, nativeValue.getObject());
return;
} catch (ReflectiveOperationException e) {
// No matching setter; fall back to storing the property.
}
}
super.setPropertyValue(name, value);
}
@Override @Override
public String toString() { public String toString() {
return object == null ? super.toString() : object.toString() + super.toString(); return object == null ? super.toString() : object.toString() + super.toString();
@@ -7,6 +7,7 @@ import dev.quoije.umd.rco.vsmx.interpreter.VsmxBaseObject;
import dev.quoije.umd.rco.vsmx.interpreter.VsmxInterpreter; import dev.quoije.umd.rco.vsmx.interpreter.VsmxInterpreter;
import dev.quoije.umd.rco.vsmx.interpreter.VsmxNativeObject; import dev.quoije.umd.rco.vsmx.interpreter.VsmxNativeObject;
import dev.quoije.umd.rco.vsmx.interpreter.VsmxObject; import dev.quoije.umd.rco.vsmx.interpreter.VsmxObject;
import dev.quoije.umd.rco.vsmx.interpreter.VsmxString;
/** /**
* The resource native object, exposing the RCO object tables to the script. * The resource native object, exposing the RCO object tables to the script.
@@ -50,6 +51,15 @@ public class Resource extends BaseNativeObject {
private static void createObjectFromEntry(VsmxInterpreter interpreter, Display display, Controller controller, private static void createObjectFromEntry(VsmxInterpreter interpreter, Display display, Controller controller,
VsmxBaseObject parent, RcoFile.Entry entry) { VsmxBaseObject parent, RcoFile.Entry entry) {
if (entry.image != null) {
VsmxNativeObject object = new VsmxNativeObject(interpreter, entry.image);
entry.image.setObject(object);
if (entry.label != null) {
object.setPropertyValue("name", new VsmxString(interpreter, entry.label));
parent.setPropertyValue(entry.label, object);
}
return;
}
if (entry.obj == null) { if (entry.obj == null) {
return; return;
} }