diff --git a/README.md b/README.md index 0cb169fad..8923f82fd 100644 --- a/README.md +++ b/README.md @@ -121,7 +121,7 @@ Thanks to Frame#5375 and AloXado320 for also helping with silhouette stuff - Use `render_multi_image` to draw large texture rectangles consisting of multiple images on the screen. - More info in `puppyprint.c` - Wiseguy's Farcall TLB mapping allows to store executable code inside uncompressed segments, that can be loaded and ran as needed, instead of it having to be loaded at all times. See `farcall.h` in the include folder for instructions and details. - +- Red Coin Stars now support up to 99 red coins! In addition, multi-area red coin missions can now be created by setting the 2nd behavior paramater of the red coin star to the number of reds required for the star to spawn. # UltraSM64 - This repo contains a full decompilation of Super Mario 64 (J), (U), (E), and (SH). diff --git a/data/behavior_data.c b/data/behavior_data.c index 50d6791bf..8962915c3 100644 --- a/data/behavior_data.c +++ b/data/behavior_data.c @@ -4581,6 +4581,7 @@ const BehaviorScript bhvRedCoin[] = { const BehaviorScript bhvBowserCourseRedCoinStar[] = { BEGIN(OBJ_LIST_LEVEL), OR_INT(oFlags, (OBJ_FLAG_PERSISTENT_RESPAWN | OBJ_FLAG_UPDATE_GFX_POS_AND_ANGLE)), + CALL_NATIVE(bhv_bowser_course_red_coin_star_init), BEGIN_LOOP(), CALL_NATIVE(bhv_bowser_course_red_coin_star_loop), END_LOOP(), @@ -4862,7 +4863,6 @@ const BehaviorScript bhvOrangeNumber[] = { BEGIN(OBJ_LIST_LEVEL), OR_INT(oFlags, OBJ_FLAG_UPDATE_GFX_POS_AND_ANGLE), BILLBOARD(), - SET_HOME(), CALL_NATIVE(bhv_orange_number_init), BEGIN_LOOP(), CALL_NATIVE(bhv_orange_number_loop), diff --git a/include/object_fields.h b/include/object_fields.h index 2a17465f5..d3487dadc 100644 --- a/include/object_fields.h +++ b/include/object_fields.h @@ -797,6 +797,9 @@ #define /*0x0F8*/ oRespawnerMinSpawnDist OBJECT_FIELD_F32(0x1C) #define /*0x0FC*/ oRespawnerBehaviorToRespawn OBJECT_FIELD_CVPTR(0x1D) +/* Orange Number */ +#define /*0x110*/ oOrangeNumberOffset OBJECT_FIELD_S32(0x22) + /* Openable Grill */ #define /*0x088*/ oOpenableGrillIsOpen OBJECT_FIELD_S32(0x00) #define /*0x0F4*/ oOpenableGrillFloorSwitchObj OBJECT_FIELD_OBJ(0x1B) @@ -998,6 +1001,7 @@ /* Hidden Star */ // Secrets/Red Coins #define /*0x0F4*/ oHiddenStarTriggerCounter OBJECT_FIELD_S32(0x1B) +#define /*0x0F8*/ oHiddenStarTriggerTotal OBJECT_FIELD_S32(0x1C) /* Sealed Door Star */ #define /*0x108*/ oUnlockDoorStarState OBJECT_FIELD_U32(0x20) diff --git a/src/game/behavior_actions.h b/src/game/behavior_actions.h index 0487d5424..1bf0d9c41 100644 --- a/src/game/behavior_actions.h +++ b/src/game/behavior_actions.h @@ -380,6 +380,7 @@ void bhv_hidden_red_coin_star_init(void); void bhv_hidden_red_coin_star_loop(void); void bhv_red_coin_init(void); void bhv_red_coin_loop(void); +void bhv_bowser_course_red_coin_star_init(void); void bhv_bowser_course_red_coin_star_loop(void); void bhv_hidden_star_init(void); void bhv_hidden_star_loop(void); diff --git a/src/game/behaviors/hidden_star.inc.c b/src/game/behaviors/hidden_star.inc.c index 757a84cc9..d41752b04 100644 --- a/src/game/behaviors/hidden_star.inc.c +++ b/src/game/behaviors/hidden_star.inc.c @@ -47,12 +47,24 @@ void bhv_hidden_star_trigger_loop(void) { } } +void bhv_bowser_course_red_coin_star_init(void) { + if (o->oBehParams2ndByte != 0) { + o->oHiddenStarTriggerTotal = o->oBehParams2ndByte; + o->oHiddenStarTriggerCounter = gRedCoinsCollected; + } + else { + s16 numRedCoinsRemaining = count_objects_with_behavior(bhvRedCoin); + o->oHiddenStarTriggerTotal = numRedCoinsRemaining + gRedCoinsCollected; + o->oHiddenStarTriggerCounter = o->oHiddenStarTriggerTotal - numRedCoinsRemaining; + } +} + void bhv_bowser_course_red_coin_star_loop(void) { gRedCoinsCollected = o->oHiddenStarTriggerCounter; switch (o->oAction) { case 0: - if (o->oHiddenStarTriggerCounter == 8) { + if (o->oHiddenStarTriggerCounter == o->oHiddenStarTriggerTotal) { o->oAction = 1; } break; diff --git a/src/game/behaviors/orange_number.inc.c b/src/game/behaviors/orange_number.inc.c index 7a03a7557..d171040f7 100644 --- a/src/game/behaviors/orange_number.inc.c +++ b/src/game/behaviors/orange_number.inc.c @@ -3,6 +3,7 @@ void bhv_orange_number_init(void) { o->oAnimState = o->oBehParams2ndByte; o->oVelY = 26.0f; + o->oHomeY = o->oPosY; } void bhv_orange_number_loop(void) { @@ -16,6 +17,13 @@ void bhv_orange_number_loop(void) { o->oVelY = 14.0f; } + s32 offsetX, offsetZ; + offsetX = o->oOrangeNumberOffset * sins(gCamera->nextYaw + 0x4000); + offsetZ = o->oOrangeNumberOffset * coss(gCamera->nextYaw + 0x4000); + + o->oPosX = o->oHomeX + offsetX; + o->oPosZ = o->oHomeZ + offsetZ; + if (o->oTimer == 35) { struct Object *sparkleObj = spawn_object(o, MODEL_SPARKLES, bhvCoinSparklesSpawner); sparkleObj->oPosY -= 30.0f; diff --git a/src/game/behaviors/red_coin.inc.c b/src/game/behaviors/red_coin.inc.c index 36a56aa10..1a17044c8 100644 --- a/src/game/behaviors/red_coin.inc.c +++ b/src/game/behaviors/red_coin.inc.c @@ -50,18 +50,35 @@ void bhv_red_coin_loop(void) { o->parentObj->oHiddenStarTriggerCounter++; // Spawn the orange number counter, as long as it isn't the last coin. - if (o->parentObj->oHiddenStarTriggerCounter != 8) { - spawn_orange_number(o->parentObj->oHiddenStarTriggerCounter, 0, 0, 0); + if (o->parentObj->oHiddenStarTriggerCounter != o->parentObj->oHiddenStarTriggerTotal) { + // Cap visible count to 99 + if (o->parentObj->oHiddenStarTriggerCounter > 99) { + spawn_orange_number(9, 28, 0, 0); + spawn_orange_number(9, -28, 0, 0); + } + else if (o->parentObj->oHiddenStarTriggerCounter >= 10) { + spawn_orange_number(o->parentObj->oHiddenStarTriggerCounter % 10, 28, 0, 0); + spawn_orange_number(o->parentObj->oHiddenStarTriggerCounter / 10, -28, 0, 0); + } + else { + spawn_orange_number(o->parentObj->oHiddenStarTriggerCounter, 0, 0, 0); + } } #ifdef JP_RED_COIN_SOUND // For JP version, play an identical sound for all coins. create_sound_spawner(SOUND_GENERAL_RED_COIN); #else - // On all versions but the JP version, each coin collected plays a higher noise. - play_sound(SOUND_MENU_COLLECT_RED_COIN - + (((u8) o->parentObj->oHiddenStarTriggerCounter - 1) << 16), - gGlobalSoundSource); + if (o->parentObj->oHiddenStarTriggerTotal - o->parentObj->oHiddenStarTriggerCounter > 7) { + // Play the first red coin sound until it gets to the final 8 + play_sound(SOUND_MENU_COLLECT_RED_COIN, gGlobalSoundSource); + } + else { + // On all versions but the JP version, each coin collected plays a higher noise. + play_sound(SOUND_MENU_COLLECT_RED_COIN + + (((u8) 7 - (o->parentObj->oHiddenStarTriggerTotal - o->parentObj->oHiddenStarTriggerCounter)) << 16), + gGlobalSoundSource); + } #endif } diff --git a/src/game/behaviors/spawn_star.inc.c b/src/game/behaviors/spawn_star.inc.c index 4dd039402..2a16979fd 100644 --- a/src/game/behaviors/spawn_star.inc.c +++ b/src/game/behaviors/spawn_star.inc.c @@ -157,14 +157,27 @@ void bhv_hidden_red_coin_star_init(void) { spawn_object(o, MODEL_TRANSPARENT_STAR, bhvRedCoinStarMarker); } - s16 numRedCoinsRemaining = count_objects_with_behavior(bhvRedCoin); - if (numRedCoinsRemaining == 0) { - starObj = spawn_object_abs_with_rot(o, 0, MODEL_STAR, bhvStar, o->oPosX, o->oPosY, o->oPosZ, 0, 0, 0); - starObj->oBehParams = o->oBehParams; - o->activeFlags = ACTIVE_FLAG_DEACTIVATED; + // check if bparam2 specifies a total number of coins that should spawn the star + if (o->oBehParams2ndByte != 0) { + o->oHiddenStarTriggerTotal = o->oBehParams2ndByte; + o->oHiddenStarTriggerCounter = gRedCoinsCollected; + if (o->oHiddenStarTriggerCounter >= o->oHiddenStarTriggerTotal) { + starObj = spawn_object_abs_with_rot(o, 0, MODEL_STAR, bhvStar, o->oPosX, o->oPosY, o->oPosZ, 0, 0, 0); + starObj->oBehParams = o->oBehParams; + o->activeFlags = ACTIVE_FLAG_DEACTIVATED; + } } - - o->oHiddenStarTriggerCounter = 8 - numRedCoinsRemaining; + else { + s16 numRedCoinsRemaining = count_objects_with_behavior(bhvRedCoin); + if (numRedCoinsRemaining == 0) { + starObj = spawn_object_abs_with_rot(o, 0, MODEL_STAR, bhvStar, o->oPosX, o->oPosY, o->oPosZ, 0, 0, 0); + starObj->oBehParams = o->oBehParams; + o->activeFlags = ACTIVE_FLAG_DEACTIVATED; + } + o->oHiddenStarTriggerTotal = numRedCoinsRemaining + gRedCoinsCollected; + o->oHiddenStarTriggerCounter = o->oHiddenStarTriggerTotal - numRedCoinsRemaining; + } + } void bhv_hidden_red_coin_star_loop(void) { @@ -172,7 +185,7 @@ void bhv_hidden_red_coin_star_loop(void) { switch (o->oAction) { case HIDDEN_STAR_ACT_INACTIVE: - if (o->oHiddenStarTriggerCounter == 8) { + if (o->oHiddenStarTriggerCounter == o->oHiddenStarTriggerTotal) { o->oAction = HIDDEN_STAR_ACT_ACTIVE; } break; diff --git a/src/game/ingame_menu.c b/src/game/ingame_menu.c index fbfddb50d..37905d2c4 100644 --- a/src/game/ingame_menu.c +++ b/src/game/ingame_menu.c @@ -1512,8 +1512,36 @@ void print_animated_red_coin(s16 x, s16 y) { void render_pause_red_coins(void) { s8 x; - for (x = 0; x < gRedCoinsCollected; x++) { - print_animated_red_coin(GFX_DIMENSIONS_FROM_RIGHT_EDGE(30) - x * 20, 16); + if (gRedCoinsCollected <= 9) { + for (x = 0; x < gRedCoinsCollected; x++) { + print_animated_red_coin(GFX_DIMENSIONS_FROM_RIGHT_EDGE(30) - x * 20, 16); + } + } + else { + print_animated_red_coin(GFX_DIMENSIONS_FROM_RIGHT_EDGE(108), 16); + Mtx *mtx; + + mtx = alloc_display_list(sizeof(*mtx)); + if (mtx == NULL) { + return; + } + guOrtho(mtx, 0.0f, SCREEN_WIDTH, 0.0f, SCREEN_HEIGHT, -10.0f, 10.0f, 1.0f); + gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(mtx), G_MTX_PROJECTION | G_MTX_LOAD | G_MTX_NOPUSH); + gSPDisplayList(gDisplayListHead++, dl_hud_img_begin); + + s8 redCoinCount = gRedCoinsCollected; + if (redCoinCount > 99) { + redCoinCount = 99; + } + + add_glyph_texture(GLYPH_MULTIPLY); + render_textrect(GFX_DIMENSIONS_FROM_RIGHT_EDGE(100), 16, 0); + add_glyph_texture(char_to_glyph_index((char) (48 + (redCoinCount / 10)))); + render_textrect(GFX_DIMENSIONS_FROM_RIGHT_EDGE(86), 16, 0); + add_glyph_texture(char_to_glyph_index((char) (48 + (redCoinCount % 10)))); + render_textrect(GFX_DIMENSIONS_FROM_RIGHT_EDGE(86), 16, 1); + + gSPDisplayList(gDisplayListHead++, dl_hud_img_end); } } diff --git a/src/game/interaction.c b/src/game/interaction.c index db7f36306..9bced36e4 100644 --- a/src/game/interaction.c +++ b/src/game/interaction.c @@ -1707,11 +1707,14 @@ u32 check_read_sign(struct MarioState *m, struct Object *obj) { && abs_angle_diff(mario_obj_angle_to_object(m, obj), m->faceAngle[1]) <= SIGN_RANGE ) { #ifdef DIALOG_INDICATOR + struct Object *orangeNumber; if (obj->behavior == segmented_to_virtual(bhvSignOnWall)) { - spawn_object_relative(ORANGE_NUMBER_A, 0, 180, 32, obj, MODEL_NUMBER, bhvOrangeNumber); + orangeNumber = spawn_object_relative(ORANGE_NUMBER_A, 0, 180, 32, obj, MODEL_NUMBER, bhvOrangeNumber); } else { - spawn_object_relative(ORANGE_NUMBER_A, 0, 160, 8, obj, MODEL_NUMBER, bhvOrangeNumber); + orangeNumber = spawn_object_relative(ORANGE_NUMBER_A, 0, 160, 8, obj, MODEL_NUMBER, bhvOrangeNumber); } + orangeNumber->oHomeX = orangeNumber->oPosX; + orangeNumber->oHomeZ = orangeNumber->oPosZ; #endif if (m->input & READ_MASK) { #else @@ -1742,11 +1745,14 @@ u32 check_npc_talk(struct MarioState *m, struct Object *obj) { && abs_angle_diff(mario_obj_angle_to_object(m, obj), m->faceAngle[1]) <= SIGN_RANGE ) { #ifdef DIALOG_INDICATOR + struct Object *orangeNumber; if (obj->behavior == segmented_to_virtual(bhvYoshi)) { - spawn_object_relative(ORANGE_NUMBER_A, 0, 256, 64, obj, MODEL_NUMBER, bhvOrangeNumber); + orangeNumber = spawn_object_relative(ORANGE_NUMBER_A, 0, 256, 64, obj, MODEL_NUMBER, bhvOrangeNumber); } else { - spawn_object_relative(ORANGE_NUMBER_A, 0, 160, 0, obj, MODEL_NUMBER, bhvOrangeNumber); + orangeNumber = spawn_object_relative(ORANGE_NUMBER_A, 0, 160, 0, obj, MODEL_NUMBER, bhvOrangeNumber); } + orangeNumber->oHomeX = orangeNumber->oPosX; + orangeNumber->oHomeZ = orangeNumber->oPosZ; #endif if (m->input & READ_MASK) { #else diff --git a/src/game/obj_behaviors.c b/src/game/obj_behaviors.c index fd5a685e0..c554d4599 100644 --- a/src/game/obj_behaviors.c +++ b/src/game/obj_behaviors.c @@ -691,6 +691,9 @@ void spawn_orange_number(s8 behParam, s16 relX, s16 relY, s16 relZ) { struct Object *orangeNumber = spawn_object_relative(behParam, relX, relY, relZ, o, MODEL_NUMBER, bhvOrangeNumber); orangeNumber->oPosY += 25.0f; + orangeNumber->oOrangeNumberOffset = relX; + orangeNumber->oHomeX = o->oPosX; + orangeNumber->oHomeZ = o->oPosZ; } /** diff --git a/src/game/print.h b/src/game/print.h index e4d4f2b24..c159453a1 100644 --- a/src/game/print.h +++ b/src/game/print.h @@ -34,5 +34,8 @@ void print_text_fmt_int(s32 x, s32 y, const char *str, s32 n); void print_text(s32 x, s32 y, const char *str); void print_text_centered(s32 x, s32 y, const char *str); void render_text_labels(void); +s32 char_to_glyph_index(char c); +void add_glyph_texture(s8 glyphIndex); +void render_textrect(s32 x, s32 y, s32 pos); #endif // PRINT_H