Flying

Revision as of 20:05, 6 July 2024 by Icecream17 (talk | contribs) (cleanup)

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. 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.

Beyond this, the pseudocode is provided but probably isn't as useful as the update_flying code itself:

  1. Joystick inputs affect (pitch,yaw) velocity.
  2. Yaw velocity affects yaw, roll, and forward velocity.
  3. Pitch affects forward velocity.
  4. Forward velocity becomes at least 0
  5. Forward velocity affects pitch (see above table)
  6. Pitch velocity affects pitch.
  7. Pitch is clamped to be inside [-0x2AAA, 0x2AAA]
  8. x,y,z velocity is more or less directly exactly correspondent to forward velocity, pitch, and yaw.
  9. xz sliding velocity updated to correspond
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 higher forward velocity.
    • The difference between 4 and 5 speed, and 16 and 17 speed is especially large.
  • At the same time, forward velocity is based on pitch. (But it is also based on:)
    • There is a base 0.1 forwardVel speed loss
    • There is anywhere from 0 to 0.5 further speed loss - more speed is lost the higher the yaw velocity is (ie the more Mario turns).
  • Pitch has a minimum and maximum and cannot overflow with standard speeds.
  • More below the tables
forwardVel's affect on pitch
<=4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ... 31 32 33
-1024 -270 -260 -250 -240 -230 -220 -210 -200 -190 -180 -170 -160 -90 -84 -76 ... -6 0 6

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 64 up or 32 down) approach 0 (by at most 64)
positive pitch velocity approach (at most 32 up or 64 down) subtract 64 vel, cap at -32 approach 0 (by at most 64)
zero pitch velocity approach (at most 32 up or 64 down) approach (at most 64 up or 32 down) approach 0 (by at most 64)
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 32 up or 16 down) approach 0 (by at most 64)
positive yaw velocity approach (at most 16 up or 32 down) subtract 64 vel, cap at -16 approach 0 (by at most 64)
zero yaw velocity approach (at most 16 up or 32 down) approach (at most 32 up or 16 down) approach 0 (by at most 64)

Key takeaways:

  1. Many joystick positions are equivalent.
  2. One cannot change velocity by more than 64 (ignoring the effects of speed)
  3. Holding joystick x=0 or y=0 will approach the corresponding 0 by at most 64.
    • In contrast, holding the opposite direction changes the angle by 32 (yaw) or 64 (pitch), unconditionally
    • In further contrast, holding less of the same direction changes the angle by 32 (yaw) or 64 (pitch)
    • In even furthest contrast, holding more of the same direction changes the angle by 16 (yaw) or 32 (pitch)
    • So it might be faster to say, modify pitch:as such: -56 to -4 to 60 instead of -56 to 8 to 40. Notice the slightly under zero instead of slightly over zero.

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]

/**
 * 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