Pokered Save Editor 2
Pokemon Red & Blue save file editor - Qt 6 C++/QML
Loading...
Searching...
No Matches
itemmarketmodel.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2020 Twilight
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15*/
16
22
23#include <algorithm>
24#include <QCollator>
25#include <QSet>
26
27#include "./itemmarketmodel.h"
34#include <pse-db/itemsdb.h>
37#include "../bridge/router.h"
38#include <pse-db/gamecornerdb.h>
44
58 file(file)
59{
60 // Setup Item Market Entry globals
64
65 // Rebuild on a currency or exchange-mode change (a different cart) or on a data
66 // reset. NOT on isBuyMode: Buy/Sell only filters the left VIEW now (the cart holds
67 // both), so toggling it must NOT rebuild/clear the cart -- the view proxy just
68 // re-filters off isBuyModeChanged.
69 connect(this, &ItemMarketModel::isMoneyCurrencyChanged, this, &ItemMarketModel::reUpdateAll);
70 connect(this, &ItemMarketModel::isExchangeModeChanged, this, &ItemMarketModel::reUpdateAll);
72
73 // Cleanup and reset on page open
74 connect(this->router, &Router::openNonModal, this, &ItemMarketModel::pageOpening);
75
76 // Any mode change
77 connect(this, &ItemMarketModel::isBuyModeChanged, this, &ItemMarketModel::isAnyChanged);
78 connect(this, &ItemMarketModel::isMoneyCurrencyChanged, this, &ItemMarketModel::isAnyChanged);
79 connect(this, &ItemMarketModel::isExchangeModeChanged, this, &ItemMarketModel::isAnyChanged);
80
81 connect(this, &ItemMarketModel::reUpdateValues, this, &ItemMarketModel::onReUpdateValues);
82
83 // Build the list up front so the model is in a valid, populated state from
84 // construction. Otherwise the cache stays empty until the first dataExpandedChanged
85 // or Pokemart page-open, and a QML binding that reads an aggregate accessor before
86 // then (the order in which the navigation signal reaches this model vs. the screen's
87 // StackView push is not guaranteed) would hit an empty list. reUpdateAll on
88 // dataExpandedChanged and pageOpening still refresh it for the current save/mode.
89 buildList();
90}
91
92int ItemMarketModel::rowCount(const QModelIndex& parent) const
93{
94 // Not a tree, just a list, there's no parent
95 Q_UNUSED(parent)
96
97 // Return list count
98 return itemListCache.size();
99}
100
101QVariant ItemMarketModel::data(const QModelIndex& index, int role) const
102{
103 // If index is invalid in any way, return nothing
104 if (!index.isValid())
105 return QVariant();
106
107 if (index.row() >= itemListCache.size())
108 return QVariant();
109
110 // Get entry from Item List Cache
111 // An entry can be one of several types
112 // * An item the player has which may or may not be sellable
113 // * An item the player can buy
114 // * A Game Corner Pokemon
115 // * Player Money / Coins
116 // * A Message
117 auto item = itemListCache.at(index.row());
118
119 if(item == nullptr)
120 return QVariant();
121
122 // Now return requested information
123 if (role == NameRole)
124 return item->name();
125 else if(role == InStockCountRole)
126 return item->inStockCount();
127 else if(role == CanSellRole)
128 return item->canSell();
129 else if(role == ItemWorthRole)
130 return item->itemWorth();
131 else if(role == WhichTypeRole)
132 return item->whichType();
133 else if(role == StackCountRole)
134 return item->stackCount();
135 else if(role == OnCartLeftRole)
136 return item->onCartLeft();
137 else if(role == CartCountRole)
138 return item->getCartCount();
139 else if(role == CartWorthRole)
140 return item->cartWorth();
141 else if(role == TotalStackCountRole)
142 return item->totalStackCount();
143 else if(role == TotalWorthRole)
144 return item->totalWorth();
145 else if(role == CanCheckoutRole)
146 return item->canCheckout();
147 else if(role == ValidItemRole)
148 return item->requestFilter();
149 else if(role == ExcludeItemRole)
150 return item->exclude;
151 else if(role == ExcludeItemRole)
152 return item->moneyLeftover();
153 else if(role == BuyModeRole)
154 return isBuyMode;
155 else if(role == MoneyCurrencyRole)
156 return isMoneyCurrency;
157 else if(role == ViewTagRole)
158 return item->viewTag;
159 else if(role == CartSignRole)
160 return item->cartSignVal;
161 else if(role == MoneyDirRole) {
162 auto money = qobject_cast<ItemMarketEntryMoney*>(item);
163 return money ? money->forceDir : -1;
164 }
165 else if(role == InfoRole)
166 return item->infoText();
167
168 return QVariant();
169}
170
171QHash<int, QByteArray> ItemMarketModel::roleNames() const
172{
173 QHash<int, QByteArray> roles;
174
175 roles[NameRole] = "dataName";
176 roles[InStockCountRole] = "dataInStockCount";
177 roles[CanSellRole] = "dataCanSell";
178 roles[ItemWorthRole] = "dataItemWorth";
179 roles[WhichTypeRole] = "dataWhichType";
180 roles[StackCountRole] = "dataStackCount";
181 roles[OnCartLeftRole] = "dataOnCartLeft";
182 roles[CartCountRole] = "dataCartCount";
183 roles[CartWorthRole] = "dataCartWorth";
184 roles[TotalStackCountRole] = "dataTotalStackCount";
185 roles[TotalWorthRole] = "dataTotalWorth";
186 roles[CanCheckoutRole] = "dataCanCheckout";
187 roles[ValidItemRole] = "dataValidItem";
188 roles[ExcludeItemRole] = "dataExcludeItem";
189 roles[MoneyLeftRole] = "dataMoneyLeft";
190 roles[BuyModeRole] = "dataBuyMode";
191 roles[MoneyCurrencyRole] = "dataMoneyCurrency";
192 roles[ViewTagRole] = "dataViewTag";
193 roles[CartSignRole] = "dataCartSign";
194 roles[MoneyDirRole] = "dataMoneyDir";
195 roles[InfoRole] = "dataInfo";
196
197 return roles;
198}
199
200bool ItemMarketModel::setData(const QModelIndex& index, const QVariant& value, int role)
201{
202 if(!index.isValid())
203 return false;
204
205 if(index.row() >= itemListCache.size())
206 return false;
207
208 auto item = itemListCache.at(index.row());
209
210 if(item == nullptr)
211 return false;
212
213 // Now set requested information
214 if (role == CartCountRole) {
215 item->setCartCount(value.toInt());
216 dataChanged(index, index);
217 reUpdateValues();
218 return true;
219 }
220
221 return false;
222}
223
225{
226 // Aggregate carried on entry 0; an empty list is an empty cart -> zero worth.
227 if(itemListCache.isEmpty())
228 return 0;
229 return itemListCache.at(0)->totalWorth();
230}
231
233{
234 int ret = 0;
235
236 for(auto el : itemListCache) {
237
238 // Money exchanging is something else entirely, it can't be mixed in with
239 // everything else because they're 2 very different things. They're properly
240 // part of one transaction and all in one cart, but they're added
241 // differently.
242 if(el->exclude)
243 continue;
244
245 ret += el->onCart;
246 }
247
248 return ret;
249}
250
252{
254 return SelBuyMoney;
255 else if(isBuyMode && !isMoneyCurrency)
256 return SelBuyCoins;
257 else if(!isBuyMode && isMoneyCurrency)
258 return SelSellMoney;
259 else if(!isBuyMode && !isMoneyCurrency)
260 return SelSellCoins;
261
262 return 0;
263}
264
266{
268 return basics->money;
269 else
270 return basics->coins;
271}
272
274{
275 // Every entry exposes the same model-wide leftover, so entry 0 is the proxy; with
276 // no entries the cart is empty, so nothing is spent and all the currency remains.
277 if(itemListCache.isEmpty())
278 return moneyStart();
279 return itemListCache.at(0)->moneyLeftover();
280}
281
283{
284 int ret = false;
285
286 for(auto el : itemListCache) {
287
288 if(el->exclude)
289 continue;
290
291 if(el->onCartLeft() < 0) {
292 ret = true;
293 break;
294 }
295 }
296
297 return ret;
298}
299
301{
302 // Aggregate carried on entry 0; with no entries there is nothing to check out.
303 if(itemListCache.isEmpty())
304 return false;
305 return itemListCache.at(0)->canAnyCheckout();
306}
307
309{
310 return basics->money;
311}
312
314{
315 return basics->coins;
316}
317
318// Money on hand after applying every carted swap -- computed by mirroring
319// ItemMarketEntryMoney::checkout() exactly (buy spends onCart money + gains coins;
320// sell gains cartWorth money), so the preview can never disagree with the commit.
322{
323 int m = basics->money;
324
325 for(auto el : itemListCache) {
326 auto money = qobject_cast<ItemMarketEntryMoney*>(el);
327 if(money != nullptr)
328 m += money->moneyDelta(); // signed: -cost buying, +gain selling
329 }
330
331 return m;
332}
333
335{
336 int c = basics->coins;
337
338 for(auto el : itemListCache) {
339 auto money = qobject_cast<ItemMarketEntryMoney*>(el);
340 if(money != nullptr)
341 c += money->coinsDelta(); // signed: +buying, -selling
342 }
343
344 return c;
345}
346
351
356
357// The two exchange rows are one net axis: +N = buying N coins (the Money=>Coins row),
358// -N = selling N coins (the Coins=>Money row). The +Coins / +Money buttons nudge this.
360{
361 int net = 0;
362 for(auto el : itemListCache) {
363 auto money = qobject_cast<ItemMarketEntryMoney*>(el);
364 if(money == nullptr)
365 continue;
366 net += money->buying() ? money->onCart : -money->onCart;
367 }
368 return net;
369}
370
372{
373 ItemMarketEntryMoney* buyRow = nullptr; // Money => Coins
374 ItemMarketEntryMoney* sellRow = nullptr; // Coins => Money
375 for(auto el : itemListCache) {
376 auto money = qobject_cast<ItemMarketEntryMoney*>(el);
377 if(money == nullptr)
378 continue;
379 if(money->buying()) buyRow = money; else sellRow = money;
380 }
381 if(buyRow == nullptr || sellRow == nullptr)
382 return;
383
384 // Work out the requested new net, then clamp it to what each side allows.
385 const int net = buyRow->onCart - sellRow->onCart;
386 int want = net + deltaCoins;
387
388 // Start both at zero so onCartLeft() reads the full available range per side.
389 buyRow->onCart = 0;
390 sellRow->onCart = 0;
391
392 if(want > 0) {
393 buyRow->onCart = qMin(want, buyRow->onCartLeft()); // cap at affordable / coin-cap
394 } else if(want < 0) {
395 sellRow->onCart = qMin(-want, sellRow->onCartLeft()); // cap at owned / money-cap
396 }
397
398 buyRow->onCartChanged();
399 sellRow->onCartChanged();
400 reUpdateValues();
401}
402
404{
405 dataChanged(index(0), index(itemListCache.size() - 1));
406}
407
409{
411 return el->canSell() && (el->buyPriceMoney() > 0) && !el->isGameCornerExclusive();
412
413 return el->canSell() && (el->buyPriceCoins() > 0) && !el->isGameCornerExclusive();
414}
415
416// Is this item sold by a reachable vendor in the actual Gen 1 R/B game? True = a real
417// shop stocks it (so it's "Normal"); false = the game only ever lets you find/sell it,
418// never buy it (so it's "Special"). EITHER way the editor can buy *and* sell it -- this
419// only labels the in-game availability, it does not gate the editor.
420//
421// "Buyable in game" = the union of every referenced Poke-Mart inventory (pokered
422// data/items/marts.asm) PLUS the Celadon-roof vending machine (data/items/
423// vending_prices.asm: Fresh Water, Soda Pop, Lemonade). The two unused/unreferenced
424// mart clerks (the bike shop and a stray spare-mart text) are excluded -- never
425// reachable, and everything they list is sold elsewhere anyway.
426//
427// Indices are the stable game item IDs (ItemDBEntry::getInd), so this needs no JSON.
429{
430 static const QSet<int> kBuyable = {
431 4, // POKE BALL
432 3, // GREAT BALL
433 2, // ULTRA BALL
434 20, // POTION
435 19, // SUPER POTION
436 18, // HYPER POTION
437 17, // MAX POTION
438 16, // FULL RESTORE
439 11, // ANTIDOTE
440 12, // BURN HEAL
441 13, // ICE HEAL
442 14, // AWAKENING
443 15, // PARLYZ HEAL
444 52, // FULL HEAL
445 53, // REVIVE
446 29, // ESCAPE ROPE
447 30, // REPEL
448 56, // SUPER REPEL
449 57, // MAX REPEL
450 32, // FIRE STONE
451 33, // THUNDER STONE
452 34, // WATER STONE
453 47, // LEAF STONE
454 35, // HP UP
455 36, // PROTEIN
456 37, // IRON
457 38, // CARBOS
458 39, // CALCIUM
459 46, // X ACCURACY
460 55, // GUARD SPEC
461 58, // DIRE HIT
462 65, // X ATTACK
463 66, // X DEFEND
464 67, // X SPEED
465 68, // X SPECIAL
466 51, // POKE DOLL
467 60, // FRESH WATER (Celadon roof vending machine)
468 61, // SODA POP (Celadon roof vending machine)
469 62, // LEMONADE (Celadon roof vending machine)
470 201, // TM01 MEGA PUNCH (Celadon Dept. TM counter)
471 202, // TM02 RAZOR WIND
472 205, // TM05 MEGA KICK
473 207, // TM07 HORN DRILL
474 209, // TM09 TAKE DOWN
475 217, // TM17 SUBMISSION
476 232, // TM32 DOUBLE TEAM
477 233, // TM33 REFLECT
478 237, // TM37 EGG BOMB
479 };
480
481 return kBuyable.contains(el->getInd());
482}
483
484// The three Celadon-roof vending-machine drinks (pokered data/items/vending_prices.asm).
485// They ARE buyable in-game (so buyableInGame() is true for them too), but the vending
486// machine is a separate vendor from the Mart, so they get their own "Vending Machine"
487// group rather than sitting in Normal Items.
489{
490 const int i = el->getInd();
491 return i == 60 || i == 61 || i == 62; // Fresh Water, Soda Pop, Lemonade
492}
493
495{
496 // Perform checkout
497 for(auto el : itemListCache) {
498 el->checkout();
499 }
500
501 // Refresh/Reset the list
502 reUpdateAll();
503
504 // Annouce changes and update other values
505 reUpdateValues();
506}
507
509{
510 for(auto el: itemListCache)
511 el->deleteLater();
512
513 itemListCache.clear();
514}
515
517{
518 clearList();
519
520 // Claim the aggregate sweep for THIS model's list. The entry-level totals
521 // (totalWorth / canAnyCheckout / totalStackCount) iterate activeList, so point it
522 // at our live cache -- never the global cross-model registry (use-after-free).
524
525 // Exchange is its own (separate-currency) list. Otherwise the cart is a single
526 // currency holding BOTH the sell rows (your items) and the buy rows (the store);
527 // the Buy/Sell strip only filters the left VIEW (see marketViewModel), it does
528 // not split the cart -- so both are built into one list here.
529 if(isExchangeMode) {
531 } else {
532 buildPlayerItemList(); // sell rows (tagged ViewSell)
533 buildMartItemList(); // buy rows (tagged ViewBuy)
534 }
535
536 reUpdateValues();
537}
538
539// The money<->coins exchange as its own list: both swap directions at once
540// (Money=>Coins and Coins=>Money), each a fixed-direction money row. Pulled out of
541// the buy/sell lists so those stay pure item lists. (Caller clears the list.)
548
549// Sell rows (your bag + storage items). Caller clears the list; the whole appended
550// range is tagged ViewSell so the Buy/Sell strip can filter the left view.
552{
553 const int from = itemListCache.size();
554
555 // Only list items the player can actually sell. An item is sellable iff it has a
556 // discernible price -- Item::canSell() is `price >= 0`, so a *free* item (price 0:
557 // Master Ball, Moon Stone, the PP restoratives, ...) still counts: you can sell it
558 // (for nothing), it just can't be bought. Items with NO price at all (Town Map, the
559 // HMs, badges, the key items -- canSell() false) can't be sold and are left off the
560 // sell list rather than shown as dead rows. canSell() is null-safe (false for an
561 // unknown/glitch id).
562 itemListCache.append(new ItemMarketEntryMessage("Bag"));
563
564 for(auto el: itemBag->items) {
565 if(!el->canSell())
566 continue;
568 }
569
570 itemListCache.append(new ItemMarketEntryMessage("Storage"));
571
572 for(auto el: itemStorage->items) {
573 if(!el->canSell())
574 continue;
576 }
577
578 for(int i = from; i < itemListCache.size(); i++)
579 itemListCache[i]->viewTag = ViewSell;
580}
581
582// Buy rows (the store stock for the active currency). Caller clears the list; the
583// whole appended range is tagged ViewBuy.
585{
586 const int from = itemListCache.size();
587
588 // Setup Collator
589 QCollator collator;
590 collator.setNumericMode(true);
591 collator.setIgnorePunctuation(true);
592
593 auto sortByName = [&collator](QVector<ItemDBEntry*>& v)
594 {
595 std::sort(v.begin(), v.end(),
596 [&collator](const ItemDBEntry* a, const ItemDBEntry* b)
597 {
598 return collator.compare(a->getReadable(), b->getReadable()) < 0;
599 });
600 };
601
602 auto appendItems = [this](const QVector<ItemDBEntry*>& v)
603 {
604 for(auto el : v)
606 };
607
608 QVector<ItemDBEntry*> tmp;
609
611 // Game Corner inventory (coins only): the coins-priced Game-Corner-exclusive
612 // items plus the Pokemon prizes.
613 if(!isMoneyCurrency) {
614 itemListCache.append(new ItemMarketEntryMessage("Inventory"));
615
616 for(auto el : ItemsDB::inst()->getStore()) {
617 if(el->isGameCornerExclusive())
618 tmp.append(el);
619 }
620 sortByName(tmp);
621 appendItems(tmp);
622 tmp.clear();
623
624 for(auto el : GameCornerDB::inst()->getStore()) {
625 if(el->getType() == "pokemon")
627 }
628 }
629
631 // Normal (non-glitch) priced items. Anything with a discernible price is buyable AND
632 // sellable *in the editor* (it infers a buy price, a half-value sell price, and a
633 // coin/money equivalent). They're grouped by how the real game treats them:
634 // * Normal Items -- a Poke-Mart shelf also sells it in-game (buy + sell everywhere).
635 // * Vending Machine-- the three Celadon-roof drinks; buyable in-game, but from a
636 // separate vendor, so they get their own group.
637 // * Special Items -- the game only lets you find/sell it, never buy it (Nugget,
638 // Rare Candy, Max Revive, every TM that isn't on a Celadon shelf)
639 // -- still fully buyable + sellable here in the editor.
640 // vendorListItem() is the price gate; an item with no price anywhere (pokered
641 // prices.asm 0 / absent -- Master Ball, Moon Stone, the PP restoratives, the key
642 // items) has no way to buy or sell and is listed in none of these sections.
643 QVector<ItemDBEntry*> normalItems;
644 QVector<ItemDBEntry*> vendingItems;
645 QVector<ItemDBEntry*> specialItems;
646
647 for(auto el : ItemsDB::inst()->getStore()) {
648 if(el->getOnce() || el->getGlitch() || !vendorListItem(el))
649 continue;
650 if(isVendingItem(el))
651 vendingItems.append(el);
652 else if(buyableInGame(el))
653 normalItems.append(el);
654 else
655 specialItems.append(el);
656 }
657
658 itemListCache.append(new ItemMarketEntryMessage("Normal Items"));
659 sortByName(normalItems);
660 appendItems(normalItems);
661
662 itemListCache.append(new ItemMarketEntryMessage("Vending Machine"));
663 sortByName(vendingItems);
664 appendItems(vendingItems);
665
666 itemListCache.append(new ItemMarketEntryMessage("Special Items"));
667 sortByName(specialItems);
668 appendItems(specialItems);
669
671 // Glitch items (unchanged): any glitch item that still carries a price.
672 itemListCache.append(new ItemMarketEntryMessage("Glitch Items"));
673
674 for(auto el : ItemsDB::inst()->getStore()) {
675 if(el->getGlitch() && vendorListItem(el))
676 tmp.append(el);
677 }
678 sortByName(tmp);
679 appendItems(tmp);
680 tmp.clear();
681
682 for(int i = from; i < itemListCache.size(); i++)
683 itemListCache[i]->viewTag = ViewBuy;
684}
685
687{
688 beginResetModel();
689 buildList();
690 endResetModel();
691}
692
694{
695 // Do nothing unless Trainer Mart Screen
696 if(path != "qrc:/ui/app/screens/non-modal/Pokemart.qml")
697 return;
698
699 buildList();
700}
int getBuyPrice() const
The buy rate (backs getBuyPrice).
static GameCornerDB * inst()
< Number of prize entries.
int getSellPrice() const
The sell rate (half the buy rate).
Market row for a Game Corner Pokemon prize (bought with coins).
Market row that just shows a message (e.g.
Market row representing the player's money/coins balance.
@ DirToMoney
Coins => Money (the "sell" direction).
@ DirToCoins
Money => Coins (the "buy" direction).
virtual int onCartLeft() override
Subtype: how many more may be added.
Market row for one of the player's own items being sold.
Market row for an item the player can buy from the store.
static QVector< ItemMarketEntry * > * activeList
Current model's live rows.
static bool * isMoneyCurrency
Shared: current currency mode.
static PlayerBasics * player
Shared: player money/coins.
int onCart
Backing cart quantity.
static bool * isBuyMode
Shared: current buy/sell mode.
Storage * storage
PC storage (for received Pokemon).
void buildExchangeList()
Build the money<->coins exchange rows (both directions).
bool setData(const QModelIndex &index, const QVariant &value, int role) override
Edit a row (cart count).
ItemStorageBox * itemStorage
The PC item box.
bool buyableInGame(ItemDBEntry *el) const
Is el sold by a reachable Gen-1 vendor (Normal vs Special)?
virtual QHash< int, QByteArray > roleNames() const override
Role -> QML name.
PlayerBasics * basics
Player money/coins.
void exchangeAdjust(int deltaCoins)
Nudge the net (+Coins = +1, +Money = -1).
void clearList()
Empty the row cache.
bool vendorListItem(ItemDBEntry *el)
Should el appear in the store list (has a price here)?
virtual int rowCount(const QModelIndex &parent) const override
Row count.
bool isVendingItem(ItemDBEntry *el) const
Is el a Celadon vending-machine drink (its own group)?
void checkout()
Apply the cart transaction to the save.
virtual QVariant data(const QModelIndex &index, int role) const override
Row+role value.
ItemStorageBox * itemBag
The player's bag.
void buildPlayerItemList()
Build rows from the player's items.
ItemMarketModel(ItemStorageBox *itemBag, ItemStorageBox *itemStorage, PlayerBasics *basics, Router *router, PlayerPokemon *playerPokemon, Storage *storage, SaveFile *file)
void buildList()
Build the rows for the current mode.
void pageOpening(QString path)
Hook when the market page opens.
void reUpdateAll()
Rebuild + recompute everything.
Router * router
For page hooks.
void onReUpdateValues()
Recompute the derived totals.
QVector< ItemMarketEntry * > itemListCache
The current market rows.
void buildMartItemList()
Build rows from the store stock.
PlayerPokemon * playerPokemon
Party (for received Pokemon).
int exchangeNet()
+N buying N coins / -N selling N coins.
SaveFile * file
The live save.
A container of Items – either the trainer's bag or a PC item box.
static ItemsDB * inst()
< Number of items.
Definition itemsdb.cpp:37
The trainer's headline values: name, ID, money, coins, badges, starter.
The player's active party – a specialized PokemonStorageBox.
Screen navigation for the UI – the QML StackView's controller.
Definition router.h:74
One loaded save: the raw 32 KB bytes, their expanded object tree, and the tools that move between the...
Definition savefile.h:46
void dataExpandedChanged(SaveFileExpanded *expanded)
SAV file has changed but the old expansion has not been replaced with exxpansion of new data.
The PC: the item storage box and all 12 Pokemon boxes.
Definition storage.h:49
One item's static data: name/flags, pricing, and where it's used.
Definition itemdbentry.h:46
bool isGameCornerExclusive() const
bool canSell() const
int buyPriceMoney() const
int buyPriceCoins() const
int getInd() const