diff options
Diffstat (limited to 'src/shop.c')
-rw-r--r-- | src/shop.c | 1251 |
1 files changed, 1251 insertions, 0 deletions
diff --git a/src/shop.c b/src/shop.c new file mode 100644 index 000000000..87dbbeb97 --- /dev/null +++ b/src/shop.c @@ -0,0 +1,1251 @@ +#include "global.h" +#include "shop.h" +#include "decompress.h" +#include "field_fadetransition.h" +#include "field_weather.h" +#include "item_menu.h" +#include "main.h" +#include "menu.h" +#include "menu_helpers.h" +#include "money.h" +#include "palette.h" +#include "script.h" +#include "sound.h" +#include "sprite.h" +#include "strings.h" +#include "task.h" +#include "tv.h" +#include "scanline_effect.h" +#include "event_object_movement.h" +#include "field_player_avatar.h" +#include "fieldmap.h" +#include "item.h" +#include "decoration.h" +#include "constants/items.h" +#include "constants/songs.h" +#include "overworld.h" +#include "decoration_inventory.h" +#include "field_camera.h" +#include "ewram.h" + +extern bool8 SellMenu_QuantityRoller(u8, u8); + +extern u8 gBuyMenuFrame_Gfx[]; +extern u16 gBuyMenuFrame_Tilemap[]; +extern u16 gMenuMoneyPal[16]; + +static void Shop_DisplayPriceInList(int firstItemId, int lastItemId, bool32 hasControlCode); +static void Shop_PrintItemDescText(void); +static void Task_ReturnToBuyMenu(u8); +static void Task_ExitBuyMenu(u8); +static void Task_ExitBuyMenuDoFade(u8); +static void Task_UpdatePurchaseHistory(u8); +static void Task_HandleShopMenuBuy(u8 taskId); +static void Task_HandleShopMenuSell(u8 taskId); +static void Task_HandleShopMenuQuit(u8 taskId); +static void Task_DoItemPurchase(u8 taskId); +static void Task_CancelItemPurchase(u8 taskId); +static void Task_DoBuySellMenu(u8); +static void Shop_FadeAndRunBuySellCallback(u8); +static void BuyMenuDrawGraphics(void); +static void sub_80B3240(void); +static void DrawFirstMartScrollIndicators(void); +static void Shop_DrawViewport(void); +static void Shop_InitMenus(int, int); +static void Shop_PrintItemDesc(void); +static void Shop_DoCursorAction(u8); +static void Shop_LoadViewportObjects(void); +static void Shop_AnimViewportObjects(void); + +// iwram +static struct MartInfo gMartInfo; + +// ewram +EWRAM_DATA u32 gMartTotalCost = 0; +EWRAM_DATA s16 gMartViewportObjects[16][4] = {0}; +EWRAM_DATA struct ItemSlot gMartPurchaseHistory[3] = {0}; +EWRAM_DATA u8 gMartPurchaseHistoryId = 0; + +EWRAM_DATA u8 gUnknown_02038731 = 0; // This really should be in fldeff_escalator, but being in a new file aligns the ewram, which doesnt match the ROM. + +// rodata +static const struct MenuAction2 sBuySellQuitMenuActions[] = +{ + { MartText_Buy, Task_HandleShopMenuBuy }, + { MartText_Sell, Task_HandleShopMenuSell }, + { MartText_Quit2, Task_HandleShopMenuQuit }, +}; + +static const u8 gMartBuySellOptionList[] = {SHOP_BUY, SHOP_SELL, SHOP_EXIT}; +static const u8 gMartBuyNoSellOptionList[] = {SHOP_BUY, SHOP_EXIT}; + +static const u16 gUnusedMartArray[] = {0x2, 0x3, 0x4, 0xD, 0x121, 0xE, 0xE, 0xE, 0xE, 0xE, 0xE, 0x0, 0x0}; + +static const struct YesNoFuncTable sShopPurchaseYesNoFuncs[] = +{ + Task_DoItemPurchase, + Task_CancelItemPurchase +}; + +static u8 CreateShopMenu(u8 martType) +{ + ScriptContext2_Enable(); + gMartInfo.martType = martType; + gMartInfo.cursor = 0; + + if (martType == MART_TYPE_0) + { + gMartInfo.numChoices = 2; + Menu_DrawStdWindowFrame(0, 0, 10, 7); + Menu_PrintItemsReordered(1, 1, 3, sBuySellQuitMenuActions, gMartBuySellOptionList); + } + else + { + gMartInfo.numChoices = 1; + Menu_DrawStdWindowFrame(0, 0, 10, 5); + Menu_PrintItemsReordered(1, 1, 2, sBuySellQuitMenuActions, gMartBuyNoSellOptionList); + } + InitMenu(0, 1, 1, gMartInfo.numChoices + 1, 0, 9); // add 1 for cancel + + return CreateTask(Task_DoBuySellMenu, 8); +} + +static void SetShopMenuCallback(void *callbackPtr) +{ + gMartInfo.callback = callbackPtr; +} + +static void SetShopItemsForSale(const u16 *items) +{ + u16 i = 0; + + gMartInfo.itemList = items; + gMartInfo.itemCount = 0; + + while (gMartInfo.itemList[i]) + { + gMartInfo.itemCount++; + i++; + } +} + +static void Task_DoBuySellMenu(u8 taskId) +{ + const u8 taskIdConst = taskId; // why is a local const needed to match? + + if (gMain.newAndRepeatedKeys & DPAD_UP) + { + if (gMartInfo.cursor) // can move cursor up? + { + PlaySE(SE_SELECT); + gMartInfo.cursor = Menu_MoveCursor(-1); + } + } + else if (gMain.newAndRepeatedKeys & DPAD_DOWN) + { + if (gMartInfo.cursor != gMartInfo.numChoices) // can move cursor down? + { + PlaySE(SE_SELECT); + gMartInfo.cursor = Menu_MoveCursor(1); + } + } + else if (gMain.newKeys & A_BUTTON) + { + PlaySE(SE_SELECT); + if (gMartInfo.martType == MART_TYPE_0) + sBuySellQuitMenuActions[gMartBuySellOptionList[gMartInfo.cursor]].func(taskIdConst); + else + sBuySellQuitMenuActions[gMartBuyNoSellOptionList[gMartInfo.cursor]].func(taskIdConst); + } + else if (gMain.newKeys & B_BUTTON) + { + PlaySE(SE_SELECT); + Task_HandleShopMenuQuit(taskIdConst); + } +} + +static void Task_HandleShopMenuBuy(u8 taskId) +{ + gTasks[taskId].data[8] = (u32)BuyMenuDrawGraphics >> 16; + gTasks[taskId].data[9] = (u32)BuyMenuDrawGraphics; + gTasks[taskId].func = Shop_FadeAndRunBuySellCallback; + FadeScreen(1, 0); +} + +static void Task_HandleShopMenuSell(u8 taskId) +{ + gTasks[taskId].data[8] = (u32)ItemMenu_LoadSellMenu >> 16; + gTasks[taskId].data[9] = (u32)ItemMenu_LoadSellMenu; + gTasks[taskId].func = Shop_FadeAndRunBuySellCallback; + FadeScreen(1, 0); +} + +static void Task_HandleShopMenuQuit(u8 taskId) +{ + Menu_DestroyCursor(); + Menu_EraseWindowRect(0, 0, 11, 8); + sub_80BE3BC(); + ScriptContext2_Disable(); + DestroyTask(taskId); + + if (gMartInfo.callback) + gMartInfo.callback(); // run the callback if it exists. +} + +static void Shop_FadeAndRunBuySellCallback(u8 taskId) +{ + if (!gPaletteFade.active) + { + SetMainCallback2((void *)((u16)gTasks[taskId].data[8] << 16 | (u16)gTasks[taskId].data[9])); + DestroyTask(taskId); + } +} + +static void ReturnToShopMenuAfterExitingSellMenu(u8 taskId) +{ + CreateShopMenu(gMartInfo.martType); + DestroyTask(taskId); +} + +static void Task_ReturnToMartMenu(u8 taskId) +{ + if (IsWeatherNotFadingIn() == 1) + { + if (gMartInfo.martType == MART_TYPE_2) + DisplayItemMessageOnField(taskId, gOtherText_CanIHelpYou, ReturnToShopMenuAfterExitingSellMenu, 0); + else + DisplayItemMessageOnField(taskId, gOtherText_AnythingElse, ReturnToShopMenuAfterExitingSellMenu, 0); + } +} + +void Shop_FadeReturnToMartMenu(void) +{ + pal_fill_black(); + CreateTask(Task_ReturnToMartMenu, 0x8); +} + +void Shop_RunExitSellMenuTask(u8 taskId) +{ + Task_ReturnToMartMenu(taskId); +} + +// unused +void Shop_LoadExitSellMenuTask(u8 taskId) +{ + gTasks[taskId].func = Task_ReturnToMartMenu; +} + +static void MainCB2(void) +{ + AnimateSprites(); + BuildOamBuffer(); + RunTasks(); + UpdatePaletteFade(); +} + +static void VBlankCB(void) +{ + LoadOam(); + ProcessSpriteCopyRequests(); + TransferPlttBuffer(); + DmaCopy16Defvars(3, gBGTilemapBuffers[1], (void *)(VRAM + 0xE800), 0x800); + DmaCopy16Defvars(3, gBGTilemapBuffers[2], (void *)(VRAM + 0xE000), 0x800); + DmaCopy16Defvars(3, gBGTilemapBuffers[3], (void *)(VRAM + 0xF000), 0x800); +} + +static void BuyMenuDrawGraphics(void) +{ + ClearVideoCallbacks(); + ScanlineEffect_Stop(); + REG_BG1HOFS = 0; + REG_BG1VOFS = 0; + REG_BG2HOFS = 0; + REG_BG2VOFS = 0; + REG_BG3HOFS = 0; + REG_BG3VOFS = 0; + gPaletteFade.bufferTransferDisabled = 1; + + /* + THEORY: This seemingly useless loop is required in order to match this + function without hacks. The reason is because it alters the 0 optimization + of a later assignment into using 2 different 0s instead of the same register. + It is speculated that at some point Game Freak insert an artificial + breakpoint here in order to look at the contents of OAM before it is cleared, + possibly because a programmer made a mistake in shop.c which corrupted its + contents. There may have been a macro here which at one point idled on the + while(1) but was changed to 0 for release due to a define somewhere. A + while(0) also matches, but it is more correct to use do {} while(0) as it + was a fix to prevent compiler warnings on older compilers. + */ + do {} while(0); + + DmaFill32Defvars(3, 0, (void*)OAM, OAM_SIZE); + LZDecompressVram(gBuyMenuFrame_Gfx, (void*)(VRAM + 0x7C00)); + LZDecompressWram(gBuyMenuFrame_Tilemap, ewram18000_2); + LoadCompressedPalette(gMenuMoneyPal, 0xC0, sizeof(gMenuMoneyPal)); + FreeAllSpritePalettes(); + ResetPaletteFade(); + ResetSpriteData(); + ResetTasks(); + Text_LoadWindowTemplate(&gWindowTemplate_81E6DFC); + InitMenuWindow(&gWindowTemplate_81E6DFC); + Shop_DrawViewport(); + gMartInfo.cursor = 0; + gMartInfo.choicesAbove = 0; + Menu_EraseWindowRect(0, 0, 0x20, 0x20); + OpenMoneyWindow(gSaveBlock1.money, 0, 0); + Shop_InitMenus(0, 7); + Shop_PrintItemDesc(); + DrawFirstMartScrollIndicators(); + CreateTask(Shop_DoCursorAction, 0x8); + sub_80B3240(); + BeginNormalPaletteFade(0xFFFFFFFF, 0, 0x10, 0, 0); + gPaletteFade.bufferTransferDisabled = 0; + SetVBlankCallback(VBlankCB); + SetMainCallback2(MainCB2); +} + +static void sub_80B3240(void) +{ + u16 colors[2] = {RGB(14, 15, 16), RGB_WHITE}; + + LoadPalette(&colors[1], 0xD1, sizeof colors[1]); + LoadPalette(&colors[0], 0xD8, sizeof colors[0]); +} + +static void DrawFirstMartScrollIndicators(void) +{ + ClearVerticalScrollIndicatorPalettes(); + + if (gMartInfo.itemCount > 7) + { + CreateVerticalScrollIndicators(TOP_ARROW, 172, 12); + CreateVerticalScrollIndicators(BOTTOM_ARROW, 172, 148); + SetVerticalScrollIndicators(TOP_ARROW, INVISIBLE); + } +} + +static void Shop_TryDrawVerticalScrollIndicators(void) +{ + if (gMartInfo.choicesAbove == 0) + SetVerticalScrollIndicators(TOP_ARROW, INVISIBLE); + else + SetVerticalScrollIndicators(TOP_ARROW, VISIBLE); + + if (gMartInfo.choicesAbove + 7 >= gMartInfo.itemCount) + SetVerticalScrollIndicators(BOTTOM_ARROW, INVISIBLE); + else + SetVerticalScrollIndicators(BOTTOM_ARROW, VISIBLE); +} + +// what is the point of this function? the tiles always get overwritten by BuyMenuDrawTextboxBG. +static void BuyMenuDrawTextboxBG_Old(u16 *array, s16 offset1, s16 offset2) +{ + array[offset1 + offset2] = 0xC3E1; + array[offset1 + offset2 + 1] = 0xC3E1; +} + +static void BuyMenuDrawMapMetatileLayer(u16 *array, s16 offset1, s16 offset2, u16 *array2) +{ + // This function draws a whole 2x2 metatile. + array[offset1 + offset2] = array2[0]; // top left + array[offset1 + offset2 + 1] = array2[1]; // top right + array[offset1 + offset2 + 32] = array2[2]; // bottom left + array[offset1 + offset2 + 33] = array2[3]; // bottom right +} + +static void BuyMenuDrawMapMetatile(int var1, int var2, u16 *var3, s32 var4) +{ + u8 tempVar4 = var4; + s16 offset1 = var1 * 2; + s16 offset2 = (var2 * 0x40) + 0x40; + + switch (tempVar4) + { + case 0: + BuyMenuDrawMapMetatileLayer(gBGTilemapBuffers[2], offset1, offset2, var3); + BuyMenuDrawMapMetatileLayer(gBGTilemapBuffers[1], offset1, offset2, var3 + 4); + break; + case 1: + BuyMenuDrawMapMetatileLayer(gBGTilemapBuffers[3], offset1, offset2, var3); + BuyMenuDrawMapMetatileLayer(gBGTilemapBuffers[2], offset1, offset2, var3 + 4); + break; + case 2: + BuyMenuDrawMapMetatileLayer(gBGTilemapBuffers[3], offset1, offset2, var3); + BuyMenuDrawMapMetatileLayer(gBGTilemapBuffers[1], offset1, offset2, var3 + 4); + break; + } +} + +// used to draw the border tiles around the viewport. +static void BuyMenuDrawMapPartialMetatile(s16 var1, int var2, u16 *var3) +{ + s16 offset1 = var1 * 2; + s16 offset2 = (var2 * 0x40) + 0x40; + + BuyMenuDrawMapMetatileLayer(gBGTilemapBuffers[3], offset1, offset2, var3); + BuyMenuDrawMapMetatileLayer(gBGTilemapBuffers[2], offset1, offset2, var3 + 4); +} + +static void Shop_DrawViewportTiles(void) +{ + s16 facingX; + s16 facingY; + s16 x; + s16 y; + + GetXYCoordsOneStepInFrontOfPlayer(&facingX, &facingY); + facingX -= 3; + facingY -= 3; + + for (y = 0; y < 6; y++) + { + for (x = 0; x < 7; x++) + { + u16 metatileId = MapGridGetMetatileIdAt(facingX + x, facingY + y); + + if (y != 5 && x != 6) + { + s32 r3 = MapGridGetMetatileLayerTypeAt(facingX + x, facingY + y); + + if (metatileId < 512) + BuyMenuDrawMapMetatile(x, y, (u16 *)gMapHeader.mapData->primaryTileset->metatiles + metatileId * 8, r3); + else + BuyMenuDrawMapMetatile(x, y, (u16 *)gMapHeader.mapData->secondaryTileset->metatiles + (metatileId - 512) * 8, r3); + } + else + { + if (metatileId < 512) + BuyMenuDrawMapPartialMetatile(x, y, (u16 *)gMapHeader.mapData->primaryTileset->metatiles + metatileId * 8); + else + BuyMenuDrawMapPartialMetatile(x, y, (u16 *)gMapHeader.mapData->secondaryTileset->metatiles + (metatileId - 512) * 8); + } + + if (y == 0 && x != 0 && x != 6) + BuyMenuDrawTextboxBG_Old(gBGTilemapBuffers[1], x * 2, 64); + } + } +} + +static void Shop_DrawViewport(void) +{ + ClearBGTilemapBuffers(); + Shop_LoadViewportObjects(); + Shop_AnimViewportObjects(); + Shop_DrawViewportTiles(); +} + +static void Shop_LoadViewportObjects(void) +{ + s16 facingX; + s16 facingY; + u8 playerHeight; + u8 y; + u8 x; + u8 r8 = 0; + + GetXYCoordsOneStepInFrontOfPlayer(&facingX, &facingY); + playerHeight = PlayerGetZCoord(); + for (y = 0; y < 16; y++) + gMartViewportObjects[y][MAP_OBJ_ID] = 16; + for (y = 0; y < 5; y++) + { + for (x = 0; x < 7; x++) + { + u8 mapObjId = GetFieldObjectIdByXYZ(facingX - 3 + x, facingY - 2 + y, playerHeight); + + if (mapObjId != 16) + { + gMartViewportObjects[r8][MAP_OBJ_ID] = mapObjId; + gMartViewportObjects[r8][X_COORD] = x; + gMartViewportObjects[r8][Y_COORD] = y; + if (gMapObjects[mapObjId].mapobj_unk_18 == 1) + gMartViewportObjects[r8][ANIM_NUM] = 0; + if (gMapObjects[mapObjId].mapobj_unk_18 == 2) + gMartViewportObjects[r8][ANIM_NUM] = 1; + if (gMapObjects[mapObjId].mapobj_unk_18 == 3) + gMartViewportObjects[r8][ANIM_NUM] = 2; + if (gMapObjects[mapObjId].mapobj_unk_18 == 4) + gMartViewportObjects[r8][ANIM_NUM] = 3; + r8++; + } + } + } +} + +static void Shop_AnimViewportObjects(void) +{ + u8 i; + + for (i = 0; i < 16; i++) // max objects? + { + if (gMartViewportObjects[i][MAP_OBJ_ID] == 16) + continue; + + StartSpriteAnim(&gSprites[AddPseudoFieldObject( + gMapObjects[gMartViewportObjects[i][MAP_OBJ_ID]].graphicsId, + SpriteCallbackDummy, + (u16)gMartViewportObjects[i][X_COORD] * 16 + 8, + (u16)gMartViewportObjects[i][Y_COORD] * 16 + 32, + 2)], + gMartViewportObjects[i][ANIM_NUM]); + } +} + +static void BuyMenuDrawTextboxBG(void) +{ + s16 i; + + for (i = 0; i < 0x400; i++) + { + if (ewram18000[i] != 0) + gBGTilemapBuffers[1][i] = ewram18000[i] + 0xC3E0; + } +} + +static void Shop_InitMenus(int firstItemId, int lastItemId) +{ + BuyMenuDrawTextboxBG(); + Shop_DisplayPriceInList(firstItemId, lastItemId, 0); + InitMenu(0, 0xE, 0x2, 0x8, gMartInfo.cursor, 0xF); +} + +// after printing the item quantity and price, restore the textbox tiles before the Yes/No prompt. +static void BuyMenuDrawTextboxBG_Restore(void) +{ + u16 i, j; + + for (i = 0; i < 8; i++) + for (j = 0; j < 14; j++) + gBGTilemapBuffers[1][32 * (i + 12) + j] = ewram18300[32 * i + j] + 0xC3E0; +} + +static void Shop_PrintItemDesc(void) +{ + Shop_PrintItemDescText(); +} + +#define tItemCount data[1] + +static void Shop_DisplayPriceInCheckoutWindow(u8 taskId) +{ + u16 itemListIndex = gMartInfo.choicesAbove + gMartInfo.cursor; + u16 itemId = gMartInfo.itemList[itemListIndex]; + u32 price = (ItemId_GetPrice(itemId) >> GetPriceReduction(1)); + + PrintMoneyAmount(gTasks[taskId].tItemCount * price, 6, 6, 11); + gStringVar1[0] = EXT_CTRL_CODE_BEGIN; + gStringVar1[1] = 0x14; + gStringVar1[2] = 0x6; + ConvertIntToDecimalStringN(&gStringVar1[3], gTasks[taskId].tItemCount, 1, 2); + Menu_PrintText(gOtherText_xString1, 1, 11); + sub_80A3FA0(gBGTilemapBuffers[1], 1, 11, 12, 2, 0xC3E1); +} + +static void Shop_DisplayNormalPriceInList(u16 itemId, u8 var2, bool32 hasControlCode) +{ + u8 *stringPtr = gStringVar1; + + if (hasControlCode) + { + stringPtr[0] = EXT_CTRL_CODE_BEGIN; + stringPtr[1] = 0x1; + stringPtr[2] = 0x2; + stringPtr += 3; + } + + CopyItemName(itemId, stringPtr); + + sub_8072A18(&gStringVar1[0], 0x70, var2 << 3, 0x58, 0x1); + stringPtr = gStringVar1; + + if (hasControlCode) + stringPtr = &gStringVar1[3]; + + GetMoneyAmountText(stringPtr, (ItemId_GetPrice(itemId) >> GetPriceReduction(1)), 0x4); + Menu_PrintTextPixelCoords(&gStringVar1[0], 0xCA, var2 << 3, 1); +} + +static void Shop_DisplayDecorationPriceInList(u16 itemId, u8 var2, bool32 hasControlCode) +{ + u8 *stringPtr = gStringVar1; + + if (hasControlCode) + { + stringPtr[0] = EXT_CTRL_CODE_BEGIN; + stringPtr[1] = 0x1; + stringPtr[2] = 0x2; + stringPtr += 3; + } + + StringCopy(stringPtr, gDecorations[itemId].name); + sub_8072A18(&gStringVar1[0], 0x70, var2 << 3, 0x58, 0x1); + stringPtr = gStringVar1; + + if (hasControlCode) + stringPtr = &gStringVar1[3]; + + // some names are the maximum string length for a shop item. Because there is no room for + // a 6 character price (including the currency), a sprite is instead used for anything that + // is the maximum decoration price in order to fit it on screen. + if (gDecorations[itemId].price == 10000) + { + Draw10000Sprite(0x19, var2, hasControlCode); + } + else + { + GetMoneyAmountText(stringPtr, gDecorations[itemId].price, 0x4); + Menu_PrintTextPixelCoords(&gStringVar1[0], 0xCA, var2 << 3, 0x1); + } +} + +static void Shop_DisplayPriceInList(int firstItemId, int lastItemId, bool32 hasControlCode) +{ + u8 i; + + for (i = firstItemId; i <= lastItemId && gMartInfo.choicesAbove + i < gMartInfo.itemCount; i++) + { + if (gMartInfo.martType == MART_TYPE_0) + Shop_DisplayNormalPriceInList(gMartInfo.itemList[gMartInfo.choicesAbove + i], (i << 1) + 2, hasControlCode); + else + Shop_DisplayDecorationPriceInList(gMartInfo.itemList[gMartInfo.choicesAbove + i], (i << 1) + 2, hasControlCode); + } + + if (i != 8 && gMartInfo.choicesAbove + i == gMartInfo.itemCount) + { + Menu_BlankWindowRect(0xE, (i << 1) + 2, 0x1C, (i << 1) + 3); + Menu_PrintText(gOtherText_CancelNoTerminator, 0xE, (i << 1) + 2); + } +} + +static void Shop_PrintItemDescText(void) +{ + if (gMartInfo.choicesAbove + gMartInfo.cursor != gMartInfo.itemCount) + { + if (gMartInfo.martType == MART_TYPE_0) + { + sub_8072AB0(ItemId_GetDescription(gMartInfo.itemList[gMartInfo.choicesAbove + gMartInfo.cursor]), + 0x4, 0x68, 0x68, 0x30, 0); + } + else + { + sub_8072AB0(gDecorations[gMartInfo.itemList[gMartInfo.choicesAbove + gMartInfo.cursor]].description, + 0x4, 0x68, 0x68, 0x30, 0); + } + } + else + { + sub_8072AB0(gOtherText_QuitShopping, 0x4, 0x68, 0x68, 0x30, 0); + } +} + +static void Shop_DoPremierBallCheck(u8 taskId) +{ + if (gMain.newKeys & A_BUTTON || gMain.newKeys & B_BUTTON) + { + Shop_DisplayPriceInList(gMartInfo.cursor, gMartInfo.cursor, 0); + PlaySE(SE_SELECT); + + if (gMartInfo.itemList[gMartInfo.choicesAbove + gMartInfo.cursor] == ITEM_POKE_BALL && gTasks[taskId].tItemCount >= 10 && AddBagItem(ITEM_PREMIER_BALL, 1) == TRUE) + DisplayItemMessageOnField(taskId, gOtherText_FreePremierBall, Task_ReturnToBuyMenu, 0xC3E1); + else + Task_ReturnToBuyMenu(taskId); + } +} + +static void Shop_DoItemTransaction(u8 taskId) +{ + IncrementGameStat(GAME_STAT_SHOPPED); + RemoveMoney(&gSaveBlock1.money, gMartTotalCost); + PlaySE(SE_REGI); + UpdateMoneyWindow(gSaveBlock1.money, 0, 0); + gTasks[taskId].func = Shop_DoPremierBallCheck; +} + +static void Shop_DoPricePrintAndReturnToBuyMenu(u8 taskId) +{ + Shop_DisplayPriceInList(gMartInfo.cursor, gMartInfo.cursor, 0); + Task_ReturnToBuyMenu(taskId); +} + +static void Task_DoItemPurchase(u8 taskId) +{ + Menu_EraseWindowRect(0x7, 0x8, 0xD, 0xD); + sub_80A3FA0(gBGTilemapBuffers[1], 8, 9, 4, 4, 0); + BuyMenuDrawTextboxBG_Restore(); + Shop_DrawViewportTiles(); + + if (IsEnoughMoney(gSaveBlock1.money, gMartTotalCost)) + { + if (gMartInfo.martType == MART_TYPE_0) + { + if (AddBagItem(gMartInfo.itemList[gMartInfo.choicesAbove + gMartInfo.cursor], gTasks[taskId].tItemCount)) + { + DisplayItemMessageOnField(taskId, gOtherText_HereYouGo, Shop_DoItemTransaction, 0xC3E1); + Task_UpdatePurchaseHistory(taskId); + } + else + { + DisplayItemMessageOnField(taskId, gOtherText_NoRoomFor, Shop_DoPricePrintAndReturnToBuyMenu, 0xC3E1); + } + } + else // a normal mart is only type 0, so types 1 and 2 are decoration marts. + { + if (IsThereStorageSpaceForDecoration(gMartInfo.itemList[gMartInfo.choicesAbove + gMartInfo.cursor])) + { + if (gMartInfo.martType == MART_TYPE_1) + DisplayItemMessageOnField(taskId, gOtherText_HereYouGo2, Shop_DoItemTransaction, 0xC3E1); + else + DisplayItemMessageOnField(taskId, gOtherText_HereYouGo3, Shop_DoItemTransaction, 0xC3E1); + } + else + { + StringExpandPlaceholders(gStringVar4, gOtherText_SpaceForIsFull); + DisplayItemMessageOnField(taskId, gStringVar4, Shop_DoPricePrintAndReturnToBuyMenu, 0xC3E1); + } + } + } + else + { + DisplayItemMessageOnField(taskId, gOtherText_NotEnoughMoney, Shop_DoPricePrintAndReturnToBuyMenu, 0xC3E1); + } +} + +static void Shop_DoYesNoPurchase(u8 taskId) +{ + DisplayYesNoMenu(7, 8, 1); + sub_80A3FA0(gBGTilemapBuffers[1], 8, 9, 4, 4, 0xC3E1); + DoYesNoFuncWithChoice(taskId, sShopPurchaseYesNoFuncs); +} + +static void Task_CancelItemPurchase(u8 taskId) +{ + Shop_DisplayPriceInList(gMartInfo.cursor, gMartInfo.cursor, 0); + Menu_EraseWindowRect(0x7, 0x8, 0xD, 0xD); + sub_80A3FA0(gBGTilemapBuffers[1], 0x8, 0x9, 0x4, 0x4, 0); + Task_ReturnToBuyMenu(taskId); +} + +static void Shop_PrintPrice(u8 taskId) +{ + if (SellMenu_QuantityRoller(taskId, gMartInfo.curItemCount) == TRUE) + Shop_DisplayPriceInCheckoutWindow(taskId); + + if (gMain.newKeys & A_BUTTON) + { + gMartTotalCost = (ItemId_GetPrice(gMartInfo.itemList[gMartInfo.choicesAbove + gMartInfo.cursor]) >> GetPriceReduction(1)) * gTasks[taskId].tItemCount; // set total cost of your purchase. + Menu_EraseWindowRect(0, 0xA, 0xD, 0xD); + sub_80A3FA0(gBGTilemapBuffers[1], 0x1, 0xB, 0xC, 0x2, 0); + BuyMenuDrawTextboxBG_Restore(); + Shop_DrawViewportTiles(); + CopyItemName(gMartInfo.itemList[gMartInfo.choicesAbove + gMartInfo.cursor], gStringVar1); + ConvertIntToDecimalStringN(gStringVar2, gTasks[taskId].tItemCount, 0, 0x2); + ConvertIntToDecimalStringN(gStringVar3, gMartTotalCost, 0, 0x8); + StringExpandPlaceholders(gStringVar4, gOtherText_ThatWillBe); + DisplayItemMessageOnField(taskId, gStringVar4, Shop_DoYesNoPurchase, 0xC3E1); + } + else if (gMain.newKeys & B_BUTTON) + { + Shop_DisplayPriceInList(gMartInfo.cursor, gMartInfo.cursor, 0); + Task_ReturnToBuyMenu(taskId); + } +} + +// set the item count in the mart info to the maximum allowed by the player's budget. +static void Shop_UpdateCurItemCountToMax(u8 taskId) +{ + u16 var; + + gTasks[taskId].tItemCount = 1; + Menu_DrawStdWindowFrame(0, 0xA, 0xD, 0xD); + Shop_DisplayPriceInCheckoutWindow(taskId); + + var = gSaveBlock1.money / (ItemId_GetPrice(gMartInfo.itemList[gMartInfo.choicesAbove + gMartInfo.cursor]) >> GetPriceReduction(1)); + if (var > 99) + gMartInfo.curItemCount = 99; + else + gMartInfo.curItemCount = var; + + gTasks[taskId].func = Shop_PrintPrice; +} + +#ifdef NONMATCHING +static void Shop_MoveItemListUp(void) +{ + u16 *r1; + u16 *r2; + register u8 *r10 asm("r10"); + s32 i; + s32 j; + struct Window *r8 = &gMenuWindow; + + r1 = r8->tilemap; + r1 += 0x1EF; + r2 = r1; + r2 += 64; + r10 = r8->tileData; + + for (i = 0; i < 14; i++) + { + for (j = 0; j < 15; j++) + { + if ((r1[j] & 0x3FF) <= r8->tileDataStartOffset + 1) + r2[j] = r8->tileDataStartOffset + 1; + else + r2[j] = r1[j] + 0x3C; + } + + r1 -= 32; + r2 -= 32; + } + + { + u8 *r1 = r10 + 0x3A20; + u8 *r2 = r1 + 0x780; + for (i = 0; i < 14; i++) + { + DmaCopy16(3, r1, r2, 0x1E0); + r2 -= 0x3C0; + r1 -= 0x3C0; + } + } +} +#else +__attribute__((naked)) +static void Shop_MoveItemListUp(void) +{ + asm(".syntax unified\n\ + push {r4-r7,lr}\n\ + mov r7, r10\n\ + mov r6, r9\n\ + mov r5, r8\n\ + push {r5-r7}\n\ + sub sp, 0x4\n\ + ldr r0, _080B4020 @ =gMenuWindow\n\ + mov r8, r0\n\ + ldr r1, [r0, 0x28]\n\ + ldr r3, _080B4024 @ =0x000003de\n\ + adds r1, r3\n\ + adds r2, r1, 0\n\ + adds r2, 0x80\n\ + ldr r7, [r0, 0x24]\n\ + mov r10, r7\n\ + ldr r0, _080B4028 @ =0x000003ff\n\ + mov r9, r0\n\ + movs r6, 0xD\n\ +_080B3FAC:\n\ + adds r3, r2, 0\n\ + subs r3, 0x40\n\ + str r3, [sp]\n\ + movs r7, 0x40\n\ + negs r7, r7\n\ + adds r7, r1\n\ + mov r12, r7\n\ + adds r3, r2, 0\n\ + adds r4, r1, 0\n\ + movs r5, 0xE\n\ +_080B3FC0:\n\ + ldrh r2, [r4]\n\ + mov r1, r9\n\ + ands r1, r2\n\ + mov r7, r8\n\ + ldrh r0, [r7, 0x1A]\n\ + adds r0, 0x1\n\ + cmp r1, r0\n\ + ble _080B3FD4\n\ + adds r0, r2, 0\n\ + adds r0, 0x3C\n\ +_080B3FD4:\n\ + strh r0, [r3]\n\ + adds r3, 0x2\n\ + adds r4, 0x2\n\ + subs r5, 0x1\n\ + cmp r5, 0\n\ + bge _080B3FC0\n\ + ldr r2, [sp]\n\ + mov r1, r12\n\ + subs r6, 0x1\n\ + cmp r6, 0\n\ + bge _080B3FAC\n\ + ldr r1, _080B402C @ =0x00003a20\n\ + add r1, r10\n\ + movs r0, 0xF0\n\ + lsls r0, 3\n\ + adds r2, r1, r0\n\ + ldr r3, _080B4030 @ =0x040000d4\n\ + ldr r5, _080B4034 @ =0x800000f0\n\ + ldr r4, _080B4038 @ =0xfffffc40\n\ + movs r6, 0xD\n\ +_080B3FFC:\n\ + str r1, [r3]\n\ + str r2, [r3, 0x4]\n\ + str r5, [r3, 0x8]\n\ + ldr r0, [r3, 0x8]\n\ + adds r2, r4\n\ + adds r1, r4\n\ + subs r6, 0x1\n\ + cmp r6, 0\n\ + bge _080B3FFC\n\ + add sp, 0x4\n\ + pop {r3-r5}\n\ + mov r8, r3\n\ + mov r9, r4\n\ + mov r10, r5\n\ + pop {r4-r7}\n\ + pop {r0}\n\ + bx r0\n\ + .align 2, 0\n\ +_080B4020: .4byte gMenuWindow\n\ +_080B4024: .4byte 0x000003de\n\ +_080B4028: .4byte 0x000003ff\n\ +_080B402C: .4byte 0x00003a20\n\ +_080B4030: .4byte 0x040000d4\n\ +_080B4034: .4byte 0x800000f0\n\ +_080B4038: .4byte 0xfffffc40\n\ + .syntax divided"); +} +#endif + +#ifdef NONMATCHING +static void Shop_MoveItemListDown(void) +{ + u16 *r1; + u16 *r2; + u8 *r10; + s32 i; + s32 j; + struct Window *r8 = &gMenuWindow; + + r1 = r8->tilemap; + r1 += 0x4F; + r2 = r1; + r2 += 64; + r10 = r8->tileData; + + for (i = 0; i < 14; i++) + { + for (j = 0; j < 15; j++) + { + if ((r1[j] & 0x3FF) <= r8->tileDataStartOffset + 1) + r2[j] = r8->tileDataStartOffset + 1; + else + r2[j] = r1[j] + 0x3C; + } + + r1 += 32; + r2 += 32; + } + + { + register u8 *r1 asm("r1") = r10 + 0x960; + register u8 *r2 asm("r2") = r1; + + r1 += 0x780; + for (i = 0; i < 14; i++) + { + DmaCopy16(3, r1, r2, 0x1E0); + r1 += 0x3C0; + r2 += 0x3C0; + } + } +} +#else +__attribute__((naked)) +static void Shop_MoveItemListDown(void) +{ + asm(".syntax unified\n\ + push {r4-r7,lr}\n\ + mov r7, r10\n\ + mov r6, r9\n\ + mov r5, r8\n\ + push {r5-r7}\n\ + sub sp, 0x4\n\ + ldr r0, _080B40D8 @ =gMenuWindow\n\ + mov r8, r0\n\ + ldr r2, [r0, 0x28]\n\ + adds r1, r2, 0\n\ + adds r1, 0x9E\n\ + adds r2, r1, 0\n\ + adds r1, 0x80\n\ + ldr r3, [r0, 0x24]\n\ + mov r10, r3\n\ + ldr r7, _080B40DC @ =0x000003ff\n\ + mov r9, r7\n\ + movs r6, 0xD\n\ +_080B4060:\n\ + adds r0, r2, 0\n\ + adds r0, 0x40\n\ + str r0, [sp]\n\ + movs r3, 0x40\n\ + adds r3, r1\n\ + mov r12, r3\n\ + adds r3, r2, 0\n\ + adds r4, r1, 0\n\ + movs r5, 0xE\n\ +_080B4072:\n\ + ldrh r2, [r4]\n\ + mov r1, r9\n\ + ands r1, r2\n\ + mov r7, r8\n\ + ldrh r0, [r7, 0x1A]\n\ + adds r0, 0x1\n\ + cmp r1, r0\n\ + ble _080B4086\n\ + adds r0, r2, 0\n\ + subs r0, 0x3C\n\ +_080B4086:\n\ + strh r0, [r3]\n\ + adds r3, 0x2\n\ + adds r4, 0x2\n\ + subs r5, 0x1\n\ + cmp r5, 0\n\ + bge _080B4072\n\ + ldr r2, [sp]\n\ + mov r1, r12\n\ + subs r6, 0x1\n\ + cmp r6, 0\n\ + bge _080B4060\n\ + movs r1, 0x96\n\ + lsls r1, 4\n\ + add r1, r10\n\ + adds r2, r1, 0\n\ + movs r0, 0xF0\n\ + lsls r0, 3\n\ + adds r1, r0\n\ + ldr r3, _080B40E0 @ =0x040000d4\n\ + ldr r5, _080B40E4 @ =0x800000f0\n\ + movs r4, 0xF0\n\ + lsls r4, 2\n\ + movs r6, 0xD\n\ +_080B40B4:\n\ + str r1, [r3]\n\ + str r2, [r3, 0x4]\n\ + str r5, [r3, 0x8]\n\ + ldr r0, [r3, 0x8]\n\ + adds r2, r4\n\ + adds r1, r4\n\ + subs r6, 0x1\n\ + cmp r6, 0\n\ + bge _080B40B4\n\ + add sp, 0x4\n\ + pop {r3-r5}\n\ + mov r8, r3\n\ + mov r9, r4\n\ + mov r10, r5\n\ + pop {r4-r7}\n\ + pop {r0}\n\ + bx r0\n\ + .align 2, 0\n\ +_080B40D8: .4byte gMenuWindow\n\ +_080B40DC: .4byte 0x000003ff\n\ +_080B40E0: .4byte 0x040000d4\n\ +_080B40E4: .4byte 0x800000f0\n\ + .syntax divided"); +} +#endif + +static void Shop_DoCursorAction(u8 taskId) +{ + if (!gPaletteFade.active) + { + if ((gMain.newAndRepeatedKeys & DPAD_ANY) == DPAD_UP) // only up can be pressed + { + if (gMartInfo.cursor == 0) + { + if (gMartInfo.choicesAbove == 0) // if there are no choices above, dont bother + return; + + PlaySE(SE_SELECT); + gMartInfo.choicesAbove--; // since cursor is at the top and there are choices above the top, scroll the menu up by updating choicesAbove. + Shop_MoveItemListUp(); + Shop_DisplayPriceInList(0, 0, 0); + Shop_PrintItemDescText(); + Shop_TryDrawVerticalScrollIndicators(); + } + else // if the cursor is not 0, choicesAbove cannot be updated yet since the cursor is at the top of the menu, so update cursor. + { + PlaySE(SE_SELECT); + gMartInfo.cursor = Menu_MoveCursor(-1); // move cursor up + Shop_PrintItemDescText(); + } + } + else if ((gMain.newAndRepeatedKeys & DPAD_ANY) == DPAD_DOWN) // only down can be pressed + { + if (gMartInfo.cursor == 7) // are you at the bottom of the menu? + { + if (gMartInfo.choicesAbove + gMartInfo.cursor == gMartInfo.itemCount) // are you at cancel? + return; + + PlaySE(SE_SELECT); + gMartInfo.choicesAbove++; + Shop_MoveItemListDown(); + Shop_DisplayPriceInList(7, 7, 0); + Shop_PrintItemDescText(); + Shop_TryDrawVerticalScrollIndicators(); + } + else if (gMartInfo.cursor != gMartInfo.itemCount) + { + PlaySE(SE_SELECT); + gMartInfo.cursor = Menu_MoveCursor(1); + Shop_PrintItemDescText(); + } + } + else if (gMain.newKeys & A_BUTTON) + { + PlaySE(SE_SELECT); + + if (gMartInfo.choicesAbove + gMartInfo.cursor != gMartInfo.itemCount) // did you not hit CANCEL? + { + PauseVerticalScrollIndicator(TOP_ARROW); + PauseVerticalScrollIndicator(BOTTOM_ARROW); + SetVerticalScrollIndicators(BOTTOM_ARROW, INVISIBLE); + Shop_DisplayPriceInList(gMartInfo.cursor, gMartInfo.cursor, 1); + Menu_DestroyCursor(); + Menu_EraseWindowRect(0, 0xC, 0xD, 0x13); + + if (gMartInfo.martType == MART_TYPE_0) + { + gMartTotalCost = (ItemId_GetPrice(gMartInfo.itemList[gMartInfo.choicesAbove + gMartInfo.cursor]) >> GetPriceReduction(1)); // set 1x price + if (!IsEnoughMoney(gSaveBlock1.money, gMartTotalCost)) + { + DisplayItemMessageOnField(taskId, gOtherText_NotEnoughMoney, Shop_DoPricePrintAndReturnToBuyMenu, 0xC3E1); // tail merge + } + else // _080B42BA + { + CopyItemName(gMartInfo.itemList[gMartInfo.choicesAbove + gMartInfo.cursor], gStringVar1); + StringExpandPlaceholders(gStringVar4, gOtherText_HowManyYouWant); + DisplayItemMessageOnField(taskId, gStringVar4, Shop_UpdateCurItemCountToMax, 0xC3E1); + } + } + else // _080B428C + { + gMartTotalCost = gDecorations[gMartInfo.itemList[gMartInfo.choicesAbove + gMartInfo.cursor]].price; + + if (!IsEnoughMoney(gSaveBlock1.money, gMartTotalCost)) + { + DisplayItemMessageOnField(taskId, gOtherText_NotEnoughMoney, Shop_DoPricePrintAndReturnToBuyMenu, 0xC3E1); // tail merge + } + else + { + StringCopy(gStringVar1, gDecorations[gMartInfo.itemList[gMartInfo.choicesAbove + gMartInfo.cursor]].name); + ConvertIntToDecimalStringN(gStringVar2, gMartTotalCost, 0, 0x8); + + if (gMartInfo.martType == MART_TYPE_1) + StringExpandPlaceholders(gStringVar4, gOtherText_ThatWillBe2); + else + StringExpandPlaceholders(gStringVar4, gOtherText_ThatWillBe3); + DisplayItemMessageOnField(taskId, gStringVar4, Shop_DoYesNoPurchase, 0xC3E1); + } + } + } + else + { + Task_ExitBuyMenu(taskId); + } + } + else if (gMain.newKeys & B_BUTTON) // go back to buy/sell/exit menu + { + PlaySE(SE_SELECT); + Task_ExitBuyMenu(taskId); + } + } +} + +static void Task_ReturnToBuyMenu(u8 taskId) +{ + Menu_EraseWindowRect(0, 0xE, 0x1D, 0x13); + Menu_EraseWindowRect(0, 0xA, 0xD, 0xD); + sub_80A3FA0(gBGTilemapBuffers[1], 0x1, 0xB, 0xC, 0x2, 0); + Shop_DrawViewportTiles(); + Shop_InitMenus(6, 7); + Shop_PrintItemDesc(); + StartVerticalScrollIndicators(TOP_ARROW); + StartVerticalScrollIndicators(BOTTOM_ARROW); + Shop_TryDrawVerticalScrollIndicators(); + gTasks[taskId].func = Shop_DoCursorAction; +} + +static void Task_ExitBuyMenu(u8 taskId) +{ + gFieldCallback = Shop_FadeReturnToMartMenu; + BeginNormalPaletteFade(-1, 0, 0, 0x10, 0); + gTasks[taskId].func = Task_ExitBuyMenuDoFade; +} + +static void Task_ExitBuyMenuDoFade(u8 taskId) +{ + if (!gPaletteFade.active) + { + CloseMoneyWindow(0, 0); + BuyMenuFreeMemory(); + SetMainCallback2(c2_exit_to_overworld_2_switch); + DestroyTask(taskId); + } +} + +// Task_UpdatePurchaseHistory +static void Task_UpdatePurchaseHistory(u8 taskId) +{ + u16 i; + + for (i = 0; i < 3; i++) + { + if (gMartPurchaseHistory[i].itemId == gMartInfo.itemList[gMartInfo.choicesAbove + gMartInfo.cursor] + && gMartPurchaseHistory[i].quantity != 0) + { + if (gMartPurchaseHistory[i].quantity + gTasks[taskId].tItemCount > 255) + gMartPurchaseHistory[i].quantity = 255; + else + gMartPurchaseHistory[i].quantity += gTasks[taskId].tItemCount; + return; + } + } + + if (gMartPurchaseHistoryId < 3) + { + gMartPurchaseHistory[gMartPurchaseHistoryId].itemId = gMartInfo.itemList[gMartInfo.choicesAbove + gMartInfo.cursor]; + gMartPurchaseHistory[gMartPurchaseHistoryId].quantity = gTasks[taskId].tItemCount; + gMartPurchaseHistoryId++; + } +} + +#undef tItemCount + +static void ClearItemPurchases(void) +{ + gMartPurchaseHistoryId = 0; + ClearItemSlots(gMartPurchaseHistory, 3); +} + +void Shop_CreatePokemartMenu(u16 *itemList) +{ + CreateShopMenu(MART_TYPE_0); + SetShopItemsForSale(itemList); + ClearItemPurchases(); + SetShopMenuCallback(EnableBothScriptContexts); +} + +void Shop_CreateDecorationShop1Menu(u16 *itemList) +{ + CreateShopMenu(MART_TYPE_1); + SetShopItemsForSale(itemList); + SetShopMenuCallback(EnableBothScriptContexts); +} + +void Shop_CreateDecorationShop2Menu(u16 *itemList) +{ + CreateShopMenu(MART_TYPE_2); + SetShopItemsForSale(itemList); + SetShopMenuCallback(EnableBothScriptContexts); +} + +#if DEBUG + +void debug_sub_80C2818(void) +{ + CreateShopMenu(MART_TYPE_0); + SetShopItemsForSale(gUnusedMartArray); + SetShopMenuCallback(NULL); +} + +#endif |