mirror of
https://github.com/HarbourMasters/Shipwright
synced 2026-08-19 22:22:18 -04:00
1785a70f22
The main effect of this PR is completely decoupling OTRGlobals.h from any non-std header making it so that the "everything is being rebuilt because I changed a header that at some point gets included in OTRGlobals" problem doesn't happen anymore.
279 lines
13 KiB
C++
279 lines
13 KiB
C++
/**
|
|
* This file handles custom messages relating to Items,
|
|
* such as Get Item messages for non-vanilla items,
|
|
* Vanilla/MQ hints when collecting Maps, Ice Trap messages,
|
|
* etc.
|
|
*/
|
|
#include <soh/OTRGlobals.h>
|
|
#include "soh/Enhancements/game-interactor/GameInteractor.h"
|
|
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
|
|
#include "soh/Enhancements/custom-message/CustomMessageTypes.h"
|
|
#include "soh/Enhancements/randomizer/Traps.h"
|
|
#include "soh/Enhancements/randomizer/item.h"
|
|
#include "soh/Enhancements/randomizer/randomizer.h"
|
|
#include "soh/ShipInit.hpp"
|
|
#include <soh/ResourceManagerHelpers.h>
|
|
|
|
#include <cstdarg>
|
|
|
|
extern "C" {
|
|
#include <variables.h>
|
|
#include <macros.h>
|
|
#include "z64item.h"
|
|
extern PlayState* gPlayState;
|
|
}
|
|
|
|
void BuildTriforcePieceMessage(CustomMessage& msg) {
|
|
uint8_t current = gSaveContext.ship.quest.data.randomizer.triforcePiecesCollected + 1;
|
|
uint8_t required = OTRGlobals::Instance->gRandomizer->GetRandoSettingValue(RSK_TRIFORCE_HUNT_PIECES_REQUIRED) + 1;
|
|
uint8_t remaining = required - current;
|
|
float percentageCollected = (float)current / (float)required;
|
|
|
|
if (percentageCollected <= 0.25) {
|
|
msg = { "You found a %yTriforce Piece%w!&%g[[current]]%w down, %c[[remaining]]%w to go. It's a start!",
|
|
"Ein %yTriforce-Splitter%w! Du hast&%g[[current]]%w von %c[[required]]%w gefunden. Es ist ein&Anfang!",
|
|
"Vous trouvez un %yFragment de la&Triforce%w! Vous en avez %g[[current]]%w, il en&reste "
|
|
"%c[[remaining]]%w à trouver. C'est un début!" };
|
|
} else if (percentageCollected <= 0.5) {
|
|
msg = { "You found a %yTriforce Piece%w!&%g[[current]]%w down, %c[[remaining]]%w to go. Progress!",
|
|
"Ein %yTriforce-Splitter%w! Du hast&%g[[current]]%w von %c[[required]]%w gefunden. Es geht voran!",
|
|
"Vous trouvez un %yFragment de la&Triforce%w! Vous en avez %g[[current]]%w, il en&reste "
|
|
"%c[[remaining]]%w à trouver. Ça avance!" };
|
|
} else if (percentageCollected <= 0.75) {
|
|
msg = { "You found a %yTriforce Piece%w!&%g[[current]]%w down, %c[[remaining]]%w to go. Over half-way&there!",
|
|
"Ein %yTriforce-Splitter%w! Du hast&schon %g[[current]]%w von %c[[required]]%w gefunden. Schon&über "
|
|
"die Hälfte!",
|
|
"Vous trouvez un %yFragment de la&Triforce%w! Vous en avez %g[[current]]%w, il en&reste "
|
|
"%c[[remaining]]%w à trouver. Il en reste un&peu moins que la moitié!" };
|
|
} else if (percentageCollected < 1.0) {
|
|
msg = {
|
|
"You found a %yTriforce Piece%w!&%g[[current]]%w down, %c[[remaining]]%w to go. Almost done!",
|
|
"Ein %yTriforce-Splitter%w! Du hast&schon %g[[current]]%w von %c[[required]]%w gefunden. Fast&geschafft!",
|
|
"Vous trouvez un %yFragment de la&Triforce%w! Vous en avez %g[[current]]%w, il en&reste %c[[remaining]]%w "
|
|
"à trouver. C'est presque&terminé!"
|
|
};
|
|
} else if (current == required) {
|
|
msg = { "You completed the %yTriforce of&Courage%w! %gGG%w!",
|
|
"Das %yTriforce des Mutes%w! Du hast&alle Splitter gefunden. %gGut gemacht%w!",
|
|
"Vous avez complété la %yTriforce&du Courage%w! %gFélicitations%w!" };
|
|
} else {
|
|
msg = { "You found a spare %yTriforce Piece%w!&You only needed %c[[required]]%w, but you have %g[[current]]%w!",
|
|
"Ein übriger %yTriforce-Splitter%w! Du&hast nun %g[[current]]%w von %c[[required]]%w nötigen gefunden.",
|
|
"Vous avez trouvé un %yFragment de&Triforce%w en plus! Vous n'aviez besoin&que de %c[[required]]%w, "
|
|
"mais vous en avez %g[[current]]%w en&tout!" };
|
|
}
|
|
msg.Replace("[[current]]", std::to_string(current));
|
|
msg.Replace("[[remaining]]", std::to_string(remaining));
|
|
msg.Replace("[[required]]", std::to_string(required));
|
|
msg.Format(ITEM_CUSTOM);
|
|
}
|
|
|
|
void BuildCustomItemMessage(Player* player, CustomMessage& msg) {
|
|
int16_t rgid;
|
|
msg = CustomMessage("You found [[article]][[color]][[name]]%w!",
|
|
"Du erhältst [[article]][[color]][[name]]%w gefunden!",
|
|
"Vous avez trouvé [[article]][[color]][[name]]%w!", TEXTBOX_TYPE_BLUE);
|
|
if (player->getItemEntry.objectId != OBJECT_INVALID) {
|
|
rgid = player->getItemEntry.getItemId;
|
|
} else {
|
|
rgid = player->getItemId;
|
|
}
|
|
CustomMessage name =
|
|
CustomMessage(Rando::StaticData::RetrieveItem(static_cast<RandomizerGet>(rgid)).GetName(), TEXTBOX_TYPE_BLUE);
|
|
CustomMessage article = CustomMessage(
|
|
Rando::StaticData::RetrieveItem(static_cast<RandomizerGet>(rgid)).GetArticle(), TEXTBOX_TYPE_BLUE);
|
|
msg.Replace("[[article]]", article);
|
|
msg.Replace("[[color]]", Rando::StaticData::RetrieveItem(static_cast<RandomizerGet>(rgid)).GetColor());
|
|
msg.Replace("[[name]]", name);
|
|
if (Rando::StaticData::RetrieveItem(static_cast<RandomizerGet>(rgid)).HasCustomIcon()) {
|
|
msg.AutoFormat(ITEM_CUSTOM);
|
|
} else {
|
|
msg.AutoFormat();
|
|
}
|
|
}
|
|
|
|
void LoadCustomItemIcon(bool displayAsEnglish) {
|
|
Player* player = GET_PLAYER(gPlayState);
|
|
const char* customIcon = nullptr;
|
|
CustomIconSize iconSize = ICON_SIZE_32;
|
|
if (player->getItemEntry.objectId != OBJECT_INVALID) {
|
|
RandomizerGet rgid = static_cast<RandomizerGet>(player->getItemEntry.getItemId);
|
|
customIcon = Rando::StaticData::RetrieveItem(rgid).GetCustomIcon();
|
|
iconSize = Rando::StaticData::RetrieveItem(rgid).GetCustomIconSize();
|
|
}
|
|
if (customIcon != nullptr) {
|
|
static int16_t sIconItem32XOffsets[] = { 74, 74, 74, 54 };
|
|
static int16_t sIconItem24XOffsets[] = { 72, 72, 72, 50 };
|
|
MessageContext* msgCtx = &gPlayState->msgCtx;
|
|
uint8_t language = displayAsEnglish ? LANGUAGE_ENG : gSaveContext.language;
|
|
if (iconSize == ICON_SIZE_32) {
|
|
R_TEXTBOX_ICON_XPOS = R_TEXT_INIT_XPOS - sIconItem32XOffsets[language];
|
|
R_TEXTBOX_ICON_YPOS = (R_TEXTBOX_Y + 10) + 6;
|
|
R_TEXTBOX_ICON_SIZE = 32;
|
|
} else {
|
|
R_TEXTBOX_ICON_XPOS = R_TEXT_INIT_XPOS - sIconItem24XOffsets[language];
|
|
R_TEXTBOX_ICON_YPOS = (R_TEXTBOX_Y + 10) + 10;
|
|
R_TEXTBOX_ICON_SIZE = 24;
|
|
}
|
|
strcpy((char*)((uintptr_t)msgCtx->textboxSegment + MESSAGE_STATIC_TEX_SIZE), customIcon);
|
|
msgCtx->msgBufPos++;
|
|
msgCtx->choiceNum = 1;
|
|
}
|
|
}
|
|
|
|
void DrawCustomItemIcon(Gfx** p) {
|
|
Gfx* gfx = *p;
|
|
MessageContext* msgCtx = &gPlayState->msgCtx;
|
|
Player* player = GET_PLAYER(gPlayState);
|
|
CustomIconSize iconSize = ICON_SIZE_32;
|
|
if (player->getItemEntry.objectId != OBJECT_INVALID) {
|
|
RandomizerGet rgid = static_cast<RandomizerGet>(player->getItemEntry.getItemId);
|
|
iconSize = Rando::StaticData::RetrieveItem(rgid).GetCustomIconSize();
|
|
}
|
|
if (iconSize == ICON_SIZE_24) {
|
|
gDPLoadTextureBlock(gfx++, (uintptr_t)msgCtx->textboxSegment + MESSAGE_STATIC_TEX_SIZE, G_IM_FMT_RGBA,
|
|
G_IM_SIZ_32b, 24, 24, 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);
|
|
} else {
|
|
gDPLoadTextureBlock(gfx++, (uintptr_t)msgCtx->textboxSegment + MESSAGE_STATIC_TEX_SIZE, G_IM_FMT_RGBA,
|
|
G_IM_SIZ_32b, 32, 32, 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);
|
|
}
|
|
*p = gfx;
|
|
}
|
|
|
|
void BuildItemMessage(u16* textId, bool* loadFromMessageTable) {
|
|
Player* player = GET_PLAYER(gPlayState);
|
|
CustomMessage msg;
|
|
|
|
if (player->getItemEntry.getItemId == RG_ICE_TRAP) {
|
|
Rando::Traps::BuildIceTrapMessage(msg, player->getItemEntry);
|
|
} else if (player->getItemEntry.getItemId == RG_TRIFORCE_PIECE) {
|
|
BuildTriforcePieceMessage(msg);
|
|
} else {
|
|
BuildCustomItemMessage(player, msg);
|
|
}
|
|
*loadFromMessageTable = false;
|
|
msg.LoadIntoFont();
|
|
}
|
|
|
|
void BuildMapMessage(uint16_t* textId, bool* loadFromMessageTable) {
|
|
GetItemEntry itemEntry = GET_PLAYER(gPlayState)->getItemEntry;
|
|
auto ctx = OTRGlobals::Instance->gRandoContext;
|
|
CustomMessage msg =
|
|
CustomMessage("You found the %g[[name]]%w! [[typeHint]]", "Du erhältst das %g[[name]]%w! [[typeHint]]",
|
|
"Vous ebtenez %g[[name]]%w! [[typeHint]]", TEXTBOX_TYPE_BLUE);
|
|
int sceneNum;
|
|
switch (itemEntry.getItemId) {
|
|
case RG_DEKU_TREE_MAP:
|
|
sceneNum = SCENE_DEKU_TREE;
|
|
break;
|
|
case RG_DODONGOS_CAVERN_MAP:
|
|
sceneNum = SCENE_DODONGOS_CAVERN;
|
|
break;
|
|
case RG_JABU_JABUS_BELLY_MAP:
|
|
sceneNum = SCENE_JABU_JABU;
|
|
break;
|
|
case RG_FOREST_TEMPLE_MAP:
|
|
sceneNum = SCENE_FOREST_TEMPLE;
|
|
break;
|
|
case RG_FIRE_TEMPLE_MAP:
|
|
sceneNum = SCENE_FIRE_TEMPLE;
|
|
break;
|
|
case RG_WATER_TEMPLE_MAP:
|
|
sceneNum = SCENE_WATER_TEMPLE;
|
|
break;
|
|
case RG_SPIRIT_TEMPLE_MAP:
|
|
sceneNum = SCENE_SPIRIT_TEMPLE;
|
|
break;
|
|
case RG_SHADOW_TEMPLE_MAP:
|
|
sceneNum = SCENE_SHADOW_TEMPLE;
|
|
break;
|
|
case RG_BOTTOM_OF_THE_WELL_MAP:
|
|
sceneNum = SCENE_BOTTOM_OF_THE_WELL;
|
|
break;
|
|
case RG_ICE_CAVERN_MAP:
|
|
sceneNum = SCENE_ICE_CAVERN;
|
|
break;
|
|
}
|
|
CustomMessage name =
|
|
CustomMessage(Rando::StaticData::RetrieveItem(static_cast<RandomizerGet>(itemEntry.getItemId)).GetName());
|
|
msg.Replace("[[name]]", name);
|
|
if (ctx->GetOption(RSK_MQ_DUNGEON_RANDOM).Is(RO_MQ_DUNGEONS_NONE) ||
|
|
(ctx->GetOption(RSK_MQ_DUNGEON_RANDOM).Is(RO_MQ_DUNGEONS_SET_NUMBER) &&
|
|
ctx->GetOption(RSK_MQ_DUNGEON_COUNT).Is(MAX_MQ_DUNGEON_COUNT))) {
|
|
msg.Replace("[[typeHint]]", "");
|
|
} else if (ResourceMgr_IsSceneMasterQuest(sceneNum)) {
|
|
msg.Replace("[[typeHint]]", Rando::StaticData::hintTextTable[RHT_DUNGEON_MASTERFUL].GetHintMessage());
|
|
} else {
|
|
msg.Replace("[[typeHint]]", Rando::StaticData::hintTextTable[RHT_DUNGEON_ORDINARY].GetHintMessage());
|
|
}
|
|
*loadFromMessageTable = false;
|
|
msg.AutoFormat(ITEM_DUNGEON_MAP);
|
|
msg.LoadIntoFont();
|
|
}
|
|
|
|
void BuildBossKeyMessage(uint16_t* textId, bool* loadFromMessageTable) {
|
|
Player* player = GET_PLAYER(gPlayState);
|
|
if (player->getItemEntry.getItemId == RG_GANONS_CASTLE_BOSS_KEY &&
|
|
!DUNGEON_ITEMS_CAN_BE_OUTSIDE_DUNGEON(RSK_GANONS_BOSS_KEY)) {
|
|
return;
|
|
}
|
|
if (player->getItemEntry.getItemId != RG_GANONS_CASTLE_BOSS_KEY &&
|
|
!DUNGEON_ITEMS_CAN_BE_OUTSIDE_DUNGEON(RSK_BOSS_KEYSANITY)) {
|
|
return;
|
|
}
|
|
CustomMessage msg;
|
|
BuildCustomItemMessage(player, msg);
|
|
*loadFromMessageTable = false;
|
|
msg.LoadIntoFont();
|
|
}
|
|
|
|
void BuildSmallKeyMessage(uint16_t* textId, bool* loadFromMessageTable) {
|
|
Player* player = GET_PLAYER(gPlayState);
|
|
if (player->getItemEntry.getItemId == RG_GERUDO_FORTRESS_SMALL_KEY &&
|
|
OTRGlobals::Instance->gRandoContext->GetOption(RSK_GERUDO_KEYS).Is(RO_GERUDO_KEYS_VANILLA)) {
|
|
return;
|
|
}
|
|
if (player->getItemEntry.getItemId != RG_GERUDO_FORTRESS_SMALL_KEY &&
|
|
DUNGEON_ITEMS_CAN_BE_OUTSIDE_DUNGEON(RSK_KEYSANITY)) {
|
|
return;
|
|
}
|
|
CustomMessage msg;
|
|
BuildCustomItemMessage(player, msg);
|
|
*loadFromMessageTable = false;
|
|
msg.LoadIntoFont();
|
|
}
|
|
|
|
void RegisterItemMessages() {
|
|
COND_ID_HOOK(OnOpenText, TEXT_RANDOMIZER_CUSTOM_ITEM, IS_RANDO, BuildItemMessage);
|
|
COND_ID_HOOK(OnOpenText, TEXT_ITEM_DUNGEON_MAP, DUNGEON_ITEMS_CAN_BE_OUTSIDE_DUNGEON(RSK_SHUFFLE_MAPANDCOMPASS),
|
|
BuildMapMessage);
|
|
COND_ID_HOOK(OnOpenText, TEXT_ITEM_COMPASS, DUNGEON_ITEMS_CAN_BE_OUTSIDE_DUNGEON(RSK_SHUFFLE_MAPANDCOMPASS),
|
|
BuildItemMessage);
|
|
COND_ID_HOOK(OnOpenText, TEXT_ITEM_KEY_BOSS,
|
|
(DUNGEON_ITEMS_CAN_BE_OUTSIDE_DUNGEON(RSK_BOSS_KEYSANITY) ||
|
|
DUNGEON_ITEMS_CAN_BE_OUTSIDE_DUNGEON(RSK_GANONS_BOSS_KEY)),
|
|
BuildBossKeyMessage);
|
|
COND_ID_HOOK(OnOpenText, TEXT_ITEM_KEY_SMALL,
|
|
(OTRGlobals::Instance->gRandoContext->GetOption(RSK_GERUDO_KEYS).IsNot(RO_GERUDO_KEYS_VANILLA) ||
|
|
DUNGEON_ITEMS_CAN_BE_OUTSIDE_DUNGEON(RSK_KEYSANITY)),
|
|
BuildSmallKeyMessage);
|
|
}
|
|
|
|
static RegisterShipInitFunc initFunc(RegisterItemMessages, { "IS_RANDO" });
|
|
|
|
void RegisterCustomIconHooks() {
|
|
COND_VB_SHOULD(VB_LOAD_ITEM_ICON, IS_RANDO, {
|
|
if (*should == false) {
|
|
LoadCustomItemIcon(static_cast<bool>(va_arg(args, int)));
|
|
}
|
|
});
|
|
COND_VB_SHOULD(VB_DRAW_ITEM_ICON, IS_RANDO, {
|
|
if (*should == false) {
|
|
DrawCustomItemIcon(va_arg(args, Gfx**));
|
|
}
|
|
});
|
|
}
|
|
|
|
static RegisterShipInitFunc customIconInitFunc(RegisterCustomIconHooks, { "IS_RANDO" }); |