
/**
 * Behavior for bhvSpiny.
 * When spawned by lakitu, its parent object is the lakitu.
 * Lakitu comes before it spawned spinies in processing order.
 */

/**
 * Hitbox for spiny both while thrown and walking. The interaction type is
 * changed to INTERACT_UNKNOWN_08 while walking.
 */
static struct ObjectHitbox sSpinyHitbox = {
    /* interactType:      */ INTERACT_MR_BLIZZARD,
    /* downOffset:        */ 0,
    /* damageOrCoinValue: */ 2,
    /* health:            */ 0,
    /* numLootCoins:      */ 0,
    /* radius:            */ 80,
    /* height:            */ 50,
    /* hurtboxRadius:     */ 40,
    /* hurtboxHeight:     */ 40,
};

/**
 * Attack handlers for spiny while walking.
 */
static u8 sSpinyWalkAttackHandlers[] = {
    /* ATTACK_PUNCH:                 */ ATTACK_HANDLER_KNOCKBACK,
    /* ATTACK_KICK_OR_TRIP:          */ ATTACK_HANDLER_KNOCKBACK,
    /* ATTACK_FROM_ABOVE:            */ ATTACK_HANDLER_NOP,
    /* ATTACK_GROUND_POUND_OR_TWIRL: */ ATTACK_HANDLER_NOP,
    /* ATTACK_FAST_ATTACK:           */ ATTACK_HANDLER_KNOCKBACK,
    /* ATTACK_FROM_BELOW:            */ ATTACK_HANDLER_KNOCKBACK,
};

/**
 * If the spiny was spawned by lakitu and mario is far away, despawn.
 */
static s32 spiny_check_active(void) {
    if (o->parentObj != o) {
        if (o->oDistanceToMario > 2500.0f) {
            //! It's possible for the lakitu to despawn while the spiny still
            //  references it. This line allows us to decrement the 0x1B field
            //  in an object that loads into the lakitu's former slot.
            //  This can be used in practice to corrupt a huge goomba to
            //  behave similar to a regular goomba.
            //  It can also be used on a bob-omb respawner to change its model
            //  to a butterfly or fish.
            o->parentObj->oEnemyLakituNumSpinies -= 1;
            mark_object_for_deletion(o);
            return FALSE;
        }
    }

    return TRUE;
}

/**
 * Walk around randomly, and dick around with oFlags, oMoveFlags, and oInteractType.
 */
static void spiny_act_walk(void) {
    if (spiny_check_active()) {
        obj_update_floor_and_walls();

        o->oGraphYOffset = -17.0f;
        set_obj_animation_and_sound_state(0);

        if (o->oMoveFlags & OBJ_MOVE_MASK_ON_GROUND) {
            // After touching the ground for the first time, stop. From now on,
            // ensure that face angle and move angle agree
            if (!(o->oFlags & OBJ_FLAG_SET_FACE_YAW_TO_MOVE_YAW)) {
                if (obj_forward_vel_approach(0.0f, 1.0f)) {
                    o->oFlags |= OBJ_FLAG_SET_FACE_YAW_TO_MOVE_YAW;
                    o->oMoveAngleYaw = o->oFaceAngleYaw;
                }
            } else {
                obj_forward_vel_approach(1.0f, 0.2f);
            }

            if (o->oSpinyTurningAwayFromWall) {
                o->oSpinyTurningAwayFromWall =
                    obj_resolve_collisions_and_turn(o->oSpinyTargetYaw, 0x80);
            } else {
                if (!(o->oSpinyTurningAwayFromWall =
                          obj_bounce_off_walls_edges_objects(&o->oSpinyTargetYaw))) {
                    // Walk and occasionally randomly change direction
                    if (o->oSpinyTimeUntilTurn != 0) {
                        o->oSpinyTimeUntilTurn -= 1;
                    } else {
                        o->oSpinyTargetYaw = o->oMoveAngleYaw + (s16) RandomSign() * 0x2000;
                        o->oSpinyTimeUntilTurn = random_linear_offset(100, 100);
                    }
                }

                obj_rotate_yaw_toward(o->oSpinyTargetYaw, 0x80);
            }

        } else if (o->oMoveFlags & OBJ_MOVE_HIT_WALL) {
            // Bounce off walls while falling
            o->oMoveAngleYaw = obj_reflect_move_angle_off_wall();
        }

        obj_move_standard(-78);

        if (obj_handle_attacks(&sSpinyHitbox, SPINY_ACT_ATTACKED_MARIO, sSpinyWalkAttackHandlers)) {
            // When attacked by mario, lessen the knockback
            o->oAction = SPINY_ACT_WALK;
            o->oForwardVel *= 0.1f;
            o->oVelY *= 0.7f;

            o->oMoveFlags = 0; // weird flex but okay

            // Don't allow mario to punch the spiny two frames in a row?
            o->oInteractType = INTERACT_MR_BLIZZARD;
        } else {
            o->oInteractType = INTERACT_UNKNOWN_08;
        }
    }
}

/**
 * Wait for the lakitu to throw the spiny. The spiny is placed in this action
 * after being spawned by a lakitu.
 */
static void spiny_act_held_by_lakitu(void) {
    o->oGraphYOffset = 15.0f;
    set_obj_animation_and_sound_state(0);

    o->oParentRelativePosX = -50.0f;
    o->oParentRelativePosY = 35.0f;
    o->oParentRelativePosZ = -100.0f;

    if (o->parentObj->prevObj == NULL) {
        o->oAction = SPINY_ACT_THROWN_BY_LAKITU;
        o->oMoveAngleYaw = o->parentObj->oFaceAngleYaw;

        // Move more quickly if the lakitu is moving forward
        o->oForwardVel =
            o->parentObj->oForwardVel * coss(o->oMoveAngleYaw - o->parentObj->oMoveAngleYaw) + 10.0f;
        o->oVelY = 30.0f;

        o->oMoveFlags = 0; // you do you, spiny
    }
}

/**
 * Spin around. After landing, enter the walk action.
 */
static void spiny_act_thrown_by_lakitu(void) {
    if (spiny_check_active()) {
        obj_update_floor_and_walls();

        o->oGraphYOffset = 15.0f;
        o->oFaceAnglePitch -= 0x2000;

        set_obj_animation_and_sound_state(0);

        if (o->oMoveFlags & OBJ_MOVE_LANDED) {
            PlaySound2(SOUND_OBJ_SPINY_UNK59);
            obj_set_model(MODEL_SPINY);
            func_8029EE20(o, spiny_seg5_anims_05016EAC, 0);
            o->oGraphYOffset = -17.0f;

            o->oFaceAnglePitch = 0;
            o->oAction = SPINY_ACT_WALK;
        } else if (o->oMoveFlags & OBJ_MOVE_HIT_WALL) {
            o->oMoveAngleYaw = obj_reflect_move_angle_off_wall();
        }

        obj_move_standard(-78);

        if (obj_check_attacks(&sSpinyHitbox, o->oAction)) {
            if (o->parentObj != o) {
                o->parentObj->oEnemyLakituNumSpinies -= 1;
            }
        }
    }
}

/**
 * Update function for bhvSpiny.
 */
void bhv_spiny_update(void) {
    // PARTIAL_UPDATE

    switch (o->oAction) {
        case SPINY_ACT_WALK:
            spiny_act_walk();
            break;
        case SPINY_ACT_HELD_BY_LAKITU:
            spiny_act_held_by_lakitu();
            break;
        case SPINY_ACT_THROWN_BY_LAKITU:
            spiny_act_thrown_by_lakitu();
            break;
        case SPINY_ACT_ATTACKED_MARIO:
            obj_move_for_one_second(SPINY_ACT_WALK);
            break;
    }
}
