diff --git a/include/menumain.h b/include/menumain.h index b98d3151..147ac00c 100644 --- a/include/menumain.h +++ b/include/menumain.h @@ -56,7 +56,16 @@ typedef struct MenuTask { s32 (*drawCb)(struct MenuTask *, s32, s32); /* 0x0C: (task, ctx, dl) -> dl (func_801F16AC). */ u16 state; /* 0x10: per-task state word, zeroed on alloc. */ s8 inUse; /* 0x12: pool slot occupied. */ - u8 pad13[0x65]; + u8 pad13[0x15]; + u32 unk28; + u8 *unk2C; + u32 unk30; + u16 pad34; + u16 unk36; + u16 unk38; + u8 pad3A[0xB]; + u8 unk45; + u8 pad46[0x32]; } MenuTask; /* 0x78 bytes */ /** diff --git a/include/menushop.h b/include/menushop.h new file mode 100644 index 00000000..fc668e34 --- /dev/null +++ b/include/menushop.h @@ -0,0 +1,53 @@ +#ifndef MENUSHOP_H +#define MENUSHOP_H + +#define SHOP_COUNT 20 +#define SHOP_ITEM_COUNT 16 +#define ITEM_PRICE_COUNT 200 +#define ITEM_SLOT_COUNT 198 + +typedef struct { + u8 itemId; /**< 0x0: item ID. */ + u8 rarity; /**< 0x1: item rarity (0x00 = Rare, 0xFF = Common). */ +} ShopItemRarity; /* 0x2 = 2 bytes */ + +typedef struct { + u8 itemId; /**< 0x0: item ID. */ + u8 visible; /**< 0x1: item visibility flag. */ +} ShopItemVisibility; /* 0x2 = 2 bytes */ + +typedef struct { + u16 basePrice; /**< 0x0: base price. */ + u8 sellRate; /**< 0x2: sell rate. */ +} ShopItemPrice; /* 0x3 = 3 bytes */ + +typedef struct { + u8 unk0[0xC7]; +} Struct_D_801EB088; + +typedef struct { + u8 pad000[0x20]; + s32 *unk20; + u8 pad024[0x12]; + u16 unk36; +} Struct_801E8B60; + +extern ShopData D_80077CC8[SHOP_COUNT]; /**< Shop data table. */ +extern u8 D_80077EBC[ITEM_SLOT_COUNT]; /**< Item slot inventory. */ +extern ShopItemPrice D_801EA3F0[ITEM_PRICE_COUNT]; /**< Item price table. */ +extern u8 D_801EA70C[]; +extern u8 D_801F7F98[]; +extern u8 D_801E9B64[]; +extern u8 D_801E9B6C[]; +extern ShopItemRarity D_801EA170[SHOP_COUNT][SHOP_ITEM_COUNT]; /**< Shop item rarity tables. */ +extern ShopItemVisibility D_801EAA28[SHOP_ITEM_COUNT]; /**< Shop item visibility table. */ +extern s32 D_801EAA48[ITEM_PRICE_COUNT]; /**< Item sell prices. */ +extern s32 D_801EAD68[ITEM_PRICE_COUNT]; /**< Item buy prices. */ +extern Struct_D_801EB088 D_801EB088; +extern s32 g_menuColor; +extern s32 D_80077E70; +extern s32 func_801E8AB0; +extern void func_801E5E90(void*); +extern s32 func_801E79D4(void*, s32, s32); + +#endif /* MENUSHOP_H */ diff --git a/src/menu/menushop/menushop.c b/src/menu/menushop/menushop.c index c497e4c3..6723752c 100644 --- a/src/menu/menushop/menushop.c +++ b/src/menu/menushop/menushop.c @@ -1,14 +1,8 @@ #include "common.h" - -extern u8 D_801EA70C[]; -extern u8 D_801F7F98[]; -extern u8 D_801E9B64[]; -extern u8 D_801E9B6C[]; -extern u8 g_menuDisplayCfg[]; -extern s32 g_menuColor; -extern s32 D_80077E70; -extern s32 func_801E6EB0; -extern s32 func_801E8AB0; +#include "gamestate.h" +#include "menu.h" +#include "menushop.h" +#include "menumain.h" /** * @brief Look up a shop item byte from a table or item data. @@ -97,18 +91,156 @@ void func_801E5BA4(s32 a0, s32 a1) { INCLUDE_ASM("asm/ovl/menushop/nonmatchings/menushop", func_801E5C08); -INCLUDE_ASM("asm/ovl/menushop/nonmatchings/menushop", func_801E5D28); +s32 func_801E5D28(void) { + s32 result; + u8* ptr1; + u8* ptr2; + s32 i; + s32 val1; + s32 val2; + + if (g_gameState.mainData.partyLockFlag & 1) { + result = g_gameState.mainData.party.dreamGil; + } else { + result = g_gameState.mainData.party.gil; + } + + ptr1 = D_80077EBC; + + for (i = 0; i < ITEM_PRICE_COUNT; i++) { + D_801EB088.unk0[i] = 0; + } + + for (i = 0; i < ITEM_SLOT_COUNT; i++) { + val1 = *ptr1; + ptr1++; + + val2 = *ptr1; + ptr1++; + + if (val1 != 0) { + D_801EB088.unk0[val1] = val2; + } + } + + return result; +} + +/** + * @brief Compacts the shop item list in place. + * + * Scans the 16 entries in D_801EAA28 and keeps only entries whose + * itemId and visible fields are both non-zero. Valid entries are moved to + * the beginning of the table in their original order, while all + * remaining entries are cleared. + */ +void func_801E5DBC(void) { + u8 sp[SHOP_ITEM_COUNT]; + s32 counter; + s32 i; + ShopItemVisibility *ptr; + + ptr = D_801EAA28; + counter = 0; -INCLUDE_ASM("asm/ovl/menushop/nonmatchings/menushop", func_801E5DBC); + for (i = 0 ; i < SHOP_ITEM_COUNT; i++, ptr++) { + if (ptr->itemId != 0 && ptr->visible != 0) { + sp[counter] = i; + counter++; + } + } + + ptr = D_801EAA28; + + for (i = 0; i < counter; i++) { + if (i != sp[i]) { + ptr[i] = ptr[sp[i]]; + } + } + + for (; i < SHOP_ITEM_COUNT; i++) { + ptr[i].itemId = 0; + ptr[i].visible = 0; + } +} void func_801E5E88(void) { } INCLUDE_ASM("asm/ovl/menushop/nonmatchings/menushop", func_801E5E90); -INCLUDE_ASM("asm/ovl/menushop/nonmatchings/menushop", func_801E6A68); +/** + * @brief Populate shop item visibility data for a shop. + * + * Copies item IDs from the shop's rarity table and visibility flags from + * the shop inventory into the shared visibility table. + */ +void func_801E6A68(s32 shopId) { + ShopItemVisibility *visibility; + ShopItemRarity *rarity; + ShopData *shop; + s32 i; + + visibility = D_801EAA28; + + rarity = D_801EA170[0]; + rarity += shopId * 16; + + shop = &D_80077CC8[shopId]; + + for (i = 0; i < SHOP_ITEM_COUNT; i++) { + visibility->itemId = rarity[i].itemId; + visibility->visible = shop->items[i]; + visibility++; + } +} + +/** + * @brief Calculates and stores shop buy and sell prices. + * + * The flags returned by func_801F72B4 determine the price modifiers: + * - bit 0 reduces the buy price by 25%. + * - bit 1 increases the sell price by 50%. + * Buy prices are stored in D_801EAD68, and sell prices in D_801EAA48. + * Prices are calculated using integer arithmetic and are clamped to a + * minimum value of 1. + */ +void func_801E6ACC(void) { + ShopItemPrice *itemPrice; + s32 result; + s32 i; + s32 x; + s32 y; + + result = func_801F72B4(); + itemPrice = D_801EA3F0; + + for (i = 0; i < ITEM_PRICE_COUNT; i++) { + if (result & 1) { + D_801EAD68[i] = (itemPrice[i].basePrice * 15) / 2; + } else { + D_801EAD68[i] = itemPrice[i].basePrice * 10; + } + + if (result & 2) { + x = itemPrice[i].sellRate * 10; + y = itemPrice[i].basePrice * x; + D_801EAA48[i] = y * 3 / 40; + } else { + x = itemPrice[i].sellRate * 10; + y = itemPrice[i].basePrice * x; + D_801EAA48[i] = y / 20; + } + + if (D_801EAA48[i] == 0) { + D_801EAA48[i] = 1; + } + if (D_801EAD68[i] == 0) { + D_801EAD68[i] = 1; + } + } +} -INCLUDE_ASM("asm/ovl/menushop/nonmatchings/menushop", func_801E6ACC); INCLUDE_ASM("asm/ovl/menushop/nonmatchings/menushop", func_801E6C3C); @@ -116,7 +248,25 @@ INCLUDE_ASM("asm/ovl/menushop/nonmatchings/menushop", func_801E6D54); INCLUDE_ASM("asm/ovl/menushop/nonmatchings/menushop", func_801E6E0C); -INCLUDE_ASM("asm/ovl/menushop/nonmatchings/menushop", func_801E6EB0); +s32 func_801E6EB0(s32 arg0, s32 arg1, s32 arg2, s32 arg3, s32 arg4) { + u8 buffer[0x80]; + s32 msg; + s32 temp_s0; + s32 temp_s1; + s32 temp_v0; + s32 result; + + msg = ((s32 *)(g_menuDisplayCfg.dataPtr))[arg2]; + result = arg1; + if (msg != 0) { + temp_v0 = (arg4 + 0xA); + temp_s0 = g_menuDisplayCfg.x + temp_v0; + temp_s1 = g_menuDisplayCfg.y + 5; + decodeMessage(msg, buffer, -1); + result = func_801F0FEC(arg0, arg1, temp_s0, temp_s1, buffer, 7); + } + return result; +} /** * @brief Configure display parameters and invoke callback for shop list rendering. @@ -133,18 +283,17 @@ INCLUDE_ASM("asm/ovl/menushop/nonmatchings/menushop", func_801E6EB0); * @param arg5 X position for the display configuration. */ void func_801E6F60(u8 *a0, s32 a1, s32 a2, s32 a3, s32 arg5) { - s32 cfg = (s32)g_menuDisplayCfg; - *(u8 *)(cfg + 0x10) = 0; - *(u8 *)(cfg + 0x11) = 0; - *(s16 *)&g_menuDisplayCfg[0] = a3; - *(s16 *)(cfg + 0x04) = 0x144; - *(s16 *)(cfg + 0x06) = 0x14; - *(u8 *)(cfg + 0x13) = 1; - *(u8 *)(cfg + 0x16) = 0; - *(u8 *)(cfg + 0x17) = 1; - *(s16 *)(cfg + 0x02) = arg5; - *(s16 *)(cfg + 0x14) = *(u16 *)(a0 + 0x3A); - *(s32 *)(cfg + 0x20) = (s32)(a0 + 0x20); + g_menuDisplayCfg.iconType = 0; + g_menuDisplayCfg.iconSubType = 0; + g_menuDisplayCfg.x = a3; + g_menuDisplayCfg.w = 0x144; + g_menuDisplayCfg.h = 0x14; + g_menuDisplayCfg.columnCount = 1; + g_menuDisplayCfg.pageStart = 0; + g_menuDisplayCfg.pageEnd = 1; + g_menuDisplayCfg.y = arg5; + g_menuDisplayCfg.scrollOffset = *(u16 *)(a0 + 0x3A); + g_menuDisplayCfg.dataPtr = (s32)(a0 + 0x20); { func_801EFBB4(a1, a2, (s32)&func_801E6EB0); } @@ -162,11 +311,47 @@ INCLUDE_ASM("asm/ovl/menushop/nonmatchings/menushop", func_801E7628); INCLUDE_ASM("asm/ovl/menushop/nonmatchings/menushop", func_801E77EC); -INCLUDE_ASM("asm/ovl/menushop/nonmatchings/menushop", func_801E791C); +void func_801E791C(s32 arg0, s32 arg1, s32 arg2, s32 arg3, s32 arg4) { + s32 var_v0; + MenuDisplayConfig *cfg; + + cfg = &g_menuDisplayCfg; + + var_v0 = arg2; + if (arg0 != 0) { + var_v0 = func_801F0FEC(arg1, var_v0, arg3 + 0xC, arg4 + 5, arg0, 7); + } + cfg->iconType = 0; + cfg->iconSubType = 0; + cfg->x = arg3; + cfg->w = 0xDA; + cfg->y = (s16) arg4; + cfg->h = 0x15; + func_801EF9AC(arg1, var_v0, 0x1000, g_menuColor); +} INCLUDE_ASM("asm/ovl/menushop/nonmatchings/menushop", func_801E79D4); -INCLUDE_ASM("asm/ovl/menushop/nonmatchings/menushop", func_801E7B9C); +void func_801E7B9C(s32 a0) { + MenuTask *temp_s0; + + temp_s0 = (MenuTask *)func_801F179C((s32)func_801E5E90, (s32)func_801E79D4); + func_801F1D2C(0, (s32)"shop.bin", (s32)D_801EA170); + func_801F1D2C(0, (s32)"price.bin", (s32)D_801EA3F0); + func_801F1D2C(0, (s32)"mitem.bin", (s32)D_801EA70C); + if (temp_s0 != NULL) { + temp_s0->unk2C = D_80077EBC; + temp_s0->unk36 = 0x1000; + temp_s0->unk30 = 0; + temp_s0->unk28 = func_801E5D28(); + temp_s0->unk45 = D_801E9B6C[func_801EFFF0()]; + func_801E6D54(temp_s0->unk45); + func_801E5E90(temp_s0); + if (func_801EFFB8() == 0x17) { + func_801F1D84(); + } + } +} /** * @brief Process shop state and dispatch to appropriate handler. @@ -265,19 +450,18 @@ INCLUDE_ASM("asm/ovl/menushop/nonmatchings/menushop", func_801E8AB0); * @param a3 Y position for the display configuration. * @param arg5 X position for the display configuration. */ -void func_801E8B60(u8 *a0, s32 a1, s32 a2, s32 a3, s32 arg5) { - s32 cfg = (s32)g_menuDisplayCfg; - *(u8 *)(cfg + 0x10) = 0; - *(u8 *)(cfg + 0x11) = 0; - *(s16 *)&g_menuDisplayCfg[0] = a3; - *(s16 *)(cfg + 0x04) = 0x144; - *(s16 *)(cfg + 0x06) = 0x14; - *(u8 *)(cfg + 0x13) = 1; - *(u8 *)(cfg + 0x16) = 0; - *(u8 *)(cfg + 0x17) = 1; - *(s16 *)(cfg + 0x02) = arg5; - *(s16 *)(cfg + 0x14) = *(u16 *)(a0 + 0x36); - *(s32 *)(cfg + 0x20) = (s32)(a0 + 0x20); +void func_801E8B60(Struct_801E8B60 *a0, s32 a1, s32 a2, s32 a3, s32 arg5) { + g_menuDisplayCfg.iconType = 0; + g_menuDisplayCfg.iconSubType = 0; + g_menuDisplayCfg.x = a3; + g_menuDisplayCfg.w = 0x144; + g_menuDisplayCfg.h = 0x14; + g_menuDisplayCfg.columnCount = 1; + g_menuDisplayCfg.pageStart = 0; + g_menuDisplayCfg.pageEnd = 1; + g_menuDisplayCfg.y = arg5; + g_menuDisplayCfg.scrollOffset = a0->unk36; + g_menuDisplayCfg.dataPtr = &a0->unk20; { func_801EFBB4(a1, a2, (s32)&func_801E8AB0); } @@ -326,16 +510,14 @@ INCLUDE_ASM("asm/ovl/menushop/nonmatchings/menushop", func_801E90F8); */ void func_801E9554(s32 a0, s32 a1, s32 a2, s32 a3, s32 arg4) { s32 result; - u8 *cfg; result = func_801E90F8(a0, a1, a2, a3, arg4); - cfg = g_menuDisplayCfg; - *(u8 *)(cfg + 0x10) = 0x57; - *(u8 *)(cfg + 0x11) = 0; - *(s16 *)&g_menuDisplayCfg[0] = a3; - *(s16 *)(cfg + 0x04) = 0x150; - *(s16 *)(cfg + 0x02) = arg4; - *(s16 *)(cfg + 0x06) = 0x26; + g_menuDisplayCfg.iconType = 0x57; + g_menuDisplayCfg.iconSubType = 0; + g_menuDisplayCfg.x = a3; + g_menuDisplayCfg.w = 0x150; + g_menuDisplayCfg.y = arg4; + g_menuDisplayCfg.h = 0x26; func_801EF9AC(a1, result, 0x1000, g_menuColor); }