
/**
 * Behaviors for bhvWaterBombSpawner, bhvWaterBomb, and bhvWaterBombShadow.
 * The spawner spawns the water bombs that fall on mario from above. These ones
 * start in the WATER_BOMB_ACT_INIT action.
 * Water bombs can also be shot by cannons. These ones stay in the
 * WATER_BOMB_ACT_SHOT_FROM_CANNON action.
 * The water bomb shadow is only spawned by water bomb spawners.
 */

/**
 * Hitbox for water bombs that are spawned by a water bomb spawner. The water
 * bombs that are shot from cannons are intangible.
 */
static struct ObjectHitbox sWaterBombHitbox = {
    /* interactType:      */ INTERACT_MR_BLIZZARD,
    /* downOffset:        */ 25,
    /* damageOrCoinValue: */ 1,
    /* health:            */ 99,
    /* numLootCoins:      */ 0,
    /* radius:            */ 80,
    /* height:            */ 50,
    /* hurtboxRadius:     */ 60,
    /* hurtboxHeight:     */ 50,
};

/**
 * Update function for bhvWaterBombSpawner.
 * Spawn water bombs targeting mario when he comes in range.
 */
void bhv_water_bomb_spawner_update(void) {
    f32 latDistToMario;
    f32 spawnerRadius = 50 * (u16)(o->oBhvParams >> 16) + 200.0f;

    latDistToMario = lateral_dist_between_objects(o, gMarioObject);

    // When mario is in range and a water bomb isn't already active
    if (!o->oWaterBombSpawnerBombActive && latDistToMario < spawnerRadius
        && gMarioObject->oPosY - o->oPosY < 1000.0f) {
        if (o->oWaterBombSpawnerTimeToSpawn != 0) {
            o->oWaterBombSpawnerTimeToSpawn--;
        } else {
            struct Object *waterBomb =
                spawn_object_relative(0, 0, 2000, 0, o, MODEL_WATER_BOMB, bhvWaterBomb);

            if (waterBomb != NULL) {
                // Drop farther ahead of mario when he is moving faster
                f32 waterBombDistToMario = 28.0f * gMarioStates[0].forwardVel + 100.0f;

                waterBomb->oAction = WATER_BOMB_ACT_INIT;

                waterBomb->oPosX =
                    gMarioObject->oPosX + waterBombDistToMario * sins(gMarioObject->oMoveAngleYaw);
                waterBomb->oPosZ =
                    gMarioObject->oPosZ + waterBombDistToMario * coss(gMarioObject->oMoveAngleYaw);

                spawn_object(waterBomb, MODEL_WATER_BOMB_SHADOW, bhvWaterBombShadow);

                o->oWaterBombSpawnerBombActive = TRUE;
                o->oWaterBombSpawnerTimeToSpawn = random_linear_offset(0, 50);
            }
        }
    }
}

/**
 * Spawn particles when the water bomb explodes.
 */
void water_bomb_spawn_explode_particles(s8 offsetY, s8 forwardVelRange, s8 velYBase) {
    static struct SpawnParticlesInfo waterBombExplodeParticles = {
        /* bhvParam:        */ 0,
        /* count:           */ 5,
        /* model:           */ MODEL_BUBBLE,
        /* offsetY:         */ 20,
        /* forwardVelBase:  */ 20,
        /* forwardVelRange: */ 60,
        /* velYBase:        */ 10,
        /* velYRange:       */ 10,
        /* gravity:         */ -2,
        /* dragStrength:    */ 0,
        /* sizeBase:        */ 35.0f,
        /* sizeRange:       */ 10.0f,
    };

    waterBombExplodeParticles.offsetY = offsetY;
    waterBombExplodeParticles.forwardVelRange = forwardVelRange;
    waterBombExplodeParticles.velYBase = velYBase;

    cur_obj_spawn_particles(&waterBombExplodeParticles);
}

/**
 * Enter the drop action with -40 y vel.
 */
static void water_bomb_act_init(void) {
    cur_obj_play_sound_2(SOUND_OBJ_SOMETHING_LANDING);

    o->oAction = WATER_BOMB_ACT_DROP;
    o->oMoveFlags = 0;
    o->oVelY = -40.0f;
}

/**
 * Explode on impact, and otherwise bounce a few times on the ground and then
 * explode.
 */
static void water_bomb_act_drop(void) {
    f32 stretch;

    obj_set_hitbox(o, &sWaterBombHitbox);

    // Explode if touched or if hit water
    if ((o->oInteractStatus & INT_STATUS_INTERACTED) || (o->oMoveFlags & OBJ_MOVE_ENTERED_WATER)) {
        create_sound_spawner(SOUND_OBJ_DIVING_IN_WATER);
        set_camera_shake_from_point(SHAKE_POS_SMALL, o->oPosX, o->oPosY, o->oPosZ);
        o->oAction = WATER_BOMB_ACT_EXPLODE;
    } else if (o->oMoveFlags & OBJ_MOVE_MASK_ON_GROUND) {
        // On impact with the ground, begin getting squished
        if (!o->oWaterBombOnGround) {
            o->oWaterBombOnGround = TRUE;

            if ((o->oWaterBombNumBounces += 1.0f) < 3.0f) {
                cur_obj_play_sound_2(SOUND_OBJ_WATER_BOMB_BOUNCING);
            } else {
                create_sound_spawner(SOUND_OBJ_DIVING_IN_WATER);
            }

            set_camera_shake_from_point(SHAKE_POS_SMALL, o->oPosX, o->oPosY, o->oPosZ);

            // Move toward mario
            o->oMoveAngleYaw = o->oAngleToMario;
            o->oForwardVel = 10.0f;
            o->oWaterBombStretchSpeed = -40.0f;
        }

        // Make less of an attempt to unsquish on each bounce
        o->oWaterBombStretchSpeed += 15.0f - o->oWaterBombNumBounces * 2.8f;

        o->oWaterBombVerticalStretch += o->oWaterBombStretchSpeed * 0.01f;

        // After being too squished, explode
        if (o->oWaterBombVerticalStretch < -0.8f) {
            o->oAction = WATER_BOMB_ACT_EXPLODE;
        } else if (o->oWaterBombVerticalStretch > 0.1f) {
            // Begin bounce trajectory
            o->oVelY = 1.8f * o->oWaterBombStretchSpeed;
        }
    } else {
        approach_f32_ptr(&o->oWaterBombVerticalStretch, 0.0f, 0.008f);
        o->oWaterBombOnGround = FALSE;
    }

    o->header.gfx.scale[1] = o->oWaterBombVerticalStretch + 1.0f;

    stretch = o->oWaterBombVerticalStretch;
    if (o->oWaterBombNumBounces == 3.0f) {
        stretch *= 4.0f;
    }
    o->header.gfx.scale[0] = o->header.gfx.scale[2] = 1.0f - stretch;

    cur_obj_move_standard(78);
}

/**
 * Spawn particles, then despawn. This action informs the water bomb shadow to
 * despawn as well.
 */
static void water_bomb_act_explode(void) {
    water_bomb_spawn_explode_particles(25, 60, 10);
    o->parentObj->oWaterBombSpawnerBombActive = FALSE;
    obj_mark_for_deletion(o);
}

/**
 * Despawn after 100 frames.
 */
static void water_bomb_act_shot_from_cannon(void) {
    static struct SpawnParticlesInfo waterBombCannonParticle = {
        /* bhvParam:        */ 0,
        /* count:           */ 1,
        /* model:           */ MODEL_BUBBLE,
        /* offsetY:         */ 236,
        /* forwardVelBase:  */ 20,
        /* forwardVelRange: */ 5,
        /* velYBase:        */ 0,
        /* velYRange:       */ 0,
        /* gravity:         */ -2,
        /* dragStrength:    */ 0,
        /* sizeBase:        */ 20.0f,
        /* sizeRange:       */ 5.0f,
    };

    if (o->oTimer > 100) {
        obj_mark_for_deletion(o);
    } else {
        if (o->oTimer < 7) {
            if (o->oTimer == 1) {
                water_bomb_spawn_explode_particles(-20, 10, 30);
            }
            cur_obj_spawn_particles(&waterBombCannonParticle);
        }

        if (o->header.gfx.scale[1] > 1.2f) {
            o->header.gfx.scale[1] -= 0.1f;
        }

        o->header.gfx.scale[0] = o->header.gfx.scale[2] = 2.0f - o->header.gfx.scale[1];
        cur_obj_set_pos_via_transform();
    }
}

/**
 * Update function for bhvWaterBomb.
 */
void bhv_water_bomb_update(void) {
    if (o->oAction == WATER_BOMB_ACT_SHOT_FROM_CANNON) {
        water_bomb_act_shot_from_cannon();
    } else {
        o->oGraphYOffset = 40.0f * o->header.gfx.scale[1];
        cur_obj_update_floor_and_walls();

        switch (o->oAction) {
            case WATER_BOMB_ACT_INIT:
                water_bomb_act_init();
                break;
            case WATER_BOMB_ACT_DROP:
                water_bomb_act_drop();
                break;
            case WATER_BOMB_ACT_EXPLODE:
                water_bomb_act_explode();
                break;
        }
    }
}

/**
 * Update function for bhvWaterBombShadow.
 * Despawn when the parent water bomb does.
 */
void bhv_water_bomb_shadow_update(void) {
    if (o->parentObj->oAction == WATER_BOMB_ACT_EXPLODE) {
        obj_mark_for_deletion(o);
    } else {
        // TODO: What is happening here
        f32 bombHeight = o->parentObj->oPosY - o->parentObj->oFloorHeight;
        if (bombHeight > 500.0f) {
            bombHeight = 500.0f;
        }

        obj_copy_pos(o, o->parentObj);
        o->oPosY = o->parentObj->oFloorHeight + bombHeight;
        obj_copy_scale(o, o->parentObj);
    }
}
