[Feature] Boss Rush (#2923)

* Ganon(dorf) cutscene skips

* Remove leftover code

* Load into chamber of sages

* Fix loading into chamber without fast file select

* Boss warps in chamber done

* Change warps back to chamber

* Initial proof of concept done

* ganon(dorf) cutscene skips

* Code cleanup & auto age equipment

* Gameplay stats timer + tweaks

* Scuffed timer

* Better timer

* remove arena props + fix arena exits

* Fix blue warps

* Attempt to fix build

* Fix build again

* And again..

* Try no. 9001

* Handle dying and saving

* Child link face fire medallion

* Fix build

* Fix warps after reset/death

* Disable doors and move player spawns in boss rooms

* Fix boss rush logo rendering

* Start of ingame options menu

* File Select cleanup

* Fix build

* Render char text PoC

* Move functions to be more generic

* Fix build

* Fix other builds

* Initial text scaling/kerning

* Special characters prep

* All special characters work now

* Attempt to fix build

* Fix build question mark

* Finish all kerning

* Start of ingame options menu with vertical scrolling

* Barebones functional options menu

* More options menu progress

* More visual elements for options menu

* Options menu visual changes, implement all options, tons of cleanup

* Cleanup and comments

* Shorter enums

* More options

* Change default heart count

* Finish French translations

* Implement timer in cosmetics editor

* Uncomment timer requirement

* Variable name change

* German translation & small UI tweaks

* Animated up/down arrows in options UI

* Better arrows in options UI

* Cleaner timer + make it usable for general gameplay

* More cleanup + ganon & ganondorf boss option

* Implement never heal option

* Slight up arrow in options UI tweak

* Add BGS option

* Reintroduce ganondorf cutscene skip

* Change encoding to UTF on bossrush.cpp

* Fix build hopefully

* Fixed static variables leading to options not properly resetting

* Fix BR completed timestamp

* Change timer to render on top of everything

* Offset final BR time by 0.1 second from boss timestamps

* Add missing check for boss rush

* Implement soh_assets.h

* Revert merge mistake

* Fix special characters with UTF-8

* Fix build

* here's the fix you can merge from your phone

* Fix quest select crash with oot.otr only

* Use OoT's kerning

* Fix HD textures on options menu

* Fix special character kerning

* "Heal every boss" fixes

* Seperate headers + bunny hood option

* Remove GetUnixTimestamp() externing

* Clean up extern "C"'s

* Address review comments

* Fix build question mark

* Remove accidental styling change

---------

Co-authored-by: briaguya <briaguya@alice>
This commit is contained in:
aMannus 2023-06-02 03:40:10 +02:00 committed by GitHub
parent fdf9086b2a
commit 2957dc61c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
50 changed files with 1351 additions and 216 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -41,10 +41,19 @@ static const ALIGN_ASSET(2) char gSkullTreasureChestSideAndTopTex[] = dgSkullTre
#define dgTitleRandomizerSubtitleTex "__OTR__objects/object_mag/gTitleRandomizerSubtitleTex"
static const ALIGN_ASSET(2) char gTitleRandomizerSubtitleTex[] = dgTitleRandomizerSubtitleTex;
#define dgTitleBossRushSubtitleTex "__OTR__objects/object_mag/gTitleBossRushSubtitleTex"
static const ALIGN_ASSET(2) char gTitleBossRushSubtitleTex[] = dgTitleBossRushSubtitleTex;
// textures
#define dgDPad "__OTR__textures/parameter_static/gDPad"
static const ALIGN_ASSET(2) char gDPadTex[] = dgDPad;
#define dgArrowUp "__OTR__textures/parameter_static/gArrowUp"
static const ALIGN_ASSET(2) char gArrowUpTex[] = dgArrowUp;
#define dgArrowDown "__OTR__textures/parameter_static/gArrowDown"
static const ALIGN_ASSET(2) char gArrowDownTex[] = dgArrowDown;
#define dgFileSelMQButtonTex "__OTR__textures/title_static/gFileSelMQButtonTex"
static const ALIGN_ASSET(2) char gFileSelMQButtonTex[] = dgFileSelMQButtonTex;
@ -57,6 +66,15 @@ static const ALIGN_ASSET(2) char gFileSelPleaseChooseAQuestFRATex[] = dgFileSelP
#define dgFileSelPleaseChooseAQuestGERTex "__OTR__textures/title_static/gFileSelPleaseChooseAQuestGERTex"
static const ALIGN_ASSET(2) char gFileSelPleaseChooseAQuestGERTex[] = dgFileSelPleaseChooseAQuestGERTex;
#define dgFileSelBossRushSettingsENGTex "__OTR__textures/title_static/gFileSelBossRushSettingsENGTex"
static const ALIGN_ASSET(2) char gFileSelBossRushSettingsENGText[] = dgFileSelBossRushSettingsENGTex;
#define dgFileSelBossRushSettingsFRATex "__OTR__textures/title_static/gFileSelBossRushSettingsFRATex"
static const ALIGN_ASSET(2) char gFileSelBossRushSettingsFRAText[] = dgFileSelBossRushSettingsFRATex;
#define dgFileSelBossRushSettingsGERTex "__OTR__textures/title_static/gFileSelBossRushSettingsGERTex"
static const ALIGN_ASSET(2) char gFileSelBossRushSettingsGERText[] = dgFileSelBossRushSettingsGERTex;
#define dgFileSelRANDButtonTex "__OTR__textures/title_static/gFileSelRANDButtonTex"
static const ALIGN_ASSET(2) char gFileSelRANDButtonTex[] = dgFileSelRANDButtonTex;

View File

@ -889,6 +889,7 @@ void KaleidoSetup_Init(PlayState* play);
void KaleidoSetup_Destroy(PlayState* play);
void func_8006EE50(Font* font, u16 arg1, u16 arg2);
void Font_LoadChar(Font* font, u8 character, u16 codePointIndex);
void* Font_FetchCharTexture(u8 character);
void Font_LoadMessageBoxIcon(Font* font, u16 icon);
void Font_LoadOrderedFont(Font* font);
s32 func_8006F0A0(s32 arg0);
@ -1054,6 +1055,7 @@ void func_800849EC(PlayState* play);
void Interface_LoadItemIcon1(PlayState* play, u16 button);
void Interface_LoadItemIcon2(PlayState* play, u16 button);
void func_80084BF4(PlayState* play, u16 flag);
uint16_t Interface_DrawTextLine(GraphicsContext* gfx, char text[], int16_t x, int16_t y, uint16_t colorR, uint16_t colorG, uint16_t colorB, uint16_t colorA, float textScale, uint8_t textShadow);
u8 Item_Give(PlayState* play, u8 item);
u16 Randomizer_Item_Give(PlayState* play, GetItemEntry giEntry);
u8 Item_CheckObtainability(u8 item);
@ -2402,6 +2404,7 @@ u8 Message_GetState(MessageContext* msgCtx);
void Message_Draw(PlayState* play);
void Message_Update(PlayState* play);
void Message_SetTables(void);
f32 Message_GetCharacterWidth(unsigned char characterIndex);
void GameOver_Init(PlayState* play);
void GameOver_FadeInLights(PlayState* play);
void GameOver_Update(PlayState* play);

View File

@ -1500,6 +1500,10 @@ typedef struct {
f32 stickAnimTween;
u8 arrowAnimState;
u8 stickAnimState;
uint8_t bossRushIndex;
uint8_t bossRushOffset;
int16_t bossRushUIAlpha;
uint16_t bossRushArrowOffset;
} FileChooseContext; // size = 0x1CAE0
typedef enum {

View File

@ -8,6 +8,7 @@
#include "soh/Enhancements/randomizer/randomizer_inf.h"
#include "soh/Enhancements/gameplaystats.h"
#include "soh/Enhancements/randomizer/randomizer_entrance.h"
#include "soh/Enhancements/boss-rush/BossRushTypes.h"
typedef enum {
/* 0x0 */ MAGIC_STATE_IDLE, // Regular gameplay
@ -280,6 +281,9 @@ typedef struct {
// #region SOH [General]
// Upstream TODO: Move these to their own struct or name to more obviously specific to SoH
/* */ uint32_t isMasterQuest;
/* */ uint32_t isBossRush;
/* */ uint32_t isBossRushPaused;
/* */ uint8_t bossRushOptions[BOSSRUSH_OPTIONS_AMOUNT];
/* */ u8 mqDungeonCount;
/* */ u8 pendingIceTrapCount;
/* */ SohStats sohStats;

View File

@ -0,0 +1,487 @@
#include "BossRush.h"
#include "soh/OTRGlobals.h"
#include "functions.h"
#include "macros.h"
#include "variables.h"
#include <array>
#include <string>
#include <vector>
typedef struct BossRushSetting {
std::array<std::string, LANGUAGE_MAX> name;
std::vector<std::array<std::string, LANGUAGE_MAX>> choices;
} BossRushSetting;
BossRushSetting BossRushOptions[BOSSRUSH_OPTIONS_AMOUNT] = {
{
{ "BOSSES:", "BOSSE:", "BOSS:" },
{
{ "All", "Alle", "Tous" },
{ "Child", "Kind", "Enfant" },
{ "Adult", "Erwachsener", "Adulte" },
{ "Ganondorf & Ganon", "Ganondorf & Ganon", "Ganondorf & Ganon" }
}
},
{
{ "HEARTS:", "HERZEN:", "COEURS:" },
{
{ "10", "10", "10" },
{ "15", "15", "15" },
{ "20", "20", "20" },
{ "3", "3", "3" },
{ "5", "5", "5" },
{ "7", "7", "7" }
}
},
{
{ "AMMO:", "MUNITION:", "MUNITIONS:" },
{
{ "Limited", "Limitiert", "Limitées" },
{ "Full", "Voll", "Pleines" },
{ "Maxed", "Maximum", "Maximum" }
}
},
{
{ "HEAL:", "REGENERATION:", "SOIN:" },
{
{ "Before Ganondorf", "Vor Ganondorf", "Avant Ganondorf" },
{ "Every Boss", "Bei jedem Boss", "Tous les Boss" },
{ "Never", "Niemals", "Jamais" }
}
},
{
{ "HYPER BOSSES:", "HYPER-BOSSE:", "HYPER BOSS:" },
{
{ "No", "Nein", "Non" },
{ "Yes", "Ja", "Oui" }
}
},
{
{ "MAGIC:", "MAGIE:", "MAGIE:" },
{
{ "Single", "Einzel", "Simple" },
{ "Double", "Doppel", "Double" }
}
},
{
{ "BIG. SWORD:", "BIG.-SCHWERT:", "EPÉE DE BIG.:" },
{
{ "No", "Nein", "Non" },
{ "Yes", "Ja", "Oui" }
}
},
{
{ "BOTTLE:", "FLASCHEN:", "BOUTEILLE:" },
{
{ "No", "Nein", "Non" },
{ "Empty", "Leer", "Vide" },
{ "Fairy", "Fee", "Fée" },
{ "Red Potion", "Rotes Elixier", "Potion Rouge" },
{ "Green Potion", "Grünes Elixier", "Potion Verte" },
{ "Blue Potion", "Blaues Elixier", "Potion Bleue" }
}
},
{
{ "LONGSHOT:", "ENTERHAKEN:", "SUPER GRAPPIN:" },
{
{ "No", "Nein", "Non" },
{ "Yes", "Ja", "Oui" }
}
},
{
{ "HOVER BOOTS:", "GLEITSTIEFEL:", "BOTTES DES AIRS:" },
{
{ "No", "Nein", "Non" },
{ "Yes", "Ja", "Oui" }
}
},
{
{ "BUNNY HOOD:", "HASENOHREN:", "MASQUE DU LAPIN:" },
{
{ "No", "Nein", "Non" },
{ "Yes", "Ja", "Oui" }
}
},
{
{ "TIMER:", "TIMER:", "TIMER:" },
{
{ "Yes", "Ja", "Oui" },
{ "No", "Nein", "Non" }
}
}
};
const char* BossRush_GetSettingName(uint8_t optionIndex, uint8_t language) {
return BossRushOptions[optionIndex].name[language].c_str();
}
const char* BossRush_GetSettingChoiceName(uint8_t optionIndex, uint8_t choiceIndex, uint8_t language) {
return BossRushOptions[optionIndex].choices[choiceIndex][language].c_str();
}
uint8_t BossRush_GetSettingOptionsAmount(uint8_t optionIndex) {
return BossRushOptions[optionIndex].choices.size();
}
void BossRush_SpawnBlueWarps(PlayState* play) {
// Spawn blue warps in Chamber of Sages based on what bosses have been defeated.
if (gSaveContext.linkAge == LINK_AGE_CHILD) {
// Forest Medallion (Gohma)
if (!Flags_GetRandomizerInf(RAND_INF_DUNGEONS_DONE_DEKU_TREE)) {
Actor_Spawn(&play->actorCtx, play, ACTOR_DOOR_WARP1, -100, 6, -170, 0, 0, 0, -1, false);
}
// Fire Medallion (King Dodongo)
if (!Flags_GetRandomizerInf(RAND_INF_DUNGEONS_DONE_DODONGOS_CAVERN)) {
Actor_Spawn(&play->actorCtx, play, ACTOR_DOOR_WARP1, 100, 6, -170, 0, 0, 0, -1, false);
}
// Water Medallion (Barinade)
if (!Flags_GetRandomizerInf(RAND_INF_DUNGEONS_DONE_JABU_JABUS_BELLY)) {
Actor_Spawn(&play->actorCtx, play, ACTOR_DOOR_WARP1, 199, 6, 0, 0, 0, 0, -1, false);
}
} else {
// Light Medallion (Ganondorf)
if (CheckDungeonCount() == 8) {
Actor_Spawn(&play->actorCtx, play, ACTOR_DOOR_WARP1, -199, 6, 0, 0, 0, 0, -1, false);
}
// Forest Medallion (Phantom Ganondorf)
if (!Flags_GetRandomizerInf(RAND_INF_DUNGEONS_DONE_FOREST_TEMPLE)) {
Actor_Spawn(&play->actorCtx, play, ACTOR_DOOR_WARP1, -100, 6, -170, 0, 0, 0, -1, false);
}
// Fire Medallion (Volvagia)
if (!Flags_GetRandomizerInf(RAND_INF_DUNGEONS_DONE_FIRE_TEMPLE)) {
Actor_Spawn(&play->actorCtx, play, ACTOR_DOOR_WARP1, 100, 6, -170, 0, 0, 0, -1, false);
}
// Water Medallion (Morpha)
if (!Flags_GetRandomizerInf(RAND_INF_DUNGEONS_DONE_WATER_TEMPLE)) {
Actor_Spawn(&play->actorCtx, play, ACTOR_DOOR_WARP1, 199, 6, 0, 0, 0, 0, -1, false);
}
// Spirit Medallion (Twinrova)
if (!Flags_GetRandomizerInf(RAND_INF_DUNGEONS_DONE_SPIRIT_TEMPLE)) {
Actor_Spawn(&play->actorCtx, play, ACTOR_DOOR_WARP1, 100, 6, 170, 0, 0, 0, -1, false);
}
// Shadow Medallion (Bongo Bongo)
if (!Flags_GetRandomizerInf(RAND_INF_DUNGEONS_DONE_SHADOW_TEMPLE)) {
Actor_Spawn(&play->actorCtx, play, ACTOR_DOOR_WARP1, -100, 6, 170, 0, 0, 0, -1, false);
}
}
}
void BossRush_HandleBlueWarp(PlayState* play, f32 warpPosX, f32 warpPosZ) {
// If warping from Chamber of Sages, choose the correct boss room to teleport to.
if (play->sceneNum == SCENE_KENJYANOMA) {
// Gohma & Phantom Ganon
if (warpPosX == -100 && warpPosZ == -170) {
if (gSaveContext.linkAge == LINK_AGE_CHILD) {
play->nextEntranceIndex = 0x040F;
} else {
play->nextEntranceIndex = 0x000C;
}
// King Dodongo & Volvagia
} else if (warpPosX == 100 && warpPosZ == -170) {
if (gSaveContext.linkAge == LINK_AGE_CHILD) {
play->nextEntranceIndex = 0x040B;
} else {
play->nextEntranceIndex = 0x0305;
}
// Barinade & Morb
} else if (warpPosX == 199 && warpPosZ == 0) {
if (gSaveContext.linkAge == LINK_AGE_CHILD) {
play->nextEntranceIndex = 0x0301;
} else {
play->nextEntranceIndex = 0x0417;
}
// Twinrova
} else if (warpPosX == 100 && warpPosZ == 170) {
play->nextEntranceIndex = 0x05EC;
// Bongo Bongo
} else if (warpPosX == -100 && warpPosZ == 170) {
play->nextEntranceIndex = 0x0413;
// Ganondork
} else if (warpPosX == -199 && warpPosZ == 0) {
play->nextEntranceIndex = 0x041F;
}
// If coming from a boss room, teleport back to Chamber of Sages and set flag.
} else {
play->nextEntranceIndex = SCENE_HAIRAL_NIWA2;
if (CheckDungeonCount() == 3) {
play->linkAgeOnLoad = LINK_AGE_ADULT;
gSaveContext.linkAge = LINK_AGE_ADULT;
// Change to Adult Link.
if (gSaveContext.bossRushOptions[BR_OPTIONS_BOSSES] == BR_CHOICE_BOSSES_ALL) {
BossRush_SetEquipment(LINK_AGE_ADULT);
// Warp to credits.
} else if (gSaveContext.bossRushOptions[BR_OPTIONS_BOSSES] == BR_CHOICE_BOSSES_CHILD) {
play->nextEntranceIndex = 0x6B;
gSaveContext.nextCutsceneIndex = 0xFFF2;
play->sceneLoadFlag = 0x14;
play->fadeTransition = 3;
}
}
}
}
void BossRush_HandleBlueWarpHeal(PlayState* play) {
// This function gets called multiple times per blue warp, so only heal when player isn't at max HP.
if (gSaveContext.bossRushOptions[BR_OPTIONS_HEAL] == BR_CHOICE_HEAL_EVERYBOSS &&
gSaveContext.health != gSaveContext.healthCapacity) {
Health_ChangeBy(play, 320);
}
}
void BossRush_HandleCompleteBoss(PlayState* play) {
if (!gSaveContext.isBossRush) {
return;
}
gSaveContext.isBossRushPaused = 1;
switch (play->sceneNum) {
case SCENE_YDAN_BOSS:
Flags_SetRandomizerInf(RAND_INF_DUNGEONS_DONE_DEKU_TREE);
break;
case SCENE_DDAN_BOSS:
Flags_SetRandomizerInf(RAND_INF_DUNGEONS_DONE_DODONGOS_CAVERN);
break;
case SCENE_BDAN_BOSS:
Flags_SetRandomizerInf(RAND_INF_DUNGEONS_DONE_JABU_JABUS_BELLY);
break;
case SCENE_MORIBOSSROOM:
Flags_SetRandomizerInf(RAND_INF_DUNGEONS_DONE_FOREST_TEMPLE);
break;
case SCENE_FIRE_BS:
Flags_SetRandomizerInf(RAND_INF_DUNGEONS_DONE_FIRE_TEMPLE);
break;
case SCENE_MIZUSIN_BS:
Flags_SetRandomizerInf(RAND_INF_DUNGEONS_DONE_WATER_TEMPLE);
break;
case SCENE_JYASINBOSS:
Flags_SetRandomizerInf(RAND_INF_DUNGEONS_DONE_SPIRIT_TEMPLE);
break;
case SCENE_HAKADAN_BS:
Flags_SetRandomizerInf(RAND_INF_DUNGEONS_DONE_SHADOW_TEMPLE);
break;
default:
break;
}
// Fully heal the player after Ganondorf
if (gSaveContext.bossRushOptions[BR_OPTIONS_HEAL] == BR_CHOICE_HEAL_EVERYBOSS &&
play->sceneNum == SCENE_GANON_BOSS) {
Health_ChangeBy(play, 320);
}
if ((CheckDungeonCount() == 3 && gSaveContext.bossRushOptions[BR_OPTIONS_BOSSES] == BR_CHOICE_BOSSES_CHILD) ||
play->sceneNum == SCENE_GANON_DEMO) {
gSaveContext.sohStats.playTimer += 2;
gSaveContext.sohStats.gameComplete = 1;
gSaveContext.sohStats.itemTimestamp[TIMESTAMP_BOSSRUSH_FINISH] = GAMEPLAYSTAT_TOTAL_TIME;
}
}
void BossRush_InitSave() {
// Set player name to Lonk for the few textboxes that show up during Boss Rush. Player can't input their own name.
std::array<char, 8> brPlayerName = { 21, 50, 49, 46, 62, 62, 62, 62 };
for (int i = 0; i < ARRAY_COUNT(gSaveContext.playerName); i++) {
gSaveContext.playerName[i] = brPlayerName[i];
}
gSaveContext.isBossRushPaused = 1;
gSaveContext.entranceIndex = 107;
gSaveContext.cutsceneIndex = 0x8000;
gSaveContext.isMagicAcquired = 1;
// Set magic
if (gSaveContext.bossRushOptions[BR_OPTIONS_MAGIC] == BR_CHOICE_MAGIC_SINGLE) {
gSaveContext.magicLevel = 1;
gSaveContext.magic = 48;
} else {
gSaveContext.isDoubleMagicAcquired = 1;
gSaveContext.magicLevel = 2;
gSaveContext.magic = 96;
}
// Set health
uint16_t health = 16;
switch (gSaveContext.bossRushOptions[BR_OPTIONS_HEARTS]) {
case BR_CHOICE_HEARTS_7:
health *= 7;
break;
case BR_CHOICE_HEARTS_10:
health *= 10;
break;
case BR_CHOICE_HEARTS_15:
health *= 15;
break;
case BR_CHOICE_HEARTS_20:
health *= 20;
break;
case BR_CHOICE_HEARTS_3:
health *= 3;
break;
case BR_CHOICE_HEARTS_5:
health *= 5;
break;
default:
break;
}
gSaveContext.healthCapacity = health;
gSaveContext.health = health;
// Skip boss cutscenes
gSaveContext.eventChkInf[7] |= 1; // gohma
gSaveContext.eventChkInf[7] |= 2; // dodongo
gSaveContext.eventChkInf[7] |= 4; // phantom ganon
gSaveContext.eventChkInf[7] |= 8; // volvagia
gSaveContext.eventChkInf[7] |= 0x10; // morpha
gSaveContext.eventChkInf[7] |= 0x20; // twinrova
gSaveContext.eventChkInf[7] |= 0x40; // barinade
gSaveContext.eventChkInf[7] |= 0x80; // bongo bongo
// Sets all rando flags to false
for (s32 i = 0; i < ARRAY_COUNT(gSaveContext.randomizerInf); i++) {
gSaveContext.randomizerInf[i] = 0;
}
// Set items
std::array<u8, 24> brItems = {
ITEM_STICK, ITEM_NUT, ITEM_BOMB, ITEM_BOW, ITEM_NONE, ITEM_NONE,
ITEM_SLINGSHOT, ITEM_NONE, ITEM_NONE, ITEM_HOOKSHOT, ITEM_NONE, ITEM_NONE,
ITEM_BOOMERANG, ITEM_LENS, ITEM_NONE, ITEM_HAMMER, ITEM_ARROW_LIGHT, ITEM_NONE,
ITEM_NONE, ITEM_NONE, ITEM_NONE, ITEM_NONE, ITEM_NONE, ITEM_NONE,
};
if (gSaveContext.bossRushOptions[BR_OPTIONS_LONGSHOT] == BR_CHOICE_LONGSHOT_YES) {
brItems[9] = ITEM_LONGSHOT;
}
switch (gSaveContext.bossRushOptions[BR_OPTIONS_BOTTLE]) {
case BR_CHOICE_BOTTLE_EMPTY:
brItems[18] = ITEM_BOTTLE;
break;
case BR_CHOICE_BOTTLE_FAIRY:
brItems[18] = ITEM_FAIRY;
break;
case BR_CHOICE_BOTTLE_REDPOTION:
brItems[18] = ITEM_POTION_RED;
break;
case BR_CHOICE_BOTTLE_GREENPOTION:
brItems[18] = ITEM_POTION_GREEN;
break;
case BR_CHOICE_BOTTLE_BLUEPOTION:
brItems[18] = ITEM_POTION_BLUE;
break;
default:
break;
}
if (gSaveContext.bossRushOptions[BR_OPTIONS_BUNNYHOOD] == BR_CHOICE_BUNNYHOOD_YES) {
brItems[23] = ITEM_MASK_BUNNY;
}
for (int item = 0; item < ARRAY_COUNT(gSaveContext.inventory.items); item++) {
gSaveContext.inventory.items[item] = brItems[item];
}
// Set consumable counts
std::array<s8, 16> brAmmo = { 5, 5, 10, 10, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
if (gSaveContext.bossRushOptions[BR_OPTIONS_AMMO] == BR_CHOICE_AMMO_FULL) {
brAmmo = { 10, 20, 20, 30, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
} else if (gSaveContext.bossRushOptions[BR_OPTIONS_AMMO] == BR_CHOICE_AMMO_MAXED) {
brAmmo = { 30, 40, 40, 50, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
}
for (int ammo = 0; ammo < ARRAY_COUNT(gSaveContext.inventory.ammo); ammo++) {
gSaveContext.inventory.ammo[ammo] = brAmmo[ammo];
}
// Equipment
gSaveContext.inventory.equipment |= 1 << 0; // Kokiri Sword
gSaveContext.inventory.equipment |= 1 << 1; // Master Sword
gSaveContext.inventory.equipment |= 1 << 4; // Deku Shield
gSaveContext.inventory.equipment |= 1 << 6; // Mirror Shield
gSaveContext.inventory.equipment |= 1 << 9; // Goron Tunic
if (gSaveContext.bossRushOptions[BR_OPTIONS_BGS] == BR_CHOICE_BGS_YES) {
gSaveContext.inventory.equipment |= 1 << 2; // Biggoron Sword
gSaveContext.bgsFlag = 1;
}
if (gSaveContext.bossRushOptions[BR_OPTIONS_HOVERBOOTS] == BR_CHOICE_HOVERBOOTS_YES) {
gSaveContext.inventory.equipment |= 1 << 14; // Hover Boots
}
// Upgrades
uint8_t upgradeLevel = 1;
if (gSaveContext.bossRushOptions[BR_OPTIONS_AMMO] == BR_CHOICE_AMMO_MAXED) {
upgradeLevel = 3;
}
Inventory_ChangeUpgrade(UPG_QUIVER, upgradeLevel);
Inventory_ChangeUpgrade(UPG_BOMB_BAG, upgradeLevel);
Inventory_ChangeUpgrade(UPG_BULLET_BAG, upgradeLevel);
Inventory_ChangeUpgrade(UPG_STICKS, upgradeLevel);
Inventory_ChangeUpgrade(UPG_NUTS, upgradeLevel);
// Set flags and Link's age based on chosen settings.
if (gSaveContext.bossRushOptions[BR_OPTIONS_BOSSES] == BR_CHOICE_BOSSES_ADULT ||
gSaveContext.bossRushOptions[BR_OPTIONS_BOSSES] == BR_CHOICE_BOSSES_GANONDORF_GANON) {
Flags_SetRandomizerInf(RAND_INF_DUNGEONS_DONE_DEKU_TREE);
Flags_SetRandomizerInf(RAND_INF_DUNGEONS_DONE_DODONGOS_CAVERN);
Flags_SetRandomizerInf(RAND_INF_DUNGEONS_DONE_JABU_JABUS_BELLY);
if (gSaveContext.bossRushOptions[BR_OPTIONS_BOSSES] == BR_CHOICE_BOSSES_GANONDORF_GANON) {
Flags_SetRandomizerInf(RAND_INF_DUNGEONS_DONE_FOREST_TEMPLE);
Flags_SetRandomizerInf(RAND_INF_DUNGEONS_DONE_FIRE_TEMPLE);
Flags_SetRandomizerInf(RAND_INF_DUNGEONS_DONE_WATER_TEMPLE);
Flags_SetRandomizerInf(RAND_INF_DUNGEONS_DONE_SPIRIT_TEMPLE);
Flags_SetRandomizerInf(RAND_INF_DUNGEONS_DONE_SHADOW_TEMPLE);
}
gSaveContext.linkAge = LINK_AGE_ADULT;
BossRush_SetEquipment(LINK_AGE_ADULT);
} else {
gSaveContext.linkAge = LINK_AGE_CHILD;
BossRush_SetEquipment(LINK_AGE_CHILD);
}
}
void BossRush_SetEquipment(uint8_t linkAge) {
std::array<u8, 8> brButtonItems;
std::array<u8, 7> brCButtonSlots;
// Set Child Equipment.
if (linkAge == LINK_AGE_CHILD) {
brButtonItems = {
ITEM_SWORD_KOKIRI, ITEM_STICK, ITEM_NUT, ITEM_BOMB, ITEM_NONE, ITEM_NONE, ITEM_NONE, ITEM_NONE
};
brCButtonSlots = { SLOT_STICK, SLOT_NUT, SLOT_BOMB, SLOT_NONE, SLOT_NONE, SLOT_NONE, SLOT_NONE };
Inventory_ChangeEquipment(EQUIP_SWORD, PLAYER_SWORD_KOKIRI);
Inventory_ChangeEquipment(EQUIP_SHIELD, PLAYER_SHIELD_DEKU);
// Set Adult equipment.
} else {
brButtonItems = { ITEM_SWORD_MASTER, ITEM_BOW, ITEM_HAMMER, ITEM_BOMB,
ITEM_NONE, ITEM_NONE, ITEM_NONE, ITEM_NONE };
brCButtonSlots = { SLOT_BOW, SLOT_HAMMER, SLOT_BOMB, SLOT_NONE, SLOT_NONE, SLOT_NONE, SLOT_NONE };
Inventory_ChangeEquipment(EQUIP_SWORD, PLAYER_SWORD_MASTER);
Inventory_ChangeEquipment(EQUIP_SHIELD, PLAYER_SHIELD_MIRROR);
Inventory_ChangeEquipment(EQUIP_TUNIC, PLAYER_TUNIC_GORON + 1); // Game expects tunic + 1, don't ask me why.
}
// Button Items
for (int button = 0; button < ARRAY_COUNT(gSaveContext.equips.buttonItems); button++) {
gSaveContext.equips.buttonItems[button] = brButtonItems[button];
}
// C buttons
for (int button = 0; button < ARRAY_COUNT(gSaveContext.equips.cButtonSlots); button++) {
gSaveContext.equips.cButtonSlots[button] = brCButtonSlots[button];
}
}

View File

@ -0,0 +1,20 @@
#pragma once
#include "BossRushTypes.h"
#include "variables.h"
#ifdef __cplusplus
extern "C" {
#endif
void BossRush_SpawnBlueWarps(PlayState* play);
void BossRush_HandleBlueWarp(PlayState* play, f32 warpPosX, f32 warpPosZ);
void BossRush_HandleBlueWarpHeal(PlayState* play);
void BossRush_InitSave();
void BossRush_SetEquipment(uint8_t linkAge);
void BossRush_HandleCompleteBoss(PlayState* play);
const char* BossRush_GetSettingName(uint8_t optionIndex, uint8_t language);
const char* BossRush_GetSettingChoiceName(uint8_t optionIndex, uint8_t choiceIndex, uint8_t language);
uint8_t BossRush_GetSettingOptionsAmount(uint8_t optionIndex);
#ifdef __cplusplus
};
#endif

View File

@ -0,0 +1,91 @@
#pragma once
#define BOSSRUSH_OPTIONS_AMOUNT 12
#define BOSSRUSH_MAX_OPTIONS_ON_SCREEN 6
typedef enum {
BR_OPTIONS_BOSSES,
BR_OPTIONS_HEARTS,
BR_OPTIONS_AMMO,
BR_OPTIONS_HEAL,
BR_OPTIONS_HYPERBOSSES,
BR_OPTIONS_MAGIC,
BR_OPTIONS_BGS,
BR_OPTIONS_BOTTLE,
BR_OPTIONS_LONGSHOT,
BR_OPTIONS_HOVERBOOTS,
BR_OPTIONS_BUNNYHOOD,
BR_OPTIONS_TIMER
} BossRushOptionEnums;
typedef enum {
BR_CHOICE_BOSSES_ALL,
BR_CHOICE_BOSSES_CHILD,
BR_CHOICE_BOSSES_ADULT,
BR_CHOICE_BOSSES_GANONDORF_GANON
} BossRushBossesChoices;
typedef enum {
BR_CHOICE_HEARTS_10,
BR_CHOICE_HEARTS_15,
BR_CHOICE_HEARTS_20,
BR_CHOICE_HEARTS_3,
BR_CHOICE_HEARTS_5,
BR_CHOICE_HEARTS_7
} BossRushHeartsChoices;
typedef enum {
BR_CHOICE_AMMO_LIMITED,
BR_CHOICE_AMMO_FULL,
BR_CHOICE_AMMO_MAXED
} BossRushAmmoChoices;
typedef enum {
BR_CHOICE_HEAL_GANONDORF,
BR_CHOICE_HEAL_EVERYBOSS,
BR_CHOICE_HEAL_NEVER
} BossRushHealChoices;
typedef enum {
BR_CHOICE_HYPERBOSSES_NO,
BR_CHOICE_HYPERBOSSES_YES
} BossRushHyperBossesChoices;
typedef enum {
BR_CHOICE_MAGIC_SINGLE,
BR_CHOICE_MAGIC_DOUBLE
} BossRushMagicChoices;
typedef enum {
BR_CHOICE_BGS_NO,
BR_CHOICE_BGS_YES
} BossRushBgsChoices;
typedef enum {
BR_CHOICE_BOTTLE_NO,
BR_CHOICE_BOTTLE_EMPTY,
BR_CHOICE_BOTTLE_FAIRY,
BR_CHOICE_BOTTLE_REDPOTION,
BR_CHOICE_BOTTLE_GREENPOTION,
BR_CHOICE_BOTTLE_BLUEPOTION
} BossRushBottleChoices;
typedef enum {
BR_CHOICE_LONGSHOT_NO,
BR_CHOICE_LONGSHOT_YES
} BossRushLongshotChoices;
typedef enum {
BR_CHOICE_HOVERBOOTS_NO,
BR_CHOICE_HOVERBOOTS_YES
} BossRushHoverBootsChoices;
typedef enum {
BR_CHOICE_BUNNYHOOD_NO,
BR_CHOICE_BUNNYHOOD_YES
} BossRushBunnyHoodChoices;
typedef enum {
BR_CHOICE_TIMER_YES,
BR_CHOICE_TIMER_NO
} BossRushTimerChoices;

View File

@ -0,0 +1,9 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
const char* Interface_ReplaceSpecialCharacters(char text[]);
#ifdef __cplusplus
};
#endif

View File

@ -1,6 +1,9 @@
#include "CustomMessageManager.h"
#include "CustomMessageInterfaceAddon.h"
#include <algorithm>
#include <stdint.h>
#include <cstring>
#include <string>
using namespace std::literals::string_literals;
@ -148,6 +151,24 @@ void CustomMessage::ReplaceSpecialCharacters() {
}
}
const char* Interface_ReplaceSpecialCharacters(char text[]) {
std::string textString(text);
for (auto specialCharacterPair : textBoxSpecialCharacters) {
size_t start_pos = 0;
std::string textBoxSpecialCharacterString = ""s;
textBoxSpecialCharacterString += specialCharacterPair.second;
while ((start_pos = textString.find(specialCharacterPair.first, start_pos)) != std::string::npos) {
textString.replace(start_pos, specialCharacterPair.first.length(), textBoxSpecialCharacterString);
start_pos += textBoxSpecialCharacterString.length();
}
}
char* textChar = new char[textString.length() + 1];
strcpy(textChar, textString.c_str());
return textChar;
}
void CustomMessage::ReplaceColors() {
for (std::string* str : { &english, &french, &german }) {
for (auto colorPair : colors) {

View File

@ -206,4 +206,4 @@ class MessageNotFoundException : public std::exception {
sprintf(message, "Message from table %s with textId %u was not found", messageTableId.c_str(), textId);
return message;
}
};
};

View File

@ -1087,7 +1087,7 @@ void DrawFlagsTab() {
for (int i = 0; i < flagTables.size(); i++) {
const FlagTable& flagTable = flagTables[i];
if (flagTable.flagTableType == RANDOMIZER_INF && !gSaveContext.n64ddFlag) {
if (flagTable.flagTableType == RANDOMIZER_INF && !gSaveContext.n64ddFlag && !gSaveContext.isBossRush) {
continue;
}

View File

@ -798,6 +798,7 @@ void SetupDisplayNames() {
strcpy(itemTimestampDisplayName[TIMESTAMP_DEFEAT_TWINROVA], "Twinrova Defeated: ");
strcpy(itemTimestampDisplayName[TIMESTAMP_DEFEAT_GANONDORF], "Ganondorf Defeated: ");
strcpy(itemTimestampDisplayName[TIMESTAMP_DEFEAT_GANON], "Ganon Defeated: ");
strcpy(itemTimestampDisplayName[TIMESTAMP_BOSSRUSH_FINISH], "Boss Rush Finished: ");
strcpy(itemTimestampDisplayName[TIMESTAMP_FOUND_GREG], "Greg Found: ");
}
@ -808,39 +809,50 @@ void SetupDisplayColors() {
case ITEM_KOKIRI_EMERALD:
case ITEM_SONG_SARIA:
case ITEM_MEDALLION_FOREST:
case TIMESTAMP_DEFEAT_GOHMA:
case TIMESTAMP_DEFEAT_PHANTOM_GANON:
case TIMESTAMP_FOUND_GREG:
itemTimestampDisplayColor[i] = COLOR_GREEN;
break;
case ITEM_SONG_BOLERO:
case ITEM_GORON_RUBY:
case ITEM_MEDALLION_FIRE:
case TIMESTAMP_DEFEAT_KING_DODONGO:
case TIMESTAMP_DEFEAT_VOLVAGIA:
itemTimestampDisplayColor[i] = COLOR_RED;
break;
case ITEM_SONG_SERENADE:
case ITEM_ZORA_SAPPHIRE:
case ITEM_MEDALLION_WATER:
case TIMESTAMP_DEFEAT_BARINADE:
case TIMESTAMP_DEFEAT_MORPHA:
itemTimestampDisplayColor[i] = COLOR_BLUE;
break;
case ITEM_SONG_LULLABY:
case ITEM_SONG_NOCTURNE:
case ITEM_MEDALLION_SHADOW:
case TIMESTAMP_DEFEAT_BONGO_BONGO:
itemTimestampDisplayColor[i] = COLOR_PURPLE;
break;
case ITEM_SONG_EPONA:
case ITEM_SONG_REQUIEM:
case ITEM_MEDALLION_SPIRIT:
case TIMESTAMP_DEFEAT_TWINROVA:
itemTimestampDisplayColor[i] = COLOR_ORANGE;
break;
case ITEM_SONG_SUN:
case ITEM_SONG_PRELUDE:
case ITEM_MEDALLION_LIGHT:
case ITEM_ARROW_LIGHT:
case TIMESTAMP_DEFEAT_GANONDORF:
case TIMESTAMP_DEFEAT_GANON:
itemTimestampDisplayColor[i] = COLOR_YELLOW;
break;
case ITEM_SONG_STORMS:
itemTimestampDisplayColor[i] = COLOR_GREY;
break;
case ITEM_SONG_TIME:
case TIMESTAMP_BOSSRUSH_FINISH:
itemTimestampDisplayColor[i] = COLOR_LIGHT_BLUE;
break;
default:

View File

@ -33,6 +33,7 @@ typedef enum {
/* 0xA7 */ TIMESTAMP_DEFEAT_TWINROVA, // z_boss_tw.c
/* 0xA8 */ TIMESTAMP_DEFEAT_GANONDORF, // z_boss_ganon.c
/* 0xA9 */ TIMESTAMP_DEFEAT_GANON, // z_boss_ganon2.c
/* 0xA9 */ TIMESTAMP_BOSSRUSH_FINISH, // z_boss_ganon2.c
/* 0xAA */ TIMESTAMP_FOUND_GREG, // z_parameter.c
/* 0xAB */ TIMESTAMP_MAX

View File

@ -2,6 +2,7 @@
#include <libultraship/bridge.h>
#include "game-interactor/GameInteractor.h"
#include "tts/tts.h"
#include "soh/Enhancements/boss-rush/BossRushTypes.h"
extern "C" {
#include <z64.h>
@ -440,8 +441,13 @@ void RegisterHyperBosses() {
actor->id == ACTOR_BOSS_GANON || // Ganondorf
actor->id == ACTOR_BOSS_GANON2; // Ganon
uint8_t hyperBossesActive =
CVarGetInteger("gHyperBosses", 0) ||
(gSaveContext.isBossRush &&
gSaveContext.bossRushOptions[BR_OPTIONS_HYPERBOSSES] == BR_CHOICE_HYPERBOSSES_YES);
// Don't apply during cutscenes because it causes weird behaviour and/or crashes on some bosses.
if (CVarGetInteger("gHyperBosses", 0) && isBossActor && !Player_InBlockingCsMode(gPlayState, player)) {
if (hyperBossesActive && isBossActor && !Player_InBlockingCsMode(gPlayState, player)) {
// Barinade needs to be updated in sequence to avoid unintended behaviour.
if (actor->id == ACTOR_BOSS_VA) {
// params -1 is BOSSVA_BODY

View File

@ -88,7 +88,6 @@ void Ctx_ReadSaveFile(uintptr_t addr, void* dramAddr, size_t size);
void Ctx_WriteSaveFile(uintptr_t addr, void* dramAddr, size_t size);
uint64_t GetPerfCounter();
uint64_t GetUnixTimestamp();
struct SkeletonHeader* ResourceMgr_LoadSkeletonByName(const char* path, SkelAnime* skelAnime);
void ResourceMgr_UnregisterSkeleton(SkelAnime* skelAnime);
void ResourceMgr_ClearSkeletons();
@ -149,4 +148,12 @@ void SaveManager_ThreadPoolWait();
int32_t GetGIID(uint32_t itemID);
#endif
#ifdef __cplusplus
extern "C" {
#endif
uint64_t GetUnixTimestamp();
#ifdef __cplusplus
};
#endif
#endif

View File

@ -7,6 +7,7 @@
#include "macros.h"
#include <variables.h>
#include <Hooks.h>
#include "soh/Enhancements/boss-rush/BossRush.h"
#include <libultraship/libultraship.h>
#define NOGDI // avoid various windows defines that conflict with things in z64.h
@ -583,6 +584,10 @@ void SaveManager::InitFileNormal() {
gSaveContext.pendingSale = ITEM_NONE;
gSaveContext.pendingSaleMod = MOD_NONE;
if (gSaveContext.isBossRush) {
BossRush_InitSave();
}
//RANDOTODO (ADD ITEMLOCATIONS TO GSAVECONTEXT)
}
@ -748,7 +753,8 @@ void SaveManager::SaveFileThreaded(int fileNum, SaveContext* saveContext, int se
// SaveSection creates a copy of gSaveContext to prevent mid-save data modification, and passes its reference to SaveFileThreaded
void SaveManager::SaveSection(int fileNum, int sectionID, bool threaded) {
if (fileNum == 0xFF) {
// Don't save in Boss rush.
if (fileNum == 0xFF || fileNum == 0xFE) {
return;
}
// Don't save a nonexistent section

View File

@ -76,6 +76,10 @@ void OTRPlay_InitScene(PlayState* play, s32 spawn) {
gSaveContext.worldMapArea = 0;
OTRScene_ExecuteCommands(play, (LUS::Scene*)play->sceneSegment);
Play_InitEnvironment(play, play->skyboxId);
// Unpause the timer for Boss Rush when the scene loaded isn't the Chamber of Sages.
if (gSaveContext.isBossRush && play->sceneNum != SCENE_KENJYANOMA) {
gSaveContext.isBossRushPaused = 0;
}
/* auto data = static_cast<LUS::Vertex*>(LUS::Context::GetInstance()
->GetResourceManager()
->LoadResource("object_link_child\\object_link_childVtx_01FE08")

View File

@ -56,7 +56,8 @@ void KaleidoScopeCall_Update(PlayState* play) {
KaleidoMgrOverlay* kaleidoScopeOvl = &gKaleidoMgrOverlayTable[KALEIDO_OVL_KALEIDO_SCOPE];
PauseContext* pauseCtx = &play->pauseCtx;
if (!gSaveContext.sohStats.gameComplete) {
if (!gSaveContext.sohStats.gameComplete &&
(!gSaveContext.isBossRush || !gSaveContext.isBossRushPaused)) {
gSaveContext.sohStats.pauseTimer++;
}

View File

@ -177,6 +177,10 @@ void Font_LoadChar(Font* font, u8 character, u16 codePointIndex) {
memcpy(&font->charTexBuf[codePointIndex], fntTbl[character], strlen(fntTbl[character]) + 1);
}
void* Font_FetchCharTexture(u8 character) {
return fntTbl[character];
}
/**
* Loads a message box icon from message_static, such as the ending triangle/square or choice arrow into the
* icon buffer.

View File

@ -709,6 +709,10 @@ f32 sFontWidths[144] = {
14.0f, // ?
};
f32 Message_GetCharacterWidth(unsigned char characterIndex) {
return sFontWidths[characterIndex] * (R_TEXT_CHAR_SCALE / 100.0f);
}
u16 Message_DrawItemIcon(PlayState* play, u16 itemId, Gfx** p, u16 i) {
s32 pad;
Gfx* gfx = *p;

View File

@ -8,6 +8,8 @@
#include "soh/Enhancements/randomizer/randomizer_entrance.h"
#include "libultraship/bridge.h"
#include "soh/Enhancements/gameplaystats.h"
#include "soh/Enhancements/boss-rush/BossRushTypes.h"
#include "soh/Enhancements/custom-message/CustomMessageInterfaceAddon.h"
#ifdef _MSC_VER
#include <stdlib.h>
@ -882,7 +884,8 @@ void func_80083108(PlayState* play) {
Interface_ChangeAlpha(12);
}
}
} else if (play->sceneNum == SCENE_KENJYANOMA) {
// Don't hide the HUD in the Chamber of Sages when in Boss Rush.
} else if (play->sceneNum == SCENE_KENJYANOMA && !gSaveContext.isBossRush) {
Interface_ChangeAlpha(1);
} else if (play->sceneNum == SCENE_TURIBORI) {
gSaveContext.unk_13E7 = 2;
@ -4942,7 +4945,10 @@ void Interface_Draw(PlayState* play) {
PosX_RC = PosX_RC_ori;
}
gDPSetPrimColor(OVERLAY_DISP++, 0, 0, rColor.r, rColor.g, rColor.b, interfaceCtx->magicAlpha);
OVERLAY_DISP = Gfx_TextureIA8(OVERLAY_DISP, gRupeeCounterIconTex, 16, 16, PosX_RC, PosY_RC, 16, 16, 1 << 10, 1 << 10);
// Draw Rupee icon. Hide in Boss Rush.
if (!gSaveContext.isBossRush) {
OVERLAY_DISP = Gfx_TextureIA8(OVERLAY_DISP, gRupeeCounterIconTex, 16, 16, PosX_RC, PosY_RC, 16, 16, 1 << 10, 1 << 10);
}
switch (play->sceneNum) {
case SCENE_BMORI1:
@ -5057,10 +5063,12 @@ void Interface_Draw(PlayState* play) {
svar2 = rupeeDigitsFirst[CUR_UPG_VALUE(UPG_WALLET)];
svar5 = rupeeDigitsCount[CUR_UPG_VALUE(UPG_WALLET)];
for (svar1 = 0, svar3 = 16; svar1 < svar5; svar1++, svar2++, svar3 += 8) {
OVERLAY_DISP =
Gfx_TextureI8(OVERLAY_DISP, ((u8*)digitTextures[interfaceCtx->counterDigits[svar2]]), 8, 16,
PosX_RC+svar3, PosY_RC, 8, 16, 1 << 10, 1 << 10);
// Draw Rupee Counter. Hide in Boss Rush.
if (!gSaveContext.isBossRush) {
for (svar1 = 0, svar3 = 16; svar1 < svar5; svar1++, svar2++, svar3 += 8) {
OVERLAY_DISP = Gfx_TextureI8(OVERLAY_DISP, ((u8*)digitTextures[interfaceCtx->counterDigits[svar2]]),
8, 16, PosX_RC + svar3, PosY_RC, 8, 16, 1 << 10, 1 << 10);
}
}
}
else {
@ -5983,7 +5991,8 @@ void Interface_Draw(PlayState* play) {
void Interface_DrawTotalGameplayTimer(PlayState* play) {
// Draw timer based on the Gameplay Stats total time.
if (CVarGetInteger("gGameplayStats.ShowIngameTimer", 0) && gSaveContext.fileNum >= 0 && gSaveContext.fileNum <= 2) {
if ((gSaveContext.isBossRush && gSaveContext.bossRushOptions[BR_OPTIONS_TIMER] == BR_CHOICE_TIMER_YES) ||
(CVarGetInteger("gGameplayStats.ShowIngameTimer", 0) && gSaveContext.fileNum >= 0 && gSaveContext.fileNum <= 2)) {
s32 X_Margins_Timer = 0;
if (CVarGetInteger("gIGTUseMargins", 0) != 0) {
@ -6065,6 +6074,8 @@ void Interface_DrawTotalGameplayTimer(PlayState* play) {
// Draw regular text. Change color based on if the timer is paused, running or the game is completed.
if (gSaveContext.sohStats.gameComplete) {
gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 120, 255, 0, 255);
} else if (gSaveContext.isBossRushPaused) {
gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 150, 150, 150, 255);
} else {
gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 255, 255, 255, 255);
}
@ -6483,3 +6494,66 @@ void Interface_Update(PlayState* play) {
}
}
}
void Interface_DrawTextCharacter(GraphicsContext* gfx, int16_t x, int16_t y, void* texture, uint16_t colorR,
uint16_t colorG, uint16_t colorB, uint16_t colorA, float textScale, uint8_t textShadow) {
int32_t scale = R_TEXT_CHAR_SCALE * textScale;
int32_t sCharTexSize = (scale / 100.0f) * 16.0f;
int32_t sCharTexScale = 1024.0f / (scale / 100.0f);
OPEN_DISPS(gfx);
gDPPipeSync(POLY_OPA_DISP++);
gDPLoadTextureBlock_4b(POLY_OPA_DISP++, texture, G_IM_FMT_I, FONT_CHAR_TEX_WIDTH, FONT_CHAR_TEX_HEIGHT, 0,
G_TX_NOMIRROR | G_TX_CLAMP, G_TX_NOMIRROR | G_TX_CLAMP, G_TX_NOMASK, G_TX_NOMASK, G_TX_NOLOD,
G_TX_NOLOD);
if (textShadow) {
// Draw drop shadow
gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, 0, 0, 0, colorA);
gSPTextureRectangle(POLY_OPA_DISP++, (x + R_TEXT_DROP_SHADOW_OFFSET) << 2, (y + R_TEXT_DROP_SHADOW_OFFSET) << 2,
(x + R_TEXT_DROP_SHADOW_OFFSET + sCharTexSize) << 2,
(y + R_TEXT_DROP_SHADOW_OFFSET + sCharTexSize) << 2, G_TX_RENDERTILE, 0, 0, sCharTexScale,
sCharTexScale);
}
gDPPipeSync(POLY_OPA_DISP++);
// Draw normal text
gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, colorR, colorG, colorB, colorA);
gSPTextureRectangle(POLY_OPA_DISP++, x << 2, y << 2, (x + sCharTexSize) << 2, (y + sCharTexSize) << 2,
G_TX_RENDERTILE, 0, 0, sCharTexScale, sCharTexScale);
CLOSE_DISPS(gfx);
}
uint16_t Interface_DrawTextLine(GraphicsContext* gfx, char text[], int16_t x, int16_t y, uint16_t colorR,
uint16_t colorG, uint16_t colorB, uint16_t colorA, float textScale, uint8_t textShadow) {
uint16_t textureIndex;
uint16_t kerningOffset = 0;
uint16_t lineOffset = 0;
void* texture;
const char* processedText = Interface_ReplaceSpecialCharacters(text);
uint8_t textLength = strlen(processedText);
for (uint16_t i = 0; i < textLength; i++) {
if (processedText[i] == '\n') {
lineOffset += 15 * textScale;
kerningOffset = 0;
} else {
textureIndex = processedText[i] - 32;
if (textureIndex != 0) {
texture = Font_FetchCharTexture(textureIndex);
Interface_DrawTextCharacter(gfx, x + kerningOffset, y + lineOffset, texture, colorR, colorG, colorB,
colorA, textScale, textShadow);
}
kerningOffset += (uint16_t)(Message_GetCharacterWidth(textureIndex) * textScale);
}
}
return kerningOffset;
}

View File

@ -1170,7 +1170,8 @@ void Play_Update(PlayState* play) {
play->gameplayFrames++;
// Gameplay stat tracking
if (!gSaveContext.sohStats.gameComplete) {
if (!gSaveContext.sohStats.gameComplete &&
(!gSaveContext.isBossRush || (gSaveContext.isBossRush && !gSaveContext.isBossRushPaused))) {
gSaveContext.sohStats.playTimer++;
gSaveContext.sohStats.sceneTimer++;
gSaveContext.sohStats.roomTimer++;

View File

@ -29,6 +29,10 @@ void Sram_InitDebugSave(void) {
Save_InitFile(true);
}
void Sram_InitBossRushSave(void) {
Save_InitFile(false);
}
/**
* Copy save currently on the buffer to Save Context and complete various tasks to open the save.
* This includes:

View File

@ -274,7 +274,8 @@ void BgBreakwall_Wait(BgBreakwall* this, PlayState* play) {
}
}
if (this->collider.base.acFlags & 2 || blueFireArrowHit) {
// Break the floor immediately in Boss Rush so the player can jump in the hole immediately.
if (this->collider.base.acFlags & 2 || blueFireArrowHit || gSaveContext.isBossRush) {
Vec3f effectPos;
s32 wallType = ((this->dyna.actor.params >> 13) & 3) & 0xFF;

View File

@ -4,6 +4,7 @@
#include "overlays/actors/ovl_Door_Warp1/z_door_warp1.h"
#include "scenes/dungeons/ddan_boss/ddan_boss_room_1.h"
#include "soh/frame_interpolation.h"
#include "soh/Enhancements/boss-rush/BossRush.h"
#define FLAGS (ACTOR_FLAG_TARGETABLE | ACTOR_FLAG_HOSTILE | ACTOR_FLAG_UPDATE_WHILE_CULLED | ACTOR_FLAG_DRAW_WHILE_CULLED)
@ -1346,6 +1347,7 @@ void BossDodongo_DeathCutscene(BossDodongo* this, PlayState* play) {
this->cameraAt.y = camera->at.y;
this->cameraAt.z = camera->at.z;
gSaveContext.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_KING_DODONGO] = GAMEPLAYSTAT_TOTAL_TIME;
BossRush_HandleCompleteBoss(play);
break;
case 5:
tempSin = Math_SinS(this->actor.shape.rot.y - 0x1388) * 150.0f;
@ -1630,10 +1632,12 @@ void BossDodongo_DeathCutscene(BossDodongo* this, PlayState* play) {
if (this->unk_1DA == 820) {
Audio_QueueSeqCmd(SEQ_PLAYER_BGM_MAIN << 24 | NA_BGM_BOSS_CLEAR);
Actor_Spawn(&play->actorCtx, play, ACTOR_ITEM_B_HEART,
Math_SinS(this->actor.shape.rot.y) * -50.0f + this->actor.world.pos.x,
this->actor.world.pos.y,
Math_CosS(this->actor.shape.rot.y) * -50.0f + this->actor.world.pos.z, 0, 0, 0, 0, true);
if (!gSaveContext.isBossRush) {
Actor_Spawn(
&play->actorCtx, play, ACTOR_ITEM_B_HEART,
Math_SinS(this->actor.shape.rot.y) * -50.0f + this->actor.world.pos.x, this->actor.world.pos.y,
Math_CosS(this->actor.shape.rot.y) * -50.0f + this->actor.world.pos.z, 0, 0, 0, 0, true);
}
}
if (this->unk_1DA == 600) {
camera = Play_GetCamera(play, MAIN_CAM);
@ -1647,8 +1651,11 @@ void BossDodongo_DeathCutscene(BossDodongo* this, PlayState* play) {
Play_ChangeCameraStatus(play, MAIN_CAM, CAM_STAT_ACTIVE);
func_80064534(play, &play->csCtx);
func_8002DF54(play, &this->actor, 7);
Actor_SpawnAsChild(&play->actorCtx, &this->actor, play, ACTOR_DOOR_WARP1, -890.0f, -1523.76f,
-3304.0f, 0, 0, 0, WARP_DUNGEON_CHILD);
if (!gSaveContext.isBossRush) {
Actor_SpawnAsChild(&play->actorCtx, &this->actor, play, ACTOR_DOOR_WARP1, -890.0f, -1523.76f, -3304.0f, 0, 0, 0, WARP_DUNGEON_CHILD);
} else {
Actor_Spawn(&play->actorCtx, play, ACTOR_DOOR_WARP1, -890.0f, -1523.76f, -3304.0f, 0, 0, 0, WARP_DUNGEON_ADULT, false);
}
this->skelAnime.playSpeed = 0.0f;
Flags_SetClear(play, play->roomCtx.curRoom.num);
}

View File

@ -913,7 +913,7 @@ void BossFd_Fly(BossFd* this, PlayState* play) {
this->actionFunc = BossFd_Wait;
this->actor.world.pos.y -= 1000.0f;
}
if (this->timers[0] == 7) {
if (this->timers[0] == 7 && !gSaveContext.isBossRush) {
Actor_Spawn(&play->actorCtx, play, ACTOR_ITEM_B_HEART, this->actor.world.pos.x,
this->actor.world.pos.y, this->actor.world.pos.z, 0, 0, 0, 0, true);
}

View File

@ -10,6 +10,7 @@
#include "overlays/actors/ovl_Door_Warp1/z_door_warp1.h"
#include "vt.h"
#include "soh/frame_interpolation.h"
#include "soh/Enhancements/boss-rush/BossRush.h"
#define FLAGS (ACTOR_FLAG_TARGETABLE | ACTOR_FLAG_HOSTILE | ACTOR_FLAG_UPDATE_WHILE_CULLED | ACTOR_FLAG_DRAW_WHILE_CULLED)
@ -788,8 +789,13 @@ void BossFd2_Death(BossFd2* this, PlayState* play) {
this->deathCamera = 0;
func_80064534(play, &play->csCtx);
func_8002DF54(play, &this->actor, 7);
Actor_SpawnAsChild(&play->actorCtx, &this->actor, play, ACTOR_DOOR_WARP1, 0.0f, 100.0f, 0.0f,
0, 0, 0, WARP_DUNGEON_ADULT);
if (!gSaveContext.isBossRush) {
Actor_SpawnAsChild(&play->actorCtx, &this->actor, play, ACTOR_DOOR_WARP1, 0.0f, 100.0f, 0.0f, 0, 0,
0, WARP_DUNGEON_ADULT);
} else {
Actor_Spawn(&play->actorCtx, play, ACTOR_DOOR_WARP1, 0.0f, 100.0f, 0.0f, 0, 0, 0,
WARP_DUNGEON_ADULT, true);
}
Flags_SetClear(play, play->roomCtx.curRoom.num);
}
break;
@ -894,6 +900,7 @@ void BossFd2_CollisionCheck(BossFd2* this, PlayState* play) {
Audio_PlayActorSound2(&this->actor, NA_SE_EN_VALVAISA_DEAD);
Enemy_StartFinishingBlow(play, &this->actor);
gSaveContext.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_VOLVAGIA] = GAMEPLAYSTAT_TOTAL_TIME;
BossRush_HandleCompleteBoss(play);
} else if (damage) {
BossFd2_SetupDamaged(this, play);
this->work[FD2_DAMAGE_FLASH_TIMER] = 10;

View File

@ -11,6 +11,7 @@
#include "assets/scenes/dungeons/ganon_boss/ganon_boss_scene.h"
#include "soh/frame_interpolation.h"
#include "soh/Enhancements/boss-rush/BossRush.h"
#include <string.h>
@ -569,7 +570,7 @@ void BossGanon_IntroCutscene(BossGanon* this, PlayState* play) {
Play_ChangeCameraStatus(play, this->csCamIndex, CAM_STAT_ACTIVE);
this->csCamFov = 60.0f;
if (gSaveContext.eventChkInf[7] & 0x100 || gSaveContext.n64ddFlag) {
if (gSaveContext.eventChkInf[7] & 0x100 || gSaveContext.n64ddFlag || gSaveContext.isBossRush) {
// watched cutscene already, skip most of it
this->csState = 17;
this->csTimer = 0;
@ -580,7 +581,9 @@ void BossGanon_IntroCutscene(BossGanon* this, PlayState* play) {
BossGanon_SetIntroCsCamera(this, 11);
this->unk_198 = 2;
this->timers[2] = 110;
gSaveContext.healthAccumulator = 0x140;
if (!(gSaveContext.isBossRush && gSaveContext.bossRushOptions[BR_OPTIONS_HEAL] == BR_CHOICE_HEAL_NEVER)) {
gSaveContext.healthAccumulator = 0x140;
}
Audio_QueueSeqCmd(NA_BGM_STOP);
} else {
this->useOpenHand = true;
@ -901,7 +904,7 @@ void BossGanon_IntroCutscene(BossGanon* this, PlayState* play) {
this->csTimer = 0;
this->csCamFov = 60.0f;
BossGanon_SetIntroCsCamera(this, 12);
if (!gSaveContext.n64ddFlag) {
if (!gSaveContext.n64ddFlag && !gSaveContext.isBossRush) {
Message_StartTextbox(play, 0x70CB, NULL);
}
}
@ -925,7 +928,9 @@ void BossGanon_IntroCutscene(BossGanon* this, PlayState* play) {
this->csState = 19;
this->csTimer = 0;
Message_StartTextbox(play, 0x70CC, NULL);
if (!gSaveContext.isBossRush) {
Message_StartTextbox(play, 0x70CC, NULL);
}
Animation_MorphToPlayOnce(&this->skelAnime, &gGanondorfRaiseHandStartAnim, -5.0f);
this->triforceType = GDF_TRIFORCE_DORF;
this->fwork[GDF_TRIFORCE_SCALE] = 10.0f;
@ -967,7 +972,7 @@ void BossGanon_IntroCutscene(BossGanon* this, PlayState* play) {
if ((this->csTimer > 80) && (Message_GetState(&play->msgCtx) == TEXT_STATE_NONE)) {
// In rando, skip past dark waves section straight to title card phase of the cutscene.
if (gSaveContext.n64ddFlag) {
if (gSaveContext.n64ddFlag || gSaveContext.isBossRush) {
this->timers[2] = 30;
this->csCamAt.x = this->unk_1FC.x - 10.0f;
this->csCamAt.y = this->unk_1FC.y + 30.0f;
@ -1274,19 +1279,16 @@ void BossGanon_DeathAndTowerCutscene(BossGanon* this, PlayState* play) {
this->actor.shape.yOffset = -7000.0f;
this->actor.shape.rot.y = 0;
// In rando, skip Ganondorf dying and go straight to next scene.
// Commented out for potential future use.
// Skip Ganondorf dying and go straight to next scene.
// The cutscene skip met a mixed reaction, so until we figure out a better way of doing it,
// it will stay not-skipped.
/*if (!gSaveContext.n64ddFlag) {
// it will stay not-skipped outside of Boss Rush (originally implemented for randomizer).
if (!gSaveContext.isBossRush) {
this->csState = 1;
this->csTimer = 0;
} else {
this->csState = 9;
this->csTimer = 170;
}*/
this->csState = 1;
this->csTimer = 0;
}
this->useOpenHand = true;
// fallthrough
case 1:
@ -1537,7 +1539,7 @@ void BossGanon_DeathAndTowerCutscene(BossGanon* this, PlayState* play) {
if (this->csTimer == 180) {
play->sceneLoadFlag = 0x14;
if (gSaveContext.n64ddFlag && Randomizer_GetSettingValue(RSK_SKIP_TOWER_ESCAPE)) {
if ((gSaveContext.n64ddFlag && Randomizer_GetSettingValue(RSK_SKIP_TOWER_ESCAPE) || gSaveContext.isBossRush)) {
Flags_SetEventChkInf(0xC7);
play->nextEntranceIndex = 0x517;
}
@ -1560,7 +1562,7 @@ void BossGanon_DeathAndTowerCutscene(BossGanon* this, PlayState* play) {
sBossGanonZelda = (EnZl3*)Actor_SpawnAsChild(&play->actorCtx, &this->actor, play, ACTOR_EN_ZL3, 0.0f,
6000.0f, 0.0f, 0, 0, 0, 0x2000);
if (!gSaveContext.n64ddFlag) {
if (!gSaveContext.n64ddFlag && !gSaveContext.isBossRush) {
this->csState = 101;
} else {
this->skelAnime.playSpeed = 1.0f;
@ -1688,7 +1690,7 @@ void BossGanon_DeathAndTowerCutscene(BossGanon* this, PlayState* play) {
// fallthrough
case 104:
// In rando, fade out the white here as the earlier part is skipped.
if (gSaveContext.n64ddFlag) {
if (gSaveContext.n64ddFlag || gSaveContext.isBossRush) {
Math_ApproachZeroF(&this->whiteFillAlpha, 1.0f, 10.0f);
}
@ -1710,7 +1712,7 @@ void BossGanon_DeathAndTowerCutscene(BossGanon* this, PlayState* play) {
if (this->csTimer == 50) {
// In rando, skip the rest of the cutscene after the crystal around Zelda dissapears.
if (!gSaveContext.n64ddFlag) {
if (!gSaveContext.n64ddFlag && !gSaveContext.isBossRush) {
sBossGanonZelda->unk_3C8 = 4;
} else {
this->csState = 108;
@ -2811,6 +2813,7 @@ void BossGanon_UpdateDamage(BossGanon* this, PlayState* play) {
Audio_QueueSeqCmd(0x100100FF);
this->screenFlashTimer = 4;
gSaveContext.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_GANONDORF] = GAMEPLAYSTAT_TOTAL_TIME;
BossRush_HandleCompleteBoss(play);
} else {
Audio_PlayActorSound2(&this->actor, NA_SE_EN_GANON_DAMAGE2);
Audio_PlayActorSound2(&this->actor, NA_SE_EN_GANON_CUTBODY);

View File

@ -7,6 +7,7 @@
#include "objects/object_ganon_anime3/object_ganon_anime3.h"
#include "objects/object_geff/object_geff.h"
#include "soh/frame_interpolation.h"
#include "soh/Enhancements/boss-rush/BossRush.h"
#include <string.h>
@ -233,7 +234,7 @@ void func_808FD5F4(BossGanon2* this, PlayState* play) {
sBossGanon2Zelda->actor.shape.rot.y = -0x7000;
// In rando, skip past the cutscene to the part where the player takes control again.
if (!gSaveContext.n64ddFlag) {
if (!gSaveContext.n64ddFlag && !gSaveContext.isBossRush) {
this->csState = 1;
this->csTimer = 0;
} else {
@ -1681,6 +1682,7 @@ void func_8090120C(BossGanon2* this, PlayState* play) {
(player->swordState != 0) && (player->heldItemAction == PLAYER_IA_SWORD_MASTER)) {
func_80064520(play, &play->csCtx);
gSaveContext.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_GANON] = GAMEPLAYSTAT_TOTAL_TIME;
BossRush_HandleCompleteBoss(play);
gSaveContext.sohStats.gameComplete = true;
this->unk_39E = Play_CreateSubCamera(play);
Play_ChangeCameraStatus(play, MAIN_CAM, CAM_STAT_WAIT);

View File

@ -11,6 +11,7 @@
#include "overlays/effects/ovl_Effect_Ss_Fhg_Flash/z_eff_ss_fhg_flash.h"
#include "overlays/effects/ovl_Effect_Ss_Hahen/z_eff_ss_hahen.h"
#include "overlays/actors/ovl_Door_Warp1/z_door_warp1.h"
#include "soh/Enhancements/boss-rush/BossRush.h"
#define FLAGS (ACTOR_FLAG_TARGETABLE | ACTOR_FLAG_HOSTILE | ACTOR_FLAG_UPDATE_WHILE_CULLED | ACTOR_FLAG_DRAW_WHILE_CULLED)
@ -958,12 +959,12 @@ void BossGanondrof_Death(BossGanondrof* this, PlayState* play) {
case DEATH_THROES:
switch (this->work[GND_ACTION_STATE]) {
case DEATH_SPASM:
if (Animation_OnFrame(&this->skelAnime, this->fwork[GND_END_FRAME]) && !gSaveContext.n64ddFlag) {
if (Animation_OnFrame(&this->skelAnime, this->fwork[GND_END_FRAME]) && !gSaveContext.n64ddFlag && !gSaveContext.isBossRush) {
this->fwork[GND_END_FRAME] = Animation_GetLastFrame(&gPhantomGanonAirDamageAnim);
Animation_Change(&this->skelAnime, &gPhantomGanonAirDamageAnim, 0.5f, 0.0f,
this->fwork[GND_END_FRAME], ANIMMODE_ONCE_INTERP, 0.0f);
this->work[GND_ACTION_STATE] = DEATH_LIMP;
} else if (gSaveContext.n64ddFlag) {
} else if (gSaveContext.n64ddFlag || gSaveContext.isBossRush) {
// Skip to death scream animation and move ganondrof to middle
this->deathState = DEATH_SCREAM;
this->timers[0] = 50;
@ -990,7 +991,7 @@ void BossGanondrof_Death(BossGanondrof* this, PlayState* play) {
bodyDecayLevel = 1;
break;
}
if (gSaveContext.n64ddFlag) {
if (gSaveContext.n64ddFlag || gSaveContext.isBossRush) {
break;
}
Math_ApproachS(&this->actor.shape.rot.y, this->work[GND_VARIANCE_TIMER] * -100, 5, 0xBB8);
@ -1087,8 +1088,8 @@ void BossGanondrof_Death(BossGanondrof* this, PlayState* play) {
bodyDecayLevel = 10;
if (this->timers[0] == 150) {
Audio_QueueSeqCmd(SEQ_PLAYER_BGM_MAIN << 24 | NA_BGM_BOSS_CLEAR);
Actor_Spawn(&play->actorCtx, play, ACTOR_DOOR_WARP1, GND_BOSSROOM_CENTER_X,
GND_BOSSROOM_CENTER_Y, GND_BOSSROOM_CENTER_Z, 0, 0, 0, WARP_DUNGEON_ADULT, true);
Actor_Spawn(&play->actorCtx, play, ACTOR_DOOR_WARP1, GND_BOSSROOM_CENTER_X, GND_BOSSROOM_CENTER_Y,
GND_BOSSROOM_CENTER_Z, 0, 0, 0, WARP_DUNGEON_ADULT, true);
}
Math_ApproachZeroF(&this->cameraEye.y, 0.05f, 1.0f); // GND_BOSSROOM_CENTER_Y + 33.0f
@ -1104,8 +1105,10 @@ void BossGanondrof_Death(BossGanondrof* this, PlayState* play) {
this->deathCamera = 0;
func_80064534(play, &play->csCtx);
func_8002DF54(play, &this->actor, 7);
Actor_Spawn(&play->actorCtx, play, ACTOR_ITEM_B_HEART, GND_BOSSROOM_CENTER_X,
GND_BOSSROOM_CENTER_Y, GND_BOSSROOM_CENTER_Z + 200.0f, 0, 0, 0, 0, true);
if (!gSaveContext.isBossRush) {
Actor_Spawn(&play->actorCtx, play, ACTOR_ITEM_B_HEART, GND_BOSSROOM_CENTER_X, GND_BOSSROOM_CENTER_Y,
GND_BOSSROOM_CENTER_Z + 200.0f, 0, 0, 0, 0, true);
}
this->actor.child = &horse->actor;
this->killActor = true;
horse->killActor = true;
@ -1246,6 +1249,7 @@ void BossGanondrof_CollisionCheck(BossGanondrof* this, PlayState* play) {
BossGanondrof_SetupDeath(this, play);
Enemy_StartFinishingBlow(play, &this->actor);
gSaveContext.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_PHANTOM_GANON] = GAMEPLAYSTAT_TOTAL_TIME;
BossRush_HandleCompleteBoss(play);
return;
}
}

View File

@ -4,6 +4,7 @@
#include "overlays/actors/ovl_En_Goma/z_en_goma.h"
#include "overlays/actors/ovl_Door_Shutter/z_door_shutter.h"
#include "overlays/actors/ovl_Door_Warp1/z_door_warp1.h"
#include "soh/Enhancements/boss-rush/BossRush.h"
#define FLAGS (ACTOR_FLAG_TARGETABLE | ACTOR_FLAG_HOSTILE | ACTOR_FLAG_UPDATE_WHILE_CULLED | ACTOR_FLAG_DRAW_WHILE_CULLED)
@ -1117,8 +1118,10 @@ void BossGoma_Defeated(BossGoma* this, PlayState* play) {
this->timer = 70;
this->decayingProgress = 0;
this->subCameraFollowSpeed = 0.0f;
Actor_Spawn(&play->actorCtx, play, ACTOR_ITEM_B_HEART, this->actor.world.pos.x,
this->actor.world.pos.y, this->actor.world.pos.z, 0, 0, 0, 0, true);
if (!gSaveContext.isBossRush) {
Actor_Spawn(&play->actorCtx, play, ACTOR_ITEM_B_HEART, this->actor.world.pos.x,
this->actor.world.pos.y, this->actor.world.pos.z, 0, 0, 0, 0, true);
}
}
break;
@ -1149,8 +1152,13 @@ void BossGoma_Defeated(BossGoma* this, PlayState* play) {
}
}
Actor_SpawnAsChild(&play->actorCtx, &this->actor, play, ACTOR_DOOR_WARP1, childPos.x,
this->actor.world.pos.y, childPos.z, 0, 0, 0, WARP_DUNGEON_CHILD);
if (!gSaveContext.isBossRush) {
Actor_SpawnAsChild(&play->actorCtx, &this->actor, play, ACTOR_DOOR_WARP1, childPos.x,
this->actor.world.pos.y, childPos.z, 0, 0, 0, WARP_DUNGEON_CHILD);
} else {
Actor_Spawn(&play->actorCtx, play, ACTOR_DOOR_WARP1, childPos.x, this->actor.world.pos.y,
childPos.z, 0, 0, 0, WARP_DUNGEON_ADULT, false);
}
Flags_SetClear(play, play->roomCtx.curRoom.num);
}
@ -1834,6 +1842,7 @@ void BossGoma_UpdateHit(BossGoma* this, PlayState* play) {
BossGoma_SetupDefeated(this, play);
Enemy_StartFinishingBlow(play, &this->actor);
gSaveContext.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_GOHMA] = GAMEPLAYSTAT_TOTAL_TIME;
BossRush_HandleCompleteBoss(play);
}
this->invincibilityFrames = 10;

View File

@ -12,6 +12,7 @@
#include "vt.h"
#include "soh/frame_interpolation.h"
#include "soh/Enhancements/boss-rush/BossRush.h"
#include <string.h>
@ -1115,11 +1116,16 @@ void BossMo_Tentacle(BossMo* this, PlayState* play) {
BossMo_SpawnDroplet(MO_FX_DROPLET, (BossMoEffect*)play->specialEffects, &spD4, &spE0,
((300 - indS1) * .0015f) + 0.13f);
}
Actor_SpawnAsChild(&play->actorCtx, &this->actor, play, ACTOR_DOOR_WARP1,
this->actor.world.pos.x, -280.0f, this->actor.world.pos.z, 0, 0, 0,
WARP_DUNGEON_ADULT);
Actor_Spawn(&play->actorCtx, play, ACTOR_ITEM_B_HEART, this->actor.world.pos.x + 200.0f,
-280.0f, this->actor.world.pos.z, 0, 0, 0, 0, true);
if (!gSaveContext.isBossRush) {
Actor_SpawnAsChild(&play->actorCtx, &this->actor, play, ACTOR_DOOR_WARP1,
this->actor.world.pos.x, -280.0f, this->actor.world.pos.z, 0, 0, 0,
WARP_DUNGEON_ADULT);
Actor_Spawn(&play->actorCtx, play, ACTOR_ITEM_B_HEART, this->actor.world.pos.x + 200.0f,
-280.0f, this->actor.world.pos.z, 0, 0, 0, 0, true);
} else {
Actor_Spawn(&play->actorCtx, play, ACTOR_DOOR_WARP1, this->actor.world.pos.x, -280.0f,
this->actor.world.pos.z, 0, 0, 0, WARP_DUNGEON_ADULT, true);
}
Audio_QueueSeqCmd(SEQ_PLAYER_BGM_MAIN << 24 | NA_BGM_BOSS_CLEAR);
Flags_SetClear(play, play->roomCtx.curRoom.num);
}
@ -1788,6 +1794,7 @@ void BossMo_CoreCollisionCheck(BossMo* this, PlayState* play) {
((sMorphaTent1->csCamera == 0) && (sMorphaTent2 != NULL) && (sMorphaTent2->csCamera == 0))) {
Enemy_StartFinishingBlow(play, &this->actor);
gSaveContext.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_MORPHA] = GAMEPLAYSTAT_TOTAL_TIME;
BossRush_HandleCompleteBoss(play);
Audio_QueueSeqCmd(0x1 << 28 | SEQ_PLAYER_BGM_MAIN << 24 | 0x100FF);
this->csState = MO_DEATH_START;
sMorphaTent1->drawActor = false;

View File

@ -11,6 +11,7 @@
#include "overlays/actors/ovl_Bg_Sst_Floor/z_bg_sst_floor.h"
#include "overlays/actors/ovl_Door_Warp1/z_door_warp1.h"
#include "soh/frame_interpolation.h"
#include "soh/Enhancements/boss-rush/BossRush.h"
#define FLAGS (ACTOR_FLAG_TARGETABLE | ACTOR_FLAG_HOSTILE | ACTOR_FLAG_UPDATE_WHILE_CULLED | ACTOR_FLAG_DRAW_WHILE_CULLED | ACTOR_FLAG_DRAGGED_BY_HOOKSHOT)
@ -1201,11 +1202,13 @@ void BossSst_HeadFinish(BossSst* this, PlayState* play) {
Flags_SetClear(play, play->roomCtx.curRoom.num);
}
} else if (this->effects[0].alpha == 0) {
Actor_Spawn(&play->actorCtx, play, ACTOR_DOOR_WARP1, ROOM_CENTER_X, ROOM_CENTER_Y, ROOM_CENTER_Z, 0,
0, 0, WARP_DUNGEON_ADULT, true);
Actor_Spawn(&play->actorCtx, play, ACTOR_ITEM_B_HEART,
(Math_SinS(this->actor.shape.rot.y) * 200.0f) + ROOM_CENTER_X, ROOM_CENTER_Y,
Math_CosS(this->actor.shape.rot.y) * 200.0f + ROOM_CENTER_Z, 0, 0, 0, 0, true);
Actor_Spawn(&play->actorCtx, play, ACTOR_DOOR_WARP1, ROOM_CENTER_X, ROOM_CENTER_Y, ROOM_CENTER_Z, 0, 0, 0,
WARP_DUNGEON_ADULT, true);
if (!gSaveContext.isBossRush) {
Actor_Spawn(&play->actorCtx, play, ACTOR_ITEM_B_HEART,
(Math_SinS(this->actor.shape.rot.y) * 200.0f) + ROOM_CENTER_X, ROOM_CENTER_Y,
Math_CosS(this->actor.shape.rot.y) * 200.0f + ROOM_CENTER_Z, 0, 0, 0, 0, true);
}
BossSst_SetCameraTargets(1.0f, 7);
this->effectMode = BONGO_NULL;
} else if (this->timer == 0) {
@ -2561,6 +2564,7 @@ void BossSst_HeadCollisionCheck(BossSst* this, PlayState* play) {
Enemy_StartFinishingBlow(play, &this->actor);
BossSst_HeadSetupDeath(this, play);
gSaveContext.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_BONGO_BONGO] = GAMEPLAYSTAT_TOTAL_TIME;
BossRush_HandleCompleteBoss(play);
} else {
BossSst_HeadSetupDamage(this);
}

View File

@ -4,6 +4,7 @@
#include "objects/object_tw/object_tw.h"
#include "overlays/actors/ovl_Door_Warp1/z_door_warp1.h"
#include "soh/frame_interpolation.h"
#include "soh/Enhancements/boss-rush/BossRush.h"
#include <string.h>
@ -2364,7 +2365,7 @@ void BossTw_DeathCSMsgSfx(BossTw* this, PlayState* play) {
sp35 = 0;
// Skip ahead to last part of the cutscene in rando
if (this->work[CS_TIMER_2] == 10 && gSaveContext.n64ddFlag) {
if (this->work[CS_TIMER_2] == 10 && (gSaveContext.n64ddFlag || gSaveContext.isBossRush)) {
this->work[CS_TIMER_2] = 860;
}
@ -2549,7 +2550,7 @@ void BossTw_DeathCSMsgSfx(BossTw* this, PlayState* play) {
// Add seperate timings for the "beam" that opens and closes around the sisters
// Needed because we skip ahead in cutscene timer value so it never gets called otherwise
if (gSaveContext.n64ddFlag) {
if (gSaveContext.n64ddFlag || gSaveContext.isBossRush) {
if (this->work[CS_TIMER_2] < 900) {
Math_ApproachF(&this->workf[UNK_F18], 255.0f, 0.1f, 5.0f);
} else if (this->work[CS_TIMER_2] > 910) {
@ -2794,9 +2795,14 @@ void BossTw_TwinrovaDeathCS(BossTw* this, PlayState* play) {
func_80064534(play, &play->csCtx);
func_8002DF54(play, &this->actor, 7);
Audio_QueueSeqCmd(SEQ_PLAYER_BGM_MAIN << 24 | NA_BGM_BOSS_CLEAR);
Actor_SpawnAsChild(&play->actorCtx, &this->actor, play, ACTOR_DOOR_WARP1, 600.0f, 230.0f,
0.0f, 0, 0, 0, WARP_DUNGEON_ADULT);
Actor_Spawn(&play->actorCtx, play, ACTOR_ITEM_B_HEART, -600.0f, 230.f, 0.0f, 0, 0, 0, 0, true);
if (!gSaveContext.isBossRush) {
Actor_SpawnAsChild(&play->actorCtx, &this->actor, play, ACTOR_DOOR_WARP1, 600.0f, 230.0f, 0.0f, 0,
0, 0, WARP_DUNGEON_ADULT);
Actor_Spawn(&play->actorCtx, play, ACTOR_ITEM_B_HEART, -600.0f, 230.f, 0.0f, 0, 0, 0, 0, true);
} else {
Actor_Spawn(&play->actorCtx, play, ACTOR_DOOR_WARP1, 600.0f, 230.0f, 0.0f, 0, 0, 0,
WARP_DUNGEON_ADULT, true);
}
this->actor.world.pos.y = -2000.0f;
this->workf[UNK_F18] = 0.0f;
sKoumePtr->visible = sKotakePtr->visible = false;
@ -5287,6 +5293,7 @@ void BossTw_TwinrovaDamage(BossTw* this, PlayState* play, u8 damage) {
Enemy_StartFinishingBlow(play, &this->actor);
Audio_PlayActorSound2(&this->actor, NA_SE_EN_TWINROBA_YOUNG_DEAD);
gSaveContext.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_TWINROVA] = GAMEPLAYSTAT_TOTAL_TIME;
BossRush_HandleCompleteBoss(play);
return;
}

View File

@ -12,8 +12,10 @@
#include "objects/object_bv/object_bv.h"
#include "overlays/actors/ovl_En_Boom/z_en_boom.h"
#include "objects/gameplay_keep/gameplay_keep.h"
#include "overlays/actors/ovl_Door_Warp1/z_door_warp1.h"
#include "soh/frame_interpolation.h"
#include "soh/Enhancements/boss-rush/BossRush.h"
#define FLAGS (ACTOR_FLAG_TARGETABLE | ACTOR_FLAG_HOSTILE | ACTOR_FLAG_UPDATE_WHILE_CULLED | ACTOR_FLAG_DRAW_WHILE_CULLED)
@ -1396,6 +1398,7 @@ void BossVa_BodyPhase4(BossVa* this, PlayState* play) {
BossVa_SetupBodyDeath(this, play);
Enemy_StartFinishingBlow(play, &this->actor);
gSaveContext.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_BARINADE] = GAMEPLAYSTAT_TOTAL_TIME;
BossRush_HandleCompleteBoss(play);
return;
}
this->actor.speedXZ = -10.0f;
@ -1650,8 +1653,10 @@ void BossVa_BodyDeath(BossVa* this, PlayState* play) {
func_8002DF54(play, &this->actor, 7);
sCsState++;
Actor_Spawn(&play->actorCtx, play, ACTOR_ITEM_B_HEART, this->actor.world.pos.x,
this->actor.world.pos.y, this->actor.world.pos.z, 0, 0, 0, 0, true);
if (!gSaveContext.isBossRush) {
Actor_Spawn(&play->actorCtx, play, ACTOR_ITEM_B_HEART, this->actor.world.pos.x,
this->actor.world.pos.y, this->actor.world.pos.z, 0, 0, 0, 0, true);
}
for (i = 2, sp7C = 2; i > 0; i--) {
if (Math_Vec3f_DistXYZ(&sWarpPos[i], &player->actor.world.pos) <
@ -1660,8 +1665,13 @@ void BossVa_BodyDeath(BossVa* this, PlayState* play) {
}
}
Actor_Spawn(&play->actorCtx, play, ACTOR_EN_RU1, sWarpPos[sp7C].x, sWarpPos[sp7C].y,
sWarpPos[sp7C].z, 0, 0, 0, 0, true);
if (!gSaveContext.isBossRush) {
Actor_Spawn(&play->actorCtx, play, ACTOR_EN_RU1, sWarpPos[sp7C].x, sWarpPos[sp7C].y,
sWarpPos[sp7C].z, 0, 0, 0, 0, true);
} else {
Actor_Spawn(&play->actorCtx, play, ACTOR_DOOR_WARP1, sWarpPos[sp7C].x, sWarpPos[sp7C].y,
sWarpPos[sp7C].z, 0, 0, 0, WARP_DUNGEON_ADULT, false);
}
}
case DEATH_FINISH:
Rand_CenteredFloat(0.5f);

View File

@ -8,6 +8,7 @@
#include "overlays/actors/ovl_En_Elf/z_en_elf.h"
#include "overlays/actors/ovl_Door_Warp1/z_door_warp1.h"
#include "objects/object_sa/object_sa.h"
#include "soh/Enhancements/boss-rush/BossRush.h"
#include "vt.h"
@ -253,11 +254,19 @@ void func_8098E960(DemoSa* this, PlayState* play) {
if ((gSaveContext.chamberCutsceneNum == 0) && (gSaveContext.sceneSetupIndex < 4)) {
player = GET_PLAYER(play);
this->action = 1;
play->csCtx.segment = D_8099010C;
gSaveContext.cutsceneTrigger = 2;
Item_Give(play, ITEM_MEDALLION_FOREST);
player->actor.world.rot.y = player->actor.shape.rot.y = this->actor.world.rot.y + 0x8000;
if (!gSaveContext.isBossRush) {
this->action = 1;
play->csCtx.segment = D_8099010C;
gSaveContext.cutsceneTrigger = 2;
Item_Give(play, ITEM_MEDALLION_FOREST);
player->actor.world.rot.y = player->actor.shape.rot.y = this->actor.world.rot.y + 0x8000;
} else {
this->action = 1;
if (gSaveContext.linkAge == LINK_AGE_CHILD) {
player->actor.world.rot.y = player->actor.shape.rot.y = -5461 + 0x8000;
}
BossRush_SpawnBlueWarps(play);
}
}
}

View File

@ -1,6 +1,7 @@
#include "z_door_warp1.h"
#include "objects/object_warp1/object_warp1.h"
#include "soh/Enhancements/randomizer/randomizer_entrance.h"
#include "soh/Enhancements/boss-rush/BossRush.h"
#define FLAGS 0
@ -251,8 +252,13 @@ void DoorWarp1_SetupBlueCrystal(DoorWarp1* this, PlayState* play) {
-255;
}
play->envCtx.adjFogNear = -500;
this->warpTimer = 30;
if (!gSaveContext.isBossRush) {
play->envCtx.adjFogNear = -500;
this->warpTimer = 30;
} else {
play->envCtx.adjFogNear = 0;
this->warpTimer = 0;
}
this->unk_1B8 = 4000;
DoorWarp1_SetupAction(this, DoorWarp1_BlueCrystal);
}
@ -293,7 +299,11 @@ void DoorWarp1_SetPlayerPos(DoorWarp1* this, PlayState* play) {
player->actor.velocity.y = 0.0f;
player->actor.world.pos.x = this->actor.world.pos.x;
player->actor.world.pos.y = this->actor.world.pos.y + 55.0f;
if (!gSaveContext.isBossRush) {
player->actor.world.pos.y = this->actor.world.pos.y + 55.0f;
} else {
player->actor.world.pos.y = this->actor.world.pos.y;
}
player->actor.world.pos.z = this->actor.world.pos.z;
}
@ -313,9 +323,13 @@ void func_80999214(DoorWarp1* this, PlayState* play) {
Math_SmoothStepToF(&this->crystalAlpha, 255.0f, 0.2f, 5.0f, 0.1f);
darkness = (f32)(40 - this->warpTimer) / 40.0f;
darkness = CLAMP_MIN(darkness, 0);
if (!gSaveContext.isBossRush) {
darkness = (f32)(40 - this->warpTimer) / 40.0f;
darkness = CLAMP_MIN(darkness, 0);
} else {
darkness = 0.0f;
}
for (i = 0; i < 3; i++) {
play->envCtx.adjAmbientColor[i] = play->envCtx.adjFogColor[i] = play->envCtx.adjLight1Color[i] =
-255.0f * darkness;
@ -352,7 +366,9 @@ void func_80999348(DoorWarp1* this, PlayState* play) {
void DoorWarp1_FloatPlayer(DoorWarp1* this, PlayState* play) {
Player* player = GET_PLAYER(play);
player->actor.gravity = -0.1f;
if (!gSaveContext.isBossRush) {
player->actor.gravity = -0.1f;
}
}
void DoorWarp1_PurpleCrystal(DoorWarp1* this, PlayState* play) {
@ -744,6 +760,11 @@ void DoorWarp1_AdultWarpIdle(DoorWarp1* this, PlayState* play) {
Audio_PlayActorSound2(&this->actor, NA_SE_EV_WARP_HOLE - SFX_FLAG);
if (DoorWarp1_PlayerInRange(this, play)) {
// Heal player in Boss Rush
if (gSaveContext.isBossRush) {
BossRush_HandleBlueWarpHeal(play);
}
player = GET_PLAYER(play);
if (gSaveContext.n64ddFlag) {
@ -805,7 +826,9 @@ void DoorWarp1_AdultWarpOut(DoorWarp1* this, PlayState* play) {
this->warpTimer++;
if (this->warpTimer > sWarpTimerTarget && gSaveContext.nextCutsceneIndex == 0xFFEF) {
if (play->sceneNum == SCENE_MORIBOSSROOM) {
if (gSaveContext.isBossRush) {
BossRush_HandleBlueWarp(play, this->actor.world.pos.x, this->actor.world.pos.z);
} else if (play->sceneNum == SCENE_MORIBOSSROOM) {
if (!(gSaveContext.eventChkInf[4] & 0x100)) {
gSaveContext.eventChkInf[4] |= 0x100;
Flags_SetRandomizerInf(RAND_INF_DUNGEONS_DONE_FOREST_TEMPLE);

View File

@ -202,6 +202,11 @@ void EnBox_Init(Actor* thisx, PlayState* play2) {
if (play->sceneNum == SCENE_BMORI1 && this->dyna.actor.params == 10222) {
this->movementFlags = ENBOX_MOVE_IMMOBILE;
}
// Delete chests in Boss Rush. Mainly for the chest in King Dodongo's boss room.
if (gSaveContext.isBossRush) {
EnBox_SetupAction(this, EnBox_Destroy);
}
}
void EnBox_Destroy(Actor* thisx, PlayState* play) {

View File

@ -277,6 +277,11 @@ void EnKusa_Destroy(Actor* thisx, PlayState* play2) {
}
void EnKusa_SetupWaitObject(EnKusa* this) {
// Kill bushes in Boss Rush. Used in Gohma's arena.
if (gSaveContext.isBossRush) {
Actor_Kill(this);
}
EnKusa_SetupAction(this, EnKusa_WaitObject);
}

View File

@ -221,6 +221,11 @@ void ObjTsubo_WaterBreak(ObjTsubo* this, PlayState* play) {
}
void ObjTsubo_SetupWaitForObject(ObjTsubo* this) {
// Remove pots in Boss Rush. Present in Barinade's and Ganondorf's arenas.
if (gSaveContext.isBossRush) {
Actor_Kill(this);
}
this->actionFunc = ObjTsubo_WaitForObject;
}

View File

@ -4437,7 +4437,8 @@ s32 func_80839800(Player* this, PlayState* play) {
if ((this->doorType != PLAYER_DOORTYPE_NONE) &&
(!(this->stateFlags1 & PLAYER_STATE1_ITEM_OVER_HEAD) ||
((this->heldActor != NULL) && (this->heldActor->id == ACTOR_EN_RU1)))) {
if (CHECK_BTN_ALL(sControlInput->press.button, BTN_A) || (func_8084F9A0 == this->func_674)) {
// Disable doors in Boss Rush so the player can't leave the boss rooms backwards.
if ((CHECK_BTN_ALL(sControlInput->press.button, BTN_A) || (func_8084F9A0 == this->func_674)) && !gSaveContext.isBossRush) {
doorActor = this->doorActor;
if (this->doorType <= PLAYER_DOORTYPE_AJAR) {

View File

@ -61,6 +61,10 @@ typedef enum {
CM_START_QUEST_MENU,
CM_QUEST_TO_MAIN,
CM_NAME_ENTRY_TO_QUEST_MENU,
CM_ROTATE_TO_BOSS_RUSH_MENU,
CM_BOSS_RUSH_MENU,
CM_START_BOSS_RUSH_MENU,
CM_BOSS_RUSH_TO_QUEST,
} ConfigMode;
typedef enum {
@ -168,6 +172,13 @@ typedef enum {
/* 99 */ FS_KBD_BTN_NONE = 99
} KeyboardButton;
typedef enum {
/* 00 */ FS_QUEST_NORMAL,
/* 01 */ FS_QUEST_MASTER,
/* 02 */ FS_QUEST_RANDOMIZER,
/* 03 */ FS_QUEST_BOSSRUSH,
} FileSelectQuest;
void FileChoose_SetupCopySource(GameState* thisx);
void FileChoose_SelectCopySource(GameState* thisx);
void FileChoose_SetupCopyDest1(GameState* thisx);

View File

@ -1,4 +1,4 @@
#include "file_choose.h"
#include "file_choose.h"
#include <string.h>
@ -11,28 +11,20 @@
#include "objects/gameplay_keep/gameplay_keep.h"
#include "soh_assets.h"
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/Enhancements/boss-rush/BossRush.h"
#include "soh/Enhancements/custom-message/CustomMessageTypes.h"
#define NORMAL_QUEST 0
#define MASTER_QUEST 1
#define RANDOMIZER_QUEST 2
#define MIN_QUEST (ResourceMgr_GameHasOriginal() ? NORMAL_QUEST : MASTER_QUEST)
u8 getMaxQuest() {
if ((strnlen(CVarGetString("gSpoilerLog", ""), 1) != 0)) {
return RANDOMIZER_QUEST;
#define MIN_QUEST (ResourceMgr_GameHasOriginal() ? FS_QUEST_NORMAL : FS_QUEST_MASTER)
#define MAX_QUEST FS_QUEST_BOSSRUSH
u8 hasRandomizerQuest() {
if (strnlen(CVarGetString("gSpoilerLog", ""), 1) != 0) {
return 1;
}
if (ResourceMgr_GameHasMasterQuest()) {
return MASTER_QUEST;
}
return NORMAL_QUEST;
return 0;
}
#define MAX_QUEST getMaxQuest()
void FileChoose_DrawTextureI8(GraphicsContext* gfxCtx, const void* texture, s16 texWidth, s16 texHeight, s16 rectLeft, s16 rectTop,
s16 rectWidth, s16 rectHeight, u16 dsdx, u16 dtdy) {
s16 rectWidth, s16 rectHeight, s16 dsdx, s16 dtdy) {
OPEN_DISPS(gfxCtx);
gDPLoadTextureBlock(POLY_OPA_DISP++, texture, G_IM_FMT_I, G_IM_SIZ_8b, texWidth, texHeight, 0, G_TX_NOMIRROR | G_TX_WRAP,
G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, G_TX_NOMASK, G_TX_NOLOD, G_TX_NOLOD);
@ -305,7 +297,7 @@ void DrawSeedHashSprites(FileChooseContext* this) {
// Draw icons on the main menu, when a rando file is selected, and when quest selection is set to rando
if ((this->configMode == CM_MAIN_MENU &&
(this->selectMode != SM_CONFIRM_FILE || Save_GetSaveMetaInfo(this->selectedFileIndex)->randoSave == 1)) ||
(this->configMode == CM_QUEST_MENU && this->questType[this->buttonIndex] == RANDOMIZER_QUEST)) {
(this->configMode == CM_QUEST_MENU && this->questType[this->buttonIndex] == FS_QUEST_RANDOMIZER)) {
if (this->fileInfoAlpha[this->selectedFileIndex] > 0) {
// Use file info alpha to match fading
@ -415,24 +407,7 @@ void FileChoose_UpdateMainMenu(GameState* thisx) {
if (!Save_GetSaveMetaInfo(this->buttonIndex)->valid) {
Audio_PlaySoundGeneral(NA_SE_SY_FSEL_DECIDE_L, &D_801333D4, 4, &D_801333E0, &D_801333E0, &D_801333E8);
this->prevConfigMode = this->configMode;
if (MIN_QUEST != MAX_QUEST) {
this->configMode = CM_ROTATE_TO_QUEST_MENU;
} else {
this->configMode = CM_ROTATE_TO_NAME_ENTRY;
gSaveContext.isMasterQuest = MIN_QUEST == MASTER_QUEST;
this->questType[this->buttonIndex] = MIN_QUEST;
CVarSetInteger("gOnFileSelectNameEntry", 1);
this->kbdButton = FS_KBD_BTN_NONE;
this->charPage = FS_CHAR_PAGE_ENG;
this->kbdX = 0;
this->kbdY = 0;
this->charIndex = 0;
this->charBgAlpha = 0;
this->newFileNameCharCount = CVarGetInteger("gLinkDefaultName", 0) ? 4 : 0;
this->nameEntryBoxPosX = 120;
this->nameEntryBoxAlpha = 0;
memcpy(Save_GetSaveMetaInfo(this->buttonIndex)->playerName, CVarGetInteger("gLinkDefaultName", 0) ? &linkName : &emptyName, 8);
}
this->configMode = CM_ROTATE_TO_QUEST_MENU;
this->logoAlpha = 0;
} else if(!FileChoose_IsSaveCompatible(Save_GetSaveMetaInfo(this->buttonIndex))) {
Audio_PlaySoundGeneral(NA_SE_SY_FSEL_ERROR, &D_801333D4, 4, &D_801333E0, &D_801333E0, &D_801333E8);
@ -588,6 +563,19 @@ void FileChoose_StartQuestMenu(GameState* thisx) {
}
}
void FileChoose_StartBossRushMenu(GameState* thisx) {
FileChooseContext* this = (FileChooseContext*)thisx;
this->logoAlpha -= 25;
this->bossRushUIAlpha = 0;
this->bossRushArrowOffset = 0;
if (this->logoAlpha >= 0) {
this->logoAlpha = 0;
this->configMode = CM_BOSS_RUSH_MENU;
}
}
void FileChoose_UpdateQuestMenu(GameState* thisx) {
static u8 emptyName[] = { 0x3E, 0x3E, 0x3E, 0x3E, 0x3E, 0x3E, 0x3E, 0x3E };
static u8 linkName[] = { 0x15, 0x2C, 0x31, 0x2E, 0x3E, 0x3E, 0x3E, 0x3E };
@ -595,31 +583,30 @@ void FileChoose_UpdateQuestMenu(GameState* thisx) {
FileChooseContext* this = (FileChooseContext*)thisx;
Input* input = &this->state.input[0];
s8 i = 0;
bool dpad = CVarGetInteger("gDpadText", 0);(dpad && CHECK_BTN_ANY(input->press.button, BTN_DDOWN | BTN_DUP));
bool dpad = CVarGetInteger("gDpadText", 0);
FileChoose_UpdateRandomizer();
if (ABS(this->stickRelX) > 30 || (dpad && CHECK_BTN_ANY(input->press.button, BTN_DLEFT | BTN_DRIGHT))) {
if (this->stickRelX > 30 || (dpad && CHECK_BTN_ANY(input->press.button, BTN_DRIGHT))) {
this->questType[this->buttonIndex] += 1;
if (this->questType[this->buttonIndex] == MASTER_QUEST && !ResourceMgr_GameHasMasterQuest()) {
// the only case not handled by the MIN/MAX_QUEST logic below. This will either put it at
// above MAX_QUEST in which case it will wrap back around, or it will put it on MAX_QUEST
// in which case if MAX_QUEST even is that number it will be a valid selection that won't
// crash.
while ((this->questType[this->buttonIndex] == FS_QUEST_MASTER && !ResourceMgr_GameHasMasterQuest()) ||
(this->questType[this->buttonIndex] == FS_QUEST_RANDOMIZER && !hasRandomizerQuest())) {
// If Master Quest is selected without a Master Quest OTR present or when Randomizer Quest is
// selected without a loaded Randomizer seed, skip past it.
this->questType[this->buttonIndex] += 1;
}
} else if (this->stickRelX < -30 || (dpad && CHECK_BTN_ANY(input->press.button, BTN_DLEFT))) {
this->questType[this->buttonIndex] -= 1;
if (this->questType[this->buttonIndex] == MASTER_QUEST && !ResourceMgr_GameHasMasterQuest()) {
// the only case not handled by the MIN/MAX_QUEST logic below. This will either put it at
// below MIN_QUEST in which case it will wrap back around, or it will put it on MIN_QUEST
// in which case if MIN_QUEST even is that number it will be a valid selection that won't
// crash.
while ((this->questType[this->buttonIndex] == FS_QUEST_MASTER && !ResourceMgr_GameHasMasterQuest()) ||
(this->questType[this->buttonIndex] == FS_QUEST_RANDOMIZER && !hasRandomizerQuest())) {
// If Master Quest is selected without a Master Quest OTR present or when Randomizer Quest is
// selected without a loaded Randomizer seed, skip past it.
this->questType[this->buttonIndex] -= 1;
}
}
// If current buttonIndex is higher or lower than the min/max value, wrap around.
if (this->questType[this->buttonIndex] > MAX_QUEST) {
this->questType[this->buttonIndex] = MIN_QUEST;
} else if (this->questType[this->buttonIndex] < MIN_QUEST) {
@ -630,25 +617,36 @@ void FileChoose_UpdateQuestMenu(GameState* thisx) {
}
if (CHECK_BTN_ALL(input->press.button, BTN_A)) {
Audio_PlaySoundGeneral(NA_SE_SY_FSEL_DECIDE_L, &D_801333D4, 4, &D_801333E0, &D_801333E0, &D_801333E8);
gSaveContext.isMasterQuest = this->questType[this->buttonIndex] == MASTER_QUEST;
gSaveContext.n64ddFlag = this->questType[this->buttonIndex] == RANDOMIZER_QUEST;
osSyncPrintf("Selected Dungeon Quest: %d\n", gSaveContext.isMasterQuest);
this->prevConfigMode = this->configMode;
this->configMode = CM_ROTATE_TO_NAME_ENTRY;
this->logoAlpha = 0;
CVarSetInteger("gOnFileSelectNameEntry", 1);
this->kbdButton = FS_KBD_BTN_NONE;
this->charPage = FS_CHAR_PAGE_ENG;
this->kbdX = 0;
this->kbdY = 0;
this->charIndex = 0;
this->charBgAlpha = 0;
this->newFileNameCharCount = CVarGetInteger("gLinkDefaultName", 0) ? 4 : 0;
this->nameEntryBoxPosX = 120;
this->nameEntryBoxAlpha = 0;
memcpy(Save_GetSaveMetaInfo(this->buttonIndex)->playerName, CVarGetInteger("gLinkDefaultName", 0) ? &linkName : &emptyName, 8);
return;
gSaveContext.isMasterQuest = this->questType[this->buttonIndex] == FS_QUEST_MASTER;
gSaveContext.n64ddFlag = this->questType[this->buttonIndex] == FS_QUEST_RANDOMIZER;
gSaveContext.isBossRush = this->questType[this->buttonIndex] == FS_QUEST_BOSSRUSH;
gSaveContext.isBossRushPaused = false;
if (this->questType[this->buttonIndex] == FS_QUEST_BOSSRUSH) {
Audio_PlaySoundGeneral(NA_SE_SY_FSEL_DECIDE_L, &D_801333D4, 4, &D_801333E0, &D_801333E0, &D_801333E8);
this->prevConfigMode = this->configMode;
this->configMode = CM_ROTATE_TO_BOSS_RUSH_MENU;
return;
} else {
Audio_PlaySoundGeneral(NA_SE_SY_FSEL_DECIDE_L, &D_801333D4, 4, &D_801333E0, &D_801333E0, &D_801333E8);
osSyncPrintf("Selected Dungeon Quest: %d\n", gSaveContext.isMasterQuest);
this->prevConfigMode = this->configMode;
this->configMode = CM_ROTATE_TO_NAME_ENTRY;
this->logoAlpha = 0;
CVarSetInteger("gOnFileSelectNameEntry", 1);
this->kbdButton = FS_KBD_BTN_NONE;
this->charPage = FS_CHAR_PAGE_ENG;
this->kbdX = 0;
this->kbdY = 0;
this->charIndex = 0;
this->charBgAlpha = 0;
this->newFileNameCharCount = CVarGetInteger("gLinkDefaultName", 0) ? 4 : 0;
this->nameEntryBoxPosX = 120;
this->nameEntryBoxAlpha = 0;
memcpy(Save_GetSaveMetaInfo(this->buttonIndex)->playerName, CVarGetInteger("gLinkDefaultName", 0) ? &linkName : &emptyName, 8);
return;
}
}
if (CHECK_BTN_ALL(input->press.button, BTN_B)) {
@ -657,6 +655,93 @@ void FileChoose_UpdateQuestMenu(GameState* thisx) {
}
}
void FileChoose_UpdateBossRushMenu(GameState* thisx) {
FileChoose_UpdateStickDirectionPromptAnim(thisx);
FileChooseContext* this = (FileChooseContext*)thisx;
Input* input = &this->state.input[0];
bool dpad = CVarGetInteger("gDpadText", 0);
// Fade in elements after opening Boss Rush options menu
this->bossRushUIAlpha += 25;
if (this->bossRushUIAlpha > 255) {
this->bossRushUIAlpha = 255;
}
// Animate up/down arrows.
this->bossRushArrowOffset += 1;
if (this->bossRushArrowOffset >= 30) {
this->bossRushArrowOffset = 0;
}
// Move menu selection up or down.
if (ABS(this->stickRelY) > 30 || (dpad && CHECK_BTN_ANY(input->press.button, BTN_DDOWN | BTN_DUP))) {
// Move down
if (this->stickRelY < -30 || (dpad && CHECK_BTN_ANY(input->press.button, BTN_DDOWN))) {
// When selecting past the last option, cycle back to the first option.
if ((this->bossRushIndex + 1) > BOSSRUSH_OPTIONS_AMOUNT - 1) {
this->bossRushIndex = 0;
this->bossRushOffset = 0;
} else {
this->bossRushIndex++;
// When last visible option is selected when moving down, offset the list down by one.
if (this->bossRushIndex - this->bossRushOffset > BOSSRUSH_MAX_OPTIONS_ON_SCREEN - 1) {
this->bossRushOffset++;
}
}
} else if (this->stickRelY > 30 || (dpad && CHECK_BTN_ANY(input->press.button, BTN_DUP))) {
// When selecting past the first option, cycle back to the last option and offset the list to view it properly.
if ((this->bossRushIndex - 1) < 0) {
this->bossRushIndex = BOSSRUSH_OPTIONS_AMOUNT - 1;
this->bossRushOffset = this->bossRushIndex - BOSSRUSH_MAX_OPTIONS_ON_SCREEN + 1;
} else {
// When first visible option is selected when moving up, offset the list up by one.
if (this->bossRushIndex - this->bossRushOffset == 0) {
this->bossRushOffset--;
}
this->bossRushIndex--;
}
}
Audio_PlaySoundGeneral(NA_SE_SY_FSEL_CURSOR, &D_801333D4, 4, &D_801333E0, &D_801333E0, &D_801333E8);
}
// Cycle through choices for currently selected option.
if (ABS(this->stickRelX) > 30 || (dpad && CHECK_BTN_ANY(input->press.button, BTN_DLEFT | BTN_DRIGHT))) {
if (this->stickRelX > 30 || (dpad && CHECK_BTN_ANY(input->press.button, BTN_DRIGHT))) {
// If exceeding the amount of choices for the selected option, cycle back to the first.
if ((gSaveContext.bossRushOptions[this->bossRushIndex] + 1) == BossRush_GetSettingOptionsAmount(this->bossRushIndex)) {
gSaveContext.bossRushOptions[this->bossRushIndex] = 0;
} else {
gSaveContext.bossRushOptions[this->bossRushIndex]++;
}
} else if (this->stickRelX < -30 || (dpad && CHECK_BTN_ANY(input->press.button, BTN_DLEFT))) {
// If cycling back when already at the first choice for the selected option, cycle back to the last choice.
if ((gSaveContext.bossRushOptions[this->bossRushIndex] - 1) < 0) {
gSaveContext.bossRushOptions[this->bossRushIndex] = BossRush_GetSettingOptionsAmount(this->bossRushIndex) - 1;
} else {
gSaveContext.bossRushOptions[this->bossRushIndex]--;
}
}
Audio_PlaySoundGeneral(NA_SE_SY_FSEL_CURSOR, &D_801333D4, 4, &D_801333E0, &D_801333E0, &D_801333E8);
}
if (CHECK_BTN_ALL(input->press.button, BTN_B)) {
this->configMode = CM_BOSS_RUSH_TO_QUEST;
return;
}
// Load into the game.
if (CHECK_BTN_ALL(input->press.button, BTN_START) || CHECK_BTN_ALL(input->press.button, BTN_A)) {
Audio_PlaySoundGeneral(NA_SE_SY_FSEL_DECIDE_L, &D_801333D4, 4, &D_801333E0, &D_801333E0, &D_801333E8);
this->buttonIndex = 0xFE;
this->menuMode = FS_MENU_MODE_SELECT;
this->selectMode = SM_FADE_OUT;
this->prevConfigMode = this->configMode;
return;
}
}
/**
* Update function for `CM_UNUSED_31`
*/
@ -689,10 +774,7 @@ void FileChoose_RotateToNameEntry(GameState* thisx) {
this->windowRot += VREG(16);
if (MIN_QUEST == MAX_QUEST && this->windowRot >= 314.0f) {
this->windowRot = 314.0f;
this->configMode = CM_START_NAME_ENTRY;
} else if (this->windowRot >= 628.0f) {
if (this->windowRot >= 628.0f) {
this->windowRot = 628.0f;
this->configMode = CM_START_NAME_ENTRY;
}
@ -719,8 +801,7 @@ void FileChoose_RotateToOptions(GameState* thisx) {
*/
void FileChoose_RotateToMain(GameState* thisx) {
FileChooseContext* this = (FileChooseContext*)thisx;
if (this->configMode == CM_QUEST_TO_MAIN || (MIN_QUEST == MAX_QUEST && this->configMode == CM_NAME_ENTRY_TO_MAIN && this->prevConfigMode != CM_MAIN_MENU) ||
this->configMode == CM_OPTIONS_TO_MAIN) {
if (this->configMode == CM_QUEST_TO_MAIN || this->configMode == CM_OPTIONS_TO_MAIN) {
this->windowRot -= VREG(16);
if (this->windowRot <= 0.0f) {
@ -732,7 +813,7 @@ void FileChoose_RotateToMain(GameState* thisx) {
if (this->configMode == CM_NAME_ENTRY_TO_MAIN && this->prevConfigMode == CM_MAIN_MENU) {
this->windowRot += VREG(16);
if (this->windowRot >= 942.0f || (MIN_QUEST == MAX_QUEST && this->windowRot >= 628.0f)) {
if (this->windowRot >= 942.0f) {
this->windowRot = 0.0f;
this->configMode = CM_MAIN_MENU;
}
@ -742,7 +823,7 @@ void FileChoose_RotateToMain(GameState* thisx) {
void FileChoose_RotateToQuest(GameState* thisx) {
FileChooseContext* this = (FileChooseContext*)thisx;
if (this->configMode == CM_NAME_ENTRY_TO_QUEST_MENU) {
if (this->configMode == CM_NAME_ENTRY_TO_QUEST_MENU || this->configMode == CM_BOSS_RUSH_TO_QUEST) {
this->windowRot -= VREG(16);
if (this->windowRot <= 314.0f) {
@ -759,6 +840,17 @@ void FileChoose_RotateToQuest(GameState* thisx) {
}
}
void FileChoose_RotateToBossRush(GameState* thisx) {
FileChooseContext* this = (FileChooseContext*)thisx;
this->windowRot += VREG(16);
if (this->windowRot >= 628.0f) {
this->windowRot = 628.0f;
this->configMode = CM_START_BOSS_RUSH_MENU;
}
}
static void (*gConfigModeUpdateFuncs[])(GameState*) = {
FileChoose_StartFadeIn, FileChoose_FinishFadeIn,
FileChoose_UpdateMainMenu, FileChoose_SetupCopySource,
@ -783,6 +875,8 @@ static void (*gConfigModeUpdateFuncs[])(GameState*) = {
FileChoose_UnusedCMDelay, FileChoose_RotateToQuest,
FileChoose_UpdateQuestMenu, FileChoose_StartQuestMenu,
FileChoose_RotateToMain, FileChoose_RotateToQuest,
FileChoose_RotateToBossRush, FileChoose_UpdateBossRushMenu,
FileChoose_StartBossRushMenu, FileChoose_RotateToQuest,
};
/**
@ -1376,6 +1470,18 @@ const char* FileChoose_GetQuestChooseTitleTexName(Language lang) {
}
}
const char* FileChoose_GetBossRushOptionsTitleTexName(Language lang) {
switch (lang) {
case LANGUAGE_ENG:
default:
return gFileSelBossRushSettingsENGText;
case LANGUAGE_FRA:
return gFileSelBossRushSettingsFRAText;
case LANGUAGE_GER:
return gFileSelBossRushSettingsGERText;
}
}
/**
* Draw most window contents including buttons, labels, and icons.
* Does not include anything from the keyboard and settings windows.
@ -1388,11 +1494,26 @@ void FileChoose_DrawWindowContents(GameState* thisx) {
s16 quadVtxIndex;
s16 isActive;
s16 pad;
char* tex = (this->configMode == CM_QUEST_MENU || this->configMode == CM_ROTATE_TO_NAME_ENTRY ||
this->configMode == CM_START_QUEST_MENU || this->configMode == CM_QUEST_TO_MAIN ||
this->configMode == CM_NAME_ENTRY_TO_QUEST_MENU)
? FileChoose_GetQuestChooseTitleTexName(gSaveContext.language)
: sTitleLabels[gSaveContext.language][this->titleLabel];
char* tex;
switch (this->configMode) {
case CM_QUEST_MENU:
case CM_ROTATE_TO_NAME_ENTRY:
case CM_START_QUEST_MENU:
case CM_QUEST_TO_MAIN:
case CM_NAME_ENTRY_TO_QUEST_MENU:
case CM_ROTATE_TO_BOSS_RUSH_MENU:
tex = FileChoose_GetQuestChooseTitleTexName(gSaveContext.language);
break;
case CM_BOSS_RUSH_MENU:
case CM_START_BOSS_RUSH_MENU:
case CM_BOSS_RUSH_TO_QUEST:
tex = FileChoose_GetBossRushOptionsTitleTexName(gSaveContext.language);
break;
default:
tex = sTitleLabels[gSaveContext.language][this->titleLabel];
break;
}
OPEN_DISPS(this->state.gfxCtx);
@ -1412,34 +1533,32 @@ void FileChoose_DrawWindowContents(GameState* thisx) {
if ((this->configMode == CM_QUEST_MENU) || (this->configMode == CM_START_QUEST_MENU) ||
this->configMode == CM_NAME_ENTRY_TO_QUEST_MENU) {
// draw control stick prompts.
if (MIN_QUEST != MAX_QUEST) {
Gfx_SetupDL_39Opa(this->state.gfxCtx);
gDPSetCombineMode(POLY_OPA_DISP++, G_CC_MODULATEIA_PRIM, G_CC_MODULATEIA_PRIM);
gDPLoadTextureBlock(POLY_OPA_DISP++, gArrowCursorTex, G_IM_FMT_IA, G_IM_SIZ_8b, 16, 24, 0,
G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, 4, G_TX_NOMASK, G_TX_NOLOD,
G_TX_NOLOD);
FileChoose_DrawTextRec(this->state.gfxCtx, this->stickLeftPrompt.arrowColorR,
this->stickLeftPrompt.arrowColorG, this->stickLeftPrompt.arrowColorB,
this->stickLeftPrompt.arrowColorA, this->stickLeftPrompt.arrowTexX,
this->stickLeftPrompt.arrowTexY, this->stickLeftPrompt.z, 0, 0, -1.0f, 1.0f);
FileChoose_DrawTextRec(this->state.gfxCtx, this->stickRightPrompt.arrowColorR,
this->stickRightPrompt.arrowColorG, this->stickRightPrompt.arrowColorB,
this->stickRightPrompt.arrowColorA, this->stickRightPrompt.arrowTexX,
this->stickRightPrompt.arrowTexY, this->stickRightPrompt.z, 0, 0, 1.0f, 1.0f);
gDPLoadTextureBlock(POLY_OPA_DISP++, gControlStickTex, G_IM_FMT_IA, G_IM_SIZ_8b, 16, 16, 0,
G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, 4, G_TX_NOMASK, G_TX_NOLOD,
G_TX_NOLOD);
FileChoose_DrawTextRec(this->state.gfxCtx, this->stickLeftPrompt.stickColorR,
this->stickLeftPrompt.stickColorG, this->stickLeftPrompt.stickColorB,
this->stickLeftPrompt.stickColorA, this->stickLeftPrompt.stickTexX,
this->stickLeftPrompt.stickTexY, this->stickLeftPrompt.z, 0, 0, -1.0f, 1.0f);
FileChoose_DrawTextRec(this->state.gfxCtx, this->stickRightPrompt.stickColorR,
this->stickRightPrompt.stickColorG, this->stickRightPrompt.stickColorB,
this->stickRightPrompt.stickColorA, this->stickRightPrompt.stickTexX,
this->stickRightPrompt.stickTexY, this->stickRightPrompt.z, 0, 0, 1.0f, 1.0f);
}
Gfx_SetupDL_39Opa(this->state.gfxCtx);
gDPSetCombineMode(POLY_OPA_DISP++, G_CC_MODULATEIA_PRIM, G_CC_MODULATEIA_PRIM);
gDPLoadTextureBlock(POLY_OPA_DISP++, gArrowCursorTex, G_IM_FMT_IA, G_IM_SIZ_8b, 16, 24, 0,
G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, 4, G_TX_NOMASK, G_TX_NOLOD,
G_TX_NOLOD);
FileChoose_DrawTextRec(this->state.gfxCtx, this->stickLeftPrompt.arrowColorR,
this->stickLeftPrompt.arrowColorG, this->stickLeftPrompt.arrowColorB,
this->stickLeftPrompt.arrowColorA, this->stickLeftPrompt.arrowTexX,
this->stickLeftPrompt.arrowTexY, this->stickLeftPrompt.z, 0, 0, -1.0f, 1.0f);
FileChoose_DrawTextRec(this->state.gfxCtx, this->stickRightPrompt.arrowColorR,
this->stickRightPrompt.arrowColorG, this->stickRightPrompt.arrowColorB,
this->stickRightPrompt.arrowColorA, this->stickRightPrompt.arrowTexX,
this->stickRightPrompt.arrowTexY, this->stickRightPrompt.z, 0, 0, 1.0f, 1.0f);
gDPLoadTextureBlock(POLY_OPA_DISP++, gControlStickTex, G_IM_FMT_IA, G_IM_SIZ_8b, 16, 16, 0,
G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, 4, G_TX_NOMASK, G_TX_NOLOD,
G_TX_NOLOD);
FileChoose_DrawTextRec(this->state.gfxCtx, this->stickLeftPrompt.stickColorR,
this->stickLeftPrompt.stickColorG, this->stickLeftPrompt.stickColorB,
this->stickLeftPrompt.stickColorA, this->stickLeftPrompt.stickTexX,
this->stickLeftPrompt.stickTexY, this->stickLeftPrompt.z, 0, 0, -1.0f, 1.0f);
FileChoose_DrawTextRec(this->state.gfxCtx, this->stickRightPrompt.stickColorR,
this->stickRightPrompt.stickColorG, this->stickRightPrompt.stickColorB,
this->stickRightPrompt.stickColorA, this->stickRightPrompt.stickTexX,
this->stickRightPrompt.stickTexY, this->stickRightPrompt.z, 0, 0, 1.0f, 1.0f);
switch (this->questType[this->buttonIndex]) {
case NORMAL_QUEST:
case FS_QUEST_NORMAL:
default:
gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, 255, 255, 255, this->logoAlpha);
FileChoose_DrawTextureI8(this->state.gfxCtx, gTitleTheLegendOfTextTex, 72, 8, 156, 108, 72, 8, 1024, 1024);
@ -1447,7 +1566,7 @@ void FileChoose_DrawWindowContents(GameState* thisx) {
FileChoose_DrawImageRGBA32(this->state.gfxCtx, 160, 135, gTitleZeldaShieldLogoTex, 160, 160);
break;
case MASTER_QUEST:
case FS_QUEST_MASTER:
gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, 255, 255, 255, this->logoAlpha);
FileChoose_DrawTextureI8(this->state.gfxCtx, gTitleTheLegendOfTextTex, 72, 8, 156, 108, 72, 8, 1024, 1024);
FileChoose_DrawTextureI8(this->state.gfxCtx, gTitleOcarinaOfTimeTMTextTex, 96, 8, 154, 163, 96, 8, 1024, 1024);
@ -1455,7 +1574,7 @@ void FileChoose_DrawWindowContents(GameState* thisx) {
FileChoose_DrawImageRGBA32(this->state.gfxCtx, 182, 180, gTitleMasterQuestSubtitleTex, 128, 32);
break;
case RANDOMIZER_QUEST:
case FS_QUEST_RANDOMIZER:
DrawSeedHashSprites(this);
gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, 255, 255, 255, this->logoAlpha);
FileChoose_DrawTextureI8(this->state.gfxCtx, gTitleTheLegendOfTextTex, 72, 8, 156, 108, 72, 8, 1024, 1024);
@ -1463,8 +1582,77 @@ void FileChoose_DrawWindowContents(GameState* thisx) {
FileChoose_DrawImageRGBA32(this->state.gfxCtx, 160, 135, ResourceMgr_GameHasOriginal() ? gTitleZeldaShieldLogoTex : gTitleZeldaShieldLogoMQTex, 160, 160);
FileChoose_DrawImageRGBA32(this->state.gfxCtx, 182, 180, gTitleRandomizerSubtitleTex, 128, 32);
break;
case FS_QUEST_BOSSRUSH:
gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, 255, 255, 255, this->logoAlpha);
FileChoose_DrawTextureI8(this->state.gfxCtx, gTitleTheLegendOfTextTex, 72, 8, 156, 108, 72, 8, 1024, 1024);
FileChoose_DrawTextureI8(this->state.gfxCtx, gTitleOcarinaOfTimeTMTextTex, 96, 8, 154, 163, 96, 8, 1024, 1024);
FileChoose_DrawImageRGBA32(this->state.gfxCtx, 160, 135, ResourceMgr_GameHasOriginal() ? gTitleZeldaShieldLogoTex : gTitleZeldaShieldLogoMQTex, 160, 160);
FileChoose_DrawImageRGBA32(this->state.gfxCtx, 182, 180, gTitleBossRushSubtitleTex, 128, 32);
break;
}
} else if (this->configMode != CM_ROTATE_TO_NAME_ENTRY) {
} else if (this->configMode == CM_BOSS_RUSH_MENU) {
uint8_t listOffset = this->bossRushOffset;
uint8_t textAlpha = this->bossRushUIAlpha;
// Draw arrows to indicate that the list can scroll up or down.
// Arrow up
if (listOffset > 0) {
uint16_t arrowUpX = 140;
uint16_t arrowUpY = 76 - (this->bossRushArrowOffset / 10);
gDPLoadTextureBlock(POLY_OPA_DISP++, gArrowUpTex, G_IM_FMT_IA,
G_IM_SIZ_16b, 16, 16, 0, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP,
G_TX_NOMASK, G_TX_NOMASK, G_TX_NOLOD, G_TX_NOLOD);
gSPWideTextureRectangle(POLY_OPA_DISP++, arrowUpX << 2, arrowUpY << 2, (arrowUpX + 8) << 2,
(arrowUpY + 8) << 2, G_TX_RENDERTILE, 0, 0, (1 << 11), (1 << 11));
}
// Arrow down
if (BOSSRUSH_OPTIONS_AMOUNT - listOffset > BOSSRUSH_MAX_OPTIONS_ON_SCREEN) {
uint16_t arrowDownX = 140;
uint16_t arrowDownY = 181 + (this->bossRushArrowOffset / 10);
gDPLoadTextureBlock(POLY_OPA_DISP++, gArrowDownTex, G_IM_FMT_IA,
G_IM_SIZ_16b, 16, 16, 0,
G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, G_TX_NOMASK,
G_TX_NOLOD, G_TX_NOLOD);
gSPWideTextureRectangle(POLY_OPA_DISP++, arrowDownX << 2, arrowDownY << 2, (arrowDownX + 8) << 2,
(arrowDownY + 8) << 2, G_TX_RENDERTILE, 0, 0, (1 << 11), (1 << 11));
}
// Draw options. There's more options than what fits on the screen, so the visible options
// depend on the current offset of the list. Currently selected option pulses in
// color and has arrows surrounding the option.
for (uint8_t i = listOffset; i - listOffset < BOSSRUSH_MAX_OPTIONS_ON_SCREEN; i++) {
uint16_t textYOffset = (i - listOffset) * 16;
// Option name.
Interface_DrawTextLine(this->state.gfxCtx, BossRush_GetSettingName(i, gSaveContext.language),
65, (87 + textYOffset), 255, 255, 80, textAlpha, 0.8f, true);
// Selected choice for option.
uint16_t finalKerning = Interface_DrawTextLine(this->state.gfxCtx, BossRush_GetSettingChoiceName(i, gSaveContext.bossRushOptions[i], gSaveContext.language),
165, (87 + textYOffset), 255, 255, 255, textAlpha, 0.8f, true);
// Draw arrows around selected option.
if (this->bossRushIndex == i) {
Gfx_SetupDL_39Opa(this->state.gfxCtx);
gDPSetCombineMode(POLY_OPA_DISP++, G_CC_MODULATEIA_PRIM, G_CC_MODULATEIA_PRIM);
gDPLoadTextureBlock(POLY_OPA_DISP++, gArrowCursorTex, G_IM_FMT_IA, G_IM_SIZ_8b, 16, 24, 0,
G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, 4, G_TX_NOMASK, G_TX_NOLOD,
G_TX_NOLOD);
FileChoose_DrawTextRec(this->state.gfxCtx, this->stickLeftPrompt.arrowColorR,
this->stickLeftPrompt.arrowColorG, this->stickLeftPrompt.arrowColorB,
textAlpha, 160, (92 + textYOffset), 0.42f, 0, 0, -1.0f,
1.0f);
FileChoose_DrawTextRec(this->state.gfxCtx, this->stickRightPrompt.arrowColorR,
this->stickRightPrompt.arrowColorG, this->stickRightPrompt.arrowColorB,
textAlpha, (171 + finalKerning),
(92 + textYOffset), 0.42f, 0, 0, 1.0f, 1.0f);
}
}
} else if (this->configMode != CM_ROTATE_TO_NAME_ENTRY && this->configMode != CM_START_BOSS_RUSH_MENU &&
this->configMode != CM_ROTATE_TO_BOSS_RUSH_MENU && this->configMode != CM_BOSS_RUSH_TO_QUEST) {
gDPPipeSync(POLY_OPA_DISP++);
gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, 255, 255, 255, this->titleAlpha[1]);
gDPLoadTextureBlock(POLY_OPA_DISP++, sTitleLabels[gSaveContext.language][this->nextTitleLabel], G_IM_FMT_IA,
@ -1702,7 +1890,7 @@ void FileChoose_ConfigModeDraw(GameState* thisx) {
if (this->windowRot != 0) {
if (this->configMode == CM_ROTATE_TO_QUEST_MENU ||
(this->configMode >= CM_MAIN_TO_OPTIONS && this->configMode <= CM_OPTIONS_TO_MAIN) ||
MIN_QUEST == MAX_QUEST || this->configMode == CM_QUEST_TO_MAIN) {
this->configMode == CM_QUEST_TO_MAIN) {
Matrix_RotateX(this->windowRot / 100.0f, MTXMODE_APPLY);
} else {
Matrix_RotateX((this->windowRot - 942.0f) / 100.0f, MTXMODE_APPLY);
@ -1736,11 +1924,7 @@ void FileChoose_ConfigModeDraw(GameState* thisx) {
Matrix_Translate(0.0f, 0.0f, -93.6f, MTXMODE_NEW);
Matrix_Scale(0.78f, 0.78f, 0.78f, MTXMODE_APPLY);
if (MIN_QUEST == MAX_QUEST) {
Matrix_RotateX((this->windowRot - 314.0f) / 100.0f, MTXMODE_APPLY);
} else {
Matrix_RotateX((this->windowRot - 628.0f) / 100.0f, MTXMODE_APPLY);
}
Matrix_RotateX((this->windowRot - 628.0f) / 100.0f, MTXMODE_APPLY);
gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(this->state.gfxCtx),
G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
@ -1790,7 +1974,7 @@ void FileChoose_ConfigModeDraw(GameState* thisx) {
// draw quest menu
if ((this->configMode == CM_QUEST_MENU) || (this->configMode == CM_ROTATE_TO_QUEST_MENU) ||
(this->configMode == CM_ROTATE_TO_NAME_ENTRY) || this->configMode == CM_QUEST_TO_MAIN ||
this->configMode == CM_NAME_ENTRY_TO_QUEST_MENU) {
this->configMode == CM_NAME_ENTRY_TO_QUEST_MENU || this->configMode == CM_ROTATE_TO_BOSS_RUSH_MENU) {
// window
gDPPipeSync(POLY_OPA_DISP++);
gDPSetCombineMode(POLY_OPA_DISP++, G_CC_MODULATEIA_PRIM, G_CC_MODULATEIA_PRIM);
@ -1819,6 +2003,36 @@ void FileChoose_ConfigModeDraw(GameState* thisx) {
FileChoose_DrawWindowContents(&this->state);
}
// Draw Boss Rush Options Menu
if (this->configMode == CM_BOSS_RUSH_MENU || this->configMode == CM_ROTATE_TO_BOSS_RUSH_MENU ||
this->configMode == CM_START_BOSS_RUSH_MENU || this->configMode == CM_BOSS_RUSH_TO_QUEST) {
// window
gDPPipeSync(POLY_OPA_DISP++);
gDPSetCombineMode(POLY_OPA_DISP++, G_CC_MODULATEIA_PRIM, G_CC_MODULATEIA_PRIM);
gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, this->windowColor[0], this->windowColor[1], this->windowColor[2],
this->windowAlpha);
gDPSetEnvColor(POLY_OPA_DISP++, 0, 0, 0, 0);
Matrix_Translate(0.0f, 0.0f, -93.6f, MTXMODE_NEW);
Matrix_Scale(0.78f, 0.78f, 0.78f, MTXMODE_APPLY);
Matrix_RotateX((this->windowRot - 628.0f) / 100.0f, MTXMODE_APPLY);
gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(this->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
gSPVertex(POLY_OPA_DISP++, &this->windowVtx[0], 32, 0);
gSPDisplayList(POLY_OPA_DISP++, gFileSelWindow1DL);
gSPVertex(POLY_OPA_DISP++, &this->windowVtx[32], 32, 0);
gSPDisplayList(POLY_OPA_DISP++, gFileSelWindow2DL);
gSPVertex(POLY_OPA_DISP++, &this->windowVtx[64], 16, 0);
gSPDisplayList(POLY_OPA_DISP++, gFileSelWindow3DL);
gDPPipeSync(POLY_OPA_DISP++);
FileChoose_DrawWindowContents(&this->state);
}
gDPPipeSync(POLY_OPA_DISP++);
FileChoose_SetView(this, 0.0f, 0.0f, 64.0f);
@ -1920,6 +2134,8 @@ void FileChoose_ConfirmFile(GameState* thisx) {
if (this->confirmButtonIndex == FS_BTN_CONFIRM_YES) {
func_800AA000(300.0f, 180, 20, 100);
Audio_PlaySoundGeneral(NA_SE_SY_FSEL_DECIDE_L, &D_801333D4, 4, &D_801333E0, &D_801333E0, &D_801333E8);
// Reset Boss Rush because it's only ever saved in memory.
gSaveContext.isBossRush = 0;
this->selectMode = SM_FADE_OUT;
func_800F6964(0xF);
} else {
@ -2033,26 +2249,28 @@ void FileChoose_LoadGame(GameState* thisx) {
u16 swordEquipMask;
s32 pad;
Audio_PlaySoundGeneral(NA_SE_SY_FSEL_DECIDE_L, &D_801333D4, 4, &D_801333E0, &D_801333E0, &D_801333E8);
gSaveContext.fileNum = this->buttonIndex;
gSaveContext.gameMode = 0;
if ((this->buttonIndex == FS_BTN_SELECT_FILE_1 && CVarGetInteger("gDebugEnabled", 0)) || this->buttonIndex == 0xFF) {
Audio_PlaySoundGeneral(NA_SE_SY_FSEL_DECIDE_L, &D_801333D4, 4, &D_801333E0, &D_801333E0, &D_801333E8);
gSaveContext.fileNum = this->buttonIndex;
if (this->buttonIndex == 0xFF) {
Sram_InitDebugSave();
} else {
Sram_OpenSave();
}
gSaveContext.gameMode = 0;
SET_NEXT_GAMESTATE(&this->state, Select_Init, SelectContext);
this->state.running = false;
} else {
Audio_PlaySoundGeneral(NA_SE_SY_FSEL_DECIDE_L, &D_801333D4, 4, &D_801333E0, &D_801333E0, &D_801333E8);
gSaveContext.fileNum = this->buttonIndex;
Sram_OpenSave();
gSaveContext.gameMode = 0;
if (this->buttonIndex == 0xFE) {
Sram_InitBossRushSave();
} else {
Sram_OpenSave();
}
SET_NEXT_GAMESTATE(&this->state, Play_Init, PlayState);
this->state.running = false;
}
this->state.running = false;
Randomizer_LoadSettings("");
Randomizer_LoadHintLocations("");
Randomizer_LoadItemLocations("", true);
@ -2573,6 +2791,9 @@ void FileChoose_InitContext(GameState* thisx) {
this->arrowAnimTween = 0;
this->stickAnimTween = 0;
this->bossRushIndex = 0;
this->bossRushOffset = 0;
ShrinkWindow_SetVal(0);
gSaveContext.skyboxTime = 0;

View File

@ -1508,8 +1508,14 @@ void KaleidoScope_DrawPages(PlayState* play, GraphicsContext* gfxCtx) {
gDPSetCombineMode(POLY_KAL_DISP++, G_CC_MODULATEIA, G_CC_MODULATEIA);
gDPSetPrimColor(POLY_KAL_DISP++, 0, 0, 255, 255, 255, pauseCtx->alpha);
POLY_KAL_DISP =
KaleidoScope_QuadTextureIA8(POLY_KAL_DISP, sPromptChoiceTexs[gSaveContext.language][0], 48, 16, 12);
if (!gSaveContext.isBossRush) {
POLY_KAL_DISP = KaleidoScope_QuadTextureIA8(
POLY_KAL_DISP, sPromptChoiceTexs[gSaveContext.language][0], 48, 16, 12);
} else {
// Show "No" twice in Boss Rush because the player can't save within it.
POLY_KAL_DISP = KaleidoScope_QuadTextureIA8(
POLY_KAL_DISP, sPromptChoiceTexs[gSaveContext.language][1], 48, 16, 12);
}
POLY_KAL_DISP =
KaleidoScope_QuadTextureIA8(POLY_KAL_DISP, sPromptChoiceTexs[gSaveContext.language][1], 48, 16, 16);
@ -3629,7 +3635,9 @@ void KaleidoScope_Update(PlayState* play)
case 6:
switch (pauseCtx->unk_1E4) {
case 0:
if (CHECK_BTN_ALL(input->press.button, BTN_START)) {
// Boss Rush skips past the "Save?" window when pressing B while paused.
if (CHECK_BTN_ALL(input->press.button, BTN_START) ||
(CHECK_BTN_ALL(input->press.button, BTN_B) && gSaveContext.isBossRush)) {
if (CVarGetInteger("gCheatEasyPauseBufferEnabled", 0) || CVarGetInteger("gCheatEasyInputBufferingEnabled", 0)) {
CVarSetInteger("gPauseBufferBlockInputFrame", 9);
}
@ -4015,7 +4023,11 @@ void KaleidoScope_Update(PlayState* play)
VREG(88) = 66;
WREG(2) = 0;
pauseCtx->alpha = 255;
pauseCtx->state = 0xE;
if (!gSaveContext.isBossRush) {
pauseCtx->state = 0xE;
} else {
pauseCtx->state = 0xF;
}
gSaveContext.deaths++;
if (gSaveContext.deaths > 999) {
gSaveContext.deaths = 999;
@ -4059,7 +4071,7 @@ void KaleidoScope_Update(PlayState* play)
case 0x10:
if (CHECK_BTN_ALL(input->press.button, BTN_A) || CHECK_BTN_ALL(input->press.button, BTN_START)) {
if (pauseCtx->promptChoice == 0) {
if (pauseCtx->promptChoice == 0 && !gSaveContext.isBossRush) {
Audio_PlaySoundGeneral(NA_SE_SY_PIECE_OF_HEART, &D_801333D4, 4, &D_801333E0, &D_801333E0,
&D_801333E8);
Play_SaveSceneFlags(play);
@ -4132,7 +4144,7 @@ void KaleidoScope_Update(PlayState* play)
R_PAUSE_MENU_MODE = 0;
func_800981B8(&play->objectCtx);
func_800418D0(&play->colCtx, play);
if (pauseCtx->promptChoice == 0) {
if (pauseCtx->promptChoice == 0 && !gSaveContext.isBossRush) {
Play_TriggerRespawn(play);
gSaveContext.respawnFlag = -2;
// In ER, handle death warp to last entrance from grottos
@ -4156,6 +4168,7 @@ void KaleidoScope_Update(PlayState* play)
osSyncPrintf(VT_RST);
} else {
play->state.running = 0;
gSaveContext.isBossRush = false;
SET_NEXT_GAMESTATE(&play->state, Opening_Init, OpeningContext);
GameInteractor_ExecuteOnExitGame(gSaveContext.fileNum);
}