summaryrefslogtreecommitdiff
path: root/src/toBeSorted/fi_context.cpp
diff options
context:
space:
mode:
authorrobojumper <robojumper@gmail.com>2025-05-12 00:38:29 +0200
committerrobojumper <robojumper@gmail.com>2025-05-15 19:05:42 +0200
commit6646fb6135e26973dd8205007ee272baa5afdf3a (patch)
tree8d82d61375e93e42a5c0a241337cefc7bf61412c /src/toBeSorted/fi_context.cpp
parentba4be30be7c83c473a2ec2d5f421ffd5abf319bb (diff)
fi_context OK (also cleanup dFlow_c)
Diffstat (limited to 'src/toBeSorted/fi_context.cpp')
-rw-r--r--src/toBeSorted/fi_context.cpp439
1 files changed, 439 insertions, 0 deletions
diff --git a/src/toBeSorted/fi_context.cpp b/src/toBeSorted/fi_context.cpp
new file mode 100644
index 00000000..7a5831f8
--- /dev/null
+++ b/src/toBeSorted/fi_context.cpp
@@ -0,0 +1,439 @@
+#include "toBeSorted/fi_context.h"
+
+#include "common.h"
+#include "d/a/d_a_item.h"
+#include "d/d_message.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(530)) {
+ // "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() {
+ if (isValid()) {
+ return mpEntry->area_shield_danger;
+ } else {
+ return -1;
+ }
+}
+
+s16 FiAnalysisHandle::getShieldMessage() {
+ 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(530)) {
+ // 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(530)) {
+ // 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(530)) {
+ // 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(136) && StoryflagManager::sInstance->getFlag(143) &&
+ StoryflagManager::sInstance->getFlag(144) && StoryflagManager::sInstance->getFlag(145) &&
+ !StoryflagManager::sInstance->getFlag(133)) {
+ // "Demise has once again broken the seal that binds him..."
+ return 70;
+ }
+
+ if (!StoryflagManager::sInstance->getCounterOrFlag(21)) {
+ if (ItemflagManager::sInstance->getFlagDirect(0xC5) && !ItemflagManager::sInstance->getFlagDirect(0xC6)) {
+ // "I project that the soil in Lanayru Province will not provide enough nourishment for the Life Tree
+ // Seedling..."
+ return 90;
+ }
+
+ if (ItemflagManager::sInstance->getFlagDirect(0xC6)) {
+ // "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;
+}
+
+extern "C" bool isLowBattery1();
+extern "C" bool isLowBattery2();
+
+u16 FiContext::prepareFiHelpIndex() {
+ u16 ret = 0xFFFF;
+ setHelpIndex(-1);
+
+ if (dLytMeter_c::getShieldLevel() > 0.0f && dLytMeter_c::getShieldGaugePercentMaybe() > 0.0f) {
+ if (dLytMeter_c::getShieldGaugePercentMaybe() <= 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(808)) {
+ ret = 6401;
+ // "Your hearts have decreased quite dramatically..."
+ setHelpIndex(0);
+ }
+
+ if (isLowBattery2() || isLowBattery1()) {
+ if (!getField_0x48()) {
+ ret = 6400;
+ if (isLowBattery1()) {
+ // "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(727) && 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;
+ mDoSpecialFiMenuHandling = 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()->getSTIFbyte4() == 0 && !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(368) &&
+ !StoryflagManager::sInstance->getFlag(200)) {
+ return true;
+ }
+ return false;
+}