Getting extra information out of Sonic 2s' Object Placement Format

Discussion in 'Discussion & Q&A' started by Pacca, Apr 17, 2021.

  1. Pacca

    Pacca Having an online identity crisis since 2019 Member

    Jul 5, 2014
    So here's some context. I'm trying to recreate Sonic CDs' time travel properly in Sonic 2. About the only thing I don't have working at this point is persistent objects (you kill a badnik, go to the future, and that badnik is still dead, or you time travel with a badnik on screen and it stays in the place it was in as you time traveled). While I was willing to ignore it in the early days when there were far more pressing concerns with time travel and the hack as a whole, it's now one of the few major issues I have left to fix. And since having a badnik suddenly respawn on top of you after you've time traveled is obviously unfair, adding it would significantly improve gameplay.

    While I'm fairly confident that I can solve most of the problems with it atm (I can preserve object ram during time travel, and the other problems I currently have with that feature are very fixable), there's one issue I can't solve with it, and that's having the object layouts differ between time periods; my current implementation requires completely identical object layouts between time periods to work properly, which limits how different those time periods can be. If anything major changes, the object respawn table falls out of sync with what's in the level, making objects disappear when they shouldn't and vice versa.

    Sonic CD appears to handle this by 1. Having object layouts be shared between time periods and 2. Using a unique object placement format that allows you to select which time period an object should appear in. It's a simple, straightforward solution, but it's difficult to recreate in Sonic 2 without completely dismantling the format it's currently using. As I'm not comfortable enough with the object manager to make drastic modifications to it or replace it outright, I'd prefer not to completely swap formats if at all possible.

    I have a couple of possible solutions, but they all have pros and cons, and I haven't been able to commit to any of them, in part because of the obstacles they provide, and in part because they're a serious commitment; I'm already going to have to replace and modify my old level layouts with any new system, and I don't want to have to do that multiple times until something sticks, preparing to do it once is demoralizing as is..

    First, the obvious route; trying to go with the apparent Sonic CD route and using object placement flags, without extending or rewriting Sonic 2s' object placement format. This is the most straight forward, but requires freeing up space in the meager 6 bytes provided. Thankfully, Sonic 2 already throws us a bone here; one bit flag is devoted solely to a subtle two player mode effect, and I've disabled two player already. That gives 1 free bit right off the bat, which is nice. However, choosing which of 4 time periods an object is allowed to be in clearly requires more then that.

    The easiest bit to free up is the upper x position bit; the level chunks end around object position $4000, and the entire upper word is devoted solely to the x position, leaving the highest bit completely unused, and the second highest bit arguably worth sacrificing (since I highly doubt vanilla places an object right on the chunk border, and I doubt I have myself). While that works, it leaves another issue; Sonlvl support. Last I heard, sonlvl wasn't all that interested in supporting arbitrarily modified level formats. Admittedly, it's been quite a while, and I don't really know where to look to see if this has changed at any point in the last few years, so a sonlvl update might have added the ability to add/modify object formats at some point without me knowing. Either way, SonLvl support is a big priority; I wouldn't be here today without Sonlvl, and editing levels without it would be painful. Having every other object appear far in the void area outside of sonlvls view is not a reasonable solution.

    The second idea is to use some unorthodox combination of other flags to signal something special (x flip + y flip + specific subtype, for example). While it's possible, it imposes significant limits on how objects can be placed. I like to be creative with object placements, and have even added explicit support for y flip to some objects, so being unable to place a badnik on the ceiling facing right would be a huge blow. And that's not even mentioning the subtype byte, which is used so heavily by vanilla sonic 2 that making any assumptions about it is bound to hurt at least one objects vanilla functionality, let alone some custom ones I've already built...

    The third is something I can definitely do, but is nasty, hacky solution, so much so that I'm reluctant to make something as important and widespread as object layouts be reliant on it. That would be to just hardcode every object to only appear in certain time zones, and/or modify how they use their subtype values to determine when they appear. This requires writing code for EVERY single object that differs between time periods, even non-badnik ones like springs, bridges, etc. While it might work for some things (like general changes to objects between time zones, like setting all bridges on fire, badnik battle damage in the bad future, etc.), doing this for specific cases is really dumb in the long run. And for some objects it might not even be possible, depending on how they're placed in each time period and how heavily they use their subtype byte.

    The fourth is another hacky option; a proxy object. Basically, it's an object which replaces itself with whatever object ID is in it's subtype byte. Then I could make seperate proxy objects that only load the object in their subtype byte in each time zone. While this is less messy then the 3rd option, it's still really hacky and stupid. It also wastes object IDs, which is lame. Most importantly though, this doesn't just limit the subtype byte like the previous option; it erases it. While this could be worked around by having invalid object IDs (Sonic, Tails' Tails, the Title Screen Menu, etc.) repoint to existing objects with special subtype bytes, that's really messy and limiting as well. And defining Sonlvl object definitions for such a proxy object would be a big mess as well.

    I've been orbiting this issue for nearly a year now, and haven't been able to move forward. Using any one of these options or any combination of them is a big commitment that I don't really want to mess up. Would love to hear your thoughts on the matter.
    RandomName and ProjectFM like this.
  2. ProjectFM

    ProjectFM Optimistic and self-dependent Member

    Oct 4, 2014
    Orono, Maine
    One way you could do it is to have two separate object position files: one for stuff that carries over between time periods, and one for stuff time period-exclusive. This is similar to how Sonic 3K organizes its level assets so that some stuff carries over between acts and others are replaced. Then you can make it so objects loaded with the first file keep their states while those loaded with the second are deleted when time travel and the second file corresponding to the new time period has its objects loaded in. You'd probably have to split the respawn table in two with objects in the first file have their data in the first half and objects in the second file have their data in the second half and the second half gets cleared when time traveling. This method could be applied to SonLVL by having two versions of each time period of each level: one where the first file is loaded and one where the second file is loaded. The downside would be that objects you only want to load in 2 or 3 time zones will respawn and objects exclusive to a time zone will respawn if you leave a time zone and come back.
    Niko and RandomName like this.