From d892f9beda681f0a5ec42b943baf374040093c43 Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Fri, 30 Jan 2026 18:03:07 -0800 Subject: [PATCH 01/17] Add Kaitai Struct definition for animation files (.ani) (#1734) Add animation.ksy documenting the binary format for LEGO Island's animation files, including keyframe data, actor references, and optional camera animation. Includes sample file pns065rd.ani. --- docs/README.md | 10 +- docs/animation.ksy | 310 ++++++++++++++++++++++++++++++++++++++ docs/samples/pns065rd.ani | Bin 0 -> 4788 bytes 3 files changed, 319 insertions(+), 1 deletion(-) create mode 100644 docs/animation.ksy create mode 100644 docs/samples/pns065rd.ani diff --git a/docs/README.md b/docs/README.md index 0ea419d7..1c309a3e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -18,6 +18,7 @@ Kaitai Struct allows you to define binary formats in a YAML-based `.ksy` file, w | [`savegame.ksy`](/docs/savegame.ksy) | `.GS` | Main game save data (game state, progress, customizations) | | [`players.ksy`](/docs/players.ksy) | `.gsi` | Player profile save data (usernames) | | [`history.ksy`](/docs/history.ksy) | `.gsi` | Score history and high scores | +| [`animation.ksy`](/docs/animation.ksy) | `.ani` | Animation data (keyframes, actor references, camera animation) | ## Using the Tools @@ -38,6 +39,9 @@ ksv samples/Players.gsi players.ksy # View a History.gsi file ksv samples/History.gsi history.ksy + +# View an animation file +ksv samples/pns065rd.ani animation.ksy ``` ### Kaitai Struct Dump (ksdump) @@ -53,11 +57,15 @@ ksdump --format json samples/Players.gsi players.ksy # Dump History.gsi to YAML ksdump --format yaml samples/History.gsi history.ksy + +# Dump an animation file to JSON +ksdump --format json samples/pns065rd.ani animation.ksy ``` ## Sample Files -The [`samples/`](/docs/samples/) directory contains example save files for testing: +The [`samples/`](/docs/samples/) directory contains example files for testing: - `G0.GS`, `G1.GS`, `G2.GS` - Sample main game save files (slots 0, 1, 2) - `Players.gsi` - Sample player profile data - `History.gsi` - Sample score history data +- `pns065rd.ani` - Sample animation file diff --git a/docs/animation.ksy b/docs/animation.ksy new file mode 100644 index 00000000..6f582904 --- /dev/null +++ b/docs/animation.ksy @@ -0,0 +1,310 @@ +meta: + id: animation + title: Animation File + application: LEGO Island + file-extension: ani + license: CC0-1.0 + endian: le +doc: | + Animation file format for LEGO Island (1997). Contains skeletal animation + data including actor references, keyframes for translation/rotation/scale, + morph visibility keys, and optional camera animation data. + + Animation files are embedded within SI (Interleaf) files and + parsed by LegoAnimPresenter. The format consists of a header with bounding + information, an actor list, animation duration, optional camera animation, + and a hierarchical tree of animation nodes. + +seq: + - id: magic + type: s4 + doc: | + Magic number identifying the file format. Must be 0x11 (17 decimal). + - id: bounding_radius + type: f4 + doc: | + Radius of the bounding sphere encompassing the entire animation. + Used for visibility culling and collision detection. + - id: center_x + type: f4 + doc: X coordinate of the bounding sphere center point. + - id: center_y + type: f4 + doc: Y coordinate of the bounding sphere center point. + - id: center_z + type: f4 + doc: Z coordinate of the bounding sphere center point. + - id: has_camera_anim + type: s4 + doc: | + Flag indicating whether camera animation data follows the actor list. + If non-zero, a camera_anim structure is present after the duration field. + - id: unused + type: s4 + doc: | + Unused field. Read by the parser but not used for anything. + - id: num_actors + type: u4 + doc: Number of actor entries in the actor list. + - id: actors + type: actor_entry + repeat: expr + repeat-expr: num_actors + doc: | + List of actors referenced by this animation. Each entry contains + the actor name and type, which determines how the actor ROI is + managed during animation playback. + - id: duration + type: s4 + doc: Total duration of the animation in milliseconds. + - id: camera_anim + type: camera_anim + if: has_camera_anim != 0 + doc: | + Camera animation data including position, target, and rotation keys. + Only present if has_camera_anim is non-zero. + - id: root_node + type: tree_node + doc: | + Root node of the animation tree. The tree structure mirrors the + skeletal hierarchy of the animated model, with each node containing + keyframe data for its corresponding bone/part. + +types: + actor_entry: + doc: | + An actor reference in the animation. The name identifies which ROI + (Realtime Object Instance) to animate, and the type determines + how the actor is managed by the character manager. + seq: + - id: name_length + type: u4 + doc: Length of the actor name in bytes. + - id: name + type: str + size: name_length + encoding: ASCII + if: name_length > 0 + doc: Actor name used to look up the ROI in the scene. + - id: actor_type + type: u4 + enum: actor_type + if: name_length > 0 + doc: | + Determines how the actor ROI is created and managed. + See actor_type enum for possible values. + + camera_anim: + doc: | + Camera animation data (LegoAnimScene). Contains keyframes for camera + position, look-at target position, and roll rotation around the + view axis. + seq: + - id: num_translation_keys + type: u2 + doc: Number of camera position keyframes. + - id: translation_keys + type: translation_key + repeat: expr + repeat-expr: num_translation_keys + doc: Camera position keyframes. + - id: num_target_keys + type: u2 + doc: Number of look-at target position keyframes. + - id: target_keys + type: translation_key + repeat: expr + repeat-expr: num_target_keys + doc: Look-at target position keyframes. + - id: num_rotation_keys + type: u2 + doc: Number of camera roll rotation keyframes. + - id: rotation_keys + type: rotation_z_key + repeat: expr + repeat-expr: num_rotation_keys + doc: Camera roll rotation keyframes (rotation around view axis). + + tree_node: + doc: | + A node in the animation tree hierarchy. Each node contains animation + data for one part of the model and references to child nodes. + seq: + - id: data + type: node_data + doc: Animation keyframe data for this node. + - id: num_children + type: u4 + doc: Number of child nodes. + - id: children + type: tree_node + repeat: expr + repeat-expr: num_children + doc: Child nodes in the animation hierarchy. + + node_data: + doc: | + Animation data for a single node (LegoAnimNodeData). Contains the + node name and arrays of keyframes for translation, rotation, scale, + and morph (visibility) animations. + seq: + - id: name_length + type: u4 + doc: Length of the node name in bytes. + - id: name + type: str + size: name_length + encoding: ASCII + if: name_length > 0 + doc: | + Node name used to match this animation data to a ROI in the scene. + Names starting with '*' indicate special handling (actor name + substitution). Names starting with '-' are ignored. + - id: num_translation_keys + type: u2 + doc: Number of translation keyframes. + - id: translation_keys + type: translation_key + repeat: expr + repeat-expr: num_translation_keys + doc: Translation (position) keyframes. + - id: num_rotation_keys + type: u2 + doc: Number of rotation keyframes. + - id: rotation_keys + type: rotation_key + repeat: expr + repeat-expr: num_rotation_keys + doc: Rotation keyframes (quaternion format). + - id: num_scale_keys + type: u2 + doc: Number of scale keyframes. + - id: scale_keys + type: scale_key + repeat: expr + repeat-expr: num_scale_keys + doc: Scale keyframes. + - id: num_morph_keys + type: u2 + doc: Number of morph (visibility) keyframes. + - id: morph_keys + type: morph_key + repeat: expr + repeat-expr: num_morph_keys + doc: Morph keyframes controlling visibility. + + anim_key: + doc: | + Base animation key containing time and flags. The time and flags + are packed into a single 32-bit value: bits 0-23 contain the time + in milliseconds, and bits 24-31 contain flags. + seq: + - id: time_and_flags + type: s4 + doc: | + Packed time and flags value. + - Bits 0-23: Time in milliseconds (mask with 0xFFFFFF) + - Bits 24-31: Flags (shift right by 24) + instances: + time: + value: time_and_flags & 0xFFFFFF + doc: Keyframe time in milliseconds. + flags: + value: (time_and_flags >> 24) & 0xFF + doc: | + Keyframe flags: + - 0x01 (active): Key has meaningful data + - 0x02 (negate_rotation): Negate quaternion for interpolation + - 0x04 (skip_interpolation): Use this key's value without blending + + translation_key: + doc: | + Translation keyframe containing position offset (LegoTranslationKey). + The translation is applied relative to the parent node's transform. + seq: + - id: key + type: anim_key + doc: Base key with time and flags. + - id: x + type: f4 + doc: X component of translation. + - id: y + type: f4 + doc: Y component of translation. + - id: z + type: f4 + doc: Z component of translation. + + rotation_key: + doc: | + Rotation keyframe containing a quaternion (LegoRotationKey). + The quaternion is stored as (angle, x, y, z) where angle is the + scalar/w component and (x, y, z) is the vector part. + seq: + - id: key + type: anim_key + doc: Base key with time and flags. + - id: angle + type: f4 + doc: | + Quaternion scalar component (w). A value of 1.0 with x=y=z=0 + represents no rotation (identity quaternion). + - id: x + type: f4 + doc: Quaternion x component. + - id: y + type: f4 + doc: Quaternion y component. + - id: z + type: f4 + doc: Quaternion z component. + + scale_key: + doc: | + Scale keyframe containing scale factors (LegoScaleKey). + Scale is applied relative to the local origin of the node. + seq: + - id: key + type: anim_key + doc: Base key with time and flags. + - id: x + type: f4 + doc: X scale factor (1.0 = no scaling). + - id: y + type: f4 + doc: Y scale factor (1.0 = no scaling). + - id: z + type: f4 + doc: Z scale factor (1.0 = no scaling). + + morph_key: + doc: | + Morph/visibility keyframe (LegoMorphKey). Controls whether the + node's ROI is visible at a given time. + seq: + - id: key + type: anim_key + doc: Base key with time and flags. + - id: visible + type: u1 + doc: Visibility flag. Non-zero means visible. + + rotation_z_key: + doc: | + Z-axis rotation keyframe (LegoRotationZKey). Used for camera roll + animation where only rotation around the view axis is needed. + seq: + - id: key + type: anim_key + doc: Base key with time and flags. + - id: z + type: f4 + doc: Rotation angle around the Z axis in radians. + +enums: + actor_type: + 2: managed_lego_actor + 3: managed_invisible_roi_trimmed + 4: managed_invisible_roi + 5: scene_roi_1 + 6: scene_roi_2 diff --git a/docs/samples/pns065rd.ani b/docs/samples/pns065rd.ani new file mode 100644 index 0000000000000000000000000000000000000000..7a9128831844dbd0d0923d6a6e3060d452169584 GIT binary patch literal 4788 zcmcgvdtA-g7XKYOqS8a&WzaN~QS=bE&TsEZ>48c^3N?k&)j4V;u191-N)d^aJQ`zg zMLBuYIlsNlq3by_%Bx(aVPXu)E24?+UcZAm&iprj+~4Q(JM~>>t+n_1uC>?N$AaTH z^TB64>l%0AqN!QfInqPJM4W`Z2JG#`-cTR-?808}fbqVeKJXn#Li}{)A$}ka>B({3 z*k6Z`pf5(cjSBT)|BDZ(!W-&i3Rq%xq0hO08om{=eV&WP){=d{`hti@-Eh*ZWmu+pEn=z1NKF&N zy~B*e{oTGbLc@aoD4yE^Bw_2#65 z#5mB#d5rN_c%#zN6(V>3w*^)FkmK@mdMKWGv$HEiA9*K)_8ny8n%rTD+s>4@00T=e zt)Q{PJYAnYwh}(^U!?>RAmd%x8xS-u*e^6V2rwt9Z`oY+T?da1C5L2Ssf)E5nazuF zL`fuK+G+4IEvdeY$+^z#g~FWR>*o+US!*jVw?ci6Pdc&vi*sYZyDrvhoV=5a?Y}7^ z%rFgJ@UAaoa`7=_pWuDMxOeUkd_ItD4_RB;_UK_HxYxy6_nzyP;mGo#AGz0;z4Ok& z8S@VIiFl9S2jmqx@k2llPsgI{S6#4ciK|GLF4nHtzVe%!NJ7BvotA~RC0qM(N z1vsG)(AWuHQ$M&Lwm*;eeX@b&frq3dHT}Vy*wC5^vmI5Fk6}Jp}KrNp%PR`ZDkj59Gt&*GuepoaA8j(8}*8 zPsb^Dl`5b{7mMm27=;4&_j*?%-S?Nc`+X9RnEG)r)5Tgd`24e>YPV?;7wWODy|44a zXYmBAbg@=r<(n1QL|rrD>048EA9c-P2t;4@-alA`_(3?FklH|={=gwuMeDqCMH^Mq)Q+_zBg zw=H)b(CbTLBJKz{>!`SJnDHnpewK5vMZ)Gq1;)Rf{&7_bIs3YV)B%pWEPF*81xKk3k+CY97h29&w&#+li}^B`w0r96dF3uS zsjw~LVaiHgcR^?8<&!J!{SZu3z&%2;MEr{3#ISFWp|>@9P+EuJe$iCa7ndw9$8&n; zVaV~NcwgMMx(RC*T*i=Jn~o?PY}$+E93!b!G1vS(e= zh-`wVjAcWrFN2KO9}B~eJIrNevyADXY#SA@`gm;^wqGIT6?VpSu+o@kRV>w)!I=$U zv29vuFUIzdm81;9htc@gyTr1v*Qy~WJ#Z{7hsG|^#TH;D$$h3mHZ28w|AKhBHh>p7 z**}@0(H7TaPtVAyJY|*0$(eptsCru^-zhkbt|^NJPNe!WsN82|0;;+&Q&@d+E`2)1 z1VQz>MI1$Ms$cWTifFncr;tDe@5v=7|A+%W{b~Y@dvQ&O?#4;TZ51-LUC2MQh@t`{8+D9RP!c_n6nCXsTa`B_(oY}%v@^O zVl7s2)wD`vdAC&Xnvy_GJPZ(UoY0$~FvA1t%oPfn+hQO$OG*EKm{)&Y38&d!SnnG0b2ZP>Wo!) zN72bUmkar$mk5w_eHkR!J7Xzc8fk}iA4-tjH!c$(!Fz|t;H9h`XBG?3DgfgeHy{CDdNnCP3o0y^O@;+1kG6NAT%vZD>CaZYSfP2j-&$dP&w?en5a0^F@;v z4rHTY9vczMsxrhR)iZy~n7?@D?=tfTNxF4jimR<7=(HDGg>C^J;wT7Xwtmbup4p}` zTS#0mTa&FV#v8!mg~a{M8{pLw7;n4Utg6~*PyKv0i#I@h8Mue%FE_yU&*R8X>;{_gflJ1UYK60QZ7bWup)_u)GX7pu{8D@w5SfpRE2(L1G z$jrAaGbt=Htp!7Q=fYwhGBd$E9-mFWjLM993Wf3#9x}6)tr5f4IR0)8$V?#fwT11a zmid~`_5zuya*V@1`Tr&1W&QXJtKB?1K2m)dWF}^`4X!@xLqk-Vd{@g^(B33!X6wmp zLz!(RvxUqAy<6iNTO*OJ0kr+y+igluADo#SAPZk_i^S8Tg0iFF$6KtylQF zdpz*HnZJk|)5Y4~>4%ais-3D*?|dMq^>uT^)R>ql(fvi;h*3c#JvQ(Ff{C(^b=1ME zqds9Bbq?#OkQz;*fNmNl@i!9Z((!A05|}8^c|U#EdE=Sh>291RQoAYCG@*vTT#srwit@7_2rAb|I`7Is1Sz!Xv>(kr2c z1=3N5tzuVvokdX3@K#xKqJsWlS*V7t*wRSQsa|<9E7mE$H#=ShUD4_gL3K;6%J@hH zy?3V-DqT{}I4BHPsdq3AzOJ7mrVHrQ|K%Aee=KBB4}A3SRq+7oVskK%L>!{%jZ>KV ziJO8B&N2|$T=VHx6f{hZ_8NImsZ9?GY|d>K(8hIB_-!T$w8q9joX@RmDM~NeB`fW! zpiY``BEz7!f$0@8y;`OR41-=8(>uiUoS7altS%S&{pm^vv1SaT z2WQZ+LDzZz!?USw$0gpmUqXpz7i%y0z{*aTcvJ zJ&M9}>iH*W3G{o;P`pJ&6;+|$q4W9XMg^5(N0If$yDHRYkRzY-tAb|qaR=6=`Z8dB V664Brc$oS* Date: Sat, 31 Jan 2026 12:01:38 -0800 Subject: [PATCH 02/17] Add Kaitai Struct definition for world database files (.wdb) (#1735) --- docs/README.md | 9 + docs/wdb.ksy | 784 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 793 insertions(+) create mode 100644 docs/wdb.ksy diff --git a/docs/README.md b/docs/README.md index 1c309a3e..9a2db4fb 100644 --- a/docs/README.md +++ b/docs/README.md @@ -19,6 +19,7 @@ Kaitai Struct allows you to define binary formats in a YAML-based `.ksy` file, w | [`players.ksy`](/docs/players.ksy) | `.gsi` | Player profile save data (usernames) | | [`history.ksy`](/docs/history.ksy) | `.gsi` | Score history and high scores | | [`animation.ksy`](/docs/animation.ksy) | `.ani` | Animation data (keyframes, actor references, camera animation) | +| [`wdb.ksy`](/docs/wdb.ksy) | `.wdb` | World database (textures, parts, models, ROI hierarchies, LODs) | ## Using the Tools @@ -42,6 +43,9 @@ ksv samples/History.gsi history.ksy # View an animation file ksv samples/pns065rd.ani animation.ksy + +# View the world database (from game installation) +ksv /path/to/lego/data/world.wdb wdb.ksy ``` ### Kaitai Struct Dump (ksdump) @@ -60,6 +64,9 @@ ksdump --format yaml samples/History.gsi history.ksy # Dump an animation file to JSON ksdump --format json samples/pns065rd.ani animation.ksy + +# Dump world database to YAML (from game installation) +ksdump --format yaml /path/to/lego/data/world.wdb wdb.ksy ``` ## Sample Files @@ -69,3 +76,5 @@ The [`samples/`](/docs/samples/) directory contains example files for testing: - `Players.gsi` - Sample player profile data - `History.gsi` - Sample score history data - `pns065rd.ani` - Sample animation file + +Note: The world database (`world.wdb`) can be found in your LEGO Island installation at `lego/data/world.wdb`. diff --git a/docs/wdb.ksy b/docs/wdb.ksy new file mode 100644 index 00000000..fec0c255 --- /dev/null +++ b/docs/wdb.ksy @@ -0,0 +1,784 @@ +meta: + id: wdb + title: World Database File + application: LEGO Island + file-extension: wdb + license: CC0-1.0 + endian: le + +doc: | + World Database file format for LEGO Island (1997). Contains world geometry + data including textures, parts (ROI definitions), and models with their + transforms and LOD (Level of Detail) information. + + The file is located at `/lego/data/world.wdb` on either + the hard drive or CD-ROM. + + File structure: + 1. World headers - list of worlds with part/model references + 2. Global textures - shared texture data (read once) + 3. Global parts - shared part definitions (read once) + 4. Part data blobs - at offsets specified in headers + 5. Model data blobs - at offsets specified in headers + +seq: + - id: num_worlds + type: s4 + doc: Number of world entries in this file. + - id: worlds + type: world_entry + repeat: expr + repeat-expr: num_worlds + doc: | + World entries containing references to parts and models. + Each world represents a distinct game area (e.g., "Act1", "Act2", "Act3"). + - id: global_textures_size + type: u4 + doc: Size in bytes of the global textures block. + - id: global_textures + type: texture_list + size: global_textures_size + doc: | + Global textures shared across all worlds. These are loaded once + when the first world is loaded and cached for subsequent worlds. + - id: global_parts_size + type: u4 + doc: Size in bytes of the global parts block. + - id: global_parts + type: part_list + size: global_parts_size + doc: | + Global parts (ROI definitions) shared across all worlds. + Like textures, these are loaded once and cached. + +types: + world_entry: + doc: | + A world entry containing references to parts and models. + Parts define reusable geometry, while models are placed instances + with specific transforms. + seq: + - id: name_length + type: s4 + doc: Length of the world name in bytes. + - id: name + type: str + size: name_length + encoding: ASCII + doc: | + World name used to identify this world (e.g., "Act1", "Act2", "Act3"). + - id: num_parts + type: s4 + doc: Number of part references in this world. + - id: parts + type: part_reference + repeat: expr + repeat-expr: num_parts + doc: References to part data stored elsewhere in the file. + - id: num_models + type: s4 + doc: Number of model entries in this world. + - id: models + type: model_entry + repeat: expr + repeat-expr: num_models + doc: | + Model entries with transform data. Each model references + geometry and specifies its position, orientation, and visibility. + + part_reference: + doc: | + Reference to part data stored at an offset in the file. + The actual part data contains ROI definitions with textures and LODs. + seq: + - id: name_length + type: u4 + doc: Length of the ROI name in bytes. + - id: name + type: str + size: name_length + encoding: ASCII + doc: ROI (Realtime Object Instance) name identifying this part. + - id: data_length + type: u4 + doc: Length of the part data in bytes. + - id: data_offset + type: u4 + doc: Absolute file offset to the part data. + instances: + data: + io: _root._io + pos: data_offset + size: data_length + type: part_data + doc: The actual part data at the specified offset. + + model_entry: + doc: | + A model entry defining a placed instance in the world. + Contains transform data (location, direction, up vector) and + a reference to the model geometry. + seq: + - id: name_length + type: u4 + doc: Length of the model name in bytes. + - id: name + type: str + size: name_length + encoding: ASCII + doc: | + Model name. Names starting with "isle" have quality variants + (isle_lo, isle, isle_hi). Names starting with "haus" have + special loading rules. + - id: data_length + type: u4 + doc: Length of the model data in bytes. + - id: data_offset + type: u4 + doc: Absolute file offset to the model data. + - id: presenter_name_length + type: u4 + doc: Length of the presenter class name in bytes. + - id: presenter_name + type: str + size: presenter_name_length + encoding: ASCII + doc: | + Presenter class name determining how the model is created. + Common values: "LegoActorPresenter", "LegoEntityPresenter". + - id: location + type: vertex3 + doc: World position of the model (X, Y, Z). + - id: direction + type: vertex3 + doc: Forward direction vector of the model. + - id: up + type: vertex3 + doc: Up direction vector of the model. + - id: visible + type: u1 + doc: Visibility flag. Non-zero means the model is initially visible. + instances: + data: + io: _root._io + pos: data_offset + size: data_length + type: model_data + doc: The model data (textures, animation, ROI) at the specified offset. + + texture_list: + doc: | + A list of named textures. Each texture includes palette and pixel data. + seq: + - id: num_textures + type: u4 + doc: Number of textures in this list. + - id: textures + type: named_texture + repeat: expr + repeat-expr: num_textures + doc: Array of named textures. + + named_texture: + doc: | + A named texture with 8-bit indexed color image data. + seq: + - id: name_length + type: u4 + doc: Length of the texture name in bytes. + - id: name + type: str + size: name_length + encoding: ASCII + doc: Texture name used for lookup. + - id: image + type: image + doc: The texture image data. + + image: + doc: | + An 8-bit indexed color image with palette. + seq: + - id: width + type: u4 + doc: Image width in pixels. + - id: height + type: u4 + doc: Image height in pixels. + - id: palette_size + type: u4 + doc: Number of entries in the color palette (max 256). + - id: palette + type: palette_entry + repeat: expr + repeat-expr: palette_size + doc: Color palette entries. + - id: pixels + size: width * height + doc: | + Pixel data as palette indices. Each byte is an index into + the palette array. + + palette_entry: + doc: RGB color palette entry. + seq: + - id: red + type: u1 + doc: Red component (0-255). + - id: green + type: u1 + doc: Green component (0-255). + - id: blue + type: u1 + doc: Blue component (0-255). + + part_list: + doc: | + A list of named parts (ROI definitions). Parts can be shared + across multiple models and worlds. + seq: + - id: texture_info_offset + type: u4 + doc: Offset within this block to texture information. + - id: num_rois + type: u4 + doc: Number of ROI definitions. + - id: rois + type: named_part + repeat: expr + repeat-expr: num_rois + doc: Array of named part definitions. + + named_part: + doc: | + A named part containing LOD (Level of Detail) definitions. + seq: + - id: name_length + type: u4 + doc: Length of the ROI name in bytes. + - id: name + type: str + size: name_length + encoding: ASCII + doc: ROI name for lookup. + - id: num_lods + type: u4 + doc: Number of LOD levels for this part. + - id: next_roi_offset + type: u4 + doc: Offset to the next ROI definition (for skipping LOD data). + - id: lods + type: lod + repeat: expr + repeat-expr: num_lods + doc: LOD definitions from highest to lowest detail. + + part_data: + doc: | + Part data blob containing textures and ROI definitions. + This is the format used for part data at offsets in the file. + seq: + - id: texture_info_offset + type: u4 + doc: Offset within this block to texture information. + - id: num_rois + type: u4 + doc: Number of ROI definitions in this part. + - id: rois + type: named_part + repeat: expr + repeat-expr: num_rois + doc: ROI definitions for this part. + + model_data: + doc: | + Model data blob containing version info, textures, animation data, + and ROI hierarchy. This is the format used for model data at offsets. + Parsed by LegoModelPresenter::CreateROI. + seq: + - id: version + type: u4 + doc: Format version. Must be 19 (MODEL_VERSION). + - id: texture_info_offset + type: u4 + doc: Offset within this blob to texture information. + - id: num_rois + type: u4 + doc: Number of ROIs (typically 1 for models). + - id: anim + type: model_anim + doc: Animation data for this model. + - id: roi + type: roi + doc: The root ROI containing the model geometry. + + model_anim: + doc: | + Animation data embedded in model data. This is a simplified form + of LegoAnim without camera/scene animation (p_parseScene=FALSE). + seq: + - id: num_actors + type: u4 + doc: Number of actor entries. + - id: actors + type: anim_actor_entry + repeat: expr + repeat-expr: num_actors + doc: Actor entries for this animation. + - id: duration + type: s4 + doc: Animation duration in milliseconds. + - id: root_node + type: anim_tree_node + doc: Root node of the animation tree. + + anim_actor_entry: + doc: | + An actor reference in the animation. The name identifies which ROI + (Realtime Object Instance) to animate, and the type determines + how the actor is managed by the character manager. + seq: + - id: name_length + type: u4 + doc: Length of the actor name in bytes. + - id: name + type: str + size: name_length + encoding: ASCII + if: name_length > 0 + doc: Actor name used to look up the ROI in the scene. + - id: actor_type + type: u4 + enum: actor_type + if: name_length > 0 + doc: | + Determines how the actor ROI is created and managed. + See actor_type enum for possible values. + + anim_tree_node: + doc: Node in the animation tree hierarchy. + seq: + - id: data + type: anim_node_data + doc: Animation keyframe data for this node. + - id: num_children + type: u4 + doc: Number of child nodes. + - id: children + type: anim_tree_node + repeat: expr + repeat-expr: num_children + doc: Child nodes. + + anim_node_data: + doc: Animation keyframe data for a single node. + seq: + - id: name_length + type: u4 + doc: Length of node name. + - id: name + type: str + size: name_length + encoding: ASCII + if: name_length > 0 + doc: Node name for matching to ROI. + - id: num_translation_keys + type: u2 + doc: Number of translation keyframes. + - id: translation_keys + type: translation_key + repeat: expr + repeat-expr: num_translation_keys + doc: Translation keyframes. + - id: num_rotation_keys + type: u2 + doc: Number of rotation keyframes. + - id: rotation_keys + type: rotation_key + repeat: expr + repeat-expr: num_rotation_keys + doc: Rotation keyframes (quaternion format). + - id: num_scale_keys + type: u2 + doc: Number of scale keyframes. + - id: scale_keys + type: scale_key + repeat: expr + repeat-expr: num_scale_keys + doc: Scale keyframes. + - id: num_morph_keys + type: u2 + doc: Number of morph (visibility) keyframes. + - id: morph_keys + type: morph_key + repeat: expr + repeat-expr: num_morph_keys + doc: Morph keyframes. + + anim_key: + doc: | + Base animation key containing time and flags. The time and flags + are packed into a single 32-bit value: bits 0-23 contain the time + in milliseconds, and bits 24-31 contain flags. + seq: + - id: time_and_flags + type: s4 + doc: | + Packed time and flags value. + - Bits 0-23: Time in milliseconds (mask with 0xFFFFFF) + - Bits 24-31: Flags (shift right by 24) + instances: + time: + value: time_and_flags & 0xFFFFFF + doc: Keyframe time in milliseconds. + flags: + value: (time_and_flags >> 24) & 0xFF + doc: | + Keyframe flags: + - 0x01 (active): Key has meaningful data + - 0x02 (negate_rotation): Negate quaternion for interpolation + - 0x04 (skip_interpolation): Use this key's value without blending + + translation_key: + doc: | + Translation keyframe containing position offset (LegoTranslationKey). + The translation is applied relative to the parent node's transform. + seq: + - id: key + type: anim_key + doc: Base key with time and flags. + - id: x + type: f4 + doc: X component of translation. + - id: y + type: f4 + doc: Y component of translation. + - id: z + type: f4 + doc: Z component of translation. + + rotation_key: + doc: | + Rotation keyframe containing a quaternion (LegoRotationKey). + The quaternion is stored as (angle, x, y, z) where angle is the + scalar/w component and (x, y, z) is the vector part. + seq: + - id: key + type: anim_key + doc: Base key with time and flags. + - id: angle + type: f4 + doc: | + Quaternion scalar component (w). A value of 1.0 with x=y=z=0 + represents no rotation (identity quaternion). + - id: x + type: f4 + doc: Quaternion x component. + - id: y + type: f4 + doc: Quaternion y component. + - id: z + type: f4 + doc: Quaternion z component. + + scale_key: + doc: | + Scale keyframe containing scale factors (LegoScaleKey). + Scale is applied relative to the local origin of the node. + seq: + - id: key + type: anim_key + doc: Base key with time and flags. + - id: x + type: f4 + doc: X scale factor (1.0 = no scaling). + - id: y + type: f4 + doc: Y scale factor (1.0 = no scaling). + - id: z + type: f4 + doc: Z scale factor (1.0 = no scaling). + + morph_key: + doc: | + Morph/visibility keyframe (LegoMorphKey). Controls whether the + node's ROI is visible at a given time. + seq: + - id: key + type: anim_key + doc: Base key with time and flags. + - id: visible + type: u1 + doc: Visibility flag. Non-zero means visible. + + roi: + doc: | + ROI (Realtime Object Instance) defining a piece of geometry. + ROIs form a hierarchy with parent-child relationships. + seq: + - id: name_length + type: u4 + doc: Length of the ROI name in bytes. + - id: name + type: str + size: name_length + encoding: ASCII + doc: ROI name used for lookup and animation binding. + - id: bounding_sphere + type: sphere + doc: Bounding sphere for visibility culling. + - id: bounding_box + type: box + doc: Axis-aligned bounding box. + - id: texture_name_length + type: u4 + doc: Length of texture/material name (0 if none). + - id: texture_name + type: str + size: texture_name_length + encoding: ASCII + if: texture_name_length > 0 + doc: | + Texture or material name. Names starting with "t_" reference + textures; other names are color aliases (e.g., "lego red"). + - id: shared_lod_list + type: u1 + doc: | + If non-zero, LODs are shared with another ROI and not stored here. + The ROI name (minus trailing digits) is used to look up shared LODs. + - id: num_lods + type: u4 + if: shared_lod_list == 0 + doc: Number of LOD levels (only if not using shared LODs). + - id: next_roi_offset + type: u4 + if: shared_lod_list == 0 and num_lods > 0 + doc: Offset to continue reading after LOD data. + - id: lods + type: lod + repeat: expr + repeat-expr: num_lods + if: shared_lod_list == 0 and num_lods > 0 + doc: LOD definitions from highest to lowest detail. + - id: num_children + type: u4 + doc: Number of child ROIs in this hierarchy. + - id: children + type: roi + repeat: expr + repeat-expr: num_children + doc: Child ROIs forming a hierarchy. + + sphere: + doc: Bounding sphere defined by center point and radius. + seq: + - id: center + type: vertex3 + doc: Center point of the sphere. + - id: radius + type: f4 + doc: Radius of the sphere. + + box: + doc: Axis-aligned bounding box defined by min and max corners. + seq: + - id: min + type: vertex3 + doc: Minimum corner (smallest X, Y, Z values). + - id: max + type: vertex3 + doc: Maximum corner (largest X, Y, Z values). + + vertex3: + doc: A 3D point or vector with X, Y, Z components. + seq: + - id: x + type: f4 + doc: X component. + - id: y + type: f4 + doc: Y component. + - id: z + type: f4 + doc: Z component. + + lod: + doc: | + Level of Detail definition containing mesh data. + LODs are ordered from highest to lowest detail. + seq: + - id: flags + type: u4 + doc: | + LOD flags. Bit 0 (0x01) indicates this is an "extra" LOD. + Other bits control visibility and rendering behavior. + - id: num_meshes + type: u4 + doc: Number of meshes in this LOD. + - id: vertex_normal_counts + type: u4 + if: num_meshes > 0 + doc: | + Packed vertex and normal counts. + Lower 16 bits: vertex count + Upper 15 bits (shifted right by 1): normal count + - id: num_texture_vertices + type: s4 + if: num_meshes > 0 + doc: Number of texture coordinate pairs. + - id: vertices + type: vertex3 + repeat: expr + repeat-expr: vertex_count + if: num_meshes > 0 and vertex_count > 0 + doc: Vertex positions shared across meshes. + - id: normals + type: vertex3 + repeat: expr + repeat-expr: normal_count + if: num_meshes > 0 and normal_count > 0 + doc: Normal vectors shared across meshes. + - id: texture_vertices + type: texture_vertex + repeat: expr + repeat-expr: num_texture_vertices + if: num_meshes > 0 and num_texture_vertices > 0 + doc: Texture coordinates (UV pairs). + - id: meshes + type: mesh + repeat: expr + repeat-expr: num_meshes + if: num_meshes > 0 + doc: Mesh definitions using the shared vertex/normal/UV data. + instances: + vertex_count: + value: '(num_meshes > 0) ? (vertex_normal_counts & 0xFFFF) : 0' + doc: Number of vertices (lower 16 bits of packed value). + normal_count: + value: '(num_meshes > 0) ? ((vertex_normal_counts >> 17) & 0x7FFF) : 0' + doc: Number of normals (upper 15 bits, shifted right by 1). + + texture_vertex: + doc: Texture coordinate pair (UV). + seq: + - id: u + type: f4 + doc: U coordinate (horizontal, 0.0-1.0). + - id: v + type: f4 + doc: V coordinate (vertical, 0.0-1.0). + + mesh: + doc: | + A mesh within an LOD, containing polygons and material properties. + seq: + - id: num_polygons + type: u2 + doc: Number of triangular polygons. + - id: num_vertices + type: u2 + doc: Number of vertices used by this mesh. + - id: polygon_indices + type: polygon_indices + repeat: expr + repeat-expr: num_polygons + doc: Vertex indices for each triangle. + - id: num_texture_indices + type: u4 + doc: Number of texture index sets (0 if untextured). + - id: texture_indices + type: polygon_indices + repeat: expr + repeat-expr: num_polygons + if: num_texture_indices > 0 + doc: Texture coordinate indices for each triangle. + - id: properties + type: mesh_properties + doc: Material and rendering properties. + + polygon_indices: + doc: | + Three vertex/normal index pairs forming a triangle. Each 32-bit value + is a packed index used by Direct3D Retained Mode, containing both + vertex and normal indices. + seq: + - id: a + type: u4 + doc: First packed vertex/normal index. + - id: b + type: u4 + doc: Second packed vertex/normal index. + - id: c + type: u4 + doc: Third packed vertex/normal index. + + mesh_properties: + doc: | + Material and rendering properties for a mesh. + seq: + - id: color + type: color_rgb + doc: Base color of the mesh. + - id: alpha + type: f4 + doc: Transparency (0.0 = fully transparent, 1.0 = opaque). + - id: shading + type: u1 + enum: shading_mode + doc: Shading mode for rendering this mesh. + - id: unknown_0x0d + type: u1 + doc: Unknown flag. When > 0, special material is applied. + - id: unknown_0x20 + type: u1 + doc: Unknown field. + - id: use_alias + type: u1 + doc: | + If non-zero, texture_name and material_name are looked up + as aliases rather than literal names. + - id: texture_name_length + type: u4 + doc: Length of texture name (0 if no texture). + - id: texture_name + type: str + size: texture_name_length + encoding: ASCII + if: texture_name_length > 0 + doc: Texture name for this mesh. + - id: material_name_length + type: u4 + doc: Length of material/color name (0 if none). + - id: material_name + type: str + size: material_name_length + encoding: ASCII + if: material_name_length > 0 + doc: | + Material or color alias name (e.g., "lego red", "lego blue"). + + color_rgb: + doc: RGB color with 8-bit components. + seq: + - id: red + type: u1 + doc: Red component (0-255). + - id: green + type: u1 + doc: Green component (0-255). + - id: blue + type: u1 + doc: Blue component (0-255). + +enums: + shading_mode: + 0: flat + 1: gouraud + 2: wireframe + + actor_type: + 2: managed_lego_actor + 3: managed_invisible_roi_trimmed + 4: managed_invisible_roi + 5: scene_roi_1 + 6: scene_roi_2 From e63449fd91da6ee898b87850197ff90cc7e3d2e8 Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Sat, 31 Jan 2026 13:12:53 -0800 Subject: [PATCH 03/17] Fix polygon and texture indices documentation in wdb.ksy (#1736) Correct the bit layout for polygon_indices to use bits 0-15 (16 bits) for vertex index instead of incorrectly claiming bits 0-14 with bit 15 unused. Add separate texture_indices type since these are simple U32 values, not packed like polygon_indices. Clarify num_texture_indices should equal num_polygons * 3 when textured. --- docs/wdb.ksy | 53 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/docs/wdb.ksy b/docs/wdb.ksy index fec0c255..b29d7fc9 100644 --- a/docs/wdb.ksy +++ b/docs/wdb.ksy @@ -685,32 +685,67 @@ types: doc: Vertex indices for each triangle. - id: num_texture_indices type: u4 - doc: Number of texture index sets (0 if untextured). + doc: | + Total number of texture indices. Should equal num_polygons * 3 + if textured, or 0 if untextured. - id: texture_indices - type: polygon_indices + type: texture_indices repeat: expr repeat-expr: num_polygons if: num_texture_indices > 0 - doc: Texture coordinate indices for each triangle. + doc: | + Texture coordinate indices for each triangle. Unlike polygon_indices, + these are simple U32 indices into the LOD's texture_vertices array, + not packed values. Each index directly references a UV coordinate pair. - id: properties type: mesh_properties doc: Material and rendering properties. polygon_indices: doc: | - Three vertex/normal index pairs forming a triangle. Each 32-bit value - is a packed index used by Direct3D Retained Mode, containing both - vertex and normal indices. + Three packed indices forming a triangle. Each 32-bit value contains + vertex index, normal index, and a "create vertex" flag used by + Direct3D Retained Mode mesh building. + + Bit layout of each packed value: + - Bits 0-15 (16 bits): When create flag is set, this is the index into + the LOD's vertices array. When create flag is clear, this is the index + into the mesh's built vertex buffer (referencing a previously created vertex). + - Bits 16-30 (15 bits): Index into the LOD's normals array + - Bit 31: Create vertex flag. When set (1), a new mesh vertex is created + combining position, normal, and texture UV. When clear (0), the value + in bits 0-15 references an existing mesh vertex by index. + + The mesh builder creates a vertex buffer where each unique position+normal+UV + combination gets an entry. Texture indices (in texture_indices) are only + consumed when the create flag is set. seq: - id: a type: u4 - doc: First packed vertex/normal index. + doc: First packed vertex/normal index with create flag. - id: b type: u4 - doc: Second packed vertex/normal index. + doc: Second packed vertex/normal index with create flag. - id: c type: u4 - doc: Third packed vertex/normal index. + doc: Third packed vertex/normal index with create flag. + + texture_indices: + doc: | + Three texture coordinate indices forming a triangle. Unlike polygon_indices, + these are simple U32 values that directly index into the LOD's texture_vertices + array. Each value is only used when the corresponding polygon_indices entry + has its create flag (bit 31) set. + seq: + - id: a + type: u4 + doc: First texture vertex index. + - id: b + type: u4 + doc: Second texture vertex index. + - id: c + type: u4 + doc: Third texture vertex index. mesh_properties: doc: | From 56ed4d33395765cd96ba3e04c0f994c4e9307ffd Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Sun, 1 Feb 2026 12:33:31 -0800 Subject: [PATCH 04/17] Fix ConvertHSVToRGB parameter labels (#1737) --- LEGO1/lego/legoomni/include/legoutils.h | 2 +- LEGO1/lego/legoomni/src/common/legoutils.cpp | 32 ++++++++++---------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/LEGO1/lego/legoomni/include/legoutils.h b/LEGO1/lego/legoomni/include/legoutils.h index dc5d951c..44b78ec8 100644 --- a/LEGO1/lego/legoomni/include/legoutils.h +++ b/LEGO1/lego/legoomni/include/legoutils.h @@ -53,7 +53,7 @@ void CalculateViewFromAnimation(LegoAnimPresenter* p_presenter); Extra::ActionType MatchActionString(const char*); void InvokeAction(Extra::ActionType p_actionId, const MxAtomId& p_pAtom, MxS32 p_streamId, LegoEntity* p_sender); void SetCameraControllerFromIsle(); -void ConvertHSVToRGB(float p_h, float p_s, float p_v, float* p_rOut, float* p_bOut, float* p_gOut); +void ConvertHSVToRGB(float p_h, float p_s, float p_v, float* p_rOut, float* p_gOut, float* p_bOut); void PlayCamAnim(LegoPathActor* p_actor, MxBool p_unused, MxU32 p_location, MxBool p_bool); void ResetViewVelocity(); MxBool RemoveFromCurrentWorld(const MxAtomId& p_atomId, MxS32 p_id); diff --git a/LEGO1/lego/legoomni/src/common/legoutils.cpp b/LEGO1/lego/legoomni/src/common/legoutils.cpp index 9b62e5b1..dc310953 100644 --- a/LEGO1/lego/legoomni/src/common/legoutils.cpp +++ b/LEGO1/lego/legoomni/src/common/legoutils.cpp @@ -385,7 +385,7 @@ void SetCameraControllerFromIsle() } // FUNCTION: LEGO1 0x1003eae0 -void ConvertHSVToRGB(float p_h, float p_s, float p_v, float* p_rOut, float* p_bOut, float* p_gOut) +void ConvertHSVToRGB(float p_h, float p_s, float p_v, float* p_rOut, float* p_gOut, float* p_bOut) { double calc; double p; @@ -403,8 +403,8 @@ void ConvertHSVToRGB(float p_h, float p_s, float p_v, float* p_rOut, float* p_bO calc = (p_v + 1.0) * sDbl; } if (calc <= 0.0) { - *p_gOut = 0.0f; *p_bOut = 0.0f; + *p_gOut = 0.0f; *p_rOut = 0.0f; return; } @@ -416,38 +416,38 @@ void ConvertHSVToRGB(float p_h, float p_s, float p_v, float* p_rOut, float* p_bO switch (hueIndex) { case 0: *p_rOut = calc; - *p_bOut = v12; - *p_gOut = p; + *p_gOut = v12; + *p_bOut = p; break; case 1: *p_rOut = v13; - *p_bOut = calc; - *p_gOut = p; + *p_gOut = calc; + *p_bOut = p; break; case 2: *p_rOut = p; - *p_bOut = calc; - *p_gOut = v12; + *p_gOut = calc; + *p_bOut = v12; break; case 3: *p_rOut = p; - *p_bOut = v13; - *p_gOut = calc; + *p_gOut = v13; + *p_bOut = calc; break; case 4: *p_rOut = v12; - *p_bOut = p; - *p_gOut = calc; + *p_gOut = p; + *p_bOut = calc; break; case 5: *p_rOut = calc; - *p_bOut = p; - *p_gOut = v13; + *p_gOut = p; + *p_bOut = v13; break; case 6: *p_rOut = calc; - *p_bOut = p; - *p_gOut = v13; + *p_gOut = p; + *p_bOut = v13; break; default: return; From 77bb4634b8034cc450a3451b6748304dc38bbeec Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Sun, 1 Feb 2026 12:43:41 -0800 Subject: [PATCH 05/17] Fix savegame.ksy: backgroundcolor uses HSV, not RGB (#1738) The backgroundcolor and tempBackgroundColor variables store HSV values (scaled 0-100), not RGB. The game converts to RGB using ConvertHSVToRGB(). --- docs/savegame.ksy | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/savegame.ksy b/docs/savegame.ksy index f08d5c22..50682556 100644 --- a/docs/savegame.ksy +++ b/docs/savegame.ksy @@ -101,7 +101,9 @@ types: if: not is_end_marker doc: | Variable value. For colors this is a color name like "lego red". - For backgroundcolor this is "set R G B". + For backgroundcolor and tempBackgroundColor this is "set H S V" where + H, S, V are HSV color values scaled 0-100 (not RGB). The game internally + converts to RGB using ConvertHSVToRGB(). For lightposition this is a number "1" or "2". instances: is_end_marker: From eaf603941ed976b495feb598f830fd015111d94e Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Sun, 1 Feb 2026 12:53:19 -0800 Subject: [PATCH 06/17] Fix lightposition documentation in savegame.ksy (#1739) The variable accepts values "0" through "5" (6 sun positions), not "1" or "2" as previously documented. --- docs/savegame.ksy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/savegame.ksy b/docs/savegame.ksy index 50682556..745b2b39 100644 --- a/docs/savegame.ksy +++ b/docs/savegame.ksy @@ -104,7 +104,7 @@ types: For backgroundcolor and tempBackgroundColor this is "set H S V" where H, S, V are HSV color values scaled 0-100 (not RGB). The game internally converts to RGB using ConvertHSVToRGB(). - For lightposition this is a number "1" or "2". + For lightposition this is a number "0" through "5" (6 sun positions). instances: is_end_marker: value: name == "END_OF_VARIABLES" From 464e59df3e3ce631bfccd4d90f2c060dcc1949b8 Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Sun, 1 Feb 2026 16:34:29 -0800 Subject: [PATCH 07/17] Add Kaitai Struct definition for animation data files (.dta) (#1740) --- docs/README.md | 8 +++ docs/dta.ksy | 135 +++++++++++++++++++++++++++++++++++++++ docs/samples/BLDRINF.DTA | Bin 0 -> 562 bytes 3 files changed, 143 insertions(+) create mode 100644 docs/dta.ksy create mode 100755 docs/samples/BLDRINF.DTA diff --git a/docs/README.md b/docs/README.md index 9a2db4fb..df933a8a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -20,6 +20,7 @@ Kaitai Struct allows you to define binary formats in a YAML-based `.ksy` file, w | [`history.ksy`](/docs/history.ksy) | `.gsi` | Score history and high scores | | [`animation.ksy`](/docs/animation.ksy) | `.ani` | Animation data (keyframes, actor references, camera animation) | | [`wdb.ksy`](/docs/wdb.ksy) | `.wdb` | World database (textures, parts, models, ROI hierarchies, LODs) | +| [`dta.ksy`](/docs/dta.ksy) | `.dta` | Animation data (world animation info, model placement) | ## Using the Tools @@ -46,6 +47,9 @@ ksv samples/pns065rd.ani animation.ksy # View the world database (from game installation) ksv /path/to/lego/data/world.wdb wdb.ksy + +# View an animation data file +ksv samples/BLDRINF.DTA dta.ksy ``` ### Kaitai Struct Dump (ksdump) @@ -67,6 +71,9 @@ ksdump --format json samples/pns065rd.ani animation.ksy # Dump world database to YAML (from game installation) ksdump --format yaml /path/to/lego/data/world.wdb wdb.ksy + +# Dump an animation data file to JSON +ksdump --format json samples/BLDRINF.DTA dta.ksy ``` ## Sample Files @@ -76,5 +83,6 @@ The [`samples/`](/docs/samples/) directory contains example files for testing: - `Players.gsi` - Sample player profile data - `History.gsi` - Sample score history data - `pns065rd.ani` - Sample animation file +- `BLDRINF.DTA` - Sample animation data file Note: The world database (`world.wdb`) can be found in your LEGO Island installation at `lego/data/world.wdb`. diff --git a/docs/dta.ksy b/docs/dta.ksy new file mode 100644 index 00000000..2590df71 --- /dev/null +++ b/docs/dta.ksy @@ -0,0 +1,135 @@ +meta: + id: dta + title: Animation Data File + application: LEGO Island + file-extension: dta + license: CC0-1.0 + endian: le + +doc: | + Animation data file format for LEGO Island (1997). Contains animation + information for world objects including their positions, orientations, + and associated models. + + DTA files are located at `/lego/data/inf.dta` where + is the world name (e.g., "isle", "act1", "act2m", etc.). They are + loaded by LegoAnimationManager::LoadWorldInfo() to populate animation + information for the current world. + + File structure: + 1. Header - version (must be 3) and animation count + 2. AnimInfo entries - animation references with nested model placement data + +seq: + - id: version + type: u4 + doc: | + File format version. Must be 3 for valid files. + The game rejects files with mismatched versions. + - id: num_anims + type: u2 + doc: Number of animation info entries in this file. + - id: anims + type: anim_info + repeat: expr + repeat-expr: num_anims + doc: Animation information entries. + +types: + anim_info: + doc: | + Animation information for a single animation (AnimInfo struct). + Contains metadata about the animation and a list of models involved. + Parsed by LegoAnimationManager::ReadAnimInfo(). + seq: + - id: name_length + type: u1 + doc: Length of the animation name in bytes. + - id: name + type: str + size: name_length + encoding: ASCII + doc: | + Animation name identifier. The last two characters are used + to look up a character index via GetCharacterIndex(). + - id: object_id + type: u4 + doc: Object ID used to identify this animation in the game. + - id: location + type: s2 + doc: | + Location index referencing a LegoLocation. A value of -1 + indicates no specific location is assigned. + - id: unk_0x0a + type: u1 + doc: Boolean flag (MxBool). Purpose unknown. + - id: unk_0x0b + type: u1 + doc: Unknown byte field. + - id: unk_0x0c + type: u1 + doc: Unknown byte field. + - id: unk_0x0d + type: u1 + doc: Unknown byte field. + - id: unk_0x10 + type: f4 + repeat: expr + repeat-expr: 4 + doc: Array of 4 unknown float values (16 bytes total). + - id: model_count + type: u1 + doc: Number of model entries that follow. + - id: models + type: model_info + repeat: expr + repeat-expr: model_count + doc: Model information for each model in this animation. + + model_info: + doc: | + Model information defining position and orientation for a single + model within an animation (ModelInfo struct). Used to place characters + and objects in the world during animation playback. + Parsed by LegoAnimationManager::ReadModelInfo(). + seq: + - id: name_length + type: u1 + doc: Length of the model name in bytes. + - id: name + type: str + size: name_length + encoding: ASCII + doc: | + Model name used to look up the character or vehicle. + Examples: "caprc01" (race car), "irt001d1" (character). + - id: unk_0x04 + type: u1 + doc: Unknown byte field. + - id: position + type: vertex3 + doc: World position (X, Y, Z) of the model. + - id: direction + type: vertex3 + doc: Forward direction vector of the model. + - id: up + type: vertex3 + doc: Up direction vector of the model. + - id: unk_0x2c + type: u1 + doc: | + Boolean flag. When non-zero, this model is considered a vehicle + and tracked in the animation's vehicle list (m_unk0x2a). + + vertex3: + doc: A 3D point or vector with X, Y, Z components. + seq: + - id: x + type: f4 + doc: X component. + - id: y + type: f4 + doc: Y component. + - id: z + type: f4 + doc: Z component. diff --git a/docs/samples/BLDRINF.DTA b/docs/samples/BLDRINF.DTA new file mode 100755 index 0000000000000000000000000000000000000000..d12752b103088e0624b49b505818b16818f1a4e7 GIT binary patch literal 562 zcmZQ(U|?Wl;K(d0F)%PpG5o^F!0`V+BZEAUMg`1FDTYj!l0zLhFA3Ytecyh7fuX@3 zq-5TbMOz?BA$&#-t>nalqGSU@=DT`j4mWi#*)=?#cHrMtA^Rtr1NVQ=vo|%^Z@rD9 z-*Maj|MmOf%8i&UwAAgnGI!d)FKPms&cJZESH-?m+GRh84Wfbk{ZOYGr5Jw2=~PCL zQ-AO@IDj-h3s*RBnmGgP)U?DKb|5jB(-|TDHcm18hTpJT3eEe9e>2-XQc(dk><8R1X0@eYwd+MQ_s6~JbpU(Q zD0;{KIZW{m*I0vqhCx8{(R8qPziG??(@<}krWpQ&8wOJgqrrx;z5KK<#ktkN Date: Sat, 7 Feb 2026 08:31:49 -0800 Subject: [PATCH 08/17] Add Kaitai Struct definition for texture data files (.tex) (#1741) --- docs/README.md | 8 ++++ docs/samples/Dbfrfn.tex | Bin 0 -> 16524 bytes docs/tex.ksy | 91 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+) create mode 100755 docs/samples/Dbfrfn.tex create mode 100644 docs/tex.ksy diff --git a/docs/README.md b/docs/README.md index df933a8a..de0f2ffe 100644 --- a/docs/README.md +++ b/docs/README.md @@ -21,6 +21,7 @@ Kaitai Struct allows you to define binary formats in a YAML-based `.ksy` file, w | [`animation.ksy`](/docs/animation.ksy) | `.ani` | Animation data (keyframes, actor references, camera animation) | | [`wdb.ksy`](/docs/wdb.ksy) | `.wdb` | World database (textures, parts, models, ROI hierarchies, LODs) | | [`dta.ksy`](/docs/dta.ksy) | `.dta` | Animation data (world animation info, model placement) | +| [`tex.ksy`](/docs/tex.ksy) | `.tex` | Texture data (named textures with palette and pixel data) | ## Using the Tools @@ -50,6 +51,9 @@ ksv /path/to/lego/data/world.wdb wdb.ksy # View an animation data file ksv samples/BLDRINF.DTA dta.ksy + +# View a texture data file +ksv samples/Dbfrfn.tex tex.ksy ``` ### Kaitai Struct Dump (ksdump) @@ -74,6 +78,9 @@ ksdump --format yaml /path/to/lego/data/world.wdb wdb.ksy # Dump an animation data file to JSON ksdump --format json samples/BLDRINF.DTA dta.ksy + +# Dump a texture data file to JSON +ksdump --format json samples/Dbfrfn.tex tex.ksy ``` ## Sample Files @@ -84,5 +91,6 @@ The [`samples/`](/docs/samples/) directory contains example files for testing: - `History.gsi` - Sample score history data - `pns065rd.ani` - Sample animation file - `BLDRINF.DTA` - Sample animation data file +- `Dbfrfn.tex` - Sample texture data file (dune buggy front fender) Note: The world database (`world.wdb`) can be found in your LEGO Island installation at `lego/data/world.wdb`. diff --git a/docs/samples/Dbfrfn.tex b/docs/samples/Dbfrfn.tex new file mode 100755 index 0000000000000000000000000000000000000000..dcda861dd192ce07dc4c263f5ca7051168a0c81b GIT binary patch literal 16524 zcmeI3y{jZ!6~${_*?OynZn~y56hxjFhz4S2GMS-?prME}J`)2$cxWOMi-Dnt7-()f z2L1#7Az~yL7!1gB{nkGF)T!!^+qdVAAk68Ws_LpbXRW>V$2nE+z9(JReN+E__=A@} zefg8G{piOpyN^H6?~nfdQTHY7zo$Lz`~Qi3uj3DWrt|gvKl@+mC;vXHe{bHr`Q-1v z`{Z@^`t|F7{PqvO`RiZ)_3OJo|NiSg|I_DRy?XWH#fx8jd;H^9KI}gJ>d(IM-M>Bi zLie3tJ%9fETc0~T{qh&p{e63{zi&($HeBbg|3?6QKGJFgM zXVAxe`Dj;;61CmGb#Xc&eT5DRCNOZUZ;S2Y;q9v9F?_&{!(Na=WM4mAJ7Vw_5*(om zK%XIDb8iArLV_a-H`l~9{EQAD;3lBcgY^gJuCd&z*?{fTIV z-@*wGQJ}RRmL(60pU^FRY{YzEuyCGDr4bSS?@ z)NgJn4GE-yP<8eoG7bPP&IB$hFvmXBUB)SLhe_zu)K&P#`U0(59HyrR%1#2tQMe#* zDGBG;w{Y0zG*_SgApFo=@z&^I0)ppG(nG$I>4&L*3ceWVhNci+^1xhoF+UtIjp294 zdU3j=Eh!kEO~IE(bfnTmMq)nKS_0<x;QVr^AM&Qajx7=SAxiD@NF%c5uv{Ovx*xR!a zSj`XXwIz+PO`}`kac~wC4jY-rrM69zhSznTgNMYo18NyMCqNB;YoF6dh)`jYfSzr% z=xCZFW{)2x5zwdW!vVNpztiPP;EVvR`ItWJZ9f6bxO)Oy&L|UrTwKv@pMyh5 zHE~9?h(8FV2TrFzHGd0Nft!Lou-_R!7$_J#ydzApAs@4eNQ+n$F*{}|@s9<3D}hrf zP|XKE?DrI65nN3fL=9g!vnk6u(uoKI;v`%i?lL-7aKNC~;Tzuf7aHv`l!AkdRtXTE zoa2}yG-47`>6g2rw}hF6yhI@FH(Kdz+c7{b3@J)lbFKcQfYd)cb4bNz7>Ema(M8+D z7m>t25rGN6)%}{e%p!Wn0C6Hb!d0P8jxlS!(}*K+;pC(C*hkDq@)_e_L2nfeMI7n&P8%BVxM*LZCCA=9* z{xT=kO0C~Gg&TCJ?kT{L1JZoCpB4lP{QfX)Iz^SJ*mfbSNs!Q*f>Q~^${-X_6?RMz z0Rp!h3xTuv0k1Vr1VEav+(P2Wa}8&IjJ9)wFhM|fX@+Jonv<1&P$o61a)`Um36S;& zzRc%t@_>Uwk0&*bD>|ZTIJ8#Gk!`9$Qbq?Q`oj-Jgt*Qy44L@r0Lb&k5AO#RZ7DQk z-=R1q2l!DhSq>a`8%YMjyv6Yz*i zc3LdW1n4Y2!8bb11XlJZexM`p+$_3-N@L*-eI+W53C_|xFLZbi0W>IHiV7eH7+xVg z)c{ydfepT&Aj@Nvb4K_W6@J(JUa}y94!;M5@Byo2Me2xT+$jY}5I2&lCcrWPE51aH z>Bi6=Bk~*q9zo$k#m#C9fp~y2XQEzi?zwiJ$go6twV+ z8t~X^djqss9}!Url{lej(M_FQP!s7iJ_CjT#fF&5NGCVX#4b^W22%gTg>OP@@83FS z5fq6OZo^mqpTpJxF1_Rg#QM%e`g{Rx0=4hoS_Fz*xyuc)wwC=4;r7K){K$4$9ZvIqzwF zJPbS$_JbZ%r!)DN^(T62Fr|$%&+w2?e;ml57JXs8&qx4$;J_z2h=MAZ2(W@Wi=Xv3 zdIr9xd_746?L|#lX~gK>T3#O&XdUAPCqj~d$#i%k`?VC%gLvw{tbd8$+OIq8_*hem zME4o_FQ7=f{Nx#GMmZ*gB8|1v4=M1Z`#_q`_{;cj@X3Gl*LxRq$9{Xy0)k&Cgn^Y0 zF&hLbpHfJ%3D-&e-LBTZ<^0>Mf6QF?v6w9Qy#3PvU^WC$AuVpuq2bd-10*1zm}+&6YoIl9p&t@#~A=yJh&d_`E`zRI-KdWy}zuP5Pkmc#ew9za#nx&-0^QUsyTmX#%%79*r2MS_7|1wk3}y1wTGQc3eszG~f~cph(O(s6Zvw!_zgUIN4*Lrb4_0I+WDh0NFUc`=K2pk&pDuG9bfP>vkE4sVTZH7OS`++V2S`UETNU8Vf5QtfR z_5lP4jGeLD4LmSlF1*`&;1z-^0@!RG-Ut-}B?#syz$p!KWD7mr>N6LA7Mhlx+t2uZ z7BD^{m<7`);QY4Pu~h`@O6b8NA~mjaV-0=%h4MMrdVVefTPK0W1DvLVLSTt4@s((x z##RS91y$(HkKFgG4~xbFNgxb1hp?M1J`Wsic}~D^D_>5BHg(dI3ipa{6&eq0_4-uS z(87M9Hbu8Ci1Q}yBS6asnzQ&+MOZ6a?6U-`t<~US8+i|hKO#Wv&C6+92-Go!LZCJN7I=J6A4y{{ zd=OC}c(sVL6@{n|3-qh=z6}5eL0gF!k~j!#;9VHP+?2t;mf)&-*Ok3BItsLT7{e~$ zsZ5cDc7ORGzO?X|ndQ@-UOo35ZB@are~cKEA~Md1<$U44h(g&{f|k+{>jcWL;M XI%a{rwErpc5ta8R-Yf8btH6H&qud2_ literal 0 HcmV?d00001 diff --git a/docs/tex.ksy b/docs/tex.ksy new file mode 100644 index 00000000..0622c30f --- /dev/null +++ b/docs/tex.ksy @@ -0,0 +1,91 @@ +meta: + id: tex + title: Texture Data File + application: LEGO Island + file-extension: tex + license: CC0-1.0 + endian: le + +doc: | + Texture data format for LEGO Island (1997). Contains one or more named + textures with 8-bit indexed color image data. + + Texture data is embedded in SI (Interleaf) container files and parsed by + LegoTexturePresenter::Read(). Each texture consists of a length-prefixed + name followed by image data with a color palette and pixel indices. + + The image format is shared with the world database (world.wdb) texture + data, using the same LegoImage and LegoPaletteEntry serialization. + + File structure: + 1. Texture count + 2. Named texture entries - name + palette + pixel data + +seq: + - id: num_textures + type: u4 + doc: Number of textures in this file. + - id: textures + type: named_texture + repeat: expr + repeat-expr: num_textures + doc: Array of named textures. + +types: + named_texture: + doc: | + A named texture with 8-bit indexed color image data. + seq: + - id: name_length + type: u4 + doc: Length of the texture name buffer in bytes. + - id: name + type: str + size: name_length + encoding: ASCII + terminator: 0 + doc: | + Texture name (e.g., "dbfrfn.gif"). The name is a null-terminated + C string within the allocated buffer. Bytes after the null + terminator are unused padding and consumed but not included + in the string value. + - id: image + type: image + doc: The texture image data. + + image: + doc: | + An 8-bit indexed color image with palette. Parsed by LegoImage::Read(). + seq: + - id: width + type: u4 + doc: Image width in pixels. + - id: height + type: u4 + doc: Image height in pixels. + - id: palette_size + type: u4 + doc: Number of entries in the color palette (max 256). + - id: palette + type: palette_entry + repeat: expr + repeat-expr: palette_size + doc: Color palette entries. + - id: pixels + size: width * height + doc: | + Pixel data as palette indices. Each byte is an index into + the palette array. + + palette_entry: + doc: RGB color palette entry. Parsed by LegoPaletteEntry::Read(). + seq: + - id: red + type: u1 + doc: Red component (0-255). + - id: green + type: u1 + doc: Green component (0-255). + - id: blue + type: u1 + doc: Blue component (0-255). From d7f594bf7e9d2d06d1a67433037def08ebdb13dc Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Sat, 7 Feb 2026 22:37:39 -0800 Subject: [PATCH 09/17] Add character names to savegame.ksy (#1742) --- docs/savegame.ksy | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/docs/savegame.ksy b/docs/savegame.ksy index 745b2b39..45e16c25 100644 --- a/docs/savegame.ksy +++ b/docs/savegame.ksy @@ -155,88 +155,127 @@ types: doc: Margaret Patricia "Maggie" Post - id: bu type: standard_character_entry + doc: Buck Pounds - id: ml type: standard_character_entry + doc: Ed Mail - id: nu type: standard_character_entry + doc: Nubby Stevens - id: na type: standard_character_entry doc: Nancy Nubbins - id: cl type: standard_character_entry + doc: Dr. Clickitt - id: en type: standard_character_entry + doc: Enter - id: re type: standard_character_entry + doc: Return - id: ro type: standard_character_entry + doc: Captain D. Rom - id: d1 type: standard_character_entry + doc: Bill Ding (Race Car) - id: d2 type: standard_character_entry + doc: Bill Ding (Helicopter) - id: d3 type: standard_character_entry + doc: Bill Ding (Dune Buggy) - id: d4 type: standard_character_entry + doc: Bill Ding (Jetski) - id: l1 type: standard_character_entry + doc: The Flying Legandos #1 - id: l2 type: standard_character_entry + doc: The Flying Legandos #2 - id: l3 type: standard_character_entry + doc: The Flying Legandos #3 - id: l4 type: standard_character_entry + doc: The Flying Legandos #4 - id: l5 type: standard_character_entry + doc: The Flying Legandos #5 - id: l6 type: standard_character_entry + doc: The Flying Legandos #6 - id: b1 type: standard_character_entry + doc: The Legobobs #1 - id: b2 type: standard_character_entry + doc: The Legobobs #2 - id: b3 type: standard_character_entry + doc: The Legobobs #3 - id: b4 type: standard_character_entry + doc: The Legobobs #4 - id: cm type: standard_character_entry + doc: Brazilian Carmen - id: gd type: standard_character_entry + doc: Gideon Worse - id: rd type: standard_character_entry + doc: Red Greenbase - id: pg type: standard_character_entry doc: Polly Gone - id: bd type: standard_character_entry + doc: Bradford Brickford - id: sy type: standard_character_entry + doc: Shiney Doris - id: gn type: standard_character_entry + doc: Glen Funberg - id: df type: standard_character_entry + doc: Dorothy Funberg - id: bs type: standard_character_entry + doc: Brian Shrimp - id: lt type: standard_character_entry + doc: Luke Tepid - id: st type: standard_character_entry + doc: Shorty Tails - id: bm type: standard_character_entry + doc: Bumpy Kindergreen - id: jk type: standard_character_entry + doc: Jack O'Trades - id: ghost type: ghost_character_entry + doc: Ghost #1 - id: ghost01 type: ghost_character_entry + doc: Ghost #2 - id: ghost02 type: ghost_character_entry + doc: Ghost #3 - id: ghost03 type: ghost_character_entry + doc: Ghost #4 - id: ghost04 type: ghost_character_entry + doc: Ghost #5 - id: ghost05 type: ghost_character_entry + doc: Ghost #6 - id: hg type: standard_character_entry - id: pntgy From 183a69874f03cd87368fd4d03717319a76582f4f Mon Sep 17 00:00:00 2001 From: jonschz <17198703+jonschz@users.noreply.github.com> Date: Sat, 14 Feb 2026 17:50:15 +0100 Subject: [PATCH 10/17] Beta matches on `LegoEdge` and others, `LegoPathActor` improvements (#1743) * BETA matches for LegoEdge and related * Some progress on CheckIntersections * Cleanup * Fix duplicate offset --------- Co-authored-by: jonschz --- LEGO1/lego/legoomni/include/legopathactor.h | 3 - LEGO1/lego/legoomni/src/entity/legoentity.cpp | 5 +- .../lego/legoomni/src/paths/legopathactor.cpp | 135 +++++++++++++----- LEGO1/lego/sources/geom/legoedge.cpp | 58 +++++--- LEGO1/lego/sources/geom/legoedge.h | 4 + LEGO1/lego/sources/geom/legoorientededge.h | 5 + LEGO1/realtime/orientableroi.cpp | 1 + 7 files changed, 158 insertions(+), 53 deletions(-) diff --git a/LEGO1/lego/legoomni/include/legopathactor.h b/LEGO1/lego/legoomni/include/legopathactor.h index 491e067c..dac4f341 100644 --- a/LEGO1/lego/legoomni/include/legopathactor.h +++ b/LEGO1/lego/legoomni/include/legopathactor.h @@ -222,9 +222,6 @@ class LegoPathActor : public LegoActor { MxFloat m_linearRotationRatio; // 0x150 }; -// FUNCTION: LEGO1 0x1002edd0 -// LegoPathActor::CheckIntersectionBothFaces - // TEMPLATE: LEGO1 0x10018b70 // List::~List diff --git a/LEGO1/lego/legoomni/src/entity/legoentity.cpp b/LEGO1/lego/legoomni/src/entity/legoentity.cpp index d02c1c71..544b3384 100644 --- a/LEGO1/lego/legoomni/src/entity/legoentity.cpp +++ b/LEGO1/lego/legoomni/src/entity/legoentity.cpp @@ -194,16 +194,18 @@ void LegoEntity::SetLocation( } // FUNCTION: LEGO1 0x10010c30 +// FUNCTION: BETA10 0x1007ea5a void LegoEntity::TransformPointOfView() { LegoWorld* world = CurrentWorld(); - if (m_cameraFlag && world && world->GetCameraController() && m_roi) { + if (GetCameraFlag() && world && world->GetCameraController() && m_roi) { world->GetCameraController()->TransformPointOfView(m_roi->GetLocal2World(), 1); } } // FUNCTION: LEGO1 0x10010c60 +// FUNCTION: BETA10 0x1007ead0 Mx3DPointFloat LegoEntity::GetWorldDirection() { if (m_roi != NULL) { @@ -215,6 +217,7 @@ Mx3DPointFloat LegoEntity::GetWorldDirection() } // FUNCTION: LEGO1 0x10010cf0 +// FUNCTION: BETA10 0x1007eb47 Mx3DPointFloat LegoEntity::GetWorldUp() { if (m_roi != NULL) { diff --git a/LEGO1/lego/legoomni/src/paths/legopathactor.cpp b/LEGO1/lego/legoomni/src/paths/legopathactor.cpp index 2fca5127..cee77829 100644 --- a/LEGO1/lego/legoomni/src/paths/legopathactor.cpp +++ b/LEGO1/lego/legoomni/src/paths/legopathactor.cpp @@ -188,23 +188,31 @@ MxResult LegoPathActor::SetTransformAndDestinationFromPoints( assert(p_destEdge); Vector3* v3 = p_destEdge->CWVertex(*p_boundary); + // LINE: LEGO1 0x1002de35 Vector3* v4 = p_destEdge->CCWVertex(*p_boundary); assert(v3 && v4); Mx3DPointFloat end, destNormal, endDirection; + // LINE: LEGO1 0x1002de8f end = *v4; end -= *v3; end *= p_destScale; + // LINE: LEGO1 0x1002deae end += *v3; + // LINE: LEGO1 0x1002deba m_boundary = p_boundary; + // LINE: LEGO1 0x1002dece m_destEdge = p_destEdge; + // LINE: LEGO1 0x1002ded4 m_destScale = p_destScale; m_traveledDistance = 0; m_transformTime = p_time; m_actorTime = p_time; + // TODO: this one fails to inline + // LINE: LEGO1 0x1002deed p_destEdge->GetFaceNormal(*p_boundary, destNormal); MxMatrix matrix; @@ -515,6 +523,95 @@ MxU32 LegoPathActor::CheckPresenterAndActorIntersections( return 0; } +#ifdef BETA10 +// FUNCTION: BETA10 0x100af35e +MxS32 LegoPathActor::CheckIntersections(Vector3& p_rayOrigin, Vector3& p_rayEnd, Vector3& p_intersectionPoint) +{ + assert(m_boundary && m_roi); + + Mx3DPointFloat rayDirection(p_rayEnd); + rayDirection -= p_rayOrigin; + + float len = rayDirection.LenSquared(); + + if (len <= 0.001) { + return 0; + } + + len = sqrt((double) len); + rayDirection /= len; + + float radius = m_roi->GetWorldBoundingSphere().Radius(); + LegoPathBoundary* b = m_boundary; + LegoOrientedEdge* local14 = *m_boundary->GetEdges(); + LegoOrientedEdge* local18 = NULL; + + while (1) { + assert(b); + + MxU32 result = + CheckPresenterAndActorIntersections(b, p_rayOrigin, rayDirection, len, radius, p_intersectionPoint); + + if (result != 0) { + return result; + } + + if (local18 == NULL) { + local18 = (LegoOrientedEdge*) local14->GetCounterclockwiseEdge(*m_boundary); + b = (LegoPathBoundary*) local14->OtherFace(m_boundary); + } + else { + b = NULL; + } + + while (!b) { + if (local18 == local14) { + return 0; + } + + b = (LegoPathBoundary*) local18->OtherFace(m_boundary); + local18 = (LegoOrientedEdge*) local18->GetCounterclockwiseEdge(*m_boundary); + } + } + + return 0; +} +#else +// FUNCTION: LEGO1 0x1002ebe0 +MxS32 LegoPathActor::CheckIntersections(Vector3& p_rayOrigin, Vector3& p_rayEnd, Vector3& p_intersectionPoint) +{ + assert(m_boundary && m_roi); + + Mx3DPointFloat rayDirection(p_rayEnd); + rayDirection -= p_rayOrigin; + + float len = rayDirection.LenSquared(); + + if (len <= 0.001) { + return 0; + } + + len = sqrt((double) len); + rayDirection /= len; + + float radius = m_roi->GetWorldBoundingSphere().Radius(); + list boundaries; + // This function is inlined once. The recursion calls into the actual function. + // Matching `CheckIntersectionBothFaces` will likely match `CheckIntersections` as well. + return CheckIntersectionBothFaces( + boundaries, + m_boundary, + p_rayOrigin, + rayDirection, + len, + radius, + p_intersectionPoint, + 0 + ); +} +#endif + +// FUNCTION: LEGO1 0x1002edd0 inline MxU32 LegoPathActor::CheckIntersectionBothFaces( list& p_checkedBoundaries, LegoPathBoundary* p_boundary, @@ -548,17 +645,22 @@ inline MxU32 LegoPathActor::CheckIntersectionBothFaces( LegoS32 numEdges = p_boundary->GetNumEdges(); for (MxS32 i = 0; i < numEdges; i++) { LegoOrientedEdge* edge = p_boundary->GetEdges()[i]; + // LINE: LEGO1 0x1002ee8c LegoPathBoundary* boundary = (LegoPathBoundary*) edge->OtherFace(p_boundary); + // LINE: LEGO1 0x1002ee9f if (boundary != NULL) { list::const_iterator it; + // LINE: LEGO1 0x1002eead for (it = p_checkedBoundaries.begin(); !(it == p_checkedBoundaries.end()); it++) { + // LINE: LEGO1 0x1002eeb3 if ((*it) == boundary) { break; } } + // LINE: LEGO1 0x1002eec4 if (it == p_checkedBoundaries.end()) { result = CheckIntersectionBothFaces( p_checkedBoundaries, @@ -581,39 +683,6 @@ inline MxU32 LegoPathActor::CheckIntersectionBothFaces( return 0; } -// FUNCTION: LEGO1 0x1002ebe0 -// FUNCTION: BETA10 0x100af35e -MxS32 LegoPathActor::CheckIntersections(Vector3& p_rayOrigin, Vector3& p_rayEnd, Vector3& p_intersectionPoint) -{ - assert(m_boundary && m_roi); - - Mx3DPointFloat rayDirection(p_rayEnd); - rayDirection -= p_rayOrigin; - - float len = rayDirection.LenSquared(); - - if (len <= 0.001) { - return 0; - } - - len = sqrt((double) len); - rayDirection /= len; - - float radius = m_roi->GetWorldBoundingSphere().Radius(); - list boundaries; - - return CheckIntersectionBothFaces( - boundaries, - m_boundary, - p_rayOrigin, - rayDirection, - len, - radius, - p_intersectionPoint, - 0 - ); -} - // FUNCTION: LEGO1 0x1002f020 // FUNCTION: BETA10 0x100af54a void LegoPathActor::ParseAction(char* p_extra) diff --git a/LEGO1/lego/sources/geom/legoedge.cpp b/LEGO1/lego/sources/geom/legoedge.cpp index d1a810d6..2bcea209 100644 --- a/LEGO1/lego/sources/geom/legoedge.cpp +++ b/LEGO1/lego/sources/geom/legoedge.cpp @@ -6,6 +6,7 @@ DECOMP_SIZE_ASSERT(LegoEdge, 0x24) // FUNCTION: LEGO1 0x1009a470 +// FUNCTION: BETA10 0x10182250 LegoEdge::LegoEdge() { m_faceA = NULL; @@ -19,36 +20,65 @@ LegoEdge::LegoEdge() } // FUNCTION: LEGO1 0x1009a4c0 +// FUNCTION: BETA10 0x101822c2 LegoEdge::~LegoEdge() { } +// FUNCTION: BETA10 0x101822e1 +LegoResult LegoEdge::SetCounterclockwiseEdge(LegoWEEdge& p_face, LegoEdge* p_edge) +{ + // unreferenced in BETA10, not in LEGO1 + if (&p_face == m_faceA) { + m_ccwA = p_edge; + return SUCCESS; + } + if (&p_face == m_faceB) { + m_ccwB = p_edge; + return SUCCESS; + } + return FAILURE; +} + +// FUNCTION: BETA10 0x1018233c +LegoResult LegoEdge::SetClockwiseEdge(LegoWEEdge& p_face, LegoEdge* p_edge) +{ + // unreferenced in BETA10, not in LEGO1 + if (&p_face == m_faceA) { + m_cwA = p_edge; + return SUCCESS; + } + if (&p_face == m_faceB) { + m_cwB = p_edge; + return SUCCESS; + } + return FAILURE; +} + // FUNCTION: LEGO1 0x1009a4d0 +// FUNCTION: BETA10 0x10182397 LegoEdge* LegoEdge::GetClockwiseEdge(LegoWEEdge& p_face) { if (&p_face == m_faceA) { return m_cwA; } - else if (&p_face == m_faceB) { + if (&p_face == m_faceB) { return m_cwB; } - else { - return NULL; - } + return NULL; } // FUNCTION: LEGO1 0x1009a4f0 +// FUNCTION: BETA10 0x101823e5 LegoEdge* LegoEdge::GetCounterclockwiseEdge(LegoWEEdge& p_face) { if (&p_face == m_faceA) { return m_ccwA; } - else if (&p_face == m_faceB) { + if (&p_face == m_faceB) { return m_ccwB; } - else { - return NULL; - } + return NULL; } // FUNCTION: LEGO1 0x1009a510 @@ -58,10 +88,8 @@ Vector3* LegoEdge::CWVertex(LegoWEEdge& p_face) if (m_faceA == &p_face) { return m_pointB; } - else { - assert(m_faceB == &p_face); - return m_pointA; - } + assert(m_faceB == &p_face); + return m_pointA; } // FUNCTION: LEGO1 0x1009a530 @@ -71,8 +99,6 @@ Vector3* LegoEdge::CCWVertex(LegoWEEdge& p_face) if (m_faceB == &p_face) { return m_pointB; } - else { - assert(m_faceA == &p_face); - return m_pointA; - } + assert(m_faceA == &p_face); + return m_pointA; } diff --git a/LEGO1/lego/sources/geom/legoedge.h b/LEGO1/lego/sources/geom/legoedge.h index 9a14eef4..31cdfddf 100644 --- a/LEGO1/lego/sources/geom/legoedge.h +++ b/LEGO1/lego/sources/geom/legoedge.h @@ -7,11 +7,14 @@ class LegoWEEdge; class Vector3; // VTABLE: LEGO1 0x100db7b8 +// VTABLE: BETA10 0x101c3728 // SIZE 0x24 struct LegoEdge { LegoEdge(); virtual ~LegoEdge(); // vtable+0x00 + LegoResult SetCounterclockwiseEdge(LegoWEEdge& p_face, LegoEdge* p_edge); + LegoResult SetClockwiseEdge(LegoWEEdge& p_face, LegoEdge* p_edge); LegoEdge* GetClockwiseEdge(LegoWEEdge& p_face); LegoEdge* GetCounterclockwiseEdge(LegoWEEdge& p_face); Vector3* CWVertex(LegoWEEdge& p_face); @@ -32,6 +35,7 @@ struct LegoEdge { Vector3* GetPointB() { return m_pointB; } // SYNTHETIC: LEGO1 0x1009a4a0 + // SYNTHETIC: BETA10 0x10182b30 // LegoEdge::`scalar deleting destructor' LegoWEEdge* m_faceA; // 0x04 diff --git a/LEGO1/lego/sources/geom/legoorientededge.h b/LEGO1/lego/sources/geom/legoorientededge.h index bb613563..05cd8009 100644 --- a/LEGO1/lego/sources/geom/legoorientededge.h +++ b/LEGO1/lego/sources/geom/legoorientededge.h @@ -8,6 +8,7 @@ #include // VTABLE: LEGO1 0x100db7f4 +// VTABLE: BETA10 0x101c3794 // SIZE 0x40 struct LegoOrientedEdge : public LegoEdge { public: @@ -99,8 +100,12 @@ struct LegoOrientedEdge : public LegoEdge { inline LegoU32 FUN_10048c40(const Vector3& p_position); // SYNTHETIC: LEGO1 0x1009a6c0 + // SYNTHETIC: BETA10 0x101840f0 // LegoOrientedEdge::`scalar deleting destructor' + // SYNTHETIC: BETA10 0x100bd390 + // LegoOrientedEdge::~LegoOrientedEdge + LegoU16 m_flags; // 0x24 Mx3DPointFloat m_dir; // 0x28 float m_length; // 0x3c diff --git a/LEGO1/realtime/orientableroi.cpp b/LEGO1/realtime/orientableroi.cpp index 3c45e90f..db46a206 100644 --- a/LEGO1/realtime/orientableroi.cpp +++ b/LEGO1/realtime/orientableroi.cpp @@ -29,6 +29,7 @@ void OrientableROI::WrappedSetLocal2WorldWithWorldDataUpdate(const Matrix4& p_lo } // FUNCTION: LEGO1 0x100a46b0 +// FUNCTION: BETA10 0x1016528f void OrientableROI::UpdateTransformationRelativeToParent(const Matrix4& p_transform) { MxMatrix mat; From 8a77540169711401609481fda92c593e5478a54b Mon Sep 17 00:00:00 2001 From: MS Date: Sat, 14 Feb 2026 14:35:50 -0500 Subject: [PATCH 11/17] LegoCarBuild::Escape to 100% (#1744) --- LEGO1/lego/legoomni/src/build/legocarbuild.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/LEGO1/lego/legoomni/src/build/legocarbuild.cpp b/LEGO1/lego/legoomni/src/build/legocarbuild.cpp index 4b9d78ea..c4ef19fe 100644 --- a/LEGO1/lego/legoomni/src/build/legocarbuild.cpp +++ b/LEGO1/lego/legoomni/src/build/legocarbuild.cpp @@ -1681,8 +1681,7 @@ void LegoCarBuild::HandleEndAnim() MxBool LegoCarBuild::Escape() { BackgroundAudioManager()->Init(); - MxS32 targetEntityId = GetBuildMovieId(m_carId); - InvokeAction(Extra::ActionType::e_stop, *g_jukeboxScript, targetEntityId, NULL); + InvokeAction(Extra::ActionType::e_stop, *g_jukeboxScript, GetBuildMovieId(m_carId), NULL); DeleteObjects(&m_atomId, 500, 999); m_buildState->m_animationState = LegoVehicleBuildState::e_none; From 27400658955d1abf7b5e331d2d2adc38a2410c08 Mon Sep 17 00:00:00 2001 From: MS Date: Sat, 14 Feb 2026 20:16:23 -0500 Subject: [PATCH 12/17] Beta match `SpheresIntersect` (#1745) * Beta match SpheresIntersect * Extra beta addresses --- LEGO1/lego/legoomni/src/common/legoutils.cpp | 4 +++- LEGO1/lego/legoomni/src/entity/legocameracontroller.cpp | 3 +++ LEGO1/lego/sources/3dmanager/lego3dview.h | 1 + LEGO1/library_msvc.h | 6 ++++++ 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/LEGO1/lego/legoomni/src/common/legoutils.cpp b/LEGO1/lego/legoomni/src/common/legoutils.cpp index dc310953..7d37af7f 100644 --- a/LEGO1/lego/legoomni/src/common/legoutils.cpp +++ b/LEGO1/lego/legoomni/src/common/legoutils.cpp @@ -90,11 +90,13 @@ void RotateY(LegoROI* p_roi, MxFloat p_angle) } // FUNCTION: LEGO1 0x1003de80 +// FUNCTION: BETA10 0x100d3684 MxBool SpheresIntersect(const BoundingSphere& p_sphere1, const BoundingSphere& p_sphere2) { // This doesn't look clean, but it matches. // p_sphere1.Center().GetData() doesn't work out - return sqrt(DISTSQRD3(&p_sphere1.Center()[0], &p_sphere2.Center()[0])) < p_sphere1.Radius() + p_sphere2.Radius(); + float distance = DISTSQRD3(p_sphere1.Center(), p_sphere2.Center()); + return sqrt(distance) < p_sphere1.Radius() + p_sphere2.Radius(); } // FUNCTION: LEGO1 0x1003ded0 diff --git a/LEGO1/lego/legoomni/src/entity/legocameracontroller.cpp b/LEGO1/lego/legoomni/src/entity/legocameracontroller.cpp index c15b4aac..c5fbf72b 100644 --- a/LEGO1/lego/legoomni/src/entity/legocameracontroller.cpp +++ b/LEGO1/lego/legoomni/src/entity/legocameracontroller.cpp @@ -186,6 +186,7 @@ void LegoCameraController::TransformPointOfView(const Matrix4& p_transform, MxU3 } // FUNCTION: LEGO1 0x10012740 +// FUNCTION: BETA10 0x10069c35 Mx3DPointFloat LegoCameraController::GetWorldUp() { if (m_lego3DView && m_lego3DView->GetPointOfView()) { @@ -199,6 +200,7 @@ Mx3DPointFloat LegoCameraController::GetWorldUp() } // FUNCTION: LEGO1 0x100127f0 +// FUNCTION: BETA10 0x10069cea Mx3DPointFloat LegoCameraController::GetWorldLocation() { if (m_lego3DView && m_lego3DView->GetPointOfView()) { @@ -212,6 +214,7 @@ Mx3DPointFloat LegoCameraController::GetWorldLocation() } // FUNCTION: LEGO1 0x100128a0 +// FUNCTION: BETA10 0x10069daa Mx3DPointFloat LegoCameraController::GetWorldDirection() { if (m_lego3DView && m_lego3DView->GetPointOfView()) { diff --git a/LEGO1/lego/sources/3dmanager/lego3dview.h b/LEGO1/lego/sources/3dmanager/lego3dview.h index 1f878a53..44187337 100644 --- a/LEGO1/lego/sources/3dmanager/lego3dview.h +++ b/LEGO1/lego/sources/3dmanager/lego3dview.h @@ -52,6 +52,7 @@ inline ViewManager* Lego3DView::GetViewManager() return m_pViewManager; } +// FUNCTION: BETA10 0x1006aae0 inline ViewROI* Lego3DView::GetPointOfView() { return m_pPointOfView; diff --git a/LEGO1/library_msvc.h b/LEGO1/library_msvc.h index fd185a53..9e38a1d9 100644 --- a/LEGO1/library_msvc.h +++ b/LEGO1/library_msvc.h @@ -873,6 +873,12 @@ // LIBRARY: BETA10 0x100fa0e0 // atof +// LIBRARY: BETA10 0x1005a500 +// sqrt + +// LIBRARY: BETA10 0x1005a530 +// sqrtf + // LIBRARY: BETA10 0x1005a9c0 // fabs From 1132fd541fdad21b44a3f7a1882e7dbf39c47617 Mon Sep 17 00:00:00 2001 From: Fabian Neundorf Date: Fri, 13 Mar 2026 22:42:12 +0100 Subject: [PATCH 13/17] Clear unknowns in `LegoAct2` (#1746) --- LEGO1/lego/legoomni/include/legoact2.h | 16 +-- LEGO1/lego/legoomni/src/actors/act2actor.cpp | 4 +- .../legoomni/src/common/legoobjectfactory.cpp | 2 +- LEGO1/lego/legoomni/src/worlds/legoact2.cpp | 102 +++++++++--------- 4 files changed, 62 insertions(+), 62 deletions(-) diff --git a/LEGO1/lego/legoomni/include/legoact2.h b/LEGO1/lego/legoomni/include/legoact2.h index d27f92d8..ae360f5a 100644 --- a/LEGO1/lego/legoomni/include/legoact2.h +++ b/LEGO1/lego/legoomni/include/legoact2.h @@ -77,19 +77,19 @@ class LegoAct2 : public LegoWorld { MxBool Escape() override; // vtable+0x64 void Enable(MxBool p_enable) override; // vtable+0x68 - void SetUnknown0x1138(Act2Actor* p_unk0x1138) { m_unk0x1138 = p_unk0x1138; } + void SetAmbulanceActor(Act2Actor* p_ambulanceActor) { m_ambulanceActor = p_ambulanceActor; } void SetDestLocation(LegoGameState::Area p_destLocation) { m_destLocation = p_destLocation; } - MxResult CreateBrick(); - void FUN_100517b0(); + MxResult CreateDroppingBrick(); + void CreateBrick(); MxResult BadEnding(); MxResult StartAction( Act2mainScript::Script p_objectId, - MxBool p_param2, - MxBool p_param3, + MxBool p_isAnimation, + MxBool p_ignoreCurrentAction, Mx3DPointFloat* p_location, Mx3DPointFloat* p_direction, - Mx3DPointFloat* p_param6 + Mx3DPointFloat* p_up ); // SYNTHETIC: LEGO1 0x1004fe20 @@ -117,7 +117,7 @@ class LegoAct2 : public LegoWorld { MxLong HandleTransitionEnd(); MxLong HandlePathStruct(LegoPathStructNotificationParam& p_param); void PlayMusic(JukeboxScript::Script p_objectId); - void FUN_10051900(); + void DisableAnimations(); void HideMaPaInfo(); void InitBricks(); void UninitBricks(); @@ -145,7 +145,7 @@ class LegoAct2 : public LegoWorld { undefined4 m_unk0x112c; // 0x112c undefined4 m_unk0x1130; // 0x1130 undefined4 m_unk0x1134; // 0x1134 - Act2Actor* m_unk0x1138; // 0x1138 + Act2Actor* m_ambulanceActor; // 0x1138 undefined m_unk0x113c; // 0x113c Act2mainScript::Script m_currentAction; // 0x1140 Act2mainScript::Script m_infomanDirecting; // 0x1144 diff --git a/LEGO1/lego/legoomni/src/actors/act2actor.cpp b/LEGO1/lego/legoomni/src/actors/act2actor.cpp index 320b918b..721c4099 100644 --- a/LEGO1/lego/legoomni/src/actors/act2actor.cpp +++ b/LEGO1/lego/legoomni/src/actors/act2actor.cpp @@ -287,7 +287,7 @@ void Act2Actor::Animate(float p_time) } SetWorldSpeed(0.0f); - ((LegoAct2*) CurrentWorld())->FUN_100517b0(); + ((LegoAct2*) CurrentWorld())->CreateBrick(); return; } #endif @@ -355,7 +355,7 @@ void Act2Actor::Animate(float p_time) m_state = e_createdBrick; m_createBrickTime = p_time; - if (((LegoAct2*) CurrentWorld())->CreateBrick() == SUCCESS) { + if (((LegoAct2*) CurrentWorld())->CreateDroppingBrick() == SUCCESS) { PlayNextVoiceOver(VoiceOver::e_behind); } #ifndef BETA10 diff --git a/LEGO1/lego/legoomni/src/common/legoobjectfactory.cpp b/LEGO1/lego/legoomni/src/common/legoobjectfactory.cpp index b8514020..4f854b96 100644 --- a/LEGO1/lego/legoomni/src/common/legoobjectfactory.cpp +++ b/LEGO1/lego/legoomni/src/common/legoobjectfactory.cpp @@ -389,7 +389,7 @@ MxCore* LegoObjectFactory::Create(const char* p_name) } else if (m_idAct2Actor == atom) { Act2Actor* actor = new Act2Actor(); - ((LegoAct2*) CurrentWorld())->SetUnknown0x1138(actor); + ((LegoAct2*) CurrentWorld())->SetAmbulanceActor(actor); object = actor; } else if (m_idAct2Brick == atom) { diff --git a/LEGO1/lego/legoomni/src/worlds/legoact2.cpp b/LEGO1/lego/legoomni/src/worlds/legoact2.cpp index 3fe704c6..a3ea80e7 100644 --- a/LEGO1/lego/legoomni/src/worlds/legoact2.cpp +++ b/LEGO1/lego/legoomni/src/worlds/legoact2.cpp @@ -52,7 +52,7 @@ MxS32 g_animationsBricksterIsLoose[] = { const LegoChar* g_charactersBricksterIsLoose[] = {"bd", "pg", "rd", "sy", "ro", "cl"}; // GLOBAL: LEGO1 0x100f4428 -MxS32 g_unk0x100f4428[] = { +MxS32 g_animationsAfterChase[] = { Act2mainScript::c_snsx07pa_RunAnim, Act2mainScript::c_snsx12ni_RunAnim, Act2mainScript::c_snsx15la_RunAnim, @@ -68,7 +68,7 @@ MxS32 g_unk0x100f4428[] = { }; // GLOBAL: LEGO1 0x100f4458 -const LegoChar* g_unk0x100f4458[] = {"papa", "nick", "laura", "cl", "pg", "rd", "sy"}; +const LegoChar* g_charactersAfterChase[] = {"papa", "nick", "laura", "cl", "pg", "rd", "sy"}; // FUNCTION: LEGO1 0x1004fce0 // FUNCTION: BETA10 0x1003a5a0 @@ -82,7 +82,7 @@ LegoAct2::LegoAct2() m_unk0x1130 = 0; m_nextBrick = 0; m_removedBricks = 0; - m_unk0x1138 = NULL; + m_ambulanceActor = NULL; m_currentAction = (Act2mainScript::Script) 0; m_infomanDirecting = (Act2mainScript::Script) 0; m_destLocation = LegoGameState::e_undefined; @@ -100,7 +100,7 @@ LegoAct2::~LegoAct2() TickleManager()->UnregisterClient(this); } - FUN_10051900(); + DisableAnimations(); InputManager()->UnRegister(this); if (UserActor()) { Remove(UserActor()); @@ -246,7 +246,7 @@ MxResult LegoAct2::Tickle() else { m_state = LegoAct2::e_goingToHide; m_timeSinceLastStage = 0; - m_unk0x1138->GoingToHide(); + m_ambulanceActor->GoingToHide(); } } @@ -303,30 +303,30 @@ MxLong LegoAct2::Notify(MxParam& p_param) LegoEntity* entity = (LegoEntity*) param.GetSender(); - Mx3DPointFloat local20(entity->GetROI()->GetWorldPosition()); - Mx3DPointFloat locale8(m_pepper->GetWorldPosition()); - Mx3DPointFloat locala4(locale8); + Mx3DPointFloat pepperToBrick(entity->GetROI()->GetWorldPosition()); + Mx3DPointFloat pepperPosition(m_pepper->GetWorldPosition()); + Mx3DPointFloat position(pepperPosition); - local20 -= locale8; + pepperToBrick -= pepperPosition; MxMatrix local2world(m_pepper->GetLocal2World()); - Vector3 local30(local2world[0]); - Vector3 localac(local2world[1]); - Vector3 local28(local2world[2]); + Vector3 right(local2world[0]); + Vector3 up(local2world[1]); + Vector3 dir(local2world[2]); - local28 = local20; - local28.Unitize(); + dir = pepperToBrick; + dir.Unitize(); - Mx3DPointFloat local90(local28); - local90 *= 1.25f; - locala4 += local90; - locala4[1] += 0.25; - local30.EqualsCross(localac, local28); - local30.Unitize(); + Mx3DPointFloat positionOffset(dir); + positionOffset *= 1.25f; + position += positionOffset; + position[1] += 0.25; + right.EqualsCross(up, dir); + right.Unitize(); - Mx3DPointFloat locald4(local2world[2]); + Mx3DPointFloat direction(local2world[2]); Mx3DPointFloat localc0(local2world[1]); - StartAction(Act2mainScript::c_tns051in_RunAnim, TRUE, TRUE, &locala4, &locald4, NULL); + StartAction(Act2mainScript::c_tns051in_RunAnim, TRUE, TRUE, &position, &direction, NULL); m_state = LegoAct2::e_allPiecesCollected; m_timeSinceLastStage = 0; @@ -414,8 +414,8 @@ MxLong LegoAct2::HandleEndAction(MxEndActionNotificationParam& p_param) ((LegoPathActor*) m_pepper->GetEntity())->SetActorState(LegoPathActor::c_disabled); AnimationManager()->EnableCamAnims(TRUE); AnimationManager()->FUN_1005f6d0(TRUE); - AnimationManager()->FUN_100604f0(g_unk0x100f4428, sizeOfArray(g_unk0x100f4428)); - AnimationManager()->FUN_10060480(g_unk0x100f4458, sizeOfArray(g_unk0x100f4458)); + AnimationManager()->FUN_100604f0(g_animationsAfterChase, sizeOfArray(g_animationsAfterChase)); + AnimationManager()->FUN_10060480(g_charactersAfterChase, sizeOfArray(g_charactersAfterChase)); break; case LegoAct2::e_distributeRemainingBricks: { LegoROI* roi; @@ -452,7 +452,7 @@ MxLong LegoAct2::HandleEndAction(MxEndActionNotificationParam& p_param) m_bricks[i].Remove(); } - FUN_10051900(); + DisableAnimations(); m_destLocation = LegoGameState::e_copterbuild; TransitionManager()->StartTransition(MxTransitionManager::e_mosaic, 50, FALSE, FALSE); break; @@ -513,7 +513,7 @@ void LegoAct2::ReadyWorld() PlaceActor(actor, "EDG00_149", 0, 0.5f, 2, 0.5f); PlayMusic(JukeboxScript::c_Jail_Music); - FUN_10051900(); + DisableAnimations(); VideoManager()->Get3DManager()->SetFrustrum(90.0f, 0.1f, 250.f); m_gameState->m_enabled = TRUE; } @@ -573,7 +573,7 @@ void LegoAct2::Enable(MxBool p_enable) m_transformOnDisable = m_pepper->GetLocal2World(); m_boundaryOnDisable = ((LegoPathActor*) m_pepper->GetEntity())->GetBoundary(); - FUN_10051900(); + DisableAnimations(); BackgroundAudioManager()->Stop(); UninitBricks(); DeleteObjects(&m_atomId, Act2mainScript::c_VOhead0_PlayWav, Act2mainScript::c_VOhide_PlayWav); @@ -599,7 +599,7 @@ MxLong LegoAct2::HandlePathStruct(LegoPathStructNotificationParam& p_param) LegoPathActor* actor = (LegoPathActor*) m_pepper->GetEntity(); actor->SetActorState(LegoPathActor::c_disabled); actor->SetWorldSpeed(0.0f); - FUN_10051900(); + DisableAnimations(); if (m_timeSinceLastStage < 90000) { StartAction(Act2mainScript::c_tra031ni_RunAnim, TRUE, TRUE, NULL, NULL, NULL); @@ -631,7 +631,7 @@ MxLong LegoAct2::HandlePathStruct(LegoPathStructNotificationParam& p_param) m_currentAction = Act2mainScript::c_VOhide_PlayWav; } - m_unk0x1138->Hide(); + m_ambulanceActor->Hide(); m_state = LegoAct2::e_hidden; m_timeSinceLastStage = 0; @@ -644,7 +644,7 @@ MxLong LegoAct2::HandlePathStruct(LegoPathStructNotificationParam& p_param) MxMatrix local2world = m_ambulance->GetLocal2World(); MxMatrix local2world2 = local2world; - LegoPathBoundary* boundary = m_unk0x1138->GetBoundary(); + LegoPathBoundary* boundary = m_ambulanceActor->GetBoundary(); local2world[3][1] += 1.5; local2world2[3][1] -= 0.1; @@ -656,7 +656,7 @@ MxLong LegoAct2::HandlePathStruct(LegoPathStructNotificationParam& p_param) // FUNCTION: LEGO1 0x100516b0 // FUNCTION: BETA10 0x1003bcbc -MxResult LegoAct2::CreateBrick() +MxResult LegoAct2::CreateDroppingBrick() { if (m_nextBrick > 4) { return FAILURE; @@ -668,7 +668,7 @@ MxResult LegoAct2::CreateBrick() MxMatrix local2world = m_ambulance->GetLocal2World(); MxMatrix local2world2 = local2world; - LegoPathBoundary* boundary = m_unk0x1138->GetBoundary(); + LegoPathBoundary* boundary = m_ambulanceActor->GetBoundary(); local2world[3][1] += 1.3; local2world2[3][1] -= 0.1; @@ -680,7 +680,7 @@ MxResult LegoAct2::CreateBrick() } // FUNCTION: LEGO1 0x100517b0 -void LegoAct2::FUN_100517b0() +void LegoAct2::CreateBrick() { Act2Brick& brick = m_bricks[m_nextBrick]; brick.Create(m_nextBrick); @@ -708,7 +708,7 @@ void LegoAct2::PlayMusic(JukeboxScript::Script p_objectId) // FUNCTION: LEGO1 0x10051900 // FUNCTION: BETA10 0x1003bed1 -void LegoAct2::FUN_10051900() +void LegoAct2::DisableAnimations() { if (AnimationManager()) { AnimationManager()->Suspend(); @@ -931,7 +931,7 @@ MxResult LegoAct2::BadEnding() m_bricks[i].Remove(); } - LegoPathActor* actor = m_unk0x1138; + LegoPathActor* actor = m_ambulanceActor; actor->SetActorState(LegoPathActor::c_disabled); m_gameState->SetState(LegoAct2State::c_badEnding); @@ -1095,7 +1095,7 @@ MxResult LegoAct2::StartAction( MxBool p_ignoreCurrentAction, Mx3DPointFloat* p_location, Mx3DPointFloat* p_direction, - Mx3DPointFloat* p_param6 + Mx3DPointFloat* p_up ) { if (m_currentAction == (Act2mainScript::Script) 0 || p_ignoreCurrentAction) { @@ -1139,19 +1139,19 @@ MxResult LegoAct2::StartAction( oneVectorNotNull = TRUE; } - if (p_param6) { - matrix[1][0] = (*p_param6)[0]; - matrix[1][1] = (*p_param6)[1]; - matrix[1][2] = (*p_param6)[2]; + if (p_up) { + matrix[1][0] = (*p_up)[0]; + matrix[1][1] = (*p_up)[1]; + matrix[1][2] = (*p_up)[2]; oneVectorNotNull = TRUE; } - Vector3 firstColumn(matrix[0]); - Vector3 secondColumn(matrix[1]); - Vector3 thirdColumn(matrix[2]); + Vector3 right(matrix[0]); + Vector3 up(matrix[1]); + Vector3 dir(matrix[2]); - firstColumn.EqualsCross(secondColumn, thirdColumn); - firstColumn.Unitize(); + right.EqualsCross(up, dir); + right.Unitize(); MxMatrix* pmatrix = NULL; @@ -1201,28 +1201,28 @@ MxResult LegoAct2::StartAction( // FUNCTION: BETA10 0x10014aa8 MxResult LegoAct2::InitializeShooting() { - LegoPathActor* actor = m_unk0x1138; + LegoPathActor* actor = m_ambulanceActor; LegoLocomotionAnimPresenter* ap; PlaceActor(actor, "EDG01_27", 2, 0.5f, 0, 0.5f); ap = (LegoLocomotionAnimPresenter*) Find("LegoAnimPresenter", "Ambul_Anim0"); assert(ap); - ap->CreateROIAndBuildMap(m_unk0x1138, 0.0f); + ap->CreateROIAndBuildMap(m_ambulanceActor, 0.0f); ap = (LegoLocomotionAnimPresenter*) Find("LegoAnimPresenter", "Ambul_Anim2"); assert(ap); - ap->CreateROIAndBuildMap(m_unk0x1138, 6.0f); + ap->CreateROIAndBuildMap(m_ambulanceActor, 6.0f); ap = (LegoLocomotionAnimPresenter*) Find("LegoAnimPresenter", "Ambul_Anim3"); assert(ap); - ap->CreateROIAndBuildMap(m_unk0x1138, 3.0f); + ap->CreateROIAndBuildMap(m_ambulanceActor, 3.0f); ap = (LegoLocomotionAnimPresenter*) Find("LegoAnimPresenter", "BrShoot"); assert(ap); - ap->CreateROIAndBuildMap(m_unk0x1138, -1.0f); + ap->CreateROIAndBuildMap(m_ambulanceActor, -1.0f); actor->SetWorldSpeed(0.0f); - m_unk0x1138->InitializeNextShot(); + m_ambulanceActor->InitializeNextShot(); return SUCCESS; } From a6ee94b680344a496bf231eeffce39435b7e7267 Mon Sep 17 00:00:00 2001 From: Fabian Neundorf Date: Fri, 13 Mar 2026 22:47:21 +0100 Subject: [PATCH 14/17] Name all referenced areas (#1747) --- LEGO1/lego/legoomni/include/legogamestate.h | 12 ++++++------ LEGO1/lego/legoomni/src/actors/helicopter.cpp | 12 ++++++------ LEGO1/lego/legoomni/src/actors/islepathactor.cpp | 12 ++++++------ LEGO1/lego/legoomni/src/worlds/legoact2.cpp | 2 +- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/LEGO1/lego/legoomni/include/legogamestate.h b/LEGO1/lego/legoomni/include/legogamestate.h index fed3a358..2e150cd4 100644 --- a/LEGO1/lego/legoomni/include/legogamestate.h +++ b/LEGO1/lego/legoomni/include/legogamestate.h @@ -107,21 +107,21 @@ class LegoGameState { e_jetskibuild, e_racecarbuild, e_helicopterSpawn, - e_unk41, - e_unk42, + e_helicopterLanded, + e_helicopterTakenOff, e_dunebuggySpawn, e_racecarSpawn, e_jetskiSpawn, e_act2main, e_act3script, - e_unk48, - e_unk49, - e_unk50, + e_helicopterLandedAct3, + e_helicopterTakenOffAct3, + e_pepperSpawnAct2, e_unk51, e_towTrackHookedUp, e_jukeboxw, e_jukeboxExterior, - e_unk55, + e_helicopterExited, e_histbook, e_bike, e_dunecar, diff --git a/LEGO1/lego/legoomni/src/actors/helicopter.cpp b/LEGO1/lego/legoomni/src/actors/helicopter.cpp index a69430eb..8a547fdf 100644 --- a/LEGO1/lego/legoomni/src/actors/helicopter.cpp +++ b/LEGO1/lego/legoomni/src/actors/helicopter.cpp @@ -95,7 +95,7 @@ void Helicopter::Exit() if (UserActor() && UserActor()->IsA("IslePathActor")) { ((IslePathActor*) UserActor()) ->SpawnPlayer( - LegoGameState::e_unk55, + LegoGameState::e_helicopterExited, TRUE, IslePathActor::c_spawnBit1 | IslePathActor::c_playMusic | IslePathActor::c_spawnBit3 ); @@ -143,7 +143,7 @@ MxLong Helicopter::HandleClick() m_script = *g_isleScript; AnimationManager()->FUN_10064670(NULL); SpawnPlayer( - LegoGameState::e_unk41, + LegoGameState::e_helicopterLanded, TRUE, IslePathActor::c_spawnBit1 | IslePathActor::c_playMusic | IslePathActor::c_spawnBit3 ); @@ -320,14 +320,14 @@ MxLong Helicopter::HandleEndAnim(LegoEndAnimNotificationParam& p_param) assert(act1State); act1State->m_state = Act1State::e_helicopter; SpawnPlayer( - LegoGameState::e_unk42, + LegoGameState::e_helicopterTakenOff, TRUE, IslePathActor::c_spawnBit1 | IslePathActor::c_playMusic | IslePathActor::c_spawnBit3 ); } else { SpawnPlayer( - LegoGameState::e_unk49, + LegoGameState::e_helicopterTakenOffAct3, TRUE, IslePathActor::c_spawnBit1 | IslePathActor::c_playMusic | IslePathActor::c_spawnBit3 ); @@ -361,14 +361,14 @@ MxLong Helicopter::HandleEndAnim(LegoEndAnimNotificationParam& p_param) assert(act1State); act1State->m_state = Act1State::e_none; SpawnPlayer( - LegoGameState::e_unk41, + LegoGameState::e_helicopterLanded, TRUE, IslePathActor::c_spawnBit1 | IslePathActor::c_playMusic | IslePathActor::c_spawnBit3 ); } else { SpawnPlayer( - LegoGameState::e_unk48, + LegoGameState::e_helicopterLandedAct3, TRUE, IslePathActor::c_spawnBit1 | IslePathActor::c_playMusic | IslePathActor::c_spawnBit3 ); diff --git a/LEGO1/lego/legoomni/src/actors/islepathactor.cpp b/LEGO1/lego/legoomni/src/actors/islepathactor.cpp index df7c3634..53349c7b 100644 --- a/LEGO1/lego/legoomni/src/actors/islepathactor.cpp +++ b/LEGO1/lego/legoomni/src/actors/islepathactor.cpp @@ -367,7 +367,7 @@ void IslePathActor::RegisterSpawnLocations() JukeboxScript::c_noneJukebox ); g_spawnLocations[17] = SpawnLocation( - LegoGameState::e_unk41, + LegoGameState::e_helicopterLanded, g_isleScript, 0, "edg02_51", @@ -415,7 +415,7 @@ void IslePathActor::RegisterSpawnLocations() JukeboxScript::c_noneJukebox ); g_spawnLocations[21] = SpawnLocation( - LegoGameState::e_unk42, + LegoGameState::e_helicopterTakenOff, g_isleScript, 0, "inv_05", @@ -427,7 +427,7 @@ void IslePathActor::RegisterSpawnLocations() JukeboxScript::c_noneJukebox ); g_spawnLocations[22] = SpawnLocation( - LegoGameState::e_unk48, + LegoGameState::e_helicopterLandedAct3, g_act3Script, 0, "edg02_51", @@ -439,7 +439,7 @@ void IslePathActor::RegisterSpawnLocations() JukeboxScript::c_noneJukebox ); g_spawnLocations[23] = SpawnLocation( - LegoGameState::e_unk49, + LegoGameState::e_helicopterTakenOffAct3, g_act3Script, 0, "inv_05", @@ -451,7 +451,7 @@ void IslePathActor::RegisterSpawnLocations() JukeboxScript::c_noneJukebox ); g_spawnLocations[24] = SpawnLocation( - LegoGameState::e_unk50, + LegoGameState::e_pepperSpawnAct2, g_act2mainScript, 0, "EDG02_51", @@ -499,7 +499,7 @@ void IslePathActor::RegisterSpawnLocations() JukeboxScript::c_noneJukebox ); g_spawnLocations[28] = SpawnLocation( - LegoGameState::e_unk55, + LegoGameState::e_helicopterExited, g_isleScript, 0, "edg02_50", diff --git a/LEGO1/lego/legoomni/src/worlds/legoact2.cpp b/LEGO1/lego/legoomni/src/worlds/legoact2.cpp index a3ea80e7..46f54b10 100644 --- a/LEGO1/lego/legoomni/src/worlds/legoact2.cpp +++ b/LEGO1/lego/legoomni/src/worlds/legoact2.cpp @@ -488,7 +488,7 @@ void LegoAct2::ReadyWorld() m_pepper = FindROI("pepper"); IslePathActor* pepper = (IslePathActor*) m_pepper->GetEntity(); pepper->SpawnPlayer( - LegoGameState::e_unk50, + LegoGameState::e_pepperSpawnAct2, TRUE, IslePathActor::c_spawnBit1 | IslePathActor::c_playMusic | IslePathActor::c_spawnBit3 ); From 2c20492bf6cbe65efd1c704211382bcd4550bff1 Mon Sep 17 00:00:00 2001 From: Fabian Neundorf Date: Fri, 13 Mar 2026 23:09:17 +0100 Subject: [PATCH 15/17] Clear unknowns in `LegoCarRaceActor` (#1748) --- LEGO1/lego/legoomni/include/legoracespecial.h | 36 +++--- LEGO1/lego/legoomni/src/race/legoracers.cpp | 4 +- .../legoomni/src/race/legoracespecial.cpp | 117 +++++++++--------- 3 files changed, 82 insertions(+), 75 deletions(-) diff --git a/LEGO1/lego/legoomni/include/legoracespecial.h b/LEGO1/lego/legoomni/include/legoracespecial.h index bd650863..570b27a4 100644 --- a/LEGO1/lego/legoomni/include/legoracespecial.h +++ b/LEGO1/lego/legoomni/include/legoracespecial.h @@ -53,27 +53,33 @@ class LegoCarRaceActor : public virtual LegoRaceActor { // LegoCarRaceActor vtable - virtual void FUN_10080590(float p_time); // vtable+0x00 + virtual void UpdateWorldSpeed(float p_time); // vtable+0x00 // FUNCTION: LEGO1 0x10012bb0 - virtual void FUN_10012bb0(float p_unk0x14) { m_unk0x14 = p_unk0x14; } // vtable+0x04 + virtual void SetAcceleration(float p_acceleration) { m_acceleration = p_acceleration; } // vtable+0x04 // FUNCTION: LEGO1 0x10012bc0 - virtual float FUN_10012bc0() { return m_unk0x14; } // vtable+0x08 + virtual float GetAcceleration() { return m_acceleration; } // vtable+0x08 // FUNCTION: LEGO1 0x10012bd0 - virtual void FUN_10012bd0(float p_unk0x10) { m_unk0x10 = p_unk0x10; } // vtable+0x0c + virtual void SetCurveSpeedFactor(float p_curveSpeedFactor) + { + m_curveSpeedFactor = p_curveSpeedFactor; + } // vtable+0x0c // FUNCTION: LEGO1 0x10012be0 - virtual float FUN_10012be0() { return m_unk0x10; } // vtable+0x10 + virtual float GetCurveSpeedFactor() { return m_curveSpeedFactor; } // vtable+0x10 // FUNCTION: LEGO1 0x10012bf0 - virtual void FUN_10012bf0(float p_unk0x18) { m_unk0x18 = p_unk0x18; } // vtable+0x14 + virtual void SetRubberBandFactor(float p_rubberBandFactor) + { + m_rubberBandFactor = p_rubberBandFactor; + } // vtable+0x14 // FUNCTION: LEGO1 0x10012c00 - virtual float FUN_10012c00() { return m_unk0x18; } // vtable+0x18 + virtual float GetRubberBandFactor() { return m_rubberBandFactor; } // vtable+0x18 - virtual MxS32 VTable0x1c(LegoPathBoundary* p_boundary, LegoEdge* p_edge); // vtable+0x1c + virtual MxS32 HandleJump(LegoPathBoundary* p_boundary, LegoEdge* p_edge); // vtable+0x1c // SYNTHETIC: LEGO1 0x10012c30 // LegoCarRaceActor::`vbase destructor' @@ -88,18 +94,18 @@ class LegoCarRaceActor : public virtual LegoRaceActor { MxFloat m_unk0x08; // 0x08 MxU8 m_animState; // 0x0c - // Could be a multiplier for the maximum speed when going straight - MxFloat m_unk0x10; // 0x10 + // A multiplier for the maximum speed when going around a curve + MxFloat m_curveSpeedFactor; // 0x10 // Could be the acceleration - MxFloat m_unk0x14; // 0x14 + MxFloat m_acceleration; // 0x14 - MxFloat m_unk0x18; // 0x18 + MxFloat m_rubberBandFactor; // 0x18 // Could be the current timestamp for time-based movement - MxFloat m_unk0x1c; // 0x1c + MxFloat m_lastAcceleration; // 0x1c - static MxFloat g_unk0x100f7aec; + static MxFloat g_maxSpeed; }; // VTABLE: LEGO1 0x100da208 LegoCarRaceActor @@ -139,7 +145,7 @@ class LegoJetskiRaceActor : public virtual LegoCarRaceActor { Vector3& p_intersectionPoint ) override; // vtable+0x6c void Animate(float p_time) override; // vtable+0x70 - MxS32 VTable0x1c(LegoPathBoundary* p_boundary, LegoEdge* p_edge) override; // vtable+0x1c + MxS32 HandleJump(LegoPathBoundary* p_boundary, LegoEdge* p_edge) override; // vtable+0x1c // SYNTHETIC: LEGO1 0x10013a80 // LegoJetskiRaceActor::`vbase destructor' diff --git a/LEGO1/lego/legoomni/src/race/legoracers.cpp b/LEGO1/lego/legoomni/src/race/legoracers.cpp index c4658ef6..ba019a2f 100644 --- a/LEGO1/lego/legoomni/src/race/legoracers.cpp +++ b/LEGO1/lego/legoomni/src/race/legoracers.cpp @@ -414,7 +414,7 @@ void LegoRaceCar::Animate(float p_time) UpdateMapLocatorPosition(); if (!m_userNavFlag) { - FUN_10080590(p_time); + UpdateWorldSpeed(p_time); return; } @@ -613,7 +613,7 @@ void LegoJetski::Animate(float p_time) UpdateMapLocatorPosition(); if (!m_userNavFlag) { - FUN_10080590(p_time); + UpdateWorldSpeed(p_time); return; } diff --git a/LEGO1/lego/legoomni/src/race/legoracespecial.cpp b/LEGO1/lego/legoomni/src/race/legoracespecial.cpp index a9d6f955..ed0ccd7c 100644 --- a/LEGO1/lego/legoomni/src/race/legoracespecial.cpp +++ b/LEGO1/lego/legoomni/src/race/legoracespecial.cpp @@ -32,11 +32,11 @@ const char* g_fuel = "FUEL"; const char* g_racing = "RACING"; // GLOBAL: LEGO1 0x100f7aec -MxFloat LegoCarRaceActor::g_unk0x100f7aec = 8.0f; +MxFloat LegoCarRaceActor::g_maxSpeed = 8.0f; // GLOBAL: LEGO1 0x100da044 // GLOBAL: BETA10 0x101be9fc -MxFloat g_unk0x100da044 = 8.0f; +MxFloat g_maxWorldSpeed = 8.0f; // FUNCTION: LEGO1 0x10080350 // FUNCTION: BETA10 0x100cd6b0 @@ -47,10 +47,10 @@ LegoCarRaceActor::LegoCarRaceActor() m_animState = 0; m_maxLinearVel = 0.0f; m_frequencyFactor = 1.0f; - m_unk0x1c = 0; - m_unk0x10 = 0.65f; - m_unk0x14 = 0.03f; - m_unk0x18 = 0.6f; + m_lastAcceleration = 0; + m_curveSpeedFactor = 0.65f; + m_acceleration = 0.03f; + m_rubberBandFactor = 0.6f; m_wallHitDirectionFactor = 0.1f; m_linearRotationRatio = -5.0f; m_canRotate = 1; @@ -59,43 +59,43 @@ LegoCarRaceActor::LegoCarRaceActor() // FUNCTION: LEGO1 0x10080590 // FUNCTION: BETA10 0x100cd8cf -void LegoCarRaceActor::FUN_10080590(float p_time) +void LegoCarRaceActor::UpdateWorldSpeed(float p_time) { MxFloat maxSpeed = m_maxLinearVel; - Mx3DPointFloat destEdgeUnknownVector; + Mx3DPointFloat edgeNormal; Mx3DPointFloat worldDirection = Mx3DPointFloat(m_roi->GetWorldDirection()); - m_destEdge->GetFaceNormal(*m_boundary, destEdgeUnknownVector); + m_destEdge->GetFaceNormal(*m_boundary, edgeNormal); - if (abs(destEdgeUnknownVector.Dot(destEdgeUnknownVector.GetData(), worldDirection.GetData())) > 0.5) { - maxSpeed *= m_unk0x10; + if (abs(edgeNormal.Dot(edgeNormal.GetData(), worldDirection.GetData())) > 0.5) { + maxSpeed *= m_curveSpeedFactor; } - MxS32 deltaUnk0x70; + MxS32 deltaPathStructs; LegoPathActor* userActor = UserActor(); if (userActor) { // All known implementations of LegoPathActor->GetLastPathStruct() return LegoPathActor::m_lastPathStruct - deltaUnk0x70 = m_lastPathStruct - userActor->GetLastPathStruct(); + deltaPathStructs = m_lastPathStruct - userActor->GetLastPathStruct(); } else { - deltaUnk0x70 = 0; + deltaPathStructs = 0; } - if (deltaUnk0x70 > 1) { - if (deltaUnk0x70 > 3) { - deltaUnk0x70 = 3; + if (deltaPathStructs > 1) { + if (deltaPathStructs > 3) { + deltaPathStructs = 3; } - maxSpeed *= (m_unk0x18 * (--deltaUnk0x70) * -0.25f + 1.0f); + maxSpeed *= (m_rubberBandFactor * (--deltaPathStructs) * -0.25f + 1.0f); } - else if (deltaUnk0x70 < -1) { + else if (deltaPathStructs < -1) { maxSpeed *= 1.3; } MxFloat deltaSpeed = maxSpeed - m_worldSpeed; - MxFloat changeInSpeed = (p_time - m_unk0x1c) * m_unk0x14; - m_unk0x1c = p_time; + MxFloat changeInSpeed = (p_time - m_lastAcceleration) * m_acceleration; + m_lastAcceleration = p_time; if (deltaSpeed < 0.0f) { changeInSpeed = -changeInSpeed; @@ -112,16 +112,17 @@ void LegoCarRaceActor::FUN_10080590(float p_time) // FUNCTION: LEGO1 0x10080740 // FUNCTION: BETA10 0x100cece0 -MxS32 LegoCarRaceActor::VTable0x1c(LegoPathBoundary* p_boundary, LegoEdge* p_edge) +MxS32 LegoCarRaceActor::HandleJump(LegoPathBoundary* p_boundary, LegoEdge* p_edge) { - Mx3DPointFloat pointUnknown; + Mx3DPointFloat targetPosition; Mx3DPointFloat destEdgeUnknownVector; - Mx3DPointFloat crossProduct; + Mx3DPointFloat targetDirection; if (m_actorState == c_ready) { m_boundary = NULL; - // Not sure where the upper bound of 11 comes from, the underlying array has a size of 16 + // The first 12 elements are used for the car race, the other 4 for jetski + // As it increments by 2, counting to 10 or 11 is the same. for (MxS32 i = 0; i < 11; i += 2) { if (LegoPathController::GetControlEdgeA(i + 1) == m_destEdge) { m_boundary = LegoPathController::GetControlBoundaryA(i + 1); @@ -147,8 +148,8 @@ MxS32 LegoCarRaceActor::VTable0x1c(LegoPathBoundary* p_boundary, LegoEdge* p_edg if (LegoPathController::GetControlEdgeA(i) == p_edge) { m_actorState = c_ready; - if (m_worldSpeed < g_unk0x100f7aec) { - m_worldSpeed = g_unk0x100f7aec; + if (m_worldSpeed < g_maxSpeed) { + m_worldSpeed = g_maxSpeed; } m_destEdge = LegoPathController::GetControlEdgeA(i + 1); @@ -167,12 +168,12 @@ MxS32 LegoCarRaceActor::VTable0x1c(LegoPathBoundary* p_boundary, LegoEdge* p_edg Vector3* v2 = m_destEdge->CWVertex(*m_boundary); assert(v1 && v2); - LERP3(pointUnknown, *v1, *v2, m_destScale); + LERP3(targetPosition, *v1, *v2, m_destScale); m_destEdge->GetFaceNormal(*m_boundary, destEdgeUnknownVector); - crossProduct.EqualsCross(*m_boundary->GetUp(), destEdgeUnknownVector); - crossProduct.Unitize(); + targetDirection.EqualsCross(*m_boundary->GetUp(), destEdgeUnknownVector); + targetDirection.Unitize(); Mx3DPointFloat worldDirection(Vector3(m_roi->GetWorldDirection())); @@ -181,10 +182,10 @@ MxS32 LegoCarRaceActor::VTable0x1c(LegoPathBoundary* p_boundary, LegoEdge* p_edg } worldDirection *= 5.0f; - crossProduct *= 5.0f; + targetDirection *= 5.0f; MxResult callResult = - SetSpline(Vector3(m_roi->GetWorldPosition()), worldDirection, pointUnknown, crossProduct); + SetSpline(Vector3(m_roi->GetWorldPosition()), worldDirection, targetPosition, targetDirection); if (callResult) { m_traveledDistance = 0; @@ -230,7 +231,7 @@ void LegoCarRaceActor::Animate(float p_time) if (strcmpi(value, g_racing) == 0) { m_animState = 1; m_transformTime = p_time - 1.0f; - m_unk0x1c = p_time; + m_lastAcceleration = p_time; } } @@ -245,7 +246,7 @@ MxResult LegoCarRaceActor::CalculateSpline() { LegoOrientedEdge* d = m_destEdge; - if (VTable0x1c(m_boundary, m_destEdge)) { + if (HandleJump(m_boundary, m_destEdge)) { LegoPathBoundary* b = m_boundary; SwitchBoundary(m_boundary, m_destEdge, m_destScale); @@ -256,27 +257,27 @@ MxResult LegoCarRaceActor::CalculateSpline() Vector3* v2 = m_destEdge->CCWVertex(*m_boundary); assert(v1 && v2); - Mx3DPointFloat point1; - LERP3(point1, *v1, *v2, m_destScale); + Mx3DPointFloat end; + LERP3(end, *v1, *v2, m_destScale); - Mx3DPointFloat point2; - Mx3DPointFloat point3; - Mx3DPointFloat point4; - Mx3DPointFloat point5; + Mx3DPointFloat startEdgeNormal; + Mx3DPointFloat endEdgeNormal; + Mx3DPointFloat startDirection; + Mx3DPointFloat endDirection; - d->GetFaceNormal(*b, point2); - m_destEdge->GetFaceNormal(*m_boundary, point3); + d->GetFaceNormal(*b, startEdgeNormal); + m_destEdge->GetFaceNormal(*m_boundary, endEdgeNormal); - point4.EqualsCross(point2, *m_boundary->GetUp()); - point5.EqualsCross(*m_boundary->GetUp(), point3); + startDirection.EqualsCross(startEdgeNormal, *m_boundary->GetUp()); + endDirection.EqualsCross(*m_boundary->GetUp(), endEdgeNormal); - point4.Unitize(); - point5.Unitize(); + startDirection.Unitize(); + endDirection.Unitize(); - point4 *= 5.0f; - point5 *= 5.0f; + startDirection *= 5.0f; + endDirection *= 5.0f; - MxResult res = SetSpline(m_roi->GetWorldPosition(), point4, point1, point5); + MxResult res = SetSpline(m_roi->GetWorldPosition(), startDirection, end, endDirection); #ifdef BETA10 if (res) { @@ -295,15 +296,15 @@ MxResult LegoCarRaceActor::CalculateSpline() // FUNCTION: BETA10 0x100a8990 LegoJetskiRaceActor::LegoJetskiRaceActor() { - m_unk0x10 = 0.95f; - m_unk0x14 = 0.04f; - m_unk0x18 = 0.5f; + m_curveSpeedFactor = 0.95f; + m_acceleration = 0.04f; + m_rubberBandFactor = 0.5f; m_linearRotationRatio = 1.5f; } // FUNCTION: LEGO1 0x10081120 // FUNCTION: BETA10 0x100ce19f -MxS32 LegoJetskiRaceActor::VTable0x1c(LegoPathBoundary* p_boundary, LegoEdge* p_edge) +MxS32 LegoJetskiRaceActor::HandleJump(LegoPathBoundary* p_boundary, LegoEdge* p_edge) { // These are almost certainly not the correct names, but they produce the correct BETA10 stack Mx3DPointFloat a; @@ -337,8 +338,8 @@ MxS32 LegoJetskiRaceActor::VTable0x1c(LegoPathBoundary* p_boundary, LegoEdge* p_ if (p_edge == LegoPathController::GetControlEdgeA(12)) { m_actorState = c_ready; - if (m_worldSpeed < g_unk0x100da044) { - m_worldSpeed = g_unk0x100da044; + if (m_worldSpeed < g_maxWorldSpeed) { + m_worldSpeed = g_maxWorldSpeed; } m_destEdge = LegoPathController::GetControlEdgeA(13); @@ -347,8 +348,8 @@ MxS32 LegoJetskiRaceActor::VTable0x1c(LegoPathBoundary* p_boundary, LegoEdge* p_ else if (p_edge == LegoPathController::GetControlEdgeA(14)) { m_actorState = c_ready; - if (m_worldSpeed < g_unk0x100da044) { - m_worldSpeed = g_unk0x100da044; + if (m_worldSpeed < g_maxWorldSpeed) { + m_worldSpeed = g_maxWorldSpeed; } m_destEdge = LegoPathController::GetControlEdgeA(15); @@ -403,7 +404,7 @@ void LegoJetskiRaceActor::Animate(float p_time) if (!stricmp(raceState, g_racing)) { m_animState = 1; m_transformTime = p_time - 1.0f; - m_unk0x1c = p_time; + m_lastAcceleration = p_time; } else if (!m_userNavFlag) { LegoAnimActor::Animate(m_transformTime + 1.0f); From 3b2980692183cc75499d6d31de10067b20824c3f Mon Sep 17 00:00:00 2001 From: Fabian Neundorf Date: Sat, 14 Mar 2026 16:55:42 +0100 Subject: [PATCH 16/17] Clear unknowns in `Act3` (#1749) --- LEGO1/lego/legoomni/include/act3.h | 52 +++++------ LEGO1/lego/legoomni/src/actors/act3actors.cpp | 2 +- LEGO1/lego/legoomni/src/actors/act3ammo.cpp | 4 +- LEGO1/lego/legoomni/src/actors/helicopter.cpp | 4 +- LEGO1/lego/legoomni/src/worlds/act3.cpp | 87 ++++++++++--------- 5 files changed, 79 insertions(+), 70 deletions(-) diff --git a/LEGO1/lego/legoomni/include/act3.h b/LEGO1/lego/legoomni/include/act3.h index ffba305b..b0713136 100644 --- a/LEGO1/lego/legoomni/include/act3.h +++ b/LEGO1/lego/legoomni/include/act3.h @@ -143,15 +143,15 @@ class Act3 : public LegoWorld { MxResult ShootPizza(LegoPathController* p_controller, Vector3& p_location, Vector3& p_direction, Vector3& p_up); MxResult ShootDonut(LegoPathController* p_controller, Vector3& p_location, Vector3& p_direction, Vector3& p_up); void TriggerHitSound(undefined4 p_param1); - MxResult FUN_10073360(Act3Ammo& p_ammo, const Vector3& p_param2); - MxResult FUN_10073390(Act3Ammo& p_ammo, const Vector3& p_param2); + MxResult HitBrickster(Act3Ammo& p_ammo, const Vector3& p_param2); + MxResult HitCop(Act3Ammo& p_ammo, const Vector3& p_param2); void SetBrickster(Act3Brickster* p_brickster); void AddCop(Act3Cop* p_cop); - void FUN_10073400(); - void FUN_10073430(); + void TransitionToGoodEnding(); + void TransitionToBadEnding(); void GoodEnding(const Matrix4& p_destination); void BadEnding(const Matrix4& p_destination); - void FUN_10073a60(); + void DisableHelicopterDot(); // BETA indicates that the following classes access certain members directly. friend class Act3Ammo; @@ -171,27 +171,27 @@ class Act3 : public LegoWorld { const MxQuaternionTransformer& p_quatTransform ); - Act3State* m_state; // 0xf8 - Act3Ammo m_pizzas[MAX_PIZZAS]; // 0xfc - Act3Ammo m_donuts[MAX_DONUTS]; // 0x217c - undefined m_unk0x41fc; // 0x41fc - Act3Cop* m_cop1; // 0x4200 - Act3Cop* m_cop2; // 0x4204 - Act3Brickster* m_brickster; // 0x4208 - Helicopter* m_copter; // 0x420c - Act3Shark* m_shark; // 0x4210 - MxFloat m_time; // 0x4214 - MxU8 m_pizzaHitSound; // 0x4218 - MxU8 m_pizzaMissSound; // 0x4219 - MxU8 m_copDonutSound; // 0x421a - MxU8 m_donutMissSound; // 0x421b - MxU8 m_islanderSound; // 0x421c - MxU8 m_bricksterDonutSound; // 0x421d - undefined m_unk0x421e; // 0x421e - Act3List m_unk0x4220; // 0x4220 - MxPresenter* m_helicopterDots[15]; // 0x4230 - Act3Script::Script m_unk0x426c; // 0x426c - LegoGameState::Area m_destLocation; // 0x4270 + Act3State* m_state; // 0xf8 + Act3Ammo m_pizzas[MAX_PIZZAS]; // 0xfc + Act3Ammo m_donuts[MAX_DONUTS]; // 0x217c + undefined m_unk0x41fc; // 0x41fc + Act3Cop* m_cop1; // 0x4200 + Act3Cop* m_cop2; // 0x4204 + Act3Brickster* m_brickster; // 0x4208 + Helicopter* m_copter; // 0x420c + Act3Shark* m_shark; // 0x4210 + MxFloat m_time; // 0x4214 + MxU8 m_pizzaHitSound; // 0x4218 + MxU8 m_pizzaMissSound; // 0x4219 + MxU8 m_copDonutSound; // 0x421a + MxU8 m_donutMissSound; // 0x421b + MxU8 m_islanderSound; // 0x421c + MxU8 m_bricksterDonutSound; // 0x421d + undefined m_helicopterDotCount; // 0x421e + Act3List m_soundList; // 0x4220 + MxPresenter* m_helicopterDots[15]; // 0x4230 + Act3Script::Script m_explanationAnimation; // 0x426c + LegoGameState::Area m_destLocation; // 0x4270 }; // TEMPLATE: LEGO1 0x10071f10 diff --git a/LEGO1/lego/legoomni/src/actors/act3actors.cpp b/LEGO1/lego/legoomni/src/actors/act3actors.cpp index e109cb54..56b2b378 100644 --- a/LEGO1/lego/legoomni/src/actors/act3actors.cpp +++ b/LEGO1/lego/legoomni/src/actors/act3actors.cpp @@ -648,7 +648,7 @@ void Act3Brickster::Animate(float p_time) assert(m_shootAnim && m_bInfo); if (m_unk0x50 < p_time) { - ((Act3*) m_world)->FUN_10073a60(); + ((Act3*) m_world)->DisableHelicopterDot(); m_unk0x58 = 0; assert(SoundManager()->GetCacheSoundManager()); SoundManager()->GetCacheSoundManager()->Play("thpt", NULL, FALSE); diff --git a/LEGO1/lego/legoomni/src/actors/act3ammo.cpp b/LEGO1/lego/legoomni/src/actors/act3ammo.cpp index 961cfb34..045df491 100644 --- a/LEGO1/lego/legoomni/src/actors/act3ammo.cpp +++ b/LEGO1/lego/legoomni/src/actors/act3ammo.cpp @@ -483,10 +483,10 @@ void Act3Ammo::Animate(float p_time) if (!annihilated) { if (IsPizza()) { - m_world->FUN_10073360(*this, position); + m_world->HitBrickster(*this, position); } else { - m_world->FUN_10073390(*this, position); + m_world->HitCop(*this, position); } m_worldSpeed = -1.0f; diff --git a/LEGO1/lego/legoomni/src/actors/helicopter.cpp b/LEGO1/lego/legoomni/src/actors/helicopter.cpp index 8a547fdf..740e92fd 100644 --- a/LEGO1/lego/legoomni/src/actors/helicopter.cpp +++ b/LEGO1/lego/legoomni/src/actors/helicopter.cpp @@ -430,10 +430,10 @@ void Helicopter::Animate(float p_time) } else { if (m_state->m_unk0x08 == 4) { - ((Act3*) m_world)->FUN_10073400(); + ((Act3*) m_world)->TransitionToGoodEnding(); } else { - ((Act3*) m_world)->FUN_10073430(); + ((Act3*) m_world)->TransitionToBadEnding(); } SetActorState(c_disabled); diff --git a/LEGO1/lego/legoomni/src/worlds/act3.cpp b/LEGO1/lego/legoomni/src/worlds/act3.cpp index e7f5bb3e..2f7638ba 100644 --- a/LEGO1/lego/legoomni/src/worlds/act3.cpp +++ b/LEGO1/lego/legoomni/src/worlds/act3.cpp @@ -102,10 +102,10 @@ Act3Script::Script g_bricksterDonutSounds[] = { }; // GLOBAL: LEGO1 0x100f7814 -MxU8 g_unk0x100f7814 = 0; +MxU8 g_copSelector = 0; // GLOBAL: LEGO1 0x100d95e8 -Act3Script::Script g_unk0x100d95e8[] = +Act3Script::Script g_explanationAnimations[] = {Act3Script::c_tlp053in_RunAnim, Act3Script::c_tlp064la_RunAnim, Act3Script::c_tlp068in_RunAnim}; // FUNCTION: LEGO1 0x10071d40 @@ -246,7 +246,7 @@ Act3::Act3() m_copter = NULL; m_shark = NULL; m_time = -1; - m_unk0x421e = 0; + m_helicopterDotCount = 0; memset(m_helicopterDots, 0, sizeof(m_helicopterDots)); @@ -326,7 +326,7 @@ MxResult Act3::ShootPizza(LegoPathController* p_controller, Vector3& p_location, for (nextPizza = 0; nextPizza < (MxS32) sizeOfArray(m_pizzas); nextPizza++) { if (!m_pizzas[nextPizza].IsValid()) { LegoPathBoundary* boundary = NULL; - MxU32 local18 = TRUE; + MxU32 noBoundaryIntersected = TRUE; m_pizzas[nextPizza].Create(this, TRUE, nextPizza); @@ -334,26 +334,26 @@ MxResult Act3::ShootPizza(LegoPathController* p_controller, Vector3& p_location, return FAILURE; } - MxFloat unk0x19c = *m_pizzas[nextPizza].GetApexParameter(); + MxFloat apexParameter = *m_pizzas[nextPizza].GetApexParameter(); if (p_controller->FindIntersectionBoundary( p_location, p_direction, m_pizzas[nextPizza].GetCoefficients(), boundary, - unk0x19c + apexParameter ) == SUCCESS) { Mx3DPointFloat direction; direction = p_direction; - direction *= unk0x19c; + direction *= apexParameter; direction += p_location; assert(m_brickster && m_brickster->GetROI()); direction -= m_brickster->GetROI()->GetLocal2World()[3]; - local18 = FALSE; - if (m_pizzas[nextPizza].Shoot(p_controller, boundary, unk0x19c) == SUCCESS) { + noBoundaryIntersected = FALSE; + if (m_pizzas[nextPizza].Shoot(p_controller, boundary, apexParameter) == SUCCESS) { p_controller->PlaceActor(&m_pizzas[nextPizza]); boundary->AddActor(&m_pizzas[nextPizza]); m_pizzas[nextPizza].SetWorldSpeed(10.0f); @@ -361,7 +361,7 @@ MxResult Act3::ShootPizza(LegoPathController* p_controller, Vector3& p_location, } } - if (local18 && m_pizzas[nextPizza].Shoot(p_controller, unk0x19c) == SUCCESS) { + if (noBoundaryIntersected && m_pizzas[nextPizza].Shoot(p_controller, apexParameter) == SUCCESS) { p_controller->PlaceActor(&m_pizzas[nextPizza]); m_pizzas[nextPizza].SetWorldSpeed(10.0f); return SUCCESS; @@ -389,22 +389,22 @@ MxResult Act3::ShootDonut(LegoPathController* p_controller, Vector3& p_location, return FAILURE; } - MxFloat unk0x19c = *m_donuts[nextDonut].GetApexParameter(); + MxFloat apexParameter = *m_donuts[nextDonut].GetApexParameter(); if (p_controller->FindIntersectionBoundary( p_location, p_direction, m_donuts[nextDonut].GetCoefficients(), boundary, - unk0x19c + apexParameter ) == SUCCESS) { - if (m_donuts[nextDonut].Shoot(p_controller, boundary, unk0x19c) == SUCCESS) { + if (m_donuts[nextDonut].Shoot(p_controller, boundary, apexParameter) == SUCCESS) { p_controller->PlaceActor(&m_donuts[nextDonut]); boundary->AddActor(&m_donuts[nextDonut]); m_donuts[nextDonut].SetWorldSpeed(10.0f); return SUCCESS; } } - else if (m_donuts[nextDonut].Shoot(p_controller, unk0x19c) == SUCCESS) { + else if (m_donuts[nextDonut].Shoot(p_controller, apexParameter) == SUCCESS) { p_controller->PlaceActor(&m_donuts[nextDonut]); m_donuts[nextDonut].SetWorldSpeed(10.0f); return SUCCESS; @@ -468,14 +468,14 @@ void Act3::TriggerHitSound(undefined4 p_param1) m_bricksterDonutSound = 0; } - m_unk0x4220.Insert(g_bricksterDonutSounds[m_bricksterDonutSound++], Act3ListElement::e_replaceAction); + m_soundList.Insert(g_bricksterDonutSounds[m_bricksterDonutSound++], Act3ListElement::e_replaceAction); return; } default: return; } - m_unk0x4220.Insert(objectId, Act3ListElement::e_onlyIfEmpty); + m_soundList.Insert(objectId, Act3ListElement::e_onlyIfEmpty); } // FUNCTION: LEGO1 0x10072c30 @@ -598,24 +598,24 @@ MxLong Act3::Notify(MxParam& p_param) MxS32 length; LegoBuildingInfo* info = BuildingManager()->GetInfoArray(length); - m_unk0x421e = 0; + m_helicopterDotCount = 0; while (--length >= 0) { if (info[length].m_counter < 0 && info[length].m_boundary != NULL && info[length].m_entity != NULL) { - m_unk0x421e++; + m_helicopterDotCount++; } } length = 0; - m_unk0x421e--; + m_helicopterDotCount--; char buf[80]; do { sprintf(buf, "HelicopterDotOn%d_Bitmap", length + 1); m_helicopterDots[length] = (MxPresenter*) Find("MxPresenter", buf); - if (m_unk0x421e > length) { + if (m_helicopterDotCount > length) { m_helicopterDots[length]->Enable(TRUE); } else { @@ -626,7 +626,7 @@ MxLong Act3::Notify(MxParam& p_param) } while (length < (MxS32) sizeOfArray(m_helicopterDots)); } else { - m_unk0x4220.RemoveByObjectIdOrFirst(param.GetAction()->GetObjectId()); + m_soundList.RemoveByObjectIdOrFirst(param.GetAction()->GetObjectId()); } } break; @@ -646,7 +646,7 @@ MxLong Act3::Notify(MxParam& p_param) case c_notificationEndAnim: if (m_state->m_state == Act3State::e_ready) { assert(m_copter && m_brickster && m_cop1 && m_cop2); - m_unk0x4220.RemoveByObjectIdOrFirst(0); + m_soundList.RemoveByObjectIdOrFirst(0); m_state->m_state = Act3State::e_initial; Disable(TRUE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); m_copter->HandleClick(); @@ -682,9 +682,18 @@ void Act3::ReadyWorld() AnimationManager()->FUN_1005f6d0(FALSE); VideoManager()->Get3DManager()->SetFrustrum(90.0f, 0.1f, 125.0f); - m_unk0x426c = g_unk0x100d95e8[rand() % 3]; - AnimationManager() - ->FUN_10060dc0(m_unk0x426c, NULL, TRUE, LegoAnimationManager::e_unk0, NULL, TRUE, FALSE, FALSE, FALSE); + m_explanationAnimation = g_explanationAnimations[rand() % 3]; + AnimationManager()->FUN_10060dc0( + m_explanationAnimation, + NULL, + TRUE, + LegoAnimationManager::e_unk0, + NULL, + TRUE, + FALSE, + FALSE, + FALSE + ); m_state->m_state = Act3State::e_ready; } @@ -697,11 +706,11 @@ MxResult Act3::Tickle() return SUCCESS; } - if (m_unk0x426c != (Act3Script::Script) 0) { - if (AnimationManager()->FUN_10064ee0(m_unk0x426c)) { + if (m_explanationAnimation != (Act3Script::Script) 0) { + if (AnimationManager()->FUN_10064ee0(m_explanationAnimation)) { Disable(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); TickleManager()->UnregisterClient(this); - m_unk0x426c = (Act3Script::Script) 0; + m_explanationAnimation = (Act3Script::Script) 0; } } @@ -710,7 +719,7 @@ MxResult Act3::Tickle() // FUNCTION: LEGO1 0x10073360 // FUNCTION: BETA10 0x100169d5 -MxResult Act3::FUN_10073360(Act3Ammo& p_ammo, const Vector3& p_param2) +MxResult Act3::HitBrickster(Act3Ammo& p_ammo, const Vector3& p_param2) { assert(m_brickster); m_brickster->FUN_100417a0(p_ammo, p_param2); @@ -720,11 +729,11 @@ MxResult Act3::FUN_10073360(Act3Ammo& p_ammo, const Vector3& p_param2) // FUNCTION: LEGO1 0x10073390 // FUNCTION: BETA10 0x10016a40 -MxResult Act3::FUN_10073390(Act3Ammo& p_ammo, const Vector3& p_param2) +MxResult Act3::HitCop(Act3Ammo& p_ammo, const Vector3& p_param2) { assert(m_cop1 && m_cop2); - if (!(g_unk0x100f7814 & 1)) { + if (!(g_copSelector & 1)) { m_cop1->FUN_10040350(p_ammo, p_param2); } else { @@ -732,7 +741,7 @@ MxResult Act3::FUN_10073390(Act3Ammo& p_ammo, const Vector3& p_param2) } TriggerHitSound(3); - g_unk0x100f7814++; + g_copSelector++; return SUCCESS; } @@ -756,7 +765,7 @@ void Act3::SetBrickster(Act3Brickster* p_brickster) } // FUNCTION: LEGO1 0x10073400 -void Act3::FUN_10073400() +void Act3::TransitionToGoodEnding() { m_state->m_state = Act3State::e_goodEnding; m_destLocation = LegoGameState::e_infomain; @@ -764,7 +773,7 @@ void Act3::FUN_10073400() } // FUNCTION: LEGO1 0x10073430 -void Act3::FUN_10073430() +void Act3::TransitionToBadEnding() { m_state->m_state = Act3State::e_badEnding; m_destLocation = LegoGameState::e_infomain; @@ -782,7 +791,7 @@ void Act3::GoodEnding(const Matrix4& p_destination) m_brickster->SetActorState(LegoPathActor::c_disabled); #ifndef BETA10 - m_unk0x4220.Clear(); + m_soundList.Clear(); m_copter->FUN_10004640(p_destination); DebugPrintf("In Good Ending..."); @@ -865,7 +874,7 @@ void Act3::BadEnding(const Matrix4& p_destination) m_cop2->SetActorState(LegoPathActor::c_disabled); m_brickster->SetActorState(LegoPathActor::c_disabled); - m_unk0x4220.Clear(); + m_soundList.Clear(); m_copter->FUN_10004670(p_destination); DebugPrintf("In Bad Ending..."); @@ -879,10 +888,10 @@ void Act3::BadEnding(const Matrix4& p_destination) } // FUNCTION: LEGO1 0x10073a60 -void Act3::FUN_10073a60() +void Act3::DisableHelicopterDot() { - m_unk0x421e--; - m_helicopterDots[m_unk0x421e]->Enable(FALSE); + m_helicopterDotCount--; + m_helicopterDots[m_helicopterDotCount]->Enable(FALSE); } // FUNCTION: LEGO1 0x10073a90 From b522b8ac3ae319bb3253c8fa7e40d4feb6c7640e Mon Sep 17 00:00:00 2001 From: Fabian Neundorf Date: Sat, 14 Mar 2026 16:56:22 +0100 Subject: [PATCH 17/17] Lego path struct (#1750) * Clear unknowns in `LegoPathStruct` * Clear unknowns cam animations * Improve path struct trigger names --------- Co-authored-by: Florian Kaiser --- LEGO1/lego/legoomni/include/legopathstruct.h | 16 +++++----- LEGO1/lego/legoomni/src/actors/ambulance.cpp | 2 +- LEGO1/lego/legoomni/src/actors/pizza.cpp | 6 ++-- LEGO1/lego/legoomni/src/actors/towtrack.cpp | 4 +-- .../legoomni/src/paths/legopathstruct.cpp | 32 +++++++++---------- LEGO1/lego/legoomni/src/race/carrace.cpp | 2 +- LEGO1/lego/legoomni/src/race/jetskirace.cpp | 6 ++-- LEGO1/lego/legoomni/src/race/legorace.cpp | 4 +-- 8 files changed, 36 insertions(+), 36 deletions(-) diff --git a/LEGO1/lego/legoomni/include/legopathstruct.h b/LEGO1/lego/legoomni/include/legopathstruct.h index 162438f5..4174be96 100644 --- a/LEGO1/lego/legoomni/include/legopathstruct.h +++ b/LEGO1/lego/legoomni/include/legopathstruct.h @@ -73,13 +73,13 @@ class LegoPathStruct : public LegoPathStructBase { public: enum Trigger { c_camAnim = 'C', - c_d = 'D', - c_e = 'E', - c_g = 'G', - c_h = 'H', + c_waypoint = 'D', + c_deleteAction = 'E', + c_nothing = 'G', + c_hideAnim = 'H', c_music = 'M', - c_s = 'S', - c_w = 'W' + c_specialMissionWaypointAndAction = 'S', + c_missionFinalWaypoint = 'W' }; // FUNCTION: LEGO1 0x100473a0 @@ -94,8 +94,8 @@ class LegoPathStruct : public LegoPathStructBase { void SetAtomId(const MxAtomId& p_atomId) { m_atomId = p_atomId; } private: - MxBool HandleTrigger(LegoPathActor* p_actor, MxBool p_direction, MxU32 p_data, MxBool p_bool); - void FUN_1001bc40(const char* p_name, MxU32 p_data, MxBool p_bool); + MxBool HandleTrigger(LegoPathActor* p_actor, MxBool p_direction, MxU32 p_data, MxBool p_invertDirection); + void HandleAction(const char* p_name, MxU32 p_data, MxBool p_start); void PlayMusic(MxBool p_direction, MxU32 p_data); LegoWorld* m_world; // 0x0c diff --git a/LEGO1/lego/legoomni/src/actors/ambulance.cpp b/LEGO1/lego/legoomni/src/actors/ambulance.cpp index 7049009f..451aff60 100644 --- a/LEGO1/lego/legoomni/src/actors/ambulance.cpp +++ b/LEGO1/lego/legoomni/src/actors/ambulance.cpp @@ -315,7 +315,7 @@ MxLong Ambulance::HandlePathStruct(LegoPathStructNotificationParam& p_param) PlayAction(IsleScript::c_Avo915In_PlayWav); } } - else if (p_param.GetTrigger() == LegoPathStruct::c_s && p_param.GetData() == 0x131 && m_atBeachTask == 0) { + else if (p_param.GetTrigger() == LegoPathStruct::c_specialMissionWaypointAndAction && p_param.GetData() == 0x131 && m_atBeachTask == 0) { m_atBeachTask = 1; m_taskState = Ambulance::e_waiting; diff --git a/LEGO1/lego/legoomni/src/actors/pizza.cpp b/LEGO1/lego/legoomni/src/actors/pizza.cpp index 291969cf..11b822ee 100644 --- a/LEGO1/lego/legoomni/src/actors/pizza.cpp +++ b/LEGO1/lego/legoomni/src/actors/pizza.cpp @@ -281,7 +281,7 @@ MxLong Pizza::HandlePathStruct(LegoPathStructNotificationParam& p_param) if (m_state->m_state == PizzaMissionState::e_delivering) { MxLong time = Timer()->GetTime() - m_mission->m_startTime; - if (p_param.GetTrigger() == LegoPathStruct::c_s && p_param.GetData() == 0x12e && + if (p_param.GetTrigger() == LegoPathStruct::c_specialMissionWaypointAndAction && p_param.GetData() == 0x12e && GameState()->GetActorId() == LegoActor::c_pepper) { m_state->m_state = PizzaMissionState::e_arrivedAtDestination; m_state->SetPlayedAction(SndanimScript::c_TRS302_OpenJailDoor); @@ -303,7 +303,7 @@ MxLong Pizza::HandlePathStruct(LegoPathStructNotificationParam& p_param) (p_param.GetData() == 0x33 && GameState()->GetActorId() == LegoActor::c_papa) || ((p_param.GetData() == 0x08 || p_param.GetData() == 0x09) && GameState()->GetActorId() == LegoActor::c_nick) || (p_param.GetData() == 0x0b && GameState()->GetActorId() == LegoActor::c_laura) - )) || (p_param.GetTrigger() == LegoPathStruct::c_w && p_param.GetData() == 0x169 && GameState()->GetActorId() == LegoActor::c_nick)) { + )) || (p_param.GetTrigger() == LegoPathStruct::c_missionFinalWaypoint && p_param.GetData() == 0x169 && GameState()->GetActorId() == LegoActor::c_nick)) { IsleScript::Script action; if (time < m_mission->GetRedFinishTime()) { @@ -353,7 +353,7 @@ MxLong Pizza::HandlePathStruct(LegoPathStructNotificationParam& p_param) MxTrace("Pizza mission: ending\n"); } - else if (p_param.GetTrigger() == LegoPathStruct::c_w) { + else if (p_param.GetTrigger() == LegoPathStruct::c_missionFinalWaypoint) { if (p_param.GetData() == 0x15e && GameState()->GetActorId() == LegoActor::c_pepper) { if (!m_playedLocationAnimation) { m_playedLocationAnimation = TRUE; diff --git a/LEGO1/lego/legoomni/src/actors/towtrack.cpp b/LEGO1/lego/legoomni/src/actors/towtrack.cpp index 1b94d2f3..e8e51060 100644 --- a/LEGO1/lego/legoomni/src/actors/towtrack.cpp +++ b/LEGO1/lego/legoomni/src/actors/towtrack.cpp @@ -300,7 +300,7 @@ MxLong TowTrack::HandlePathStruct(LegoPathStructNotificationParam& p_param) if (m_state->m_state == TowTrackMissionState::e_hookedUp && ((p_param.GetTrigger() == LegoPathStruct::c_camAnim && (p_param.GetData() == 9 || p_param.GetData() == 8)) || - (p_param.GetTrigger() == LegoPathStruct::c_w && p_param.GetData() == 0x169))) { + (p_param.GetTrigger() == LegoPathStruct::c_missionFinalWaypoint && p_param.GetData() == 0x169))) { m_state->m_state = TowTrackMissionState::e_none; MxLong time = Timer()->GetTime() - m_state->m_startTime; @@ -327,7 +327,7 @@ MxLong TowTrack::HandlePathStruct(LegoPathStructNotificationParam& p_param) Leave(); PlayFinalAnimation(IsleScript::c_wrt060bm_RunAnim); } - else if (p_param.GetTrigger() == LegoPathStruct::c_w && m_state->m_state == TowTrackMissionState::e_started) { + else if (p_param.GetTrigger() == LegoPathStruct::c_missionFinalWaypoint && m_state->m_state == TowTrackMissionState::e_started) { if (p_param.GetData() == 0x15f) { if (m_treeBlockageTriggered == 0) { m_treeBlockageTriggered = 1; diff --git a/LEGO1/lego/legoomni/src/paths/legopathstruct.cpp b/LEGO1/lego/legoomni/src/paths/legopathstruct.cpp index 4f40ace4..b81723eb 100644 --- a/LEGO1/lego/legoomni/src/paths/legopathstruct.cpp +++ b/LEGO1/lego/legoomni/src/paths/legopathstruct.cpp @@ -19,24 +19,24 @@ DECOMP_SIZE_ASSERT(LegoPathStruct, 0x14) extern MxU32 g_isleFlags; // GLOBAL: LEGO1 0x100f119c -MxBool g_unk0x100f119c = FALSE; +MxBool g_triggerHandlingIgnoreDirection = FALSE; // FUNCTION: LEGO1 0x1001b700 void LegoPathStruct::HandleTrigger(LegoPathActor* p_actor, MxBool p_direction, MxU32 p_data) { - if (!HandleTrigger(p_actor, p_direction, p_data, FALSE) && g_unk0x100f119c) { + if (!HandleTrigger(p_actor, p_direction, p_data, FALSE) && g_triggerHandlingIgnoreDirection) { HandleTrigger(p_actor, p_direction, p_data, TRUE); } } // FUNCTION: LEGO1 0x1001b740 // FUNCTION: BETA10 0x100c26c5 -MxBool LegoPathStruct::HandleTrigger(LegoPathActor* p_actor, MxBool p_direction, MxU32 p_data, MxBool p_bool) +MxBool LegoPathStruct::HandleTrigger(LegoPathActor* p_actor, MxBool p_direction, MxU32 p_data, MxBool p_invertDirection) { MxBool triggered = FALSE; - MxBool bool2 = p_bool ? !p_direction : p_direction; + MxBool actualDirection = p_invertDirection ? !p_direction : p_direction; - MxU32 flags = bool2 ? c_bit5 : c_bit6; + MxU32 flags = actualDirection ? c_bit5 : c_bit6; flags |= p_actor->GetCameraFlag() ? c_bit1 : (c_bit2 | c_bit3 | c_bit4); if ((m_flags & flags & (c_bit5 | c_bit6 | c_bit7)) && (m_flags & flags & (c_bit1 | c_bit2 | c_bit3 | c_bit4))) { @@ -45,10 +45,10 @@ MxBool LegoPathStruct::HandleTrigger(LegoPathActor* p_actor, MxBool p_direction, switch (m_name[2]) { case c_camAnim: if (g_isleFlags & Isle::c_playCamAnims) { - PlayCamAnim(p_actor, bool2, p_data, TRUE); + PlayCamAnim(p_actor, actualDirection, p_data, TRUE); } break; - case c_d: { + case c_waypoint: { p_actor->SetLastPathStruct(p_data); LegoPathStructNotificationParam param(c_notificationPathStruct, p_actor, m_name[2], p_data); @@ -60,12 +60,12 @@ MxBool LegoPathStruct::HandleTrigger(LegoPathActor* p_actor, MxBool p_direction, } break; } - case c_e: - FUN_1001bc40(m_name, p_data, !(p_bool == FALSE)); + case c_deleteAction: + HandleAction(m_name, p_data, !(p_invertDirection == FALSE)); break; - case c_g: + case c_nothing: break; - case c_h: { + case c_hideAnim: { LegoHideAnimPresenter* presenter = m_world->GetHideAnimPresenter(); if (presenter != NULL) { presenter->ApplyVisibility(p_data * 100); @@ -77,7 +77,7 @@ MxBool LegoPathStruct::HandleTrigger(LegoPathActor* p_actor, MxBool p_direction, PlayMusic(p_direction, p_data); } break; - case c_s: { + case c_specialMissionWaypointAndAction: { LegoWorld* world = CurrentWorld(); if (world != NULL) { LegoPathStructNotificationParam param(c_notificationPathStruct, p_actor, m_name[2], p_data); @@ -87,10 +87,10 @@ MxBool LegoPathStruct::HandleTrigger(LegoPathActor* p_actor, MxBool p_direction, } } - FUN_1001bc40(m_name, p_data, p_bool == FALSE); + HandleAction(m_name, p_data, p_invertDirection == FALSE); break; } - case c_w: { + case c_missionFinalWaypoint: { LegoWorld* world = CurrentWorld(); if (world != NULL) { LegoPathStructNotificationParam param(c_notificationPathStruct, p_actor, m_name[2], p_data); @@ -106,13 +106,13 @@ MxBool LegoPathStruct::HandleTrigger(LegoPathActor* p_actor, MxBool p_direction, // FUNCTION: LEGO1 0x1001bc40 // FUNCTION: BETA10 0x100c2a6c -void LegoPathStruct::FUN_1001bc40(const char* p_name, MxU32 p_data, MxBool p_bool) +void LegoPathStruct::HandleAction(const char* p_name, MxU32 p_data, MxBool p_start) { MxDSAction action; action.SetObjectId(p_data); action.SetAtomId(m_atomId); - if (p_bool) { + if (p_start) { action.SetUnknown24(-1); Start(&action); } diff --git a/LEGO1/lego/legoomni/src/race/carrace.cpp b/LEGO1/lego/legoomni/src/race/carrace.cpp index 8a540154..9e479e29 100644 --- a/LEGO1/lego/legoomni/src/race/carrace.cpp +++ b/LEGO1/lego/legoomni/src/race/carrace.cpp @@ -189,7 +189,7 @@ MxLong CarRace::HandlePathStruct(LegoPathStructNotificationParam& p_param) { MxLong result = 0; - if (p_param.GetTrigger() == LegoPathStruct::c_d) { + if (p_param.GetTrigger() == LegoPathStruct::c_waypoint) { MxEntity* sender = (MxEntity*) p_param.GetSender(); MxS32 paramData = p_param.GetData(); diff --git a/LEGO1/lego/legoomni/src/race/jetskirace.cpp b/LEGO1/lego/legoomni/src/race/jetskirace.cpp index 94891925..239882ba 100644 --- a/LEGO1/lego/legoomni/src/race/jetskirace.cpp +++ b/LEGO1/lego/legoomni/src/race/jetskirace.cpp @@ -23,7 +23,7 @@ #include "scripts.h" // Defined in legopathstruct.cpp -extern MxBool g_unk0x100f119c; +extern MxBool g_triggerHandlingIgnoreDirection; // Defined in jetski.cpp extern const char* g_varJSFRNTY5; @@ -70,7 +70,7 @@ MxResult JetskiRace::Create(MxDSAction& p_dsAction) InvokeAction(Extra::e_start, m_atomId, raceCarDashboardStreamId, NULL); InvokeAction(Extra::e_start, m_atomId, JetraceScript::c_JetskiDashboard, NULL); - g_unk0x100f119c = TRUE; + g_triggerHandlingIgnoreDirection = TRUE; return result; } @@ -158,7 +158,7 @@ MxLong JetskiRace::HandlePathStruct(LegoPathStructNotificationParam& p_param) MxLong result = 0; MxEntity* sender = (MxEntity*) p_param.GetSender(); - if (p_param.GetTrigger() == LegoPathStruct::c_d) { + if (p_param.GetTrigger() == LegoPathStruct::c_waypoint) { MxS32 paramData = p_param.GetData(); switch (sender->GetEntityId()) { diff --git a/LEGO1/lego/legoomni/src/race/legorace.cpp b/LEGO1/lego/legoomni/src/race/legorace.cpp index 5f869095..a3076dbc 100644 --- a/LEGO1/lego/legoomni/src/race/legorace.cpp +++ b/LEGO1/lego/legoomni/src/race/legorace.cpp @@ -12,7 +12,7 @@ DECOMP_SIZE_ASSERT(RaceState::Entry, 0x06) DECOMP_SIZE_ASSERT(RaceState, 0x2c) // Defined in legopathstruct.cpp -extern MxBool g_unk0x100f119c; +extern MxBool g_triggerHandlingIgnoreDirection; // FUNCTION: LEGO1 0x10015aa0 LegoRace::LegoRace() @@ -56,7 +56,7 @@ MxResult LegoRace::Create(MxDSAction& p_dsAction) // FUNCTION: BETA10 0x100c7ab5 LegoRace::~LegoRace() { - g_unk0x100f119c = FALSE; + g_triggerHandlingIgnoreDirection = FALSE; if (m_pathActor) { SetUserActor(m_pathActor); NavController()->ResetMaxLinearVel(m_pathActor->GetMaxLinearVel());