#include #include "sm64.h" #include "game_init.h" #include "memory.h" #include "ingame_menu.h" #include "envfx_snow.h" #include "envfx_bubbles.h" #include "engine/surface_collision.h" #include "engine/math_util.h" #include "engine/behavior_script.h" #include "audio/external.h" #include "obj_behaviors.h" /** * This file implements environment effects that are not snow: * Flowers (unused), lava bubbles and jetsream/whirlpool bubbles. * Refer to 'envfx_snow.c' for more info about environment effects. * Note that the term 'bubbles' is used as a collective name for * effects in this file even though flowers aren't bubbles. For the * sake of concise naming, flowers fall under bubbles. */ s16 gEnvFxBubbleConfig[10]; static Gfx *sGfxCursor; // points to end of display list for bubble particles static s32 sBubbleParticleCount; static s32 sBubbleParticleMaxCount; UNUSED s32 D_80330690 = 0; UNUSED s32 D_80330694 = 0; /// Template for a bubble particle triangle Vtx_t gBubbleTempVtx[3] = { { { 0, 0, 0 }, 0, { 1544, 964 }, { 0xFF, 0xFF, 0xFF, 0xFF } }, { { 0, 0, 0 }, 0, { 522, -568 }, { 0xFF, 0xFF, 0xFF, 0xFF } }, { { 0, 0, 0 }, 0, { -498, 964 }, { 0xFF, 0xFF, 0xFF, 0xFF } }, }; extern void *flower_bubbles_textures_ptr_0B002008; extern void *lava_bubble_ptr_0B006020; extern void *bubble_ptr_0B006848; extern void *tiny_bubble_dl_0B006AB0; extern void *tiny_bubble_dl_0B006D38; extern void *tiny_bubble_dl_0B006D68; /** * Check whether the particle with the given index is * laterally within distance of point (x, z). Used to * kill flower and bubble particles. */ s32 particle_is_laterally_close(s32 index, s32 x, s32 z, s32 distance) { s32 xPos = (gEnvFxBuffer + index)->xPos; s32 zPos = (gEnvFxBuffer + index)->zPos; if (sqr(xPos - x) + sqr(zPos - z) > sqr(distance)) { return 0; } return 1; } /** * Generate a uniform random number in range [-2000, -1000[ or [1000, 2000[ * Used to position flower particles */ s32 random_flower_offset() { s32 result = random_float() * 2000.0f - 1000.0f; if (result < 0) { result -= 1000; } else { result += 1000; } return result; } /** * Update flower particles. Flowers are scattered randomly in front of the * camera, and can land on any ground */ void envfx_update_flower(Vec3s centerPos) { s32 i; struct FloorGeometry *floorGeo; // unused s32 timer = gGlobalTimer; s16 centerX = centerPos[0]; UNUSED s16 centerY = centerPos[1]; s16 centerZ = centerPos[2]; for (i = 0; i < sBubbleParticleMaxCount; i++) { (gEnvFxBuffer + i)->isAlive = particle_is_laterally_close(i, centerX, centerZ, 3000); if ((gEnvFxBuffer + i)->isAlive == 0) { (gEnvFxBuffer + i)->xPos = random_flower_offset() + centerX; (gEnvFxBuffer + i)->zPos = random_flower_offset() + centerZ; (gEnvFxBuffer + i)->yPos = find_floor_height_and_data((gEnvFxBuffer + i)->xPos, 10000.0f, (gEnvFxBuffer + i)->zPos, &floorGeo); (gEnvFxBuffer + i)->isAlive = 1; (gEnvFxBuffer + i)->animFrame = random_float() * 5.0f; } else if ((timer & 0x03) == 0) { (gEnvFxBuffer + i)->animFrame += 1; if ((gEnvFxBuffer + i)->animFrame > 5) { (gEnvFxBuffer + i)->animFrame = 0; } } } } /** * Update the position of a lava bubble to be somewhere around centerPos * Uses find_floor to find the height of lava, if no floor or a non-lava * floor is found the bubble y is set to -10000, which is why you can see * occasional lava bubbles far below the course in Lethal Lava Land. * In the second Bowser fight arena, the visual lava is above the lava * floor so lava-bubbles are not normally visible, only if you bring the * camera below the lava plane. */ void envfx_set_lava_bubble_position(s32 index, Vec3s centerPos) { struct Surface *surface; s16 floorY; s16 centerX, centerY, centerZ; centerX = centerPos[0]; centerY = centerPos[1]; centerZ = centerPos[2]; (gEnvFxBuffer + index)->xPos = random_float() * 6000.0f - 3000.0f + centerX; (gEnvFxBuffer + index)->zPos = random_float() * 6000.0f - 3000.0f + centerZ; if ((gEnvFxBuffer + index)->xPos > 8000) { (gEnvFxBuffer + index)->xPos = 16000 - (gEnvFxBuffer + index)->xPos; } if ((gEnvFxBuffer + index)->xPos < -8000) { (gEnvFxBuffer + index)->xPos = -16000 - (gEnvFxBuffer + index)->xPos; } if ((gEnvFxBuffer + index)->zPos > 8000) { (gEnvFxBuffer + index)->zPos = 16000 - (gEnvFxBuffer + index)->zPos; } if ((gEnvFxBuffer + index)->zPos < -8000) { (gEnvFxBuffer + index)->zPos = -16000 - (gEnvFxBuffer + index)->zPos; } floorY = find_floor((gEnvFxBuffer + index)->xPos, centerY + 500, (gEnvFxBuffer + index)->zPos, &surface); if (surface == NULL) { (gEnvFxBuffer + index)->yPos = -10000; return; } if (surface->type == SURFACE_BURNING) { (gEnvFxBuffer + index)->yPos = floorY; } else { (gEnvFxBuffer + index)->yPos = -10000; } } /** * Update lava bubble animation and give the bubble a new position if the * animation is over. */ void envfx_update_lava(Vec3s centerPos) { s32 i; s32 timer = gGlobalTimer; s8 chance; UNUSED s16 centerX, centerY, centerZ; centerX = centerPos[0]; centerY = centerPos[1]; centerZ = centerPos[2]; for (i = 0; i < sBubbleParticleMaxCount; i++) { if ((gEnvFxBuffer + i)->isAlive == 0) { envfx_set_lava_bubble_position(i, centerPos); (gEnvFxBuffer + i)->isAlive = 1; } else if ((timer & 0x01) == 0) { (gEnvFxBuffer + i)->animFrame += 1; if ((gEnvFxBuffer + i)->animFrame > 8) { (gEnvFxBuffer + i)->isAlive = 0; (gEnvFxBuffer + i)->animFrame = 0; } } } if ((chance = (s32)(random_float() * 16.0f)) == 8) { play_sound(SOUND_GENERAL_QUIET_BUBBLE2, gDefaultSoundArgs); } } /** * Rotate the input x, y and z around the rotation origin of the whirlpool * according to the pitch and yaw of the whirlpool. */ void envfx_rotate_around_whirlpool(s32 *x, s32 *y, s32 *z) { s32 vecX = *x - gEnvFxBubbleConfig[ENVFX_STATE_DEST_X]; s32 vecY = *y - gEnvFxBubbleConfig[ENVFX_STATE_DEST_Y]; s32 vecZ = *z - gEnvFxBubbleConfig[ENVFX_STATE_DEST_Z]; f32 cosPitch = coss(gEnvFxBubbleConfig[ENVFX_STATE_PITCH]); f32 sinPitch = sins(gEnvFxBubbleConfig[ENVFX_STATE_PITCH]); f32 cosMYaw = coss(-gEnvFxBubbleConfig[ENVFX_STATE_YAW]); f32 sinMYaw = sins(-gEnvFxBubbleConfig[ENVFX_STATE_YAW]); f32 rotatedX = vecX * cosMYaw - sinMYaw * cosPitch * vecY - sinPitch * sinMYaw * vecZ; f32 rotatedY = vecX * sinMYaw + cosPitch * cosMYaw * vecY - sinPitch * cosMYaw * vecZ; f32 rotatedZ = vecY * sinPitch + cosPitch * vecZ; *x = gEnvFxBubbleConfig[ENVFX_STATE_DEST_X] + (s32) rotatedX; *y = gEnvFxBubbleConfig[ENVFX_STATE_DEST_Y] + (s32) rotatedY; *z = gEnvFxBubbleConfig[ENVFX_STATE_DEST_Z] + (s32) rotatedZ; } /** * Check whether a whirlpool bubble is alive. A bubble respawns when it is too * low or close to the center. */ s32 envfx_is_whirlpool_bubble_alive(s32 index) { s32 UNUSED sp4; if ((gEnvFxBuffer + index)->bubbleY < gEnvFxBubbleConfig[ENVFX_STATE_DEST_Y] - 100) { return 0; } if ((gEnvFxBuffer + index)->angleAndDist[1] < 10) { return 0; } return 1; } /** * Update whirlpool particles. Whirlpool particles start high and far from * the center and get sucked into the sink in a spiraling motion. */ void envfx_update_whirlpool(void) { s32 i; for (i = 0; i < sBubbleParticleMaxCount; i++) { (gEnvFxBuffer + i)->isAlive = envfx_is_whirlpool_bubble_alive(i); if ((gEnvFxBuffer + i)->isAlive == 0) { (gEnvFxBuffer + i)->angleAndDist[1] = random_float() * 1000.0f; (gEnvFxBuffer + i)->angleAndDist[0] = random_float() * 65536.0f; (gEnvFxBuffer + i)->xPos = gEnvFxBubbleConfig[ENVFX_STATE_SRC_X] + sins((gEnvFxBuffer + i)->angleAndDist[0]) * (gEnvFxBuffer + i)->angleAndDist[1]; (gEnvFxBuffer + i)->zPos = gEnvFxBubbleConfig[ENVFX_STATE_SRC_Z] + coss((gEnvFxBuffer + i)->angleAndDist[0]) * (gEnvFxBuffer + i)->angleAndDist[1]; (gEnvFxBuffer + i)->bubbleY = gEnvFxBubbleConfig[ENVFX_STATE_SRC_Y] + (random_float() * 100.0f - 50.0f); (gEnvFxBuffer + i)->yPos = (i + gEnvFxBuffer)->bubbleY; (gEnvFxBuffer + i)->unusedBubbleVar = 0; (gEnvFxBuffer + i)->isAlive = 1; envfx_rotate_around_whirlpool(&(gEnvFxBuffer + i)->xPos, &(gEnvFxBuffer + i)->yPos, &(gEnvFxBuffer + i)->zPos); } else { (gEnvFxBuffer + i)->angleAndDist[1] -= 40; (gEnvFxBuffer + i)->angleAndDist[0] += (s16)(3000 - (gEnvFxBuffer + i)->angleAndDist[1] * 2) + 0x400; (gEnvFxBuffer + i)->xPos = gEnvFxBubbleConfig[ENVFX_STATE_SRC_X] + sins((gEnvFxBuffer + i)->angleAndDist[0]) * (gEnvFxBuffer + i)->angleAndDist[1]; (gEnvFxBuffer + i)->zPos = gEnvFxBubbleConfig[ENVFX_STATE_SRC_Z] + coss((gEnvFxBuffer + i)->angleAndDist[0]) * (gEnvFxBuffer + i)->angleAndDist[1]; (gEnvFxBuffer + i)->bubbleY -= 40 - ((s16)(gEnvFxBuffer + i)->angleAndDist[1] / 100); (gEnvFxBuffer + i)->yPos = (i + gEnvFxBuffer)->bubbleY; envfx_rotate_around_whirlpool(&(gEnvFxBuffer + i)->xPos, &(gEnvFxBuffer + i)->yPos, &(gEnvFxBuffer + i)->zPos); } } } /** * Check whether a jetstream bubble should respawn. Happens if it is laterally * 1000 units away from the source or 1500 units above it. */ s32 envfx_is_jestream_bubble_alive(s32 index) { UNUSED s32 unk; if (!particle_is_laterally_close(index, gEnvFxBubbleConfig[ENVFX_STATE_SRC_X], gEnvFxBubbleConfig[ENVFX_STATE_SRC_Z], 1000) || gEnvFxBubbleConfig[ENVFX_STATE_SRC_Y] + 1500 < (gEnvFxBuffer + index)->yPos) { return 0; } return 1; } /** * Update the positions of jestream bubble particles. * They move up and outwards. */ void envfx_update_jetstream(void) { s32 i; for (i = 0; i < sBubbleParticleMaxCount; i++) { (gEnvFxBuffer + i)->isAlive = envfx_is_jestream_bubble_alive(i); if ((gEnvFxBuffer + i)->isAlive == 0) { (gEnvFxBuffer + i)->angleAndDist[1] = random_float() * 300.0f; (gEnvFxBuffer + i)->angleAndDist[0] = random_u16(); (gEnvFxBuffer + i)->xPos = gEnvFxBubbleConfig[ENVFX_STATE_SRC_X] + sins((gEnvFxBuffer + i)->angleAndDist[0]) * (gEnvFxBuffer + i)->angleAndDist[1]; (gEnvFxBuffer + i)->zPos = gEnvFxBubbleConfig[ENVFX_STATE_SRC_Z] + coss((gEnvFxBuffer + i)->angleAndDist[0]) * (gEnvFxBuffer + i)->angleAndDist[1]; (gEnvFxBuffer + i)->yPos = gEnvFxBubbleConfig[ENVFX_STATE_SRC_Y] + (random_float() * 400.0f - 200.0f); } else { (gEnvFxBuffer + i)->angleAndDist[1] += 10; (gEnvFxBuffer + i)->xPos += sins((gEnvFxBuffer + i)->angleAndDist[0]) * 10.0f; (gEnvFxBuffer + i)->zPos += coss((gEnvFxBuffer + i)->angleAndDist[0]) * 10.0f; (gEnvFxBuffer + i)->yPos -= ((gEnvFxBuffer + i)->angleAndDist[1] / 30) - 50; } } } /** * Initialize bubble (or flower) effect by allocating a buffer to store * the state of each particle and setting the initial and max count. * Analogous to init_snow_particles, but for bubbles. */ s32 envfx_init_bubble(s32 mode) { s32 i; switch (mode) { case ENVFX_MODE_NONE: return 0; case ENVFX_FLOWERS: sBubbleParticleCount = 30; sBubbleParticleMaxCount = 30; break; case ENVFX_LAVA_BUBBLES: sBubbleParticleCount = 15; sBubbleParticleMaxCount = 15; break; case ENVFX_WHIRLPOOL_BUBBLES: sBubbleParticleCount = 60; break; case ENVFX_JETSTREAM_BUBBLES: sBubbleParticleCount = 60; break; } gEnvFxBuffer = mem_pool_alloc(gEffectsMemoryPool, sBubbleParticleCount * sizeof(struct EnvFxParticle)); if (!gEnvFxBuffer) { return 0; } bzero(gEnvFxBuffer, sBubbleParticleCount * sizeof(struct EnvFxParticle)); bzero(gEnvFxBubbleConfig, sizeof(gEnvFxBubbleConfig)); if (mode == ENVFX_LAVA_BUBBLES) { //! Dead code if (0) { } for (i = 0; i < sBubbleParticleCount; i++) { (gEnvFxBuffer + i)->animFrame = random_float() * 7.0f; } if (0) { } } gEnvFxMode = mode; return 1; } /** * Update particles depending on mode. * Also sets the given vertices to the correct shape for each mode, * though they are not being rotated yet. */ void envfx_bubbles_update_switch(s32 mode, Vec3s camTo, Vec3s vertex1, Vec3s vertex2, Vec3s vertex3) { switch (mode) { case ENVFX_FLOWERS: envfx_update_flower(camTo); vertex1[0] = 50; vertex1[1] = 0; vertex1[2] = 0; vertex2[0] = 0; vertex2[1] = 75; vertex2[2] = 0; vertex3[0] = -50; vertex3[1] = 0; vertex3[2] = 0; break; case ENVFX_LAVA_BUBBLES: envfx_update_lava(camTo); vertex1[0] = 100; vertex1[1] = 0; vertex1[2] = 0; vertex2[0] = 0; vertex2[1] = 150; vertex2[2] = 0; vertex3[0] = -100; vertex3[1] = 0; vertex3[2] = 0; break; case ENVFX_WHIRLPOOL_BUBBLES: envfx_update_whirlpool(); vertex1[0] = 40; vertex1[1] = 0; vertex1[2] = 0; vertex2[0] = 0; vertex2[1] = 60; vertex2[2] = 0; vertex3[0] = -40; vertex3[1] = 0; vertex3[2] = 0; break; case ENVFX_JETSTREAM_BUBBLES: envfx_update_jetstream(); vertex1[0] = 40; vertex1[1] = 0; vertex1[2] = 0; vertex2[0] = 0; vertex2[1] = 60; vertex2[2] = 0; vertex3[0] = -40; vertex3[1] = 0; vertex3[2] = 0; break; } } /** * Append 15 vertices to 'gfx', which is enough for 5 bubbles starting at * 'index'. The 3 input vertices represent the roated triangle around (0,0,0) * that will be translated to bubble positions to draw the bubble image * * TODO: (Scrub C) */ void append_bubble_vertex_buffer(Gfx *gfx, s32 index, Vec3s vertex1, Vec3s vertex2, Vec3s vertex3, Vtx *template) { s32 i = 0; Vtx *vertBuf = alloc_display_list(15 * sizeof(Vtx)); #ifdef VERSION_EU Vtx *p; #endif if (vertBuf == NULL) { return; } for (i = 0; i < 15; i += 3) { vertBuf[i] = template[0]; #ifdef VERSION_EU p = vertBuf; p += i; p[0].v.ob[0] = gEnvFxBuffer[index + i / 3].xPos + vertex1[0]; p[0].v.ob[1] = gEnvFxBuffer[index + i / 3].yPos + vertex1[1]; p[0].v.ob[2] = gEnvFxBuffer[index + i / 3].zPos + vertex1[2]; #else vertBuf[i].v.ob[0] = gEnvFxBuffer[index + i / 3].xPos + vertex1[0]; vertBuf[i].v.ob[1] = gEnvFxBuffer[index + i / 3].yPos + vertex1[1]; vertBuf[i].v.ob[2] = gEnvFxBuffer[index + i / 3].zPos + vertex1[2]; #endif vertBuf[i + 1] = template[1]; #ifdef VERSION_EU p = vertBuf; p += i; p[1].v.ob[0] = gEnvFxBuffer[index + i / 3].xPos + vertex2[0]; p[1].v.ob[1] = gEnvFxBuffer[index + i / 3].yPos + vertex2[1]; p[1].v.ob[2] = gEnvFxBuffer[index + i / 3].zPos + vertex2[2]; #else vertBuf[i + 1].v.ob[0] = gEnvFxBuffer[index + i / 3].xPos + vertex2[0]; vertBuf[i + 1].v.ob[1] = gEnvFxBuffer[index + i / 3].yPos + vertex2[1]; vertBuf[i + 1].v.ob[2] = gEnvFxBuffer[index + i / 3].zPos + vertex2[2]; #endif vertBuf[i + 2] = template[2]; #ifdef VERSION_EU p = vertBuf; p += i; p[2].v.ob[0] = gEnvFxBuffer[index + i / 3].xPos + vertex3[0]; p[2].v.ob[1] = gEnvFxBuffer[index + i / 3].yPos + vertex3[1]; p[2].v.ob[2] = gEnvFxBuffer[index + i / 3].zPos + vertex3[2]; #else vertBuf[i + 2].v.ob[0] = gEnvFxBuffer[index + i / 3].xPos + vertex3[0]; vertBuf[i + 2].v.ob[1] = gEnvFxBuffer[index + i / 3].yPos + vertex3[1]; vertBuf[i + 2].v.ob[2] = gEnvFxBuffer[index + i / 3].zPos + vertex3[2]; #endif } gSPVertex(gfx, VIRTUAL_TO_PHYSICAL(vertBuf), 15, 0); } /** * Appends to the enfvx display list a command setting the appropriate texture * for a specific particle. The display list is not passed as parameter but uses * the global sGfxCursor instead. */ void envfx_set_bubble_texture(s32 mode, s16 index) { void **imageArr; s16 frame = (gEnvFxBuffer + index)->animFrame; switch (mode) { case ENVFX_FLOWERS: imageArr = segmented_to_virtual(&flower_bubbles_textures_ptr_0B002008); frame = (gEnvFxBuffer + index)->animFrame; break; case ENVFX_LAVA_BUBBLES: imageArr = segmented_to_virtual(&lava_bubble_ptr_0B006020); frame = (gEnvFxBuffer + index)->animFrame; break; case ENVFX_WHIRLPOOL_BUBBLES: case ENVFX_JETSTREAM_BUBBLES: imageArr = segmented_to_virtual(&bubble_ptr_0B006848); frame = 0; break; } gDPSetTextureImage(sGfxCursor++, G_IM_FMT_RGBA, G_IM_SIZ_16b, 1, *(imageArr + frame)); gSPDisplayList(sGfxCursor++, &tiny_bubble_dl_0B006D68); } /** * Updates the bubble particle positions, then generates and returns a display * list drawing them. */ Gfx *envfx_update_bubble_particles(s32 mode, UNUSED Vec3s marioPos, Vec3s camFrom, Vec3s camTo) { s32 i; s16 radius, pitch, yaw; Vec3s vertex1; Vec3s vertex2; Vec3s vertex3; Gfx *gfxStart; gfxStart = alloc_display_list(((sBubbleParticleMaxCount / 5) * 10 + sBubbleParticleMaxCount + 3) * sizeof(Gfx)); if (gfxStart == NULL) { return NULL; } sGfxCursor = gfxStart; orbit_from_positions(camTo, camFrom, &radius, &pitch, &yaw); envfx_bubbles_update_switch(mode, camTo, vertex1, vertex2, vertex3); rotate_triangle_vertices(vertex1, vertex2, vertex3, pitch, yaw); gSPDisplayList(sGfxCursor++, &tiny_bubble_dl_0B006D38); for (i = 0; i < sBubbleParticleMaxCount; i += 5) { gDPPipeSync(sGfxCursor++); envfx_set_bubble_texture(mode, i); append_bubble_vertex_buffer(sGfxCursor++, i, vertex1, vertex2, vertex3, (Vtx *) gBubbleTempVtx); gSP1Triangle(sGfxCursor++, 0, 1, 2, 0); gSP1Triangle(sGfxCursor++, 3, 4, 5, 0); gSP1Triangle(sGfxCursor++, 6, 7, 8, 0); gSP1Triangle(sGfxCursor++, 9, 10, 11, 0); gSP1Triangle(sGfxCursor++, 12, 13, 14, 0); } gSPDisplayList(sGfxCursor++, &tiny_bubble_dl_0B006AB0); gSPEndDisplayList(sGfxCursor++); return gfxStart; } /** * Set the maximum particle count from the gEnvFxBubbleConfig variable, * which is set by the whirlpool or jetstream behavior. */ void envfx_set_max_bubble_particles(s32 mode) { switch (mode) { case ENVFX_WHIRLPOOL_BUBBLES: sBubbleParticleMaxCount = gEnvFxBubbleConfig[ENVFX_STATE_PARTICLECOUNT]; break; case ENVFX_JETSTREAM_BUBBLES: sBubbleParticleMaxCount = gEnvFxBubbleConfig[ENVFX_STATE_PARTICLECOUNT]; break; } } /** * Update bubble-like environment effects. Assumes the mode is larger than 10, * lower modes are snow effects which are updated in a different function. * Returns a display list drawing the particles. */ Gfx *envfx_update_bubbles(s32 mode, Vec3s marioPos, Vec3s camTo, Vec3s camFrom) { Gfx *gfx; if (gEnvFxMode == 0 && !envfx_init_bubble(mode)) { return NULL; } envfx_set_max_bubble_particles(mode); if (sBubbleParticleMaxCount == 0) { return NULL; } switch (mode) { case ENVFX_FLOWERS: gfx = envfx_update_bubble_particles(ENVFX_FLOWERS, marioPos, camFrom, camTo); break; case ENVFX_LAVA_BUBBLES: gfx = envfx_update_bubble_particles(ENVFX_LAVA_BUBBLES, marioPos, camFrom, camTo); break; case ENVFX_WHIRLPOOL_BUBBLES: gfx = envfx_update_bubble_particles(ENVFX_WHIRLPOOL_BUBBLES, marioPos, camFrom, camTo); break; case ENVFX_JETSTREAM_BUBBLES: gfx = envfx_update_bubble_particles(ENVFX_JETSTREAM_BUBBLES, marioPos, camFrom, camTo); break; default: return NULL; } return gfx; }