Alchemy
Loading...
Searching...
No Matches

buy.c

Go to the documentation of this file.
1/// @file
2/// Buying: the stock list, the money and party windows, quantities and prices.
3/// Part of shop.c, the shop object, compiled only through it.
4
5#include "../shop_internal.h"
6
7// Shop_DrawUnitItem reads the unit and writes its icons as bytes; these are
8// the offsets of the unit's inventory and the icon's depth.
9#define SHOP_INVENTORY_OFFSET ((s32)offsetof(struct BattleUnit, inventory))
10#define SHOP_ICON_DEPTH_OFFSET ((s32)offsetof(struct RenderOutput, sentinel))
11
12#define SHOP_STACK_LIMIT 30 ///< maximum quantity accepted by Inventory_AddItem
13
14/// Runs the Buy and Artifacts counters: the player browses the stock seven
15/// items a page, picks one, chooses who gets it and how many, and pays. Checks
16/// room in the bag and coins first, and warns when the member cannot equip
17/// it. An artifact bought takes its stock counter down, and the list is
18/// rebuilt. Repeats until B. Called by Shop_Run.
20 struct ShopRuntime *shop = gMenuWork;
21 struct UiWindow *priceWindow = 0;
22 struct UiWindow *stockWindow;
23 struct UiWindow *messageWindow;
24 struct UiWindow *partyWindow;
25 s32 selected = 0;
26 s32 quantity;
27 struct ItemDefinition *selectedDefinition;
28 s32 repromptRecipient;
29 struct UiWindow *itemWindow;
30 s32 redraw;
31 s32 selectionIndex;
32 s32 stockCount;
33 s32 itemId;
34 struct ItemDefinition *stockDefinition;
35 s32 selectionResult;
36 s32 previousIndex;
37 s32 unitId;
38 s32 capacitySlot;
39 s32 removedCount;
40 s16 *stockIds;
41
42 shop->itemWindow = 0;
43 shop->moneyWindow =
46 priceWindow = UiWindow_CreateFar(0, 8, SHOP_PRICE_WIDTH, 4, 2);
47
48next_page:
49 // Browse stock first; recipient and quantity selection follow confirmation.
50 selectionIndex = selected;
51 stockWindow = UiWindow_CreateFar(0, 12, 30, 4, 2);
52 shop->cursor.anchor->active = 18;
53 shop->mode = 12;
54 messageWindow = UiWindow_CreateFar(0, 17, 30, 3, 2);
55 redraw = 1;
56
57 while (1) {
58 stockCount = shop->stockCount;
59 if (redraw != 0) {
60 stockIds = shop->stockItemIds;
61 itemId = stockIds[selectionIndex];
62 stockDefinition = Item_GetDirectFar(itemId);
63 redraw = 0;
64 Shop_PlaceCursor(stockWindow,
65 selectionIndex % SHOP_STOCK_PAGE_ITEMS * 32 - 8, 8);
66 shop->mode = 4;
67 Shop_DrawStock(stockWindow, selectionIndex);
68 Shop_DrawMsg(messageWindow, itemId + MsgItemPlainName);
70 Shop_DrawItemPrice(priceWindow, itemId, stockDefinition->price, 0);
71 }
72 if ((gKeysPressed & KEY_A) != 0) {
73 selected = selectionIndex;
75 selectionResult = 0;
76 break;
77 }
78 if ((gKeysPressed & KEY_B) != 0) {
80 selectionResult = -1;
81 break;
82 }
83 if ((gKeysRepeat & KEY_LEFT) != 0) {
84 previousIndex = selectionIndex;
85 selectionIndex--;
86 selectionIndex = (selectionIndex + stockCount) % stockCount;
87 if (previousIndex != selectionIndex) {
89 redraw = 1;
90 }
91 }
92 if ((gKeysRepeat & KEY_RIGHT) != 0) {
93 previousIndex = selectionIndex;
94 selectionIndex++;
95 selectionIndex = (selectionIndex + stockCount) % stockCount;
96 if (previousIndex != selectionIndex) {
98 redraw = 1;
99 }
100 }
101 if ((gKeysRepeat & KEY_UP) != 0 &&
102 selectionIndex - SHOP_STOCK_PAGE_ITEMS >= 0) {
103 selectionIndex -= SHOP_STOCK_PAGE_ITEMS;
104 redraw = 1;
105 }
106 if ((gKeysRepeat & KEY_DOWN) != 0) {
107 if (selectionIndex + SHOP_STOCK_PAGE_ITEMS <
108 (stockCount + (SHOP_STOCK_PAGE_ITEMS - 1)) / SHOP_STOCK_PAGE_ITEMS *
110 selectionIndex += SHOP_STOCK_PAGE_ITEMS;
111 redraw = 1;
112 }
113 if (selectionIndex > stockCount - 1)
114 selectionIndex = stockCount - 1;
115 }
116 WaitFrames(1);
117 }
118
119 UiWork_FinalizeFar(messageWindow, 2);
120 UiWork_FinalizeFar(stockWindow, 2);
121 WaitFrames(1);
122 if (selectionResult != 0)
123 goto close;
124
125 shop->selectedItem = shop->stockItemIds[selected];
126 UiMessage_ShowAndWait(MsgWhoGetsThis);
127 selectedDefinition = Item_GetDirectFar(shop->selectedItem);
128 quantity = 1;
129 unitId = 0;
130 repromptRecipient = 0;
131 selectionIndex = 0;
132 partyWindow = UiWindow_CreateFar(0, 14, 13, 3, 2);
133 shop->cursor.anchor->active = 4;
134 shop->mode = 12;
135 PsynergyMenu_InitializeEntryObjectsFar(partyWindow, 2, 0, 8, 0);
136 itemWindow = UiWindow_CreateFar(16, 11, 14, 9, 2);
137 redraw = 1;
138
139 while (1) {
140 if (repromptRecipient != 0) {
141 repromptRecipient = 0;
142 UiMessage_ShowAndWait(MsgWhoGetsThis);
143 redraw = 1;
144 }
145 if (redraw != 0) {
146 redraw = 0;
147 selectionIndex =
148 (selectionIndex + shop->partyMemberCount) % shop->partyMemberCount;
149 unitId = shop->partyMemberIds[selectionIndex];
150 Shop_PlaceCursor(partyWindow, selectionIndex * 24 - 12, 0);
151 shop->mode = 3;
152 Shop_DrawParty(partyWindow, selectionIndex, shop->selectedItem);
154 Shop_DrawUnitItem(itemWindow, unitId, shop->selectedItem);
155 else
156 Shop_DrawEquipComparison(itemWindow, unitId, shop->selectedItem);
157 }
158 if ((gKeysPressed & KEY_A) != 0) {
159 // Probe capacity with the original add/remove before checking funds.
160 capacitySlot = Inventory_AddItemFar(unitId, shop->selectedItem);
161 if (capacitySlot < 0) {
163 UiWork_PushValueSlotFar(unitId, 1);
165 if (Inventory_CountFar(unitId) == INVENTORY_SLOTS)
166 UiMessage_ShowAndWait(MsgPackFull);
167 else
168 UiMessage_ShowAndWait(MsgCannotCarryItem);
169 continue;
170 }
171 Inventory_RemoveFar(unitId, capacitySlot);
172 if ((u32)selectedDefinition->price > gGameState.coins) {
174 UiMessage_ShowAndRestoreState(MsgBuyNotEnoughMoney);
175 selectionResult = -1;
176 break;
177 }
178 if (Item_IsCompatibleWithOwnerFar(unitId, shop->selectedItem) == 0) {
179 UiWork_PushValueSlotFar(unitId, 1);
180 UiMessage_ShowAndWait(MsgCannotEquipStillWant);
181 if (UiMessage_ShowChoice(0) != 0) {
182 repromptRecipient = 1;
183 continue;
184 }
185 }
187 WaitFrames(1);
188 quantity = Shop_SelectBuyNum(unitId, shop->selectedItem);
189 repromptRecipient = 1;
190 if (quantity == -1)
191 continue;
192 Shop_BuyDone(unitId, shop->selectedItem, quantity);
193 Shop_BuySpecialItem(partyWindow, itemWindow);
194 selectionResult = 0;
195 break;
196 }
197 if ((gKeysPressed & KEY_B) != 0) {
199 selectionResult = -1;
200 break;
201 }
202 if ((gKeysRepeat & KEY_LEFT) != 0) {
204 selectionIndex--;
205 redraw = 1;
206 }
207 if ((gKeysRepeat & KEY_RIGHT) != 0) {
209 selectionIndex++;
210 redraw = 1;
211 }
212 WaitFrames(1);
213 }
214
216 UiWork_FinalizeFar(itemWindow, 2);
217 UiWork_FinalizeFar(partyWindow, 2);
218 WaitFrames(1);
219 // Artifact quantities are consumed only after the purchase completes.
220 if (selectionResult == 0 && shop->partyAction == SHOP_ACTION_ARTIFACTS) {
221 for (removedCount = 0; removedCount < quantity; removedCount++)
224 goto close;
225 if (selected > shop->stockCount - 1)
226 selected = shop->stockCount - 1;
227 }
228 UiMessage_ShowAndWait(MsgBuyAnythingElse);
229 goto next_page;
230
231close:
232 UiWork_FinalizeFar(priceWindow, 2);
234 WaitFrames(1);
235 return 0;
236}
237
238/// Draws a page of seven stock items with their prices, the page arrows, and
239/// the chosen item lifted and pulsing. Called by Shop_SelectBuy each time the
240/// cursor moves.
241void Shop_DrawStock(struct UiWindow *window, s32 selected) {
242
243 struct ShopRuntime *shop;
244 s16 *itemIds;
245 s16 *stockCursor;
246 s32 itemCount;
247 s32 stockIndex;
248 s32 pageSlot;
249 s32 priceX;
250 s32 zero;
251 s32 highlightFrame;
252 s16 itemId;
253 struct ItemDefinition *definition;
254 struct RenderOutput *icon;
255
256 shop = gMenuWork;
257 itemIds = shop->stockItemIds;
258 itemCount = shop->stockCount;
259 stockIndex = selected - selected % SHOP_STOCK_PAGE_ITEMS;
260 if (window != 0) {
262 if (stockIndex != 0) {
263 icon = RenderOutput_CreateFar(shop->previousPageIcon, 0x40000000, window,
264 216, -16);
265 zero = 0;
266 icon->kind = zero;
267 icon->active = 17;
268 icon->animStep = zero;
269 }
270 if (stockIndex + SHOP_STOCK_PAGE_ITEMS < itemCount) {
271 icon = RenderOutput_CreateFar(shop->nextPageIcon, 0x40000000, window, 216,
272 24);
273 zero = 0;
274 icon->kind = zero;
275 icon->active = 15;
276 icon->animStep = zero;
277 }
278 pageSlot = 0;
279 if (stockIndex < (u32)itemCount) {
280 stockCursor = (s16 *)((stockIndex * 2) + (u32)itemIds);
281 priceX = 16;
282 for (;
283 (u32)pageSlot < SHOP_STOCK_PAGE_ITEMS && stockIndex < (u32)itemCount;
284 priceX += 32, pageSlot++, stockCursor++, stockIndex++) {
285 itemId = *stockCursor;
286 definition = Item_GetDirectFar(itemId);
287 icon =
288 RenderOutput_CreateLoadedFar(itemId, 1, window, pageSlot * 32, 0);
289 icon->sentinel = 252;
290 if (stockIndex == selected) {
291 icon->active = 9;
292 highlightFrame = 10;
293 icon->animStep = highlightFrame;
294 icon->sentinel = 253;
295 }
296 icon = Shop_CreatePriceSprite(definition->price, window, priceX, 0);
297 icon->sentinel = 251;
298 }
299 }
300 }
301}
302
303/// Draws the party's coins in the coins window, in the edition's layout.
304/// Called when a counter opens and after each purchase, sale or repair.
305void Shop_DrawMoney(void) {
306 struct ShopRuntime *shop;
307 struct UiWindow *window;
308
309 shop = gMenuWork;
310 window = shop->moneyWindow;
311 if (window != 0) {
312#if (LANGUAGE != LANGUAGE_JAPANESE)
313 UiText_DrawCharacterAtOffsetFar(MsgYourCoins, window, 0, 0);
314 UiText_DrawNumberInWindowFar(gGameState.coins, 6, window, 0x20, 8);
315#else
316 s32 msg = MsgYourCoins;
317
318 UiText_DrawCharacterAtOffsetFar(msg, window, 0, 0);
320 UiText_DrawCharacterAtOffsetFar(msg - 2, window, 48, 8);
321#endif
322 }
323}
324
325/// Draws the item's name and below it its price. With a price of 0 it says
326/// instead that the shop does not buy it (`priceKind` 1) or that it is not
327/// broken (2). Called by the buy, sell and repair lists.
328void Shop_DrawItemPrice(struct UiWindow *window, s32 itemId, s32 price,
329 s32 priceKind) {
330 // A structured switch with early returns reduced the English native extent
331 // from 0x98 to 0x90, so the existing shared draw tail remains.
332 s32 labelMessage;
333
334 if (window == 0)
335 return;
336 goto draw;
337cannot_buy:
338 UiText_DrawCharacterAtOffsetFar(MsgCannotBuyThat, window, SHOP_ITEM_NOTE_X,
339 8);
340 return;
341show_price:
342 labelMessage = MsgPrice;
343 UiText_DrawCharacterAtOffsetFar(labelMessage, window, SHOP_ITEM_NOTE_X, 8);
344 labelMessage -= 3;
346 UiText_DrawCharacterAtOffsetFar(labelMessage, window, SHOP_PRICE_LABEL_X, 8);
347 return;
348draw:
350 UiText_DrawCharacterAtOffsetFar(itemId + MsgItemName, window, 0, 0);
351 if (price != 0)
352 goto show_price;
353 switch (priceKind) {
354 case 1:
355 goto cannot_buy;
356 case 2:
357 UiText_DrawCharacterAtOffsetFar(MsgNotBroken, window, SHOP_ITEM_NOTE_X, 8);
358 break;
359 default:
360 goto show_price;
361 }
362 return;
363}
364
365/// Shows `message`, an item's description, in the help window; a NULL window
366/// is ignored. Called by the counters' lists each time the cursor moves.
367void Shop_DrawMsg(struct UiWindow *window, s32 message) {
368 if (window != 0) {
369#if TBS_PATCH_1
371#else
373 WaitFrames(1);
374#endif
375 SHOP_DRAW_MESSAGE(message, window, 0, 0);
376 }
377}
378
379/// Shows the chosen member walking and the others standing, and draws at four
380/// fifths of their size the members who cannot equip `itemId`. Called by
381/// every counter's member choice.
382void Shop_DrawParty(struct UiWindow *window, s32 selected, s32 itemId) {
383 struct ShopRuntime *shop = gMenuWork;
384 s32 memberIndex;
385
386 if (window != 0) {
387 for (memberIndex = 0; memberIndex < shop->partyMemberCount; memberIndex++) {
388 if (memberIndex == selected)
390 30);
391 else
393 1);
394 shop->partyMemberScale[memberIndex] = 0x10000;
395 if (!Item_IsCompatibleWithOwnerFar(shop->partyMemberIds[memberIndex],
396 itemId))
397 shop->partyMemberScale[memberIndex] = 0xcccc;
398 }
399 }
400}
401
402/// Shows how Attack, Defense and Agility would change if the member wore the
403/// item: equips it in place of what it would replace, recomputes, puts the
404/// slot back and recomputes again, then draws both values with an arrow and
405/// the name of the item it would replace. Called by Shop_SelectBuy for
406/// weapons and armor.
407void Shop_DrawEquipComparison(struct UiWindow *window, s32 unitId, s32 itemId) {
408 struct ShopRuntime *shop = gMenuWork;
409 struct BattleUnit *unit = UnitGetFar(unitId);
410 struct ItemDefinition *item = Item_GetDirectFar(itemId);
411 s32 replacedItemId = -1;
412 s32 previewSlot;
413 s32 savedEntry;
414 s32 index;
415 s32 baseStats[4];
416 s32 previewStats[4];
417
418 if (window == 0)
419 return;
421 if (!Item_CanOwnerEquip(unitId, itemId)) {
422 UiText_DrawResourceFar(MsgShopCannotEquip, window, 8, 24);
423 return;
424 }
425 previewSlot = Inventory_FindEquippedFar(unitId, item->type);
426 // With nothing of the type equipped, borrow the first slot that is not
427 // equipped, else the first Psynergy item's, else slot 0.
428 if (previewSlot == -1) {
429 for (index = 0; index < INVENTORY_SLOTS; index++) {
430 if (!(unit->inventory[index] & INVENTORY_EQUIPPED))
431 break;
432 }
433 if (index == INVENTORY_SLOTS) {
434 for (index = 0; index < INVENTORY_SLOTS; index++) {
435 if (Item_GetDirectFar(unit->inventory[index])->type ==
437 break;
438 }
439 if (index == INVENTORY_SLOTS)
440 index = 0;
441 }
442 previewSlot = index;
443 } else {
444 replacedItemId = unit->inventory[previewSlot] & ITEM_ID_MASK;
445 }
446 // Borrow a live slot so UnitCalc includes every equipment effect; restore
447 // both the slot and the stats before returning to the counter.
448 savedEntry = unit->inventory[previewSlot];
449 unit->inventory[previewSlot] = itemId | INVENTORY_EQUIPPED;
450 UnitCalcFar(unitId);
451 previewStats[0] = unit->atk;
452 previewStats[1] = unit->def;
453 previewStats[2] = unit->agi;
454 previewStats[3] = unit->luck;
455 unit->inventory[previewSlot] = savedEntry;
456 UnitCalcFar(unitId);
457 baseStats[0] = unit->atk;
458 baseStats[1] = unit->def;
459 baseStats[2] = unit->agi;
460 baseStats[3] = unit->luck;
461 for (index = 0; index <= 2; index++) {
462 if (baseStats[index] > previewStats[index])
463 RenderOutput_CreateFar(shop->statDownIcon, 0x40000000, window, 56,
464 index * 16 - 4)
465 ->kind = 0;
466 else if (baseStats[index] < previewStats[index])
467 RenderOutput_CreateFar(shop->statUpIcon, 0x40000000, window, 56,
468 index * 16 - 4)
469 ->kind = 0;
470 UiText_DrawNumberInWindowFar(baseStats[index], 3, window, 32, index * 16);
471 if (baseStats[index] != previewStats[index])
472 UiText_DrawNumberInWindowFar(previewStats[index], 3, window, 72,
473 index * 16);
474 UiText_DrawCharacterAtOffsetFar(MsgShopStatLabel + index, window, 0,
475 index * 16);
476 UiWindow_DrawDividerLineFar(window, 0, index * 2 + 2, 13, index * 2 + 2);
477 }
478 if (replacedItemId != -1)
479 UiText_DrawCharacterAtOffsetFar(replacedItemId + MsgItemName, window, 0,
480 48);
481}
482/// Shows the quantity already carried and the member's inventory icons.
483/// Called by Shop_SelectBuy for items that are not equipment.
484void Shop_DrawUnitItem(struct UiWindow *window, s32 unitId, s32 itemId) {
485 // A typed RenderOutput icon store kept the English text extent but changed 76
486 // text bytes; retain the existing byte view of the sprite.
487
488 u8 *unitBytes;
489 s32 x;
490 s32 y;
491 s32 itemIndex;
492 s32 slot;
493 s32 quantityOffset;
494 s32 firstOffset;
495 s32 itemOffset;
496 s32 nextOffset;
497 u8 *icon;
498
499 unitBytes = (u8 *)UnitGetFar(unitId);
500 x = 8;
501 y = 8;
502 if (window != 0) {
504 slot = Inventory_FindFar(unitId, itemId);
505 if (slot != -1) {
506 quantityOffset = slot * 2 + SHOP_INVENTORY_OFFSET;
508 (*(u16 *)(unitBytes + quantityOffset) >> INVENTORY_QUANTITY_SHIFT) +
509 1,
510 5);
511 UiText_DrawCharacterAtOffsetFar(MsgYouHave, window, 0, 0);
512 } else {
513 UiText_DrawCharacterAtOffsetFar(MsgShopNoneInStock, window, 0, 0);
514 }
515 itemIndex = 0;
516 firstOffset = SHOP_INVENTORY_OFFSET;
517 if (*(u16 *)(unitBytes + firstOffset) != 0) {
518 for (;;) {
519 itemOffset = itemIndex * 2 + SHOP_INVENTORY_OFFSET;
521 *(u16 *)(unitBytes + itemOffset), 27, window, x, y);
523 x += 16;
524 if (itemIndex == 4) {
525 x = 8;
526 y += 16;
527 }
528 if (itemIndex == 9) {
529 x = 8;
530 y += 16;
531 }
532 itemIndex++;
533 if (itemIndex > 14)
534 break;
535 nextOffset = itemIndex * 2 + SHOP_INVENTORY_OFFSET;
536 if (*(u16 *)(unitBytes + nextOffset) == 0)
537 break;
538 }
539 }
540 }
541}
542
543/// Asks how many to buy when the item stacks, limited by what the coins
544/// buy, the artifact's stock and a stack of 30 less what the member carries.
545/// Returns the count (1 for other items), or -1 when cancelled. Called by
546/// Shop_SelectBuy.
547s32 Shop_SelectBuyNum(s32 unitId, s32 itemId) {
548 struct ShopRuntime *shop;
549 struct BattleUnit *unit;
550 struct ItemDefinition *item;
551 s32 carriedQuantity;
552 s32 quantityLimit;
553 s32 slot;
554 s32 artifactLimit;
555 s32 selectedQuantity;
556
557 shop = gMenuWork;
558 unit = UnitGetFar(unitId);
559 item = Item_GetDirectFar(itemId);
560 selectedQuantity = 1;
561 if (item->flags & ITEM_STACKABLE) {
562 UiMessage_ShowAndWait(MsgHowManyToBuy);
563 slot = Inventory_FindFar(unitId, itemId);
564 if (slot != -1) {
565 carriedQuantity = (unit->inventory[slot] >> INVENTORY_QUANTITY_SHIFT) + 1;
566 } else {
567 carriedQuantity = 0;
568 }
569
570 quantityLimit = SHOP_STACK_LIMIT;
571 if (item->price != 0)
572 quantityLimit = (u32)gGameState.coins / item->price;
573
574 if (shop->partyAction == SHOP_ACTION_ARTIFACTS) {
575 artifactLimit = Item_AdjustCounterFar(itemId, 0);
576 if (quantityLimit > artifactLimit)
577 artifactLimit = Item_AdjustCounterFar(itemId, 0);
578 else
579 artifactLimit = quantityLimit;
580 quantityLimit = artifactLimit;
581 }
582
583 quantityLimit += carriedQuantity;
584 if (quantityLimit > SHOP_STACK_LIMIT)
585 quantityLimit = SHOP_STACK_LIMIT;
586
587 shop->mode = 12;
589 selectedQuantity =
590 Shop_SelectQuantity(carriedQuantity, quantityLimit, item->price);
591 }
592 return selectedQuantity;
593}
594
595#if (LANGUAGE == LANGUAGE_GERMAN) || (LANGUAGE == LANGUAGE_FRENCH)
596#define SHOP_QUANTITY_NUMBER_X 64
597#define SHOP_QUANTITY_COIN_LABEL_X 128
598#elif (LANGUAGE == LANGUAGE_SPANISH)
599#define SHOP_QUANTITY_NUMBER_X 72
600#define SHOP_QUANTITY_COIN_LABEL_X 144
601#else
602#define SHOP_QUANTITY_NUMBER_X 72
603#define SHOP_QUANTITY_COIN_LABEL_X 136
604#endif
605
606/// Chooses how many items to buy or sell, returning the quantity above
607/// `minimum` or -1 when cancelled. The bar's fixed prefix is `minimum`
608/// (items already carried when buying); `maximum` must exceed it so at least
609/// one can be chosen. Holds heap slot 14 for the bar's tiles. Called by the
610/// buy and sell quantity pickers.
612 void *tileBuffer;
613 struct UiWindow *window;
614 s32 redraw;
615 s32 resourceSlot;
616 s32 quantityIndex;
617 s32 selectedQuantity;
618 struct RenderOutput *quantitySprite;
619#if (LANGUAGE == LANGUAGE_JAPANESE)
620 s32 unitMessageBase;
621#endif
622
623 tileBuffer = Runtime_AllocateBlock(14, 0x400);
624 redraw = 1;
625 maximum -= minimum;
626 selectedQuantity = -1;
627 quantityIndex = 0;
628 window = UiWindow_CreateFar(7, 4, 23, 3, 2);
629 if (window != 0) {
630 resourceSlot = Resource_FindFreeEntry();
631 if (resourceSlot != VRAM_CACHE_ENTRY_COUNT) {
632 VramCache_Load(resourceSlot, 0x100, 0);
633 RenderOutput_CreateFar(resourceSlot, 0x40004000, window, 0, 0);
634 quantitySprite =
635 RenderOutput_CreateFar(resourceSlot, 0x40004000, window, 32, 0);
636 quantitySprite->oam.index += 4;
637 for (;;) {
638 if (gKeysPressed & KEY_A) {
640 selectedQuantity = quantityIndex + 1;
641 break;
642 }
643 if (gKeysPressed & KEY_B) {
645 selectedQuantity = -1;
646 break;
647 }
648 if (gKeysRepeat & KEY_LEFT) {
650 redraw = 1;
651 quantityIndex--;
652 }
653 if (gKeysRepeat & KEY_RIGHT) {
655 redraw = 1;
656 quantityIndex++;
657 }
658 if (redraw) {
659 redraw = 0;
660 quantityIndex = (quantityIndex + maximum) % maximum;
661 Dma_Set(gShop_QuantityTiles, tileBuffer,
662 (DMA_ENABLE | DMA_32BIT) << 16 | 0x40, REG_DMA3);
663 // Paint capacity, available stock, selection and carried items in
664 // that order; each shorter prefix overwrites the previous colour.
665 Shop_FillSelector(SHOP_STACK_LIMIT, 14, tileBuffer);
666 Shop_FillSelector(minimum + maximum, 0, tileBuffer);
667 Shop_FillSelector(minimum + quantityIndex + 1, 10, tileBuffer);
668 Shop_FillSelector(minimum, 2, tileBuffer);
669 VramCache_Load(resourceSlot, 0x100, tileBuffer);
670 selectedQuantity = quantityIndex + 1;
671#if (LANGUAGE != LANGUAGE_JAPANESE)
672 UiText_DrawNumberInWindowFar(selectedQuantity, 2, window,
674 UiText_DrawNumberInWindowFar(price * selectedQuantity, 6, window,
675 SHOP_QUANTITY_NUMBER_X + 16, 0);
676 UiText_DrawCharacterAtOffsetFar(MsgCoins, window,
678#else
679 UiText_DrawNumberInWindowFar(selectedQuantity, 2, window, 64, 0);
680 unitMessageBase = MsgPiece;
681 UiText_DrawCharacterAtOffsetFar(unitMessageBase, window, 80, 0);
682 UiText_DrawNumberInWindowFar(price * selectedQuantity, 6, window, 96,
683 0);
684 UiText_DrawCharacterAtOffsetFar(unitMessageBase - 1, window, 144, 0);
685#endif
686 }
687 WaitFrames(1);
688 }
689 WaitFrames(1);
690 UiWork_FinalizeFar(window, 2);
691 }
692 }
694 return selectedQuantity;
695}
696
697/// Adds each item bought to the member, charging its price and adding it to
698/// the shop credit one at a time, then offers to equip it and, if equipped,
699/// to sell what it replaced. Called by Shop_SelectBuy.
700void Shop_BuyDone(s32 unitId, s32 itemId, s32 quantity) {
701 s32 replacedSlot;
702 s32 remainingQuantity;
703 s32 lastAddedSlot;
704 struct ItemDefinition *item;
705
706 remainingQuantity = quantity;
707 item = Item_GetDirectFar(itemId);
708 lastAddedSlot = 0;
709 replacedSlot = Inventory_FindEquippedFar(unitId, item->type);
710 // Keep each inventory addition, coin debit and shop-credit increment in
711 // order.
713 if (lastAddedSlot < remainingQuantity) {
714 do {
715 lastAddedSlot = Inventory_AddItemFar(unitId, itemId);
716 Party_AdjustCoinsFar(0 - item->price);
717 remainingQuantity -= 1;
720 } while (remainingQuantity != 0);
721 }
722 UiMessage_ShowAndRestoreState(MsgHereYouGo);
723 if (Shop_ConfirmEquip(unitId, lastAddedSlot) != 0) {
724 Shop_SellOld(unitId, replacedSlot);
725 }
726}
727
728/// Offers to equip the item just bought, unless it is equipped already, the
729/// member cannot equip it, or what it would replace is cursed on. Equips it
730/// on Yes, redraws the grid and warns of a curse. Returns 1 when it was
731/// equipped. Called by Shop_BuyDone.
733 struct ShopRuntime *shop = gMenuWork;
734 struct BattleUnit *unit = UnitGetFar(unitId);
735 s32 itemId = unit->inventory[slot] & ITEM_ID_MASK;
736 struct ItemDefinition *definition = Item_GetDirectFar(itemId);
737 s32 replacedSlot;
738 struct UiWindow *itemWindow;
739
740 if (unit->inventory[slot] & INVENTORY_EQUIPPED)
741 return 0;
742
743 if (Item_CanOwnerEquip(unitId, itemId) == 0)
744 return 0;
745
746 replacedSlot = Inventory_FindEquippedFar(unitId, definition->type);
747 if (replacedSlot != -1) {
748 struct ItemDefinition *oldInfo =
749 Item_GetDirectFar(unit->inventory[replacedSlot]);
750
751 if (oldInfo->flags & ITEM_CANNOT_UNEQUIP)
752 return 0;
753 }
754
755 UiWork_PushValueSlotFar(unitId, 1);
756 UiMessage_ShowAndWait(MsgEquipNowPrompt);
757 if (UiMessage_ShowChoice(0) != 0)
758 return 0;
759
760 Inventory_EquipFar(unitId, slot);
761 itemWindow = shop->itemWindow;
762 if (itemWindow != 0)
763 Shop_DrawUnitGrid(itemWindow, unitId);
764
765 if (definition->flags & ITEM_CURSED) {
768 UiText_OpenMessageWindowFar(MsgBecameCursed, 8, 4, 2);
769 while (UiWork_IsCompleteFar() == 0) {
770 WaitFrames(1);
771 }
772 }
773
774 UiMessage_ShowAndRestoreState(MsgLookBolder);
775 return 1;
776}
777
778/// Offers to sell the item the new one replaced, unless there is none or it
779/// is a Psynergy item or indispensable. Returns 1 when it offered. Called by
780/// Shop_BuyDone.
781s32 Shop_SellOld(s32 unitId, s32 slot) {
782 struct BattleUnit *unit;
783 s32 itemId;
784
785 unit = UnitGetFar(unitId);
786 if (slot == -1) {
787 return 0;
788 }
789 itemId = unit->inventory[slot] & ITEM_ID_MASK;
790 if (Item_GetDirectFar(itemId)->type == ITEM_TYPE_PSYNERGY) {
791 return 0;
792 }
793 if (Item_GetDirectFar(itemId)->flags & ITEM_INDISPENSABLE) {
794 return 0;
795 }
796 Shop_SellItem(unitId, slot, -1);
797 return 1;
798}
799
800/// Returns what the shops pay for an inventory entry: nothing for an
801/// indispensable item, half the price for a broken one, three quarters
802/// otherwise.
803s32 Shop_SalePrice(s32 inventoryEntry) {
804 s32 salePrice;
805
806 salePrice = Item_GetDirectFar(inventoryEntry)->price;
807
808 if (Item_GetDirectFar(inventoryEntry)->flags & ITEM_INDISPENSABLE) {
809 salePrice = 0;
810 } else if (inventoryEntry & INVENTORY_BROKEN) {
811 salePrice = salePrice / 2;
812 } else {
813 salePrice = salePrice * 3 / 4;
814 }
815 return salePrice;
816}
817
818#undef SHOP_QUANTITY_NUMBER_X
819#undef SHOP_QUANTITY_COIN_LABEL_X
820#undef SHOP_INVENTORY_OFFSET
821#undef SHOP_ICON_DEPTH_OFFSET
822#undef SHOP_STACK_LIMIT
void Shop_BuySpecialItem(struct UiWindow *window, struct UiWindow *itemWindow)
Gives a Game Ticket once the party's shop credit (what it has spent in shops) reaches the next ticket...
Definition artifacts.c:32
#define SHOP_ICON_DEPTH_OFFSET
Definition buy.c:10
void Shop_DrawParty(struct UiWindow *window, s32 selected, s32 itemId)
Shows the chosen member walking and the others standing, and draws at four fifths of their size the m...
Definition buy.c:382
void Shop_DrawUnitItem(struct UiWindow *window, s32 unitId, s32 itemId)
Shows the quantity already carried and the member's inventory icons.
Definition buy.c:484
s32 Shop_ConfirmEquip(s32 unitId, s32 slot)
Offers to equip the item just bought, unless it is equipped already, the member cannot equip it,...
Definition buy.c:732
#define SHOP_QUANTITY_COIN_LABEL_X
Definition buy.c:603
s32 Shop_SalePrice(s32 inventoryEntry)
Returns what the shops pay for an inventory entry: nothing for an indispensable item,...
Definition buy.c:803
void Shop_DrawEquipComparison(struct UiWindow *window, s32 unitId, s32 itemId)
Shows how Attack, Defense and Agility would change if the member wore the item: equips it in place of...
Definition buy.c:407
s32 Shop_SellOld(s32 unitId, s32 slot)
Offers to sell the item the new one replaced, unless there is none or it is a Psynergy item or indisp...
Definition buy.c:781
void Shop_BuyDone(s32 unitId, s32 itemId, s32 quantity)
Adds each item bought to the member, charging its price and adding it to the shop credit one at a tim...
Definition buy.c:700
#define SHOP_QUANTITY_NUMBER_X
Definition buy.c:602
void Shop_DrawMoney(void)
Draws the party's coins in the coins window, in the edition's layout.
Definition buy.c:305
s32 Shop_SelectBuy(void)
Runs the Buy and Artifacts counters: the player browses the stock seven items a page,...
Definition buy.c:19
void Shop_DrawMsg(struct UiWindow *window, s32 message)
Shows message, an item's description, in the help window; a NULL window is ignored.
Definition buy.c:367
s32 Shop_SelectQuantity(s32 minimum, s32 maximum, s32 price)
Chooses how many items to buy or sell, returning the quantity above minimum or -1 when cancelled.
Definition buy.c:611
s32 Shop_SelectBuyNum(s32 unitId, s32 itemId)
Asks how many to buy when the item stacks, limited by what the coins buy, the artifact's stock and a ...
Definition buy.c:547
void Shop_DrawItemPrice(struct UiWindow *window, s32 itemId, s32 price, s32 priceKind)
Draws the item's name and below it its price.
Definition buy.c:328
void Shop_DrawStock(struct UiWindow *window, s32 selected)
Draws a page of seven stock items with their prices, the page arrows, and the chosen item lifted and ...
Definition buy.c:241
#define SHOP_INVENTORY_OFFSET
Definition buy.c:9
#define SHOP_STACK_LIMIT
maximum quantity accepted by Inventory_AddItem
Definition buy.c:12
void Shop_PlaceCursor(struct UiWindow *window, s32 x, s32 y)
Starts the hand cursor's glide to (x, y) pixels inside window (the screen when it is NULL),...
Definition cursor.c:273
struct RenderOutput * Shop_CreatePriceSprite(s32 value, struct UiWindow *window, s32 x, s32 y)
Builds a price of up to five digits in scratch tiles (heap slot 14), loads them into a free VRAM cach...
Definition cursor.c:81
void Shop_FillSelector(s32 itemCount, s32 paletteSelector, u8 *tileBuffer)
Colours the first itemCount segments of the quantity bar with palette paletteSelector,...
Definition cursor.c:25
static __inline__ void Dma_Set(const volatile void *source, volatile void *destination, u32 control, volatile u32 *channel)
Starts a DMA transfer: stores the source, destination and control words into channel's registers with...
Definition dma.h:62
s32 AnimationObjects_SelectAnimationFar(struct AnimationObject *state, s32 flags)
struct BattleUnit * UnitGetFar(u32 owner)
struct ItemDefinition * Item_GetDirectFar(s32 item)
s32 Item_AdjustCounterFar(s32 item_id, s32 delta)
s32 Inventory_CountFar(s32 owner)
s32 Inventory_FindEquippedFar(s32 owner, s32 type)
s32 Inventory_AddItemFar(s32 owner_id, s32 item_id)
s32 Item_GetEquipmentGroupFar(s32 item_id)
s32 Item_IsCompatibleWithOwnerFar(s32 unit, s32 item)
s32 Inventory_RemoveFar(s32 owner, s32 slot)
s32 Inventory_EquipFar(s32 owner, s32 slot)
s32 Item_CanOwnerEquip(s32 unit, s32 item)
Far call to Item_CanOwnerEquipDirect.
s32 Inventory_FindFar(s32 owner, s32 item_id)
void UnitCalcFar(s32 owner)
s32 Party_AdjustShopCreditFar(s32 amount)
s32 Party_AdjustCoinsFar(s32 amount)
void Menu_ReleaseEntryObjectsFar(void)
void PsynergyMenu_InitializeEntryObjectsFar(void *source, s32 originX, s32 originY, s32 spacing, s32 unused)
void AudioCommand_PlayFar(s32 id)
void RenderOutput_PrepareForRedrawFar(struct UiWindow *window)
struct UiWindow * UiWindow_CreateFar(s32 x, s32 y, s32 width, s32 height, s32 attrs)
void RenderOutput_ClearListFar(void *work)
void UiText_DrawNumberInWindowFar(s32 arg0, s32 arg1, struct UiWindow *arg2, s32 arg3, s32 arg4)
struct RenderOutput * RenderOutput_CreateLoadedFar(s32 code, s32 layers, struct UiWindow *input, s32 x, s32 y)
void UiWindow_DrawDividerLineFar(struct UiWindow *win, u32 x1, u32 y1, u32 x2, u32 y2)
void UiText_DrawCharacterAtOffsetFar(s32 character, const struct UiWindow *position, u32 offsetX, u32 offsetY)
struct RenderOutput * RenderOutput_CreateFar(s32 slot, s32 flags, struct UiWindow *input, s32 offsetX, s32 offsetY)
void UiText_DrawResourceFar(s32 arg0, struct UiWindow *arg1, s32 arg2, s32 arg3)
void UiWork_FinalizeFar(struct UiWindow *work, s32 release)
void UiWork_PushValueSlotFar(u32 value, u32 kind)
s32 UiWork_IsCompleteFar(void)
void RenderOutput_RedrawSavedRectFar(struct UiWindow *window)
struct UiWindow * UiText_OpenMessageWindowFar(s32 message, s32 x, s32 y, u32 packed)
void UiWork_FinalizePendingCoreFar(void)
void WaitFrames(s32 frames)
The game's frame loop: runs frames frames and returns.
Definition frame.c:97
struct GameState gGameState
void * gMenuWork
void * Runtime_AllocateBlock(s32 kind, s32 size)
As Runtime_AllocateHeapBlock, but tries EWRAM first, for blocks that need room more than speed: the b...
Definition heap.c:80
@ KEY_UP
Definition io_reg.h:249
@ KEY_B
Definition io_reg.h:244
@ KEY_LEFT
Definition io_reg.h:248
@ KEY_RIGHT
Definition io_reg.h:247
@ KEY_A
Definition io_reg.h:243
@ KEY_DOWN
Definition io_reg.h:250
#define DMA_32BIT
Definition io_reg.h:210
#define REG_DMA3
Definition io_reg.h:153
#define DMA_ENABLE
Definition io_reg.h:217
@ ITEM_TYPE_PSYNERGY
The items that teach Psynergy while held: Orb of Force to Catch Beads.
Definition item.h:52
@ INVENTORY_EQUIPPED
Definition item.h:17
@ INVENTORY_SLOTS
Definition item.h:23
@ ITEM_ID_MASK
Definition item.h:16
@ INVENTORY_QUANTITY_SHIFT
Definition item.h:20
@ INVENTORY_BROKEN
Definition item.h:18
@ ITEM_CANNOT_UNEQUIP
Definition item.h:28
@ ITEM_STACKABLE
Definition item.h:31
@ ITEM_CURSED
Definition item.h:27
@ ITEM_INDISPENSABLE
Definition item.h:30
volatile u32 gKeysPressed
Keys that went down this frame.
volatile u32 gKeysRepeat
Pressed keys, repeating while held: after 19 frames, then every 6.
void Runtime_ReleaseHeapBlock(s32 slot)
void UiMessage_ShowAndRestoreState(s32 messageId)
Shows a shopkeeper's line as UiMessage_ShowAndWait does, hiding the hand cursor while it shows and pu...
Definition run.c:239
s32 Shop_ListAvailableArtifacts(void)
Lists the current shop's artifacts: every item of the shop's type whose shared stock counter is above...
Definition run.c:11
s32 UiMessage_ShowChoice(s32 choice)
Runs the Yes or No choice beside the shopkeeper's line and returns it, keeping the hand cursor's moti...
Definition run.c:278
void UiMessage_ShowAndWait(s32 messageId)
Shows a shopkeeper's line and waits for its window to finish, one more frame included.
Definition run.c:214
void Shop_SellItem(s32 unitId, s32 slot, s32 count)
Sells count of the item in slot: refuses an item the shop does not buy or one equipped and cursed on,...
Definition sell.c:269
void Shop_DrawUnitGrid(struct UiWindow *window, s32 unitId)
Draws the member's items as icons in rows of five, or a line saying it has none.
Definition sell.c:190
const u8 gShop_QuantityTiles[]
What every part of the shop object shares: its includes, the shop actions and sanctum services,...
#define SHOP_QUANTITY_CURSOR_X
#define SHOP_ITEM_NOTE_X
#define SHOP_DRAW_MESSAGE
@ SHOP_STOCK_PAGE_ITEMS
#define SHOP_PRICE_NUMBER_X
#define SHOP_PRICE_LABEL_X
@ SHOP_ACTION_ARTIFACTS
#define SE_SHOP_PURCHASE
Definition songs.h:94
#define SE_MENU_CANCEL
Definition songs.h:106
#define SE_MENU_CURSOR_MOVE
Definition songs.h:104
#define SE_MENU_CONFIRM
Definition songs.h:105
#define SE_103
Definition songs.h:96
A unit in battle: a party member's or an enemy's stats, Psynergy, items, Djinn and ailments.
u8 luck
0 to 99 (UnitCalc)
u16 atk
Attack, 0 to 999 (UnitCalc).
u16 agi
Agility, 0 to 999: the order of the round's actions.
u16 def
Defense, 0 to 999 (UnitCalc).
u16 inventory[15]
Items held, one entry each: bits 15..11 quantity | 10 broken | 9 equipped | 8..0 item id (INVENTORY_*...
An item's definition: its price, type, who can equip it, its bonuses and unleash, its effects and the...
Definition item.h:104
u8 type
enum ItemType; 0 for an item that is not equipment
Definition item.h:106
s16 price
coins
Definition item.h:105
u8 flags
enum ItemFlags
Definition item.h:107
u16 icon
indexes gUiIcon_ItemIconPointers
Definition item.h:109
A render output: a sprite in a window's output list (a character, an icon or a portrait),...
struct RenderOam oam
u8 sentinel
depth slot it is drawn at (254 for glyphs)
s8 active
its UI_SPRITE_ motion; 0 while free
u16 animStep
step of its motion's animation
s8 kind
0 for a glyph; otherwise it owns VRAM cache slot index, freed with it, and 2 (a portrait) also its pa...
struct RenderOutput * anchor
the cursor's sprite, moved with its OAM
Definition cursor.h:14
A shop's work: its windows, the party members' icons, the stock, the cursor and its icons,...
Definition shop.h:18
s32 partyMemberScale[8]
16.16 fixed point, 0x10000 full size
Definition shop.h:27
u16 previousPageIcon
Definition shop.h:36
s8 partyMemberCount
Definition shop.h:47
struct UiWindow * itemWindow
Definition shop.h:22
struct ShopCursor cursor
the hand cursor
Definition shop.h:31
s16 partyMemberIds[9]
the party in order (Party_ListActiveOwners)
Definition shop.h:30
struct AnimationObject * partyMemberIcons[8]
the party row's sprites
Definition shop.h:24
u16 statDownIcon
Definition shop.h:40
struct UiWindow * moneyWindow
the coins window
Definition shop.h:20
u16 selectedItem
the item being bought, sold or repaired
Definition shop.h:43
u16 nextPageIcon
Definition shop.h:37
s8 stockCount
Definition shop.h:46
s16 stockItemIds[129]
the items on sale, ended by 0
Definition shop.h:29
s8 partyAction
the counter chosen (enum ShopAction)
Definition shop.h:50
u8 mode
passed on as the hand cursor's kind; set to 12 at first
Definition shop.h:48
u16 statUpIcon
Definition shop.h:39
A window: a framed rectangle of the tilemap with its sprite outputs, its opening or closing animation...
Definition uiwindow.h:11
#define SHOP_MONEY_WIDTH
#define SHOP_PRICE_WIDTH
signed int s32
Definition types.h:14
unsigned int u32
Definition types.h:15
signed short s16
Definition types.h:12
unsigned short u16
Definition types.h:13
unsigned char u8
Definition types.h:11
s32 Resource_FindFreeEntry(void)
Returns the number of the first free cache entry, or VRAM_CACHE_ENTRY_COUNT when all are taken.
Definition vram_cache.c:211
s32 VramCache_Load(u32 no, u32 size, const void *src)
Gives cache entry no size bytes of sprite VRAM and copies src there.
Definition vram_cache.c:143
@ VRAM_CACHE_ENTRY_COUNT
Definition vram_tab.h:12