Flying

Revision as of 18:20, 19 December 2023 by Icecream17 (talk | contribs) (make it screenshottable)

Flying is an action that can occurs when Mario triple jumps or does a cannon shot while wearing the wing cap.

Flying
Properties
Hex 0x10880899
Action Flags air, diving, attacking, swimming/flying
Action Group Airborne
ID 0x099
Transitions
Into Airborner cancels: Water Plunge, Squished, Vertical Wind (theoretically), (Technically, because of the flying triple jump code): Double Jump Land, Lava Boost (theoretically), non cancel: Ground Pound, Freefall, Dive Slide, Backwards Air Kb, Lava Boost (again) (theoretically)
Out of Shot From Cannon, Flying Triple Jump, when spawning in some levels like Tower of the Wing Cap
Other
Animation 0x5B fly from cannon, 0xCF forwards spinning flip, 0x29 wing cap fly

Entering flying

  • Cannon shot: when y vel < 0 (and Mario does not cancel) (Action argument: 0)
  • Flying Triple Jump: when y vel < 4 (and Mario does not cancel, dive, or ground pound) (Action argument: 1)

Behavior

As with all airborne actions, a variety of "cancels" are checked prior to actually performing any airborne action. See Jump#Airborne cancels.

Then:[1]

  1. If Z is pressed
    1. If Mario's camera mode is CAMERA_MODE_BEHIND_MARIO, set_camera_mode to m->area->camera->defMode
    2. Ground Pound
  2. If Mario is not wearing the wing cap
    1. If Mario's camera mode is CAMERA_MODE_BEHIND_MARIO, set_camera_mode to m->area->camera->defMode
    2. Freefall
  3. If Mario's camera mode is not CAMERA_MODE_BEHIND_MARIO, set_camera_mode to CAMERA_MODE_BEHIND_MARIO
  4. If the action state is 0
    1. If the action argument is 0 (was shot from cannon), set Mario's animation to MARIO_ANIM_FLY_FROM_CANNON, else set Mario's animation to MARIO_ANIM_FORWARD_SPINNING_FLIP
    2. If the animation finished,
      1. If the action argument is 2 (spawned in), load_level_init_text and set action argument to 1
      2. Set Mario's animation to MARIO_ANIM_WING_CAP_FLY
      3. Set action state to 1
  5. Call update_flying
  6. switch Movement_steps#Perform_Air_Step: (Note: if there are no floor/wall/ceiling/oob interactions this just adds vel[0,1,2] to pos[0,1,2].)
    • air step none:
      1. Update graphics (camera) angle to be behind Mario
      2. Set action timer to 0
    • air step land:
      1. Set action to Dive Slide
      2. Set animation to MARIO_ANIM_DIVE
      3. Set animation frame to 7
      4. Set facing angle (x) to 0
      5. Set camera mode to m->area->camera->defMode
    • air step hit wall:
      1. todo
      2. if wall is not null, stuff, Backwards Air Kb
      3. else, stuff (but no knockback, this is probably out of bounds)
    • air step hit lava wall:
      1. stop holding, stop riding, Lava Boost
  7. finally, play the flying sound (adjust sound for speed)

Notice that there is no code handling air steps for ledge grabbing or hanging on a ceiling, so such transitions are impossible.

update_flying

Casually, tilt the joystick left to move left, right to move right, up to move down, down to move up.

I am too lazy to describe the exact details of update_flying, so here is the code

void update_flying(struct MarioState *m) {
    UNUSED u8 filler[4];

    update_flying_pitch(m);
    update_flying_yaw(m);

    m->forwardVel -= 2.0f * ((f32) m->faceAngle[0] / 0x4000) + 0.1f;
    m->forwardVel -= 0.5f * (1.0f - coss(m->angleVel[1]));

    if (m->forwardVel < 0.0f) {
        m->forwardVel = 0.0f;
    }

    if (m->forwardVel > 16.0f) {
        m->faceAngle[0] += (m->forwardVel - 32.0f) * 6.0f;
    } else if (m->forwardVel > 4.0f) {
        m->faceAngle[0] += (m->forwardVel - 32.0f) * 10.0f;
    } else {
        m->faceAngle[0] -= 0x400;
    }

    m->faceAngle[0] += m->angleVel[0];

    if (m->faceAngle[0] > 0x2AAA) {
        m->faceAngle[0] = 0x2AAA;
    }
    if (m->faceAngle[0] < -0x2AAA) {
        m->faceAngle[0] = -0x2AAA;
    }

    m->vel[0] = m->forwardVel * coss(m->faceAngle[0]) * sins(m->faceAngle[1]);
    m->vel[1] = m->forwardVel * sins(m->faceAngle[0]);
    m->vel[2] = m->forwardVel * coss(m->faceAngle[0]) * coss(m->faceAngle[1]);

    m->slideVelX = m->vel[0];
    m->slideVelZ = m->vel[2];
}

Key takeaways:

  • Pitching to move up happens faster with lower forward velocity. Try to be just under 4 or 16 forward velocity (since those are the cutoffs).
forwardVel's affect in pitch
<=4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ... 31 32 33
-400 -270 -260 -250 -240 -230 -220 -210 -200 -190 -180 -170 -160 -90 -84 -76 ... -6 0 6
  • However, the pitch is capped to within +- 0x2AAA (in decimal: 10922)
  • TODO: I heard moving down then up can be faster then just the direct line. Explain big picture movements, what happens in a practical example.
  • In summary:
  1. Joystick inputs affect (pitch,yaw) velocity.
  2. Yaw velocity affects yaw, roll, and forward velocity.
  3. Pitch affects forward velocity.
  4. Forward velocity affects pitch.
  5. Pitch velocity affects pitch.
  6. x,y,z velocity is more or less directly exactly correspondent to forward velocity, pitch, and yaw.

Here are some tables summarizing the approach_s32, update_flying_pitch, and update_flying_yaw functions.

update_flying_pitch (target vel = -(stickX * (forwardVel / 5)))
joystick is down (move up) joystick is up (move down) joystick is neutral
negative pitch velocity add 64 vel, cap at 32 approach (at most 40 up or 20 down) approach 0 (by at most 40)
positive pitch velocity approach (at most 20 down or 40 up) subtract 64 vel, cap at -32 approach 0 (by at most 40)
zero pitch velocity approach (at most 20 down or 40 up) approach (at most 40 up or 20 down) approach 0 (by at most 40)
update_flying_yaw (target vel = -(stickY * (forwardVel / 4)))
joystick is left joystick is right joystick is neutral
negative yaw velocity add 64 vel, cap at 16 approach (at most 20 up or 10 down) approach 0 (by at most 40)
positive yaw velocity approach (at most 10 up or 20 down) subtract 64 vel, cap at -16 approach 0 (by at most 40)
zero yaw velocity approach (at most 10 up or 20 down) approach (at most 20 up or 10 down) approach 0 (by at most 40)

After that, update_flying_yaw does:

  1. Add yaw velocity to yaw
  2. Set roll to yaw times negative twenty (-20)

Note that turning left = positive yaw (up = positive pitch).[2] As can be seen, many joystick positions will be equivalent, and the pitch velocity or yaw velocity change by at most 40.

/**
 * Return the value 'current' after it tries to approach target, going up at
 * most 'inc' and going down at most 'dec'.
 */
s32 approach_s32(s32 current, s32 target, s32 inc, s32 dec) {
    //! If target is close to the max or min s32, then it's possible to overflow
    // past it without stopping.

    if (current < target) {
        current += inc;
        if (current > target) {
            current = target;
        }
    } else {
        current -= dec;
        if (current < target) {
            current = target;
        }
    }
    return current;
}

void update_flying_yaw(struct MarioState *m) {
    s16 targetYawVel = -(s16)(m->controller->stickX * (m->forwardVel / 4.0f));

    if (targetYawVel > 0) {
        if (m->angleVel[1] < 0) {
            m->angleVel[1] += 0x40;
            if (m->angleVel[1] > 0x10) {
                m->angleVel[1] = 0x10;
            }
        } else {
            m->angleVel[1] = approach_s32(m->angleVel[1], targetYawVel, 0x10, 0x20);
        }
    } else if (targetYawVel < 0) {
        if (m->angleVel[1] > 0) {
            m->angleVel[1] -= 0x40;
            if (m->angleVel[1] < -0x10) {
                m->angleVel[1] = -0x10;
            }
        } else {
            m->angleVel[1] = approach_s32(m->angleVel[1], targetYawVel, 0x20, 0x10);
        }
    } else {
        m->angleVel[1] = approach_s32(m->angleVel[1], 0, 0x40, 0x40);
    }

    m->faceAngle[1] += m->angleVel[1];
    m->faceAngle[2] = 20 * -m->angleVel[1];
}

void update_flying_pitch(struct MarioState *m) {
    s16 targetPitchVel = -(s16)(m->controller->stickY * (m->forwardVel / 5.0f));

    if (targetPitchVel > 0) {
        if (m->angleVel[0] < 0) {
            m->angleVel[0] += 0x40;
            if (m->angleVel[0] > 0x20) {
                m->angleVel[0] = 0x20;
            }
        } else {
            m->angleVel[0] = approach_s32(m->angleVel[0], targetPitchVel, 0x20, 0x40);
        }
    } else if (targetPitchVel < 0) {
        if (m->angleVel[0] > 0) {
            m->angleVel[0] -= 0x40;
            if (m->angleVel[0] < -0x20) {
                m->angleVel[0] = -0x20;
            }
        } else {
            m->angleVel[0] = approach_s32(m->angleVel[0], targetPitchVel, 0x40, 0x20);
        }
    } else {
        m->angleVel[0] = approach_s32(m->angleVel[0], 0, 0x40, 0x40);
    }
}

References