mirror of
https://github.com/zeldaret/ss
synced 2026-05-25 07:23:00 -04:00
443 lines
14 KiB
C++
443 lines
14 KiB
C++
#include "toBeSorted/fi_context.h"
|
|
|
|
#include "common.h"
|
|
#include "d/a/d_a_item.h"
|
|
#include "d/d_message.h"
|
|
#include "d/d_pad.h"
|
|
#include "d/d_pouch.h"
|
|
#include "d/d_sc_game.h"
|
|
#include "d/d_stage_mgr.h"
|
|
#include "d/flag/itemflag_manager.h"
|
|
#include "d/flag/storyflag_manager.h"
|
|
#include "d/lyt/meter/d_lyt_meter.h"
|
|
#include "sized_string.h"
|
|
#include "toBeSorted/file_manager.h"
|
|
#include "toBeSorted/unk_save_time.h"
|
|
|
|
// https://github.com/lepelog/skywardsword-tools/wiki/Navi-Table-(Fi-Advice)
|
|
|
|
// Portability hazard, only works for 32 bit architectures
|
|
|
|
struct AdviceSummaryEntry {
|
|
/* 0x00 */ u16 storyflag;
|
|
/* 0x02 */ u16 KEN0_entry_num;
|
|
};
|
|
|
|
struct AdviceHintEntry {
|
|
/* 0x00 */ char *stage_name_ptr;
|
|
/* 0x04 */ s16 storyflag;
|
|
/* 0x06 */ u16 KEN1_entry_num;
|
|
};
|
|
|
|
struct ObjectiveEntry {
|
|
/* 0x00 */ u16 storyflag;
|
|
/* 0x02 */ u16 KEN2_entry_num;
|
|
};
|
|
|
|
struct AnalysisEntry {
|
|
/* 0x00 */ char *stage_name_ptr;
|
|
/* 0x04 */ s16 storyflag;
|
|
/* 0x06 */ u16 KEN3_danger_entry_num;
|
|
/* 0x08 */ u8 sutability_percent[4];
|
|
/* 0x0C */ u16 KEN3_sutability_entry_num[4];
|
|
/* 0x14 */ u16 area_shield_danger; // -1-no danger, 0-wooden burns, 1- iron electrifies
|
|
/* 0x16 */ u16 KEN3_shield_entry_num;
|
|
};
|
|
|
|
struct FileHeader {
|
|
/* 0x00 */ u32 magic; // Is `V001`
|
|
/* 0x04 */ u16 summary_count;
|
|
/* 0x06 */ u16 hint_count;
|
|
/* 0x08 */ u16 objective_count;
|
|
/* 0x0A */ u16 analysis_count;
|
|
/* 0x0C */ AdviceSummaryEntry *summary; // ptr to AdviceSummaryEntry[summary_count]
|
|
/* 0x10 */ AdviceHintEntry *hint; // ptr to AdviceHintEntry[hint_count]
|
|
/* 0x14 */ ObjectiveEntry *objective; // ptr to ObjectiveEntry[objective_count]
|
|
/* 0x18 */ AnalysisEntry *analysis; // ptr to AnalysisEntry[analysis_count]
|
|
};
|
|
|
|
bool FiAnalysisHandle::isValid() const {
|
|
return mpEntry != nullptr;
|
|
}
|
|
|
|
s16 FiAnalysisHandle::getAreaIndexForFiAreaName() const {
|
|
if (isValid()) {
|
|
return mpEntry->KEN3_danger_entry_num;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
FiAnalysisHandle::FiAnalysisEquipmentFocus_e FiAnalysisHandle::getCurrentFocus() {
|
|
s32 pouchContents = summarizePouchContents(nullptr, nullptr, nullptr);
|
|
if (pouchContents == 0) {
|
|
return FOCUS_COMBAT;
|
|
} else if (pouchContents == 1) {
|
|
return FOCUS_BALANCED;
|
|
} else if (pouchContents == 2) {
|
|
return FOCUS_TREASURE;
|
|
} else if (pouchContents == 3) {
|
|
return FOCUS_SURVIVAL;
|
|
}
|
|
return FOCUS_COMBAT;
|
|
}
|
|
|
|
s16 FiAnalysisHandle::getEquipmentFocus() const {
|
|
static const s16 sEquipmentFocuses[] = {400, 403, 402, 401};
|
|
if (!isValid()) {
|
|
return -1;
|
|
} else {
|
|
if (StoryflagManager::sInstance->getCounterOrFlag(STORYFLAG_BOSS_RUSH_ACTIVE)) {
|
|
// "I cannot offer my usual analysis because you are currently engaged in a challenge created by the Thunder
|
|
// Dragon Lanayru."
|
|
return 180;
|
|
}
|
|
return sEquipmentFocuses[getCurrentFocus()];
|
|
}
|
|
}
|
|
|
|
s32 FiAnalysisHandle::getSuitabilityPercentageArg() const {
|
|
if (!isValid()) {
|
|
return -1;
|
|
} else {
|
|
return mpEntry->sutability_percent[getCurrentFocus()];
|
|
}
|
|
}
|
|
|
|
s16 FiAnalysisHandle::getEquipmentRecommendation() const {
|
|
if (!isValid()) {
|
|
return -1;
|
|
} else {
|
|
return mpEntry->KEN3_sutability_entry_num[getCurrentFocus()];
|
|
}
|
|
}
|
|
|
|
s16 FiAnalysisHandle::getThreatenedShield() const {
|
|
if (isValid()) {
|
|
return mpEntry->area_shield_danger;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
s16 FiAnalysisHandle::getShieldMessage() const {
|
|
if (isValid()) {
|
|
return mpEntry->KEN3_shield_entry_num;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
FiContext *FiContext::sInstance;
|
|
static FileHeader *sNaviTable;
|
|
|
|
#define BIND_PTR(type, header, pointer) \
|
|
pointer = reinterpret_cast<type *>(reinterpret_cast<char *>(header) + reinterpret_cast<u32>(pointer))
|
|
|
|
void FiContext::initialize(void *data) {
|
|
FileHeader *header = reinterpret_cast<FileHeader *>(data);
|
|
BIND_PTR(AdviceSummaryEntry, header, header->summary);
|
|
BIND_PTR(AdviceHintEntry, header, header->hint);
|
|
AdviceHintEntry *hint = header->hint;
|
|
for (s32 i = 0; i < header->hint_count; i++) {
|
|
BIND_PTR(char, header, hint[i].stage_name_ptr);
|
|
}
|
|
BIND_PTR(ObjectiveEntry, header, header->objective);
|
|
BIND_PTR(AnalysisEntry, header, header->analysis);
|
|
AnalysisEntry *analysis = header->analysis;
|
|
for (s32 i = 0; i < header->analysis_count; i++) {
|
|
BIND_PTR(char, header, analysis[i].stage_name_ptr);
|
|
}
|
|
sNaviTable = header;
|
|
}
|
|
|
|
s16 FiContext::getNaviTableProgressSummary() {
|
|
if (StoryflagManager::sInstance->getFlag(STORYFLAG_BOSS_RUSH_ACTIVE)) {
|
|
// When in boss rush, no progress is available since story flags
|
|
// are unreliable here.
|
|
// "You are currently reliving some of your previous battles within the Thunder Dragon's Lightning Round..."
|
|
return 40;
|
|
} else {
|
|
// Find the last entry with a set story flag
|
|
AdviceSummaryEntry *last = sNaviTable->summary + (sNaviTable->summary_count - 1);
|
|
for (s32 i = 0; i < sNaviTable->summary_count; i++) {
|
|
if (StoryflagManager::sInstance->getFlag(last->storyflag)) {
|
|
return last->KEN0_entry_num;
|
|
}
|
|
last--;
|
|
}
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
s16 FiContext::getFiAdviceHintEntry() {
|
|
if (StoryflagManager::sInstance->getFlag(STORYFLAG_BOSS_RUSH_ACTIVE)) {
|
|
// When in boss rush, no hint since location doesn't make sense
|
|
// "Master, this is a world built from your memories by Lanayru, the Thunder Dragon"
|
|
return 37;
|
|
} else {
|
|
// Find the first entry that matches the current stage, if its story flag matches (if any)
|
|
AdviceHintEntry *first = sNaviTable->hint;
|
|
for (s32 i = 0; i < sNaviTable->hint_count; i++) {
|
|
if (dScGame_c::isCurrentStage(first->stage_name_ptr) &&
|
|
(first->storyflag < 0 || StoryflagManager::sInstance->getFlag(first->storyflag))) {
|
|
return first->KEN1_entry_num;
|
|
}
|
|
first++;
|
|
}
|
|
// "This place has no name registered in my memory, and I have no useful data..."
|
|
return 31;
|
|
}
|
|
}
|
|
|
|
s16 FiContext::getObjective() {
|
|
if (StoryflagManager::sInstance->getFlag(STORYFLAG_BOSS_RUSH_ACTIVE)) {
|
|
// When in boss rush, objective is to beat the mode
|
|
// "Master, your primary objective is to defeat the enemy here and overcome the grueling task the Thunder
|
|
// Dragon, Lanayru, has set before you..."
|
|
return 92;
|
|
} else {
|
|
if (StoryflagManager::sInstance->getFlag(STORYFLAG_IMPRISONED_FIGHT) &&
|
|
StoryflagManager::sInstance->getFlag(STORYFLAG_IMPRISONED_1_FIGHT) &&
|
|
StoryflagManager::sInstance->getFlag(STORYFLAG_IMPRISONED_2_FIGHT) &&
|
|
StoryflagManager::sInstance->getFlag(STORYFLAG_IMPRISONED_3_FIGHT) &&
|
|
!StoryflagManager::sInstance->getFlag(STORYFLAG_IMPRISONED_3_DEFEATED)) {
|
|
// "Demise has once again broken the seal that binds him..."
|
|
return 70;
|
|
}
|
|
|
|
if (!StoryflagManager::sInstance->getCounterOrFlag(STORYFLAG_THUNDER_DRAGON_SOTH_COMPLETED)) {
|
|
if (ItemflagManager::sInstance->getFlagDirect(ITEM_LIFE_TREE_SEED) &&
|
|
!ItemflagManager::sInstance->getFlagDirect(ITEM_LIFE_TREE_FRUIT)) {
|
|
// "I project that the soil in Lanayru Province will not provide enough nourishment for the Life Tree
|
|
// Seedling..."
|
|
return 90;
|
|
}
|
|
|
|
if (ItemflagManager::sInstance->getFlagDirect(ITEM_LIFE_TREE_FRUIT)) {
|
|
// "Master, I recommend you take the Life Tree Fruit to the Thunder Dragon..."
|
|
return 91;
|
|
}
|
|
}
|
|
|
|
// Find the last entry with a set story flag
|
|
ObjectiveEntry *last = sNaviTable->objective + (sNaviTable->objective_count - 1);
|
|
for (s32 i = 0; i < sNaviTable->objective_count; i++) {
|
|
if (StoryflagManager::sInstance->getFlag(last->storyflag)) {
|
|
return last->KEN2_entry_num;
|
|
}
|
|
last--;
|
|
}
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
FiAnalysisHandle FiContext::getNaviTableEquipmentCheckEntry() {
|
|
AnalysisEntry *first = sNaviTable->analysis;
|
|
for (s32 i = 0; i < sNaviTable->analysis_count; i++) {
|
|
// Find the first entry that matches the current stage, if its story flag matches (if any)
|
|
if (dScGame_c::isCurrentStage(first->stage_name_ptr) &&
|
|
(first->storyflag < 0 || StoryflagManager::sInstance->getFlag(first->storyflag))) {
|
|
return FiAnalysisHandle(first);
|
|
}
|
|
first++;
|
|
}
|
|
return FiAnalysisHandle(nullptr);
|
|
}
|
|
|
|
s32 FiContext::setTargetedActorTextId(s32 id) {
|
|
setTargetActorId(-1);
|
|
s32 ret;
|
|
if (id < 256) {
|
|
ret = 6600; // Flow?
|
|
setTargetActorId(id);
|
|
} else if (id < 512) {
|
|
ret = 6500; // Flow?
|
|
setTargetActorId(id - 256);
|
|
} else if (id < 768) {
|
|
ret = 6700; // Flow?
|
|
setTargetActorId(id - 512);
|
|
} else {
|
|
ret = 6500; // Flow?
|
|
setTargetActorId(id - 668);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
u8 FiContext::rateBattlePerformance(u8 id) {
|
|
u8 ret = 0xFF;
|
|
u16 killCount = FileManager::GetInstance()->getEnemyKillCount(id);
|
|
u16 enemyHitCount = FileManager::GetInstance()->getHitCountFromEnemy(id);
|
|
if (killCount > 3 || enemyHitCount > 5) {
|
|
if (killCount == 0) {
|
|
ret = 6;
|
|
} else {
|
|
f32 ratio = (f32)enemyHitCount / (f32)killCount;
|
|
if (ratio <= 0.2f) {
|
|
ret = 0;
|
|
} else if (ratio <= (1.0f / 3.0f)) {
|
|
ret = 1;
|
|
} else if (ratio <= 1.0f) {
|
|
ret = 2;
|
|
} else if (ratio <= 1.5f) {
|
|
ret = 3;
|
|
} else if (ratio <= 2.5f) {
|
|
ret = 4;
|
|
} else if (ratio <= 4.0f) {
|
|
ret = 5;
|
|
} else {
|
|
ret = 6;
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
u16 FiContext::prepareFiHelpIndex() {
|
|
u16 ret = 0xFFFF;
|
|
setHelpIndex(-1);
|
|
|
|
if (dLytMeter_c::getShieldMaxDurability() > 0.0f && dLytMeter_c::getShieldCurrentDurability() > 0.0f) {
|
|
if (dLytMeter_c::getShieldCurrentDurability() <= 6.0f) {
|
|
if (!getField_0x4A()) {
|
|
ret = 6402;
|
|
// "The integrity of your shield has weakened..."
|
|
setHelpIndex(1);
|
|
}
|
|
} else {
|
|
if (getField_0x4A()) {
|
|
setField_0x4A(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (FileManager::GetInstance()->getCurrentHealth() <= 12 &&
|
|
!StoryflagManager::sInstance->getCounterOrFlag(STORYFLAG_LOW_HEART_NOTICE)) {
|
|
ret = 6401;
|
|
// "Your hearts have decreased quite dramatically..."
|
|
setHelpIndex(0);
|
|
}
|
|
|
|
if (dPad::ex_c::getInstance()->isLowBattery() || dPad::ex_c::getInstance()->isOutOfBattery()) {
|
|
if (!getField_0x48()) {
|
|
ret = 6400;
|
|
if (dPad::ex_c::getInstance()->isOutOfBattery()) {
|
|
// "Master, the batteries in your Wii Remote will be depleted any moment."
|
|
setHelpIndex(3);
|
|
} else {
|
|
// "Master, the batteries in your Wii Remote are nearly depleted."
|
|
setHelpIndex(2);
|
|
}
|
|
}
|
|
} else {
|
|
if (getField_0x48()) {
|
|
setField_0x48(false);
|
|
}
|
|
}
|
|
|
|
u32 numRupees = dAcItem_c::getRupeeCounter();
|
|
u32 walletCapacity = dAcItem_c::getCurrentWalletCapacity();
|
|
|
|
if (!StoryflagManager::sInstance->getFlag(STORYFLAG_WALLET_FULL_ACK) && walletCapacity != 0 &&
|
|
numRupees == walletCapacity && !getField_0x47()) {
|
|
ret = 6400;
|
|
// "Master, your wallet is full..."
|
|
setHelpIndex(5);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
s32 FiContext::getGlobalFiInfo0(s32 idx) {
|
|
if (sInstance != nullptr) {
|
|
return sInstance->mFiButtonOptions[idx];
|
|
}
|
|
return KEN8_None;
|
|
}
|
|
|
|
const wchar_t *FiContext::getButtonText(s32 idx) {
|
|
SizedString<16> str;
|
|
str.sprintf("KEN8_1%02d", idx + 1);
|
|
return dMessage_c::getTextMessageByLabel(str, true, nullptr, 0);
|
|
}
|
|
|
|
void FiContext::create() {
|
|
sInstance = new FiContext();
|
|
sInstance->reset();
|
|
}
|
|
|
|
void FiContext::reset() {
|
|
for (s32 i = 0; i < 10; i++) {
|
|
mFiButtonOptions[i] = KEN8_None;
|
|
field_0x3D[i] = 0xFF;
|
|
}
|
|
field_0x47 = false;
|
|
field_0x48 = false;
|
|
field_0x49 = false;
|
|
field_0x4A = false;
|
|
mSaveTimeRelated = 0xFF;
|
|
|
|
fn_8016CB40();
|
|
|
|
mTargetActorId = -1;
|
|
mFiHelpIndex = -1;
|
|
mIsInFiMainMenu = false;
|
|
}
|
|
|
|
void FiContext::resetSaveTimeRelated() {
|
|
mSaveTimeRelated = 0xFF;
|
|
}
|
|
|
|
extern "C" bool isInAnySkyKeepRoom();
|
|
|
|
void FiContext::prepareFiCallOptions() {
|
|
s32 seconds1 = OS_TICKS_TO_SEC(SaveTimeRelated::GetInstance()->getField_0x30());
|
|
s32 seconds2 = OS_TICKS_TO_SEC(SaveTimeRelated::GetInstance()->getField_0x08());
|
|
u8 val = 2;
|
|
if (seconds2 < 300) {
|
|
val = 0;
|
|
} else if (seconds1 < 300) {
|
|
val = 1;
|
|
}
|
|
if (mSaveTimeRelated != val) {
|
|
mSaveTimeRelated = val;
|
|
}
|
|
|
|
mFiButtonOptions[0] = KEN8_Advice;
|
|
if (dStageMgr_c::GetInstance()->getSTIFArea() == dStageMgr_c::STIF_AREA_SKY && !isInAnySkyKeepRoom() &&
|
|
!isInLeviasFightMaybe()) {
|
|
// In the Sky, show play time
|
|
mFiButtonOptions[1] = KEN8_PlayTime;
|
|
} else {
|
|
// Elswhere, and in Sky Keep and Levias fight, present analysis
|
|
mFiButtonOptions[1] = KEN8_Analysis;
|
|
}
|
|
mFiButtonOptions[2] = KEN8_Objective;
|
|
}
|
|
|
|
void FiContext::setAdviceOptions(s32 unused) {
|
|
mFiButtonOptions[0] = KEN8_Hint;
|
|
mFiButtonOptions[1] = KEN8_Rumors;
|
|
mFiButtonOptions[2] = KEN8_Summary;
|
|
}
|
|
|
|
void FiContext::resetAdviceOptions() {
|
|
mFiButtonOptions[0] = KEN8_Hint;
|
|
mFiButtonOptions[1] = KEN8_Rumors;
|
|
mFiButtonOptions[2] = KEN8_Summary;
|
|
}
|
|
|
|
void FiContext::fn_8016CB40() {
|
|
field_0x28 = 0;
|
|
field_0x2C = 1;
|
|
field_0x30 = 2;
|
|
}
|
|
|
|
bool FiContext::isInLeviasFightMaybe() {
|
|
if (dScGame_c::isCurrentStage("F023") && StoryflagManager::sInstance->getFlag(STORYFLAG_LEVIAS_FIGHT_STARTED) &&
|
|
!StoryflagManager::sInstance->getFlag(STORYFLAG_LEVIAS_FIGHT_DEFEATED)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|