pokeemerald/src/shop.c

1269 lines
38 KiB
C

#include "global.h"
#include "bg.h"
#include "data.h"
#include "decompress.h"
#include "decoration.h"
#include "decoration_inventory.h"
#include "event_object_movement.h"
#include "field_player_avatar.h"
#include "field_screen_effect.h"
#include "field_weather.h"
#include "fieldmap.h"
#include "gpu_regs.h"
#include "graphics.h"
#include "international_string_util.h"
#include "item.h"
#include "item_icon.h"
#include "item_menu.h"
#include "list_menu.h"
#include "main.h"
#include "malloc.h"
#include "menu.h"
#include "menu_helpers.h"
#include "money.h"
#include "overworld.h"
#include "palette.h"
#include "party_menu.h"
#include "scanline_effect.h"
#include "script.h"
#include "shop.h"
#include "sound.h"
#include "sprite.h"
#include "string_util.h"
#include "strings.h"
#include "text_window.h"
#include "tv.h"
#include "constants/decorations.h"
#include "constants/items.h"
#include "constants/metatile_behaviors.h"
#include "constants/rgb.h"
#include "constants/songs.h"
#define TAG_SCROLL_ARROW 2100
#define TAG_ITEM_ICON_BASE 2110
#define MAX_ITEMS_SHOWN 8
enum {
WIN_BUY_SELL_QUIT,
WIN_BUY_QUIT,
};
enum {
WIN_MONEY,
WIN_ITEM_LIST,
WIN_ITEM_DESCRIPTION,
WIN_QUANTITY_IN_BAG,
WIN_QUANTITY_PRICE,
WIN_MESSAGE,
};
enum {
COLORID_NORMAL, // Item descriptions, quantity in bag, and quantity/price
COLORID_ITEM_LIST, // The text in the item list, and the cursor normally
COLORID_GRAY_CURSOR, // When the cursor has selected an item to purchase
};
enum {
MART_TYPE_NORMAL,
MART_TYPE_DECOR,
MART_TYPE_DECOR2,
};
// shop view window NPC info enum
enum
{
OBJ_EVENT_ID,
X_COORD,
Y_COORD,
ANIM_NUM,
LAYER_TYPE
};
struct MartInfo
{
void (*callback)(void);
const struct MenuAction *menuActions;
const u16 *itemList;
u16 itemCount;
u8 windowId;
u8 martType;
};
struct ShopData
{
u16 tilemapBuffers[4][0x400];
u32 totalCost;
u16 itemsShowed;
u16 selectedRow;
u16 scrollOffset;
u8 maxQuantity;
u8 scrollIndicatorsTaskId;
u8 iconSlot;
u8 itemSpriteIds[2];
s16 viewportObjects[OBJECT_EVENTS_COUNT][5];
};
static EWRAM_DATA struct MartInfo sMartInfo = {0};
static EWRAM_DATA struct ShopData *sShopData = NULL;
static EWRAM_DATA struct ListMenuItem *sListMenuItems = NULL;
static EWRAM_DATA u8 (*sItemNames)[ITEM_NAME_LENGTH + 2] = {0};
static EWRAM_DATA u8 sPurchaseHistoryId = 0;
EWRAM_DATA struct ItemSlot gMartPurchaseHistory[SMARTSHOPPER_NUM_ITEMS] = {0};
static void Task_ShopMenu(u8 taskId);
static void Task_HandleShopMenuQuit(u8 taskId);
static void CB2_InitBuyMenu(void);
static void Task_GoToBuyOrSellMenu(u8 taskId);
static void MapPostLoadHook_ReturnToShopMenu(void);
static void Task_ReturnToShopMenu(u8 taskId);
static void ShowShopMenuAfterExitingBuyOrSellMenu(u8 taskId);
static void BuyMenuDrawGraphics(void);
static void BuyMenuAddScrollIndicatorArrows(void);
static void Task_BuyMenu(u8 taskId);
static void BuyMenuBuildListMenuTemplate(void);
static void BuyMenuInitBgs(void);
static void BuyMenuInitWindows(void);
static void BuyMenuDecompressBgGraphics(void);
static void BuyMenuSetListEntry(struct ListMenuItem *, u16, u8 *);
static void BuyMenuAddItemIcon(u16, u8);
static void BuyMenuRemoveItemIcon(u16, u8);
static void BuyMenuPrint(u8 windowId, const u8 *text, u8 x, u8 y, s8 speed, u8 colorSet);
static void BuyMenuDrawMapGraphics(void);
static void BuyMenuCopyMenuBgToBg1TilemapBuffer(void);
static void BuyMenuCollectObjectEventData(void);
static void BuyMenuDrawObjectEvents(void);
static void BuyMenuDrawMapBg(void);
static bool8 BuyMenuCheckForOverlapWithMenuBg(int, int);
static void BuyMenuDrawMapMetatile(s16, s16, const u16 *, u8);
static void BuyMenuDrawMapMetatileLayer(u16 *dest, s16 offset1, s16 offset2, const u16 *src);
static bool8 BuyMenuCheckIfObjectEventOverlapsMenuBg(s16 *);
static void ExitBuyMenu(u8 taskId);
static void Task_ExitBuyMenu(u8 taskId);
static void BuyMenuTryMakePurchase(u8 taskId);
static void BuyMenuReturnToItemList(u8 taskId);
static void Task_BuyHowManyDialogueInit(u8 taskId);
static void BuyMenuConfirmPurchase(u8 taskId);
static void BuyMenuPrintItemQuantityAndPrice(u8 taskId);
static void Task_BuyHowManyDialogueHandleInput(u8 taskId);
static void BuyMenuSubtractMoney(u8 taskId);
static void RecordItemPurchase(u8 taskId);
static void Task_ReturnToItemListAfterItemPurchase(u8 taskId);
static void Task_ReturnToItemListAfterDecorationPurchase(u8 taskId);
static void Task_HandleShopMenuBuy(u8 taskId);
static void Task_HandleShopMenuSell(u8 taskId);
static void BuyMenuPrintItemDescriptionAndShowItemIcon(s32 item, bool8 onInit, struct ListMenu *list);
static void BuyMenuPrintPriceInList(u8 windowId, u32 itemId, u8 y);
static const struct YesNoFuncTable sShopPurchaseYesNoFuncs =
{
BuyMenuTryMakePurchase,
BuyMenuReturnToItemList
};
static const struct MenuAction sShopMenuActions_BuySellQuit[] =
{
{ gText_ShopBuy, {.void_u8=Task_HandleShopMenuBuy} },
{ gText_ShopSell, {.void_u8=Task_HandleShopMenuSell} },
{ gText_ShopQuit, {.void_u8=Task_HandleShopMenuQuit} }
};
static const struct MenuAction sShopMenuActions_BuyQuit[] =
{
{ gText_ShopBuy, {.void_u8=Task_HandleShopMenuBuy} },
{ gText_ShopQuit, {.void_u8=Task_HandleShopMenuQuit} }
};
static const struct WindowTemplate sShopMenuWindowTemplates[] =
{
[WIN_BUY_SELL_QUIT] = {
.bg = 0,
.tilemapLeft = 2,
.tilemapTop = 1,
.width = 9,
.height = 6,
.paletteNum = 15,
.baseBlock = 0x0008,
},
// Separate shop menu window for decorations, which can't be sold
[WIN_BUY_QUIT] = {
.bg = 0,
.tilemapLeft = 2,
.tilemapTop = 1,
.width = 9,
.height = 4,
.paletteNum = 15,
.baseBlock = 0x0008,
}
};
static const struct ListMenuTemplate sShopBuyMenuListTemplate =
{
.items = NULL,
.moveCursorFunc = BuyMenuPrintItemDescriptionAndShowItemIcon,
.itemPrintFunc = BuyMenuPrintPriceInList,
.totalItems = 0,
.maxShowed = 0,
.windowId = WIN_ITEM_LIST,
.header_X = 0,
.item_X = 8,
.cursor_X = 0,
.upText_Y = 1,
.cursorPal = 2,
.fillValue = 0,
.cursorShadowPal = 3,
.lettersSpacing = 0,
.itemVerticalPadding = 0,
.scrollMultiple = LIST_NO_MULTIPLE_SCROLL,
.fontId = FONT_NARROW,
.cursorKind = CURSOR_BLACK_ARROW
};
static const struct BgTemplate sShopBuyMenuBgTemplates[] =
{
{
.bg = 0,
.charBaseIndex = 2,
.mapBaseIndex = 31,
.screenSize = 0,
.paletteMode = 0,
.priority = 0,
.baseTile = 0
},
{
.bg = 1,
.charBaseIndex = 0,
.mapBaseIndex = 30,
.screenSize = 0,
.paletteMode = 0,
.priority = 1,
.baseTile = 0
},
{
.bg = 2,
.charBaseIndex = 0,
.mapBaseIndex = 29,
.screenSize = 0,
.paletteMode = 0,
.priority = 2,
.baseTile = 0
},
{
.bg = 3,
.charBaseIndex = 0,
.mapBaseIndex = 28,
.screenSize = 0,
.paletteMode = 0,
.priority = 3,
.baseTile = 0
}
};
static const struct WindowTemplate sShopBuyMenuWindowTemplates[] =
{
[WIN_MONEY] = {
.bg = 0,
.tilemapLeft = 1,
.tilemapTop = 1,
.width = 10,
.height = 2,
.paletteNum = 15,
.baseBlock = 0x001E,
},
[WIN_ITEM_LIST] = {
.bg = 0,
.tilemapLeft = 14,
.tilemapTop = 2,
.width = 15,
.height = 16,
.paletteNum = 15,
.baseBlock = 0x0032,
},
[WIN_ITEM_DESCRIPTION] = {
.bg = 0,
.tilemapLeft = 0,
.tilemapTop = 13,
.width = 14,
.height = 6,
.paletteNum = 15,
.baseBlock = 0x0122,
},
[WIN_QUANTITY_IN_BAG] = {
.bg = 0,
.tilemapLeft = 1,
.tilemapTop = 11,
.width = 12,
.height = 2,
.paletteNum = 15,
.baseBlock = 0x0176,
},
[WIN_QUANTITY_PRICE] = {
.bg = 0,
.tilemapLeft = 18,
.tilemapTop = 11,
.width = 10,
.height = 2,
.paletteNum = 15,
.baseBlock = 0x018E,
},
[WIN_MESSAGE] = {
.bg = 0,
.tilemapLeft = 2,
.tilemapTop = 15,
.width = 27,
.height = 4,
.paletteNum = 15,
.baseBlock = 0x01A2,
},
DUMMY_WIN_TEMPLATE
};
static const struct WindowTemplate sShopBuyMenuYesNoWindowTemplates =
{
.bg = 0,
.tilemapLeft = 21,
.tilemapTop = 9,
.width = 5,
.height = 4,
.paletteNum = 15,
.baseBlock = 0x020E,
};
static const u8 sShopBuyMenuTextColors[][3] =
{
[COLORID_NORMAL] = {1, 2, 3},
[COLORID_ITEM_LIST] = {0, 2, 3},
[COLORID_GRAY_CURSOR] = {0, 3, 2},
};
static u8 CreateShopMenu(u8 martType)
{
int numMenuItems;
LockPlayerFieldControls();
sMartInfo.martType = martType;
if (martType == MART_TYPE_NORMAL)
{
struct WindowTemplate winTemplate = sShopMenuWindowTemplates[WIN_BUY_SELL_QUIT];
winTemplate.width = GetMaxWidthInMenuTable(sShopMenuActions_BuySellQuit, ARRAY_COUNT(sShopMenuActions_BuySellQuit));
sMartInfo.windowId = AddWindow(&winTemplate);
sMartInfo.menuActions = sShopMenuActions_BuySellQuit;
numMenuItems = ARRAY_COUNT(sShopMenuActions_BuySellQuit);
}
else
{
struct WindowTemplate winTemplate = sShopMenuWindowTemplates[WIN_BUY_QUIT];
winTemplate.width = GetMaxWidthInMenuTable(sShopMenuActions_BuyQuit, ARRAY_COUNT(sShopMenuActions_BuyQuit));
sMartInfo.windowId = AddWindow(&winTemplate);
sMartInfo.menuActions = sShopMenuActions_BuyQuit;
numMenuItems = ARRAY_COUNT(sShopMenuActions_BuyQuit);
}
SetStandardWindowBorderStyle(sMartInfo.windowId, FALSE);
PrintMenuTable(sMartInfo.windowId, numMenuItems, sMartInfo.menuActions);
InitMenuInUpperLeftCornerNormal(sMartInfo.windowId, numMenuItems, 0);
PutWindowTilemap(sMartInfo.windowId);
CopyWindowToVram(sMartInfo.windowId, COPYWIN_MAP);
return CreateTask(Task_ShopMenu, 8);
}
static void SetShopMenuCallback(void (* callback)(void))
{
sMartInfo.callback = callback;
}
static void SetShopItemsForSale(const u16 *items)
{
u16 i = 0;
sMartInfo.itemList = items;
sMartInfo.itemCount = 0;
// Read items until ITEM_NONE / DECOR_NONE is reached
while (sMartInfo.itemList[i])
{
sMartInfo.itemCount++;
i++;
}
}
static void Task_ShopMenu(u8 taskId)
{
s8 inputCode = Menu_ProcessInputNoWrap();
switch (inputCode)
{
case MENU_NOTHING_CHOSEN:
break;
case MENU_B_PRESSED:
PlaySE(SE_SELECT);
Task_HandleShopMenuQuit(taskId);
break;
default:
sMartInfo.menuActions[inputCode].func.void_u8(taskId);
break;
}
}
#define tItemCount data[1]
#define tItemId data[5]
#define tListTaskId data[7]
#define tCallbackHi data[8]
#define tCallbackLo data[9]
static void Task_HandleShopMenuBuy(u8 taskId)
{
s16 *data = gTasks[taskId].data;
tCallbackHi = (u32)CB2_InitBuyMenu >> 16;
tCallbackLo = (u32)CB2_InitBuyMenu;
gTasks[taskId].func = Task_GoToBuyOrSellMenu;
FadeScreen(FADE_TO_BLACK, 0);
}
static void Task_HandleShopMenuSell(u8 taskId)
{
s16 *data = gTasks[taskId].data;
tCallbackHi = (u32)CB2_GoToSellMenu >> 16;
tCallbackLo = (u32)CB2_GoToSellMenu;
gTasks[taskId].func = Task_GoToBuyOrSellMenu;
FadeScreen(FADE_TO_BLACK, 0);
}
void CB2_ExitSellMenu(void)
{
gFieldCallback = MapPostLoadHook_ReturnToShopMenu;
SetMainCallback2(CB2_ReturnToField);
}
static void Task_HandleShopMenuQuit(u8 taskId)
{
ClearStdWindowAndFrameToTransparent(sMartInfo.windowId, 2); // Incorrect use, making it not copy it to vram.
RemoveWindow(sMartInfo.windowId);
TryPutSmartShopperOnAir();
UnlockPlayerFieldControls();
DestroyTask(taskId);
if (sMartInfo.callback)
sMartInfo.callback();
}
static void Task_GoToBuyOrSellMenu(u8 taskId)
{
s16 *data = gTasks[taskId].data;
if (!gPaletteFade.active)
{
DestroyTask(taskId);
SetMainCallback2((void *)((u16)tCallbackHi << 16 | (u16)tCallbackLo));
}
}
static void MapPostLoadHook_ReturnToShopMenu(void)
{
FadeInFromBlack();
CreateTask(Task_ReturnToShopMenu, 8);
}
static void Task_ReturnToShopMenu(u8 taskId)
{
if (IsWeatherNotFadingIn() == TRUE)
{
if (sMartInfo.martType == MART_TYPE_DECOR2)
DisplayItemMessageOnField(taskId, gText_CanIHelpWithAnythingElse, ShowShopMenuAfterExitingBuyOrSellMenu);
else
DisplayItemMessageOnField(taskId, gText_AnythingElseICanHelp, ShowShopMenuAfterExitingBuyOrSellMenu);
}
}
static void ShowShopMenuAfterExitingBuyOrSellMenu(u8 taskId)
{
CreateShopMenu(sMartInfo.martType);
DestroyTask(taskId);
}
static void CB2_BuyMenu(void)
{
RunTasks();
AnimateSprites();
BuildOamBuffer();
DoScheduledBgTilemapCopiesToVram();
UpdatePaletteFade();
}
static void VBlankCB_BuyMenu(void)
{
LoadOam();
ProcessSpriteCopyRequests();
TransferPlttBuffer();
}
static void CB2_InitBuyMenu(void)
{
u8 taskId;
switch (gMain.state)
{
case 0:
SetVBlankHBlankCallbacksToNull();
CpuFastFill(0, (void *)OAM, OAM_SIZE);
ScanlineEffect_Stop();
ResetTempTileDataBuffers();
FreeAllSpritePalettes();
ResetPaletteFade();
ResetSpriteData();
ResetTasks();
ClearScheduledBgCopiesToVram();
sShopData = AllocZeroed(sizeof(struct ShopData));
sShopData->scrollIndicatorsTaskId = TASK_NONE;
sShopData->itemSpriteIds[0] = SPRITE_NONE;
sShopData->itemSpriteIds[1] = SPRITE_NONE;
BuyMenuBuildListMenuTemplate();
BuyMenuInitBgs();
FillBgTilemapBufferRect_Palette0(0, 0, 0, 0, 0x20, 0x20);
FillBgTilemapBufferRect_Palette0(1, 0, 0, 0, 0x20, 0x20);
FillBgTilemapBufferRect_Palette0(2, 0, 0, 0, 0x20, 0x20);
FillBgTilemapBufferRect_Palette0(3, 0, 0, 0, 0x20, 0x20);
BuyMenuInitWindows();
BuyMenuDecompressBgGraphics();
gMain.state++;
break;
case 1:
if (!FreeTempTileDataBuffersIfPossible())
gMain.state++;
break;
default:
BuyMenuDrawGraphics();
BuyMenuAddScrollIndicatorArrows();
taskId = CreateTask(Task_BuyMenu, 8);
gTasks[taskId].tListTaskId = ListMenuInit(&gMultiuseListMenuTemplate, 0, 0);
BlendPalettes(PALETTES_ALL, 16, RGB_BLACK);
BeginNormalPaletteFade(PALETTES_ALL, 0, 16, 0, RGB_BLACK);
SetVBlankCallback(VBlankCB_BuyMenu);
SetMainCallback2(CB2_BuyMenu);
break;
}
}
static void BuyMenuFreeMemory(void)
{
Free(sShopData);
Free(sListMenuItems);
Free(sItemNames);
FreeAllWindowBuffers();
}
static void BuyMenuBuildListMenuTemplate(void)
{
u16 i;
sListMenuItems = Alloc((sMartInfo.itemCount + 1) * sizeof(*sListMenuItems));
sItemNames = Alloc((sMartInfo.itemCount + 1) * sizeof(*sItemNames));
for (i = 0; i < sMartInfo.itemCount; i++)
BuyMenuSetListEntry(&sListMenuItems[i], sMartInfo.itemList[i], sItemNames[i]);
StringCopy(sItemNames[i], gText_Cancel2);
sListMenuItems[i].name = sItemNames[i];
sListMenuItems[i].id = LIST_CANCEL;
gMultiuseListMenuTemplate = sShopBuyMenuListTemplate;
gMultiuseListMenuTemplate.items = sListMenuItems;
gMultiuseListMenuTemplate.totalItems = sMartInfo.itemCount + 1;
if (gMultiuseListMenuTemplate.totalItems > MAX_ITEMS_SHOWN)
gMultiuseListMenuTemplate.maxShowed = MAX_ITEMS_SHOWN;
else
gMultiuseListMenuTemplate.maxShowed = gMultiuseListMenuTemplate.totalItems;
sShopData->itemsShowed = gMultiuseListMenuTemplate.maxShowed;
}
static void BuyMenuSetListEntry(struct ListMenuItem *menuItem, u16 item, u8 *name)
{
if (sMartInfo.martType == MART_TYPE_NORMAL)
CopyItemName(item, name);
else
StringCopy(name, gDecorations[item].name);
menuItem->name = name;
menuItem->id = item;
}
static void BuyMenuPrintItemDescriptionAndShowItemIcon(s32 item, bool8 onInit, struct ListMenu *list)
{
const u8 *description;
if (onInit != TRUE)
PlaySE(SE_SELECT);
if (item != LIST_CANCEL)
BuyMenuAddItemIcon(item, sShopData->iconSlot);
else
BuyMenuAddItemIcon(ITEM_LIST_END, sShopData->iconSlot);
BuyMenuRemoveItemIcon(item, sShopData->iconSlot ^ 1);
sShopData->iconSlot ^= 1;
if (item != LIST_CANCEL)
{
if (sMartInfo.martType == MART_TYPE_NORMAL)
description = ItemId_GetDescription(item);
else
description = gDecorations[item].description;
}
else
{
description = gText_QuitShopping;
}
FillWindowPixelBuffer(WIN_ITEM_DESCRIPTION, PIXEL_FILL(0));
BuyMenuPrint(WIN_ITEM_DESCRIPTION, description, 3, 1, 0, COLORID_NORMAL);
}
static void BuyMenuPrintPriceInList(u8 windowId, u32 itemId, u8 y)
{
u8 x;
if (itemId != LIST_CANCEL)
{
if (sMartInfo.martType == MART_TYPE_NORMAL)
{
ConvertIntToDecimalStringN(
gStringVar1,
ItemId_GetPrice(itemId) >> IsPokeNewsActive(POKENEWS_SLATEPORT),
STR_CONV_MODE_LEFT_ALIGN,
5);
}
else
{
ConvertIntToDecimalStringN(
gStringVar1,
gDecorations[itemId].price,
STR_CONV_MODE_LEFT_ALIGN,
5);
}
StringExpandPlaceholders(gStringVar4, gText_PokedollarVar1);
x = GetStringRightAlignXOffset(FONT_NARROW, gStringVar4, 120);
AddTextPrinterParameterized4(windowId, FONT_NARROW, x, y, 0, 0, sShopBuyMenuTextColors[COLORID_ITEM_LIST], TEXT_SKIP_DRAW, gStringVar4);
}
}
static void BuyMenuAddScrollIndicatorArrows(void)
{
if (sShopData->scrollIndicatorsTaskId == TASK_NONE && sMartInfo.itemCount + 1 > MAX_ITEMS_SHOWN)
{
sShopData->scrollIndicatorsTaskId = AddScrollIndicatorArrowPairParameterized(
SCROLL_ARROW_UP,
172,
12,
148,
sMartInfo.itemCount - (MAX_ITEMS_SHOWN - 1),
TAG_SCROLL_ARROW,
TAG_SCROLL_ARROW,
&sShopData->scrollOffset);
}
}
static void BuyMenuRemoveScrollIndicatorArrows(void)
{
if (sShopData->scrollIndicatorsTaskId != TASK_NONE)
{
RemoveScrollIndicatorArrowPair(sShopData->scrollIndicatorsTaskId);
sShopData->scrollIndicatorsTaskId = TASK_NONE;
}
}
static void BuyMenuPrintCursor(u8 scrollIndicatorsTaskId, u8 colorSet)
{
u8 y = ListMenuGetYCoordForPrintingArrowCursor(scrollIndicatorsTaskId);
BuyMenuPrint(WIN_ITEM_LIST, gText_SelectorArrow2, 0, y, 0, colorSet);
}
static void BuyMenuAddItemIcon(u16 item, u8 iconSlot)
{
u8 spriteId;
u8 *spriteIdPtr = &sShopData->itemSpriteIds[iconSlot];
if (*spriteIdPtr != SPRITE_NONE)
return;
if (sMartInfo.martType == MART_TYPE_NORMAL || item == ITEM_LIST_END)
{
spriteId = AddItemIconSprite(iconSlot + TAG_ITEM_ICON_BASE, iconSlot + TAG_ITEM_ICON_BASE, item);
if (spriteId != MAX_SPRITES)
{
*spriteIdPtr = spriteId;
gSprites[spriteId].x2 = 24;
gSprites[spriteId].y2 = 88;
}
}
else
{
spriteId = AddDecorationIconObject(item, 20, 84, 1, iconSlot + TAG_ITEM_ICON_BASE, iconSlot + TAG_ITEM_ICON_BASE);
if (spriteId != MAX_SPRITES)
*spriteIdPtr = spriteId;
}
}
static void BuyMenuRemoveItemIcon(u16 item, u8 iconSlot)
{
u8 *spriteIdPtr = &sShopData->itemSpriteIds[iconSlot];
if (*spriteIdPtr == SPRITE_NONE)
return;
FreeSpriteTilesByTag(iconSlot + TAG_ITEM_ICON_BASE);
FreeSpritePaletteByTag(iconSlot + TAG_ITEM_ICON_BASE);
DestroySprite(&gSprites[*spriteIdPtr]);
*spriteIdPtr = SPRITE_NONE;
}
static void BuyMenuInitBgs(void)
{
ResetBgsAndClearDma3BusyFlags(0);
InitBgsFromTemplates(0, sShopBuyMenuBgTemplates, ARRAY_COUNT(sShopBuyMenuBgTemplates));
SetBgTilemapBuffer(1, sShopData->tilemapBuffers[1]);
SetBgTilemapBuffer(2, sShopData->tilemapBuffers[3]);
SetBgTilemapBuffer(3, sShopData->tilemapBuffers[2]);
SetGpuReg(REG_OFFSET_BG0HOFS, 0);
SetGpuReg(REG_OFFSET_BG0VOFS, 0);
SetGpuReg(REG_OFFSET_BG1HOFS, 0);
SetGpuReg(REG_OFFSET_BG1VOFS, 0);
SetGpuReg(REG_OFFSET_BG2HOFS, 0);
SetGpuReg(REG_OFFSET_BG2VOFS, 0);
SetGpuReg(REG_OFFSET_BG3HOFS, 0);
SetGpuReg(REG_OFFSET_BG3VOFS, 0);
SetGpuReg(REG_OFFSET_BLDCNT, 0);
SetGpuReg(REG_OFFSET_DISPCNT, DISPCNT_MODE_0 | DISPCNT_OBJ_ON | DISPCNT_OBJ_1D_MAP);
ShowBg(0);
ShowBg(1);
ShowBg(2);
ShowBg(3);
}
static void BuyMenuDecompressBgGraphics(void)
{
DecompressAndCopyTileDataToVram(1, gShopMenu_Gfx, 0x3A0, 0x3E3, 0);
LZDecompressWram(gShopMenu_Tilemap, sShopData->tilemapBuffers[0]);
LoadCompressedPalette(gShopMenu_Pal, BG_PLTT_ID(12), PLTT_SIZE_4BPP);
}
static void BuyMenuInitWindows(void)
{
InitWindows(sShopBuyMenuWindowTemplates);
DeactivateAllTextPrinters();
LoadUserWindowBorderGfx(WIN_MONEY, 1, BG_PLTT_ID(13));
LoadMessageBoxGfx(WIN_MONEY, 0xA, BG_PLTT_ID(14));
PutWindowTilemap(WIN_MONEY);
PutWindowTilemap(WIN_ITEM_LIST);
PutWindowTilemap(WIN_ITEM_DESCRIPTION);
}
static void BuyMenuPrint(u8 windowId, const u8 *text, u8 x, u8 y, s8 speed, u8 colorSet)
{
AddTextPrinterParameterized4(windowId, FONT_NORMAL, x, y, 0, 0, sShopBuyMenuTextColors[colorSet], speed, text);
}
static void BuyMenuDisplayMessage(u8 taskId, const u8 *text, TaskFunc callback)
{
DisplayMessageAndContinueTask(taskId, WIN_MESSAGE, 10, 14, FONT_NORMAL, GetPlayerTextSpeedDelay(), text, callback);
ScheduleBgCopyTilemapToVram(0);
}
static void BuyMenuDrawGraphics(void)
{
BuyMenuDrawMapGraphics();
BuyMenuCopyMenuBgToBg1TilemapBuffer();
AddMoneyLabelObject(19, 11);
PrintMoneyAmountInMoneyBoxWithBorder(WIN_MONEY, 1, 13, GetMoney(&gSaveBlock1Ptr->money));
ScheduleBgCopyTilemapToVram(0);
ScheduleBgCopyTilemapToVram(1);
ScheduleBgCopyTilemapToVram(2);
ScheduleBgCopyTilemapToVram(3);
}
static void BuyMenuDrawMapGraphics(void)
{
BuyMenuCollectObjectEventData();
BuyMenuDrawObjectEvents();
BuyMenuDrawMapBg();
}
static void BuyMenuDrawMapBg(void)
{
s16 i, j;
s16 x, y;
const struct MapLayout *mapLayout;
u16 metatile;
u8 metatileLayerType;
mapLayout = gMapHeader.mapLayout;
GetXYCoordsOneStepInFrontOfPlayer(&x, &y);
x -= 4;
y -= 4;
for (j = 0; j < 10; j++)
{
for (i = 0; i < 15; i++)
{
metatile = MapGridGetMetatileIdAt(x + i, y + j);
if (BuyMenuCheckForOverlapWithMenuBg(i, j) == TRUE)
metatileLayerType = MapGridGetMetatileLayerTypeAt(x + i, y + j);
else
metatileLayerType = METATILE_LAYER_TYPE_COVERED;
if (metatile < NUM_METATILES_IN_PRIMARY)
BuyMenuDrawMapMetatile(i, j, mapLayout->primaryTileset->metatiles + metatile * NUM_TILES_PER_METATILE, metatileLayerType);
else
BuyMenuDrawMapMetatile(i, j, mapLayout->secondaryTileset->metatiles + ((metatile - NUM_METATILES_IN_PRIMARY) * NUM_TILES_PER_METATILE), metatileLayerType);
}
}
}
static void BuyMenuDrawMapMetatile(s16 x, s16 y, const u16 *src, u8 metatileLayerType)
{
u16 offset1 = x * 2;
u16 offset2 = y * 64;
switch (metatileLayerType)
{
case METATILE_LAYER_TYPE_NORMAL:
BuyMenuDrawMapMetatileLayer(sShopData->tilemapBuffers[3], offset1, offset2, src);
BuyMenuDrawMapMetatileLayer(sShopData->tilemapBuffers[1], offset1, offset2, src + 4);
break;
case METATILE_LAYER_TYPE_COVERED:
BuyMenuDrawMapMetatileLayer(sShopData->tilemapBuffers[2], offset1, offset2, src);
BuyMenuDrawMapMetatileLayer(sShopData->tilemapBuffers[3], offset1, offset2, src + 4);
break;
case METATILE_LAYER_TYPE_SPLIT:
BuyMenuDrawMapMetatileLayer(sShopData->tilemapBuffers[2], offset1, offset2, src);
BuyMenuDrawMapMetatileLayer(sShopData->tilemapBuffers[1], offset1, offset2, src + 4);
break;
}
}
static void BuyMenuDrawMapMetatileLayer(u16 *dest, s16 offset1, s16 offset2, const u16 *src)
{
// This function draws a whole 2x2 metatile.
dest[offset1 + offset2] = src[0]; // top left
dest[offset1 + offset2 + 1] = src[1]; // top right
dest[offset1 + offset2 + 32] = src[2]; // bottom left
dest[offset1 + offset2 + 33] = src[3]; // bottom right
}
static void BuyMenuCollectObjectEventData(void)
{
s16 facingX;
s16 facingY;
u8 y;
u8 x;
u8 numObjects = 0;
GetXYCoordsOneStepInFrontOfPlayer(&facingX, &facingY);
for (y = 0; y < OBJECT_EVENTS_COUNT; y++)
sShopData->viewportObjects[y][OBJ_EVENT_ID] = OBJECT_EVENTS_COUNT;
for (y = 0; y < 5; y++)
{
for (x = 0; x < 7; x++)
{
u8 objEventId = GetObjectEventIdByXY(facingX - 4 + x, facingY - 2 + y);
if (objEventId != OBJECT_EVENTS_COUNT)
{
sShopData->viewportObjects[numObjects][OBJ_EVENT_ID] = objEventId;
sShopData->viewportObjects[numObjects][X_COORD] = x;
sShopData->viewportObjects[numObjects][Y_COORD] = y;
sShopData->viewportObjects[numObjects][LAYER_TYPE] = MapGridGetMetatileLayerTypeAt(facingX - 4 + x, facingY - 2 + y);
switch (gObjectEvents[objEventId].facingDirection)
{
case DIR_SOUTH:
sShopData->viewportObjects[numObjects][ANIM_NUM] = ANIM_STD_FACE_SOUTH;
break;
case DIR_NORTH:
sShopData->viewportObjects[numObjects][ANIM_NUM] = ANIM_STD_FACE_NORTH;
break;
case DIR_WEST:
sShopData->viewportObjects[numObjects][ANIM_NUM] = ANIM_STD_FACE_WEST;
break;
case DIR_EAST:
default:
sShopData->viewportObjects[numObjects][ANIM_NUM] = ANIM_STD_FACE_EAST;
break;
}
numObjects++;
}
}
}
}
static void BuyMenuDrawObjectEvents(void)
{
u8 i;
u8 spriteId;
const struct ObjectEventGraphicsInfo *graphicsInfo;
for (i = 0; i < OBJECT_EVENTS_COUNT; i++)
{
if (sShopData->viewportObjects[i][OBJ_EVENT_ID] == OBJECT_EVENTS_COUNT)
continue;
graphicsInfo = GetObjectEventGraphicsInfo(gObjectEvents[sShopData->viewportObjects[i][OBJ_EVENT_ID]].graphicsId);
spriteId = CreateObjectGraphicsSprite(
gObjectEvents[sShopData->viewportObjects[i][OBJ_EVENT_ID]].graphicsId,
SpriteCallbackDummy,
(u16)sShopData->viewportObjects[i][X_COORD] * 16 + 8,
(u16)sShopData->viewportObjects[i][Y_COORD] * 16 + 48 - graphicsInfo->height / 2,
2);
if (BuyMenuCheckIfObjectEventOverlapsMenuBg(sShopData->viewportObjects[i]) == TRUE)
{
gSprites[spriteId].subspriteTableNum = 4;
gSprites[spriteId].subspriteMode = SUBSPRITES_ON;
}
StartSpriteAnim(&gSprites[spriteId], sShopData->viewportObjects[i][ANIM_NUM]);
}
}
static bool8 BuyMenuCheckIfObjectEventOverlapsMenuBg(s16 *object)
{
if (!BuyMenuCheckForOverlapWithMenuBg(object[X_COORD], object[Y_COORD] + 2) && object[LAYER_TYPE] != METATILE_LAYER_TYPE_COVERED)
return TRUE;
else
return FALSE;
}
static void BuyMenuCopyMenuBgToBg1TilemapBuffer(void)
{
s16 i;
u16 *dest = sShopData->tilemapBuffers[1];
const u16 *src = sShopData->tilemapBuffers[0];
for (i = 0; i < 1024; i++)
{
if (src[i] != 0)
dest[i] = src[i] + 0xC3E3;
}
}
static bool8 BuyMenuCheckForOverlapWithMenuBg(int x, int y)
{
const u16 *metatile = sShopData->tilemapBuffers[0];
int offset1 = x * 2;
int offset2 = y * 64;
if (metatile[offset2 + offset1] == 0 &&
metatile[offset2 + offset1 + 32] == 0 &&
metatile[offset2 + offset1 + 1] == 0 &&
metatile[offset2 + offset1 + 33] == 0)
return TRUE;
return FALSE;
}
static void Task_BuyMenu(u8 taskId)
{
s16 *data = gTasks[taskId].data;
if (!gPaletteFade.active)
{
s32 itemId = ListMenu_ProcessInput(tListTaskId);
ListMenuGetScrollAndRow(tListTaskId, &sShopData->scrollOffset, &sShopData->selectedRow);
switch (itemId)
{
case LIST_NOTHING_CHOSEN:
break;
case LIST_CANCEL:
PlaySE(SE_SELECT);
ExitBuyMenu(taskId);
break;
default:
PlaySE(SE_SELECT);
tItemId = itemId;
ClearWindowTilemap(WIN_ITEM_DESCRIPTION);
BuyMenuRemoveScrollIndicatorArrows();
BuyMenuPrintCursor(tListTaskId, COLORID_GRAY_CURSOR);
if (sMartInfo.martType == MART_TYPE_NORMAL)
sShopData->totalCost = (ItemId_GetPrice(itemId) >> IsPokeNewsActive(POKENEWS_SLATEPORT));
else
sShopData->totalCost = gDecorations[itemId].price;
if (!IsEnoughMoney(&gSaveBlock1Ptr->money, sShopData->totalCost))
{
BuyMenuDisplayMessage(taskId, gText_YouDontHaveMoney, BuyMenuReturnToItemList);
}
else
{
if (sMartInfo.martType == MART_TYPE_NORMAL)
{
CopyItemName(itemId, gStringVar1);
if (ItemId_GetPocket(itemId) == POCKET_TM_HM)
{
StringCopy(gStringVar2, gMoveNames[ItemIdToBattleMoveId(itemId)]);
BuyMenuDisplayMessage(taskId, gText_Var1CertainlyHowMany2, Task_BuyHowManyDialogueInit);
}
else
{
BuyMenuDisplayMessage(taskId, gText_Var1CertainlyHowMany, Task_BuyHowManyDialogueInit);
}
}
else
{
StringCopy(gStringVar1, gDecorations[itemId].name);
ConvertIntToDecimalStringN(gStringVar2, sShopData->totalCost, STR_CONV_MODE_LEFT_ALIGN, 6);
if (sMartInfo.martType == MART_TYPE_DECOR)
StringExpandPlaceholders(gStringVar4, gText_Var1IsItThatllBeVar2);
else // MART_TYPE_DECOR2
StringExpandPlaceholders(gStringVar4, gText_YouWantedVar1ThatllBeVar2);
BuyMenuDisplayMessage(taskId, gStringVar4, BuyMenuConfirmPurchase);
}
}
break;
}
}
}
static void Task_BuyHowManyDialogueInit(u8 taskId)
{
s16 *data = gTasks[taskId].data;
u16 quantityInBag = CountTotalItemQuantityInBag(tItemId);
u16 maxQuantity;
DrawStdFrameWithCustomTileAndPalette(WIN_QUANTITY_IN_BAG, FALSE, 1, 13);
ConvertIntToDecimalStringN(gStringVar1, quantityInBag, STR_CONV_MODE_RIGHT_ALIGN, MAX_ITEM_DIGITS + 1);
StringExpandPlaceholders(gStringVar4, gText_InBagVar1);
BuyMenuPrint(WIN_QUANTITY_IN_BAG, gStringVar4, 0, 1, 0, COLORID_NORMAL);
tItemCount = 1;
DrawStdFrameWithCustomTileAndPalette(WIN_QUANTITY_PRICE, FALSE, 1, 13);
BuyMenuPrintItemQuantityAndPrice(taskId);
ScheduleBgCopyTilemapToVram(0);
maxQuantity = GetMoney(&gSaveBlock1Ptr->money) / sShopData->totalCost;
if (maxQuantity > MAX_BAG_ITEM_CAPACITY)
sShopData->maxQuantity = MAX_BAG_ITEM_CAPACITY;
else
sShopData->maxQuantity = maxQuantity;
gTasks[taskId].func = Task_BuyHowManyDialogueHandleInput;
}
static void Task_BuyHowManyDialogueHandleInput(u8 taskId)
{
s16 *data = gTasks[taskId].data;
if (AdjustQuantityAccordingToDPadInput(&tItemCount, sShopData->maxQuantity) == TRUE)
{
sShopData->totalCost = (ItemId_GetPrice(tItemId) >> IsPokeNewsActive(POKENEWS_SLATEPORT)) * tItemCount;
BuyMenuPrintItemQuantityAndPrice(taskId);
}
else
{
if (JOY_NEW(A_BUTTON))
{
PlaySE(SE_SELECT);
ClearStdWindowAndFrameToTransparent(WIN_QUANTITY_PRICE, FALSE);
ClearStdWindowAndFrameToTransparent(WIN_QUANTITY_IN_BAG, FALSE);
ClearWindowTilemap(WIN_QUANTITY_PRICE);
ClearWindowTilemap(WIN_QUANTITY_IN_BAG);
PutWindowTilemap(WIN_ITEM_LIST);
CopyItemName(tItemId, gStringVar1);
ConvertIntToDecimalStringN(gStringVar2, tItemCount, STR_CONV_MODE_LEFT_ALIGN, BAG_ITEM_CAPACITY_DIGITS);
ConvertIntToDecimalStringN(gStringVar3, sShopData->totalCost, STR_CONV_MODE_LEFT_ALIGN, 6);
BuyMenuDisplayMessage(taskId, gText_Var1AndYouWantedVar2, BuyMenuConfirmPurchase);
}
else if (JOY_NEW(B_BUTTON))
{
PlaySE(SE_SELECT);
ClearStdWindowAndFrameToTransparent(WIN_QUANTITY_PRICE, FALSE);
ClearStdWindowAndFrameToTransparent(WIN_QUANTITY_IN_BAG, FALSE);
ClearWindowTilemap(WIN_QUANTITY_PRICE);
ClearWindowTilemap(WIN_QUANTITY_IN_BAG);
BuyMenuReturnToItemList(taskId);
}
}
}
static void BuyMenuConfirmPurchase(u8 taskId)
{
CreateYesNoMenuWithCallbacks(taskId, &sShopBuyMenuYesNoWindowTemplates, 1, 0, 0, 1, 13, &sShopPurchaseYesNoFuncs);
}
static void BuyMenuTryMakePurchase(u8 taskId)
{
s16 *data = gTasks[taskId].data;
PutWindowTilemap(WIN_ITEM_LIST);
if (sMartInfo.martType == MART_TYPE_NORMAL)
{
if (AddBagItem(tItemId, tItemCount) == TRUE)
{
BuyMenuDisplayMessage(taskId, gText_HereYouGoThankYou, BuyMenuSubtractMoney);
RecordItemPurchase(taskId);
}
else
{
BuyMenuDisplayMessage(taskId, gText_NoMoreRoomForThis, BuyMenuReturnToItemList);
}
}
else
{
if (DecorationAdd(tItemId))
{
if (sMartInfo.martType == MART_TYPE_DECOR)
BuyMenuDisplayMessage(taskId, gText_ThankYouIllSendItHome, BuyMenuSubtractMoney);
else // MART_TYPE_DECOR2
BuyMenuDisplayMessage(taskId, gText_ThanksIllSendItHome, BuyMenuSubtractMoney);
}
else
{
BuyMenuDisplayMessage(taskId, gText_SpaceForVar1Full, BuyMenuReturnToItemList);
}
}
}
static void BuyMenuSubtractMoney(u8 taskId)
{
IncrementGameStat(GAME_STAT_SHOPPED);
RemoveMoney(&gSaveBlock1Ptr->money, sShopData->totalCost);
PlaySE(SE_SHOP);
PrintMoneyAmountInMoneyBox(WIN_MONEY, GetMoney(&gSaveBlock1Ptr->money), 0);
if (sMartInfo.martType == MART_TYPE_NORMAL)
gTasks[taskId].func = Task_ReturnToItemListAfterItemPurchase;
else
gTasks[taskId].func = Task_ReturnToItemListAfterDecorationPurchase;
}
static void Task_ReturnToItemListAfterItemPurchase(u8 taskId)
{
s16 *data = gTasks[taskId].data;
if (JOY_NEW(A_BUTTON | B_BUTTON))
{
PlaySE(SE_SELECT);
// Purchasing 10+ Poke Balls gets the player a Premier Ball
if (tItemId == ITEM_POKE_BALL && tItemCount >= 10 && AddBagItem(ITEM_PREMIER_BALL, 1) == TRUE)
BuyMenuDisplayMessage(taskId, gText_ThrowInPremierBall, BuyMenuReturnToItemList);
else
BuyMenuReturnToItemList(taskId);
}
}
static void Task_ReturnToItemListAfterDecorationPurchase(u8 taskId)
{
if (JOY_NEW(A_BUTTON | B_BUTTON))
{
PlaySE(SE_SELECT);
BuyMenuReturnToItemList(taskId);
}
}
static void BuyMenuReturnToItemList(u8 taskId)
{
s16 *data = gTasks[taskId].data;
ClearDialogWindowAndFrameToTransparent(WIN_MESSAGE, FALSE);
BuyMenuPrintCursor(tListTaskId, COLORID_ITEM_LIST);
PutWindowTilemap(WIN_ITEM_LIST);
PutWindowTilemap(WIN_ITEM_DESCRIPTION);
ScheduleBgCopyTilemapToVram(0);
BuyMenuAddScrollIndicatorArrows();
gTasks[taskId].func = Task_BuyMenu;
}
static void BuyMenuPrintItemQuantityAndPrice(u8 taskId)
{
s16 *data = gTasks[taskId].data;
FillWindowPixelBuffer(WIN_QUANTITY_PRICE, PIXEL_FILL(1));
PrintMoneyAmount(WIN_QUANTITY_PRICE, 38, 1, sShopData->totalCost, TEXT_SKIP_DRAW);
ConvertIntToDecimalStringN(gStringVar1, tItemCount, STR_CONV_MODE_LEADING_ZEROS, BAG_ITEM_CAPACITY_DIGITS);
StringExpandPlaceholders(gStringVar4, gText_xVar1);
BuyMenuPrint(WIN_QUANTITY_PRICE, gStringVar4, 0, 1, 0, COLORID_NORMAL);
}
static void ExitBuyMenu(u8 taskId)
{
gFieldCallback = MapPostLoadHook_ReturnToShopMenu;
BeginNormalPaletteFade(PALETTES_ALL, 0, 0, 16, RGB_BLACK);
gTasks[taskId].func = Task_ExitBuyMenu;
}
static void Task_ExitBuyMenu(u8 taskId)
{
if (!gPaletteFade.active)
{
RemoveMoneyLabelObject();
BuyMenuFreeMemory();
SetMainCallback2(CB2_ReturnToField);
DestroyTask(taskId);
}
}
static void ClearItemPurchases(void)
{
sPurchaseHistoryId = 0;
memset(gMartPurchaseHistory, 0, sizeof(gMartPurchaseHistory));
}
static void RecordItemPurchase(u8 taskId)
{
s16 *data = gTasks[taskId].data;
u16 i;
for (i = 0; i < ARRAY_COUNT(gMartPurchaseHistory); i++)
{
if (gMartPurchaseHistory[i].itemId == tItemId && gMartPurchaseHistory[i].quantity != 0)
{
if (gMartPurchaseHistory[i].quantity + tItemCount > 255)
gMartPurchaseHistory[i].quantity = 255;
else
gMartPurchaseHistory[i].quantity += tItemCount;
return;
}
}
if (sPurchaseHistoryId < ARRAY_COUNT(gMartPurchaseHistory))
{
gMartPurchaseHistory[sPurchaseHistoryId].itemId = tItemId;
gMartPurchaseHistory[sPurchaseHistoryId].quantity = tItemCount;
sPurchaseHistoryId++;
}
}
#undef tItemCount
#undef tItemId
#undef tListTaskId
#undef tCallbackHi
#undef tCallbackLo
void CreatePokemartMenu(const u16 *itemsForSale)
{
CreateShopMenu(MART_TYPE_NORMAL);
SetShopItemsForSale(itemsForSale);
ClearItemPurchases();
SetShopMenuCallback(ScriptContext_Enable);
}
void CreateDecorationShop1Menu(const u16 *itemsForSale)
{
CreateShopMenu(MART_TYPE_DECOR);
SetShopItemsForSale(itemsForSale);
SetShopMenuCallback(ScriptContext_Enable);
}
void CreateDecorationShop2Menu(const u16 *itemsForSale)
{
CreateShopMenu(MART_TYPE_DECOR2);
SetShopItemsForSale(itemsForSale);
SetShopMenuCallback(ScriptContext_Enable);
}