Making Custom Elevators for R&C1 Multiplayer
For the multiplayer mod I've made for Ratchet & Clank (2002), I wanted to recreate the behavior that the vanilla in-game elevators have. Basically, I wanted to have a platform the player can stand on they move with. The game doesn't move the player with mobys (Insomniac's name for "game objects") without extra work and configuration. It took me far too long to figure out how to properly configure mobys as elevators.
All mobys use the same 0x100-byte struct that the game engine reads to place and give behavior to mobys. This struct has info about which model type it is, flags, color, state, collision data, and much more. Importantly, it also has a pointer to the moby's update function, which is called every game tick to allow it to control its behavior.
Setting the right mode bit
The moby instance struct also has a field for flags, or mode_bits as Insomniac calls it. mode_bits decides a bunch of different properties for mobys like making them targettable, updateless, invisible, and more that we haven't uncovered yet. One of these flags, 0x20, marks a moby as a movable platform. This alone makes Ratchet move with the moby when he's standing on top of it, but only when he's standing still. You can see this behavior in the video below.
Note
Insomniac used, for the most part, CamelCase in their naming, but I've been using snake_case for the multiplayer mod so that's what I'm using here too.
Ratchet moves with the platform when he's standing still, but once he starts moving you can see he starts to slide off. You see it particularly well when he's moving very slowly.
Reversing other elevators in the game
Taking a look at Ghidra's decompilation for some of the other elevators and moving platforms in the game should let us understand what more we need to do to get ours working properly.
I mainly looked at these two types of elevators, because they are fairly simple and don't do much other than being elevators.

If we look at their update functions, this is roughly what they have in common:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | |
All the elevators and moving platforms I looked at called get_moby_orientation_info with delta position, last rotation, current rotation, and some place to store the results of the calculations. So the solution would seem really simple, we just call that function with the relevant data and we're good, right? Not right.
get_moby_orientation_info does not save any data in globals or anything to signal to the engine that there's something moving here, it only performs calculations and stores it in the pointer you gave it. All of the elevators stored is somewhere in their p_vars, although they did not all store it at the same offset. p_vars in a moby is a pointer to somewhere the moby can store variables that are specific to its instance and its specific type. Enemies, elevators, and crates all have the same 0x100 byte sized instance struct, but they need different variables at runtime, and this is stored in p_vars.
I tried simply storing the results of the calculations at p_vars + 0x60 and p_vars + 0x20 as I'd seen other platforms do, but I wasn't getting any new behavior, so that clearly wasn't it. From the Ghidra decomp above we can also see some configuration starting at p_vars + 0x20:
1 2 3 4 | |
But setting this also didn't really change any of the behavior in my custom moby.
Almost all of the movable platform mobys are statically added to the level files. Dynamically spawned mobys, like mine, can differ from those placed in level data. It wasn't clear to me whether they carried any special flags that marked them as movable platforms before being spawned. Luckily, there are some movable platforms spawned in at runtime, like the escalator steps you'd find on Kerwan:

I found the relevant function that spawns them in Ghidra:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | |
Beyond seeing that it also set the 0x20 (marked as MOVABLE_PLATFORM_MOBY here) flag in the moby's mode_bits, I couldn't really tell how else they were marked as movable platforms. A crucial piece of the puzzle I had missed when I originally did this research was this line:
1 | |
Configuring p_vars correctly
After spending at least 2 evenings trying to figure this out, I finally did what I should've done much earlier. I dumped the p_vars of an elevator while the game was running to try to figure out what was going on.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | |
Almost immediately I recognized that at p_vars + 0x0 and p_vars + 0x8 there are pointers to within this moby's p_vars, pointing to p_vars + 0x20 (0x443d3ae0) and p_vars + 0x60 (0x443d3b20), respectively. This immediately clarifies what we were seeing in those first elevators we looked at the decomp for:
1 2 3 4 5 6 7 8 | |
The first pointer is to a configuration struct for the platform, likely configuring the material and behavior type, and the second pointer to the movement and orientation data. These were the missing pieces to making my custom elevator moby work correctly.
Finally, we're not sliding around while moving on the platform. Now the server can spawn elevators and taxis that sync across players and work just like the vanilla in-game ones do.
In the end, my test moby had an update function equivalent to this code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | |