
/**
 * Behavior for bhvAlphaBooKey and bhvBetaBooKey.
 * They were apparently intended to be a key that would be contained in boos
 * and would fall out, like coins do. There is a model, MODEL_BETA_BOO_KEY, that
 * is loaded in script_func_global_10, which contains boo-themed models used in
 * BBH and the castle courtyard. It is used in a macro preset with bhvAlphaBooKey,
 * which is also grouped near other boo/BBH-related macros. This is evidence that
 * bhvAlphaBooKey was supposed to be a key. bhvBetaBooKey has code similar to
 * bhvAlphaBooKey's for rotation and collection, and functions correctly when
 * spawned as a child of a boo (it checks the death status of the boo to know when
 * to drop, so this is almost definitely what was intended). It appears that
 * bhvAlphaBooKey was abandoned for reasons unknown and replaced with bhvBetaBooKey.
 */

/**
 * Update function for bhvAlphaBooKey.
 * It rotates the key, and deletes it when collected.
 * The code in this function is similar to that found in
 * bhvBetaBooKey code, which implies that these are 2 versions
 * of the same object. It is a less developed version of
 * bhvBetaBooKey, hence the "alpha" moniker.
 */
void bhv_alpha_boo_key_loop(void) {
    // Rotate the key
    o->oFaceAngleRoll += 0x200;
    o->oFaceAngleYaw += 0x200;

    if (obj_check_if_collided_with_object(o, gMarioObject)) {
        // This line makes the object inside the key's parent boo drop.
        // Was this intended to make the boo die when the key is collected?
        // Boos don't read from oBooDeathStatus, they only set it to let the
        // objects inside them know when to drop.
        // Due to this line, the key will cause the game to crash if collected
        // when its parent object is NULL.
        // Another theory is that the boo key was intended to be spawned by a
        // spawner that used object field 0x00 for something else. This
        // is elaborated on more in beta_boo_key_dropped_loop.
        o->parentObj->oBooDeathStatus = BOO_DEATH_STATUS_DYING;

        // Delete the object and spawn sparkles
        obj_mark_for_deletion(o);
        spawn_object(o, MODEL_SPARKLES, bhvCoinSparklesSpawner);
    }
}

// For some reason, the action functions for the beta boo key
// are written in reverse order.

/**
 * Continue to make the key fall, and handle collection.
 */
static void beta_boo_key_dropped_loop(void) {
    // Apply standard physics to the key
    cur_obj_update_floor_and_walls();
    cur_obj_move_standard(78);

    // Slowly increase the Y offset to make the model aligned correctly.
    // This is spread out over 13 frames so that it's not noticable.
    if (o->oGraphYOffset < 26.0f) {
        o->oGraphYOffset += 2.0f;
    }

    // Transition from rotating in both the yaw and the roll axes
    // to just in the yaw axis. This is done by truncating the key's roll
    // to the nearest multiple of 0x800, then continuously adding 0x800
    // until it reaches a multiple of 0x10000, at which point &-ing with
    // 0xFFFF returns 0 and the key stops rotating in the roll direction.
    if (o->oFaceAngleRoll & 0xFFFF) {
        o->oFaceAngleRoll &= 0xF800;
        o->oFaceAngleRoll += 0x800;
    }

    // Once the key stops bouncing, stop its horizontal movement on the ground.
    if (o->oMoveFlags & OBJ_MOVE_ON_GROUND) {
        o->oVelX = 0.0f;
        o->oVelZ = 0.0f;
    }

    // Rotate the key
    o->oFaceAngleYaw += 0x800;

    // If the key hits the floor or 90 frames have elapsed since it was dropped,
    // become tangible and handle collision.
    if (o->oTimer > 90 || o->oMoveFlags & OBJ_MOVE_LANDED) {
        cur_obj_become_tangible();

        if (obj_check_if_collided_with_object(o, gMarioObject)) {
            // This interaction status is 0x01, the first interaction status flag.
            // It was only used for Hoot in the final game, but it seems it could've
            // treated as a TRUE/FALSE statement or held some special meaning in beta.
            // Earlier, in beta_boo_key_drop (called when the parent boo is killed),
            // o->parentObj is set to the parent boo's parentObj. This means that
            // here, the parentObj is actually the parent of the old parent boo.
            // One theory about this code is that there was a boo spawner, which
            // spawned "false" boos and one "true" boo with the key, and the player
            // was intended to find the one with the key to progress.
            o->parentObj->oInteractStatus = TRUE; //! Note: Not a flag, treated as a TRUE/FALSE statement

            // Delete the object and spawn sparkles
            obj_mark_for_deletion(o);
            spawn_object(o, MODEL_SPARKLES, bhvCoinSparklesSpawner);
        }
    }
}

/**
 * Drop the key. This function is run once, the frame after the boo dies;
 * It immediately sets the action to BETA_BOO_KEY_ACT_DROPPED.
 */
static void beta_boo_key_drop(void) {
    s16 velocityDirection;
    f32 velocityMagnitude;

    // Update the key to be inside the boo
    struct Object *parent = o->parentObj;
    obj_copy_pos(o, parent);

    // This if statement to only run this code on the first frame
    // is redundant, since it instantly sets the action to BETA_BOO_KEY_ACT_DROPPED
    // which stops this function from running again.
    if (o->oTimer == 0) {
        // Separate from the parent boo
        o->parentObj = parent->parentObj;

        o->oAction = BETA_BOO_KEY_ACT_DROPPED;

        // Make the key move laterally away from Mario at 3 units/frame
        // (as if he transferred kinetic energy to it)
        velocityDirection = gMarioObject->oMoveAngleYaw;
        velocityMagnitude = 3.0f;

        o->oVelX = sins(velocityDirection) * velocityMagnitude;
        o->oVelZ = coss(velocityDirection) * velocityMagnitude;

        // Give it an initial Y velocity of 40 units/frame
        o->oVelY = 40.0f;
    }

    // Rotate the key
    o->oFaceAngleYaw += 0x200;
    o->oFaceAngleRoll += 0x200;
}

/**
 * Update the key to be inside its parent boo, and handle the boo dying.
 */
static void beta_boo_key_inside_boo_loop(void) {
    // Update the key to be inside the boo at all times
    struct Object *parent = o->parentObj;
    obj_copy_pos(o, parent);

    // Use a Y offset of 40 to make the key model aligned correctly.
    // (Why didn't they use oGraphYOffset?)
    o->oPosY += 40.0f;

    // If the boo is dying/dead, set the action to BETA_BOO_KEY_ACT_DROPPING.
    if (parent->oBooDeathStatus != BOO_DEATH_STATUS_ALIVE) {
        o->oAction = BETA_BOO_KEY_ACT_DROPPING;
    }

    // Rotate the key
    o->oFaceAngleRoll += 0x200;
    o->oFaceAngleYaw += 0x200;
}

static ObjActionFunc sBetaBooKeyActions[] = {
    beta_boo_key_inside_boo_loop,
    beta_boo_key_drop,
    beta_boo_key_dropped_loop,
};

/**
 * Update function for bhvBetaBooKey.
 */
void bhv_beta_boo_key_loop(void) {
    cur_obj_call_action_function(sBetaBooKeyActions);
}
