Skip to content

Keep the Camera from Making Waves

psf edited this page Apr 5, 2024 · 4 revisions

The Bug

A common bug that appears in binary rom hacks is where the free-moving camera (created with the SpawnCameraObject special script call) causes ripples in the water or rustling in the grass. The vanilla game gets around this bug in the engine by making sure to only use the free-moving camera in places where no ground effects spawn, such as the roiling waters of the ocean while the legendaries fight in Sootopolis (note how the camera doesn't actually shift back the same way after the clear waters return after the fighting).

Fixed Not Fixed
on off

The Solution

It turns out, this is a simple fix in src/event_object_movement.c: we simply need to add a check for if event objects are invisible. Because it turns out that all SpawnCameraObject does is spawns a random youngster event object and make it invisible and attach the camera to that invisible object. This will also cover cases where the player is invisible on the map for whatever reason.

static void DoGroundEffects_OnSpawn(struct ObjectEvent *objEvent, struct Sprite *sprite)
{
    u32 flags;

-   if (objEvent->triggerGroundEffectsOnMove)
+   if (objEvent->triggerGroundEffectsOnMove && !objEvent->invisible)
    {
        flags = 0;
        UpdateObjectEventZCoordAndPriority(objEvent, sprite);
        GetAllGroundEffectFlags_OnSpawn(objEvent, &flags);
        SetObjectEventSpriteOamTableForLongGrass(objEvent, sprite);
        DoFlaggedGroundEffects(objEvent, sprite, flags);
        objEvent->triggerGroundEffectsOnMove = 0;
        objEvent->disableCoveringGroundEffects = 0;
    }
}

static void DoGroundEffects_OnBeginStep(struct ObjectEvent *objEvent, struct Sprite *sprite)
{
    u32 flags;

-   if (objEvent->triggerGroundEffectsOnMove)
+   if (objEvent->triggerGroundEffectsOnMove && !objEvent->invisible)
    {
        flags = 0;
        UpdateObjectEventZCoordAndPriority(objEvent, sprite);
        GetAllGroundEffectFlags_OnBeginStep(objEvent, &flags);
        SetObjectEventSpriteOamTableForLongGrass(objEvent, sprite);
        filters_out_some_ground_effects(objEvent, &flags);
        DoFlaggedGroundEffects(objEvent, sprite, flags);
        objEvent->triggerGroundEffectsOnMove = 0;
        objEvent->disableCoveringGroundEffects = 0;
    }
}

static void DoGroundEffects_OnFinishStep(struct ObjectEvent *objEvent, struct Sprite *sprite)
{
    u32 flags;

-   if (objEvent->triggerGroundEffectsOnStop)
+   if (objEvent->triggerGroundEffectsOnStop && !objEvent->invisible)
    {
        flags = 0;
        UpdateObjectEventZCoordAndPriority(objEvent, sprite);
        GetAllGroundEffectFlags_OnFinishStep(objEvent, &flags);
        SetObjectEventSpriteOamTableForLongGrass(objEvent, sprite);
        FilterOutStepOnPuddleGroundEffectIfJumping(objEvent, &flags);
        DoFlaggedGroundEffects(objEvent, sprite, flags);
        objEvent->triggerGroundEffectsOnStop = 0;
        objEvent->landingJump = 0;
    }
}

Alternate Solution

If for some reason you still want other invisible objects to still do ground effects (maybe there's someone with a Cloak of Invisibility walking around, and that use of the ground effects is something you want to indicate where they are), instead of the above, you can check the localId of the object walking around. The camera spawned by SpawnCameraObject always has the localId OBJ_EVENT_ID_CAMERA, so we can check for that instead:

-   if (objEvent->triggerGroundEffectsOnStop)
+   if (objEvent->triggerGroundEffectsOnStop && objEvent->localId != OBJ_EVENT_ID_CAMERA)
Clone this wiki locally