Skip to content
Pokered Save Editor 2
Pokemon Red & Blue save file editor - Qt 6 C++/QML
Loading...
Searching...
No Matches
itemstoragebox.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 "../../qmlownership.h"
26
27#include "./itemstoragebox.h"
28#include "./item.h"
30#include "../../savefile.h"
33
34#include "../savefileexpanded.h"
35#include "../player/player.h"
36#include "../storage.h"
37
38#include <pse-db/itemsdb.h>
39#include <pse-common/random.h>
40
41ItemStorageBox::ItemStorageBox(bool isBag, int maxSize, SaveFile* saveFile, int offset)
44{
45 load(saveFile, offset);
46}
47
49{
50 for(auto item : items)
51 item->deleteLater();
52}
53
55{
56 return items.size();
57}
58
60{
61 int ret = 0;
62
63 for(auto el : items) {
64 ret += el->amount;
65 }
66
67 return ret;
68}
69
71{
72 return maxSize;
73}
74
76{
77 return isBag;
78}
79
81{
82 auto dest = destBox();
83
84 if(dest->items.size() >= dest->itemsMax())
85 return true;
86
87 return false;
88}
89
91{
92 if(ind >= items.size())
93 return nullptr;
94
95 return qmlCppOwned(items.at(ind));
96}
97
99{
100 // Between None and 3/4 of max capacity
101 var8 count = Random::inst()->rangeInclusive(0, maxSize * .75);
102
103 // Create that many random items
104 for(var8 i = 0; i < count; i++) {
105 itemNew();
106 }
107}
108
110{
111 // Essentials
112 items.append(new Item("TOWN MAP", 1));
114
115 items.append(new Item("POKE BALL", Random::inst()->rangeInclusive(5, 15)));
117
118 items.append(new Item("POTION", Random::inst()->rangeInclusive(5, 10)));
120
121 items.append(new Item("ANTIDOTE", Random::inst()->rangeInclusive(1, 3)));
123
124 items.append(new Item("PARLYZ HEAL", Random::inst()->rangeInclusive(1, 3)));
126
127 items.append(new Item("AWAKENING", Random::inst()->rangeInclusive(1, 3)));
129
130 // Again I have no idea where this will drop you so prepare for an escape
131 // If need be, also because you only get 1 HM Slave that doesn't know dig.
132 // This is your dig.
133 items.append(new Item("ESCAPE ROPE", Random::inst()->rangeInclusive(1, 5)));
135
136 // 25% chance of having these items
137 bool giveSuperPotion = Random::inst()->chanceSuccess(25);
138 if(giveSuperPotion) {
139 items.append(new Item("SUPER POTION", 1));
141 }
142
143 bool giveGreatBall = Random::inst()->chanceSuccess(25);
144 if(giveGreatBall) {
145 items.append(new Item("GREAT BALL", 1));
147 }
148
149 // Up to 5 more completely random items
150 var8 count = Random::inst()->rangeInclusive(0, 5);
151
152 // Create that many random items
153 for(var8 i = 0; i < count; i++) {
154 itemNew();
155 }
156}
157
158bool ItemStorageBox::itemMove(int from, int to)
159{
160 if(items.size() <= 0 ||
161 from == to ||
162 from >= items.size() ||
163 from < 0 ||
164 to >= items.size() ||
165 to < 0)
166 return false;
167
168 // Grab and remove item
169 auto eFrom = items.at(from);
170 items.removeAt(from);
171
172 // Insert it elsewhere
173 items.insert(to, eFrom);
174
175 itemMoveChange(from, to);
176 itemsChanged();
177
178 return true;
179}
180
182{
183 if(items.size() <= 0 ||
184 ind < 0 ||
185 ind >= items.size())
186 return;
187
188 items.at(ind)->deleteLater();
189 items.removeAt(ind);
190 itemRemoveChange(ind);
191 itemsChanged();
192}
193
195{
196 for(auto el : items) {
197 if(el->ind == ind)
198 return true;
199 }
200
201 return false;
202}
203
205{
206 int total = 0;
207
208 // Sum every matching row's amount (a box may legitimately hold the same item
209 // in more than one row -- pre-existing duplicate save data is supported).
210 for(auto el : items) {
211 if(el->ind == ind)
212 total += el->amount;
213 }
214
215 return total;
216}
217
219{
220 if(ind < 0)
221 return 0;
222
223 int room = 0;
224
225 // Unused space in existing matching rows (each stack caps at 99).
226 for(auto el : items) {
227 if(el->ind == ind)
228 room += (99 - el->amount);
229 }
230
231 // Plus a full stack for each free row slot.
232 room += (maxSize - items.size()) * 99;
233
234 return room;
235}
236
237int ItemStorageBox::addAmount(int ind, int amount)
238{
239 if(ind < 0 || amount <= 0)
240 return 0;
241
242 int added = 0;
243
244 // Top up existing matching rows first (keep stacks tidy).
245 for(auto el : items) {
246 if(added >= amount)
247 break;
248 if(el->ind != ind)
249 continue;
250 const int put = qMin(99 - el->amount, amount - added);
251 if(put <= 0)
252 continue;
253 el->setAmount(el->amount + put);
254 added += put;
255 }
256
257 // Then spill the remainder into new rows, up to the box capacity.
258 while(added < amount && items.size() < maxSize) {
259 const int put = qMin(99, amount - added);
260 items.append(new Item(static_cast<var8>(ind), static_cast<var8>(put)));
262 added += put;
263 }
264
265 if(added > 0)
266 itemsChanged();
267
268 return added;
269}
270
271int ItemStorageBox::removeAmount(int ind, int amount)
272{
273 if(ind < 0 || amount <= 0)
274 return 0;
275
276 int removed = 0;
277
278 // Drain matching rows from the last toward the first, deleting a row once empty
279 // (iterate backward so removeAt() doesn't shift rows we haven't visited).
280 for(int i = items.size() - 1; i >= 0 && removed < amount; --i) {
281 Item* el = items.at(i);
282 if(el->ind != ind)
283 continue;
284
285 const int take = qMin(el->amount, amount - removed);
286 if(take >= el->amount) {
287 el->deleteLater();
288 items.removeAt(i);
290 } else {
291 el->setAmount(el->amount - take);
292 }
293 removed += take;
294 }
295
296 if(removed > 0)
297 itemsChanged();
298
299 return removed;
300}
301
303{
304 // Build the candidate pool: every real (non-glitch, non-once) item that this
305 // box doesn't already hold. Uniqueness is per-list, so we only check our own
306 // items -- the paired box is irrelevant.
307 QVector<int> pool;
308 for(auto entry : ItemsDB::inst()->getStore()) {
309 if(entry->getGlitch() || entry->getOnce())
310 continue;
311
312 if(hasItemInd(entry->getInd()))
313 continue;
314
315 pool.append(entry->getInd());
316 }
317
318 // The box already holds one of every available item -- nothing unique is left.
319 if(pool.isEmpty())
320 return -1;
321
322 return pool.at(Random::inst()->rangeExclusive(0, pool.size()));
323}
324
326{
327 if(items.size() >= maxSize)
328 return;
329
330 int ind = randomUniqueInd();
331
332 // Don't add a duplicate: if every available item is already here, do nothing.
333 if(ind < 0)
334 return;
335
336 // Amount range matches Item::randomize() (1-5).
337 items.append(new Item(static_cast<var8>(ind), Random::inst()->rangeInclusive(1, 5)));
339 itemsChanged();
340}
341
343{
344 auto dest = destBox();
345
346 bool ret = true;
347
348 while(items.size() > 0 && dest->items.size() < dest->itemsMax()) {
349 if(!relocateOne(0))
350 ret = false;
351 }
352
353 return ret;
354}
355
357{
358 auto dest = destBox();
359
360 if(items.size() <= 0 ||
361 ind < 0 ||
362 ind >= items.size() ||
363 dest->items.size() >= dest->itemsMax())
364 return false;
365
366 auto el = items.at(ind);
367 beforeItemRelocate(el);
368
369 items.removeAt(ind);
370 itemRemoveChange(ind);
371 itemsChanged();
372
373 dest->items.append(el);
374 dest->itemInsertChange();
375 dest->itemsChanged();
376
377 return true;
378}
379
381{
382 if(items.size() <= 0)
383 return;
384
385 // Setup Collator
386 QCollator collator;
387 collator.setNumericMode(true);
388 collator.setIgnorePunctuation(true);
389
390 std::sort(
391 items.begin(),
392 items.end(),
393 [&collator](Item* item1, Item* item2)
394 {
395 if(item1->toItem() == nullptr || item2->toItem() == nullptr)
396 return collator.compare("", "") < 0;
397
398 return collator.compare(item1->toItem()->getReadable(), item2->toItem()->getReadable()) < 0;
399 });
400
402 itemsChanged();
403}
404
405void ItemStorageBox::load(SaveFile* saveFile, int offset)
406{
407 reset();
408
409 this->file = saveFile;
410
411 if(saveFile == nullptr)
412 return;
413
414 auto toolset = saveFile->toolset;
415
416 auto it = saveFile->iterator()->offsetTo(offset+1);
417
418 for (var8 i = 0; i < toolset->getByte(offset) && i < maxSize; i++) {
419 auto item = new Item(it);
420 items.append(item);
421
422 connect(item, &Item::itemChanged, this, &ItemStorageBox::itemsChanged);
424 }
425
426 itemsChanged();
427
428 delete it;
429}
430
431void ItemStorageBox::save(SaveFile* saveFile, int offset)
432{
433 // Save all box items
434 auto it = saveFile->iterator()->offsetTo(offset);
435 it->setByte(items.size());
436 for (var8 i = 0; i < items.size() && i < maxSize; i++) {
437 it->setByte(items.at(i)->ind);
438 it->setByte(items.at(i)->amount);
439 }
440 it->setByte(0xFF);
441 delete it;
442}
443
445{
446 return (isBag)
447 ? file->dataExpanded->storage->items
448 : file->dataExpanded->player->items;
449}
450
452{
453 int ret = 0;
454
455 for(auto el : items) {
456 ret += el->buyPriceAllMoney();
457 }
458
459 return ret;
460}
461
463{
464 int ret = 0;
465
466 for(auto el : items) {
467 ret += el->buyPriceAllCoins();
468 }
469
470 return ret;
471}
472
474{
475 int ret = 0;
476
477 for(auto el : items) {
478 ret += el->sellPriceAllMoney();
479 }
480
481 return ret;
482}
483
485{
486 int ret = 0;
487
488 for(auto el : items) {
489 ret += el->sellPriceAllCoins();
490 }
491
492 return ret;
493}
494
496{
497 for(auto item : items) {
498 item->deleteLater();
499 }
500
501 items.clear();
503 itemsChanged();
504}
505
507{
508 reset();
509
510 if(isBag)
511 randomizeBag();
512 else
514
515 // Re-roll sorts by default (itemNew already guarantees no duplicates).
516 sort();
517
518 itemsChanged();
519}
void sort()
Sort the box contents.
QVector< Item * > items
The stored items.
void save(SaveFile *saveFile, int offset)
Flatten the box to the save.
void reset()
Empty the box.
bool getIsBag()
Is this the bag?
void itemsResetChange()
The box was reset.
void randomizeBag()
Randomizer path for the bag.
bool isBag
Bag vs PC (set at construction; treat as read-only).
void load(SaveFile *saveFile=nullptr, int offset=0)
Expand the box from the save.
ItemStorageBox * destBox()
The paired box for relocation.
virtual ~ItemStorageBox()
int maxSize
Capacity (set at construction; treat as read-only).
void itemNew()
Add a fresh random item (never a duplicate of one already here).
ItemStorageBox(bool isBag, int maxSize, SaveFile *saveFile=nullptr, int offset=0)
How many items are there.
int itemsCountBulk()
Item count including stack amounts.
void itemInsertChange()
An item was inserted.
void itemMoveChange(int from, int to)
An item moved slot.
bool relocateAll()
Move every item to the paired box.
int randomUniqueInd()
A random non-glitch/non-once item index absent from this box, or -1 if none remain.
bool itemMove(int from, int to)
Reorder an item.
bool relocateOne(int ind)
Move one item to the paired box.
void randomizeStorage()
Randomizer path for a PC item box.
int itemsMax()
Capacity.
bool relocateFull()
Is relocation blocked because the paired box is full?
int capacityForInd(int ind)
Room to add more of item ind: the unused space in existing matching rows (up to 99 each) plus 99 for ...
Item * itemAt(int ind)
Item slot ind (GC-protected return).
bool hasItemInd(int ind)
Does this box already contain an item with index ind? (Q_INVOKABLE: the SelectItem dropdown greys out...
void randomize()
Randomize (dispatches to bag/storage path); sorts afterward.
int removeAmount(int ind, int amount)
Remove amount of item ind from this box (drains matching rows from the last, deleting emptied rows).
SaveFile * file
Owning save.
int amountOfInd(int ind)
Total amount of item ind across all rows in this box (Q_INVOKABLE: the SelectItem dropdown shows the ...
int itemsCount()
Distinct item count.
void itemRemoveChange(int ind)
An item was removed.
void itemsChanged()
Box contents changed.
void itemRemove(int ind)
Remove item ind.
int addAmount(int ind, int amount)
Add amount of item ind: top up existing matching rows to 99 first, then add new rows (up to maxSize).
One inventory slot: an item index and an amount, with live pricing.
Definition item.h:36
int ind
Item index (backs property).
Definition item.h:106
int amount
Item amount (max 99 in Gen 1; backs property).
Definition item.h:109
void setAmount(int val)
Set amount (backs property WRITE; clamped to the Gen 1 max).
Definition item.cpp:179
void itemChanged()
static ItemsDB * inst()
< Number of items.
Definition itemsdb.cpp:37
bool chanceSuccess(const int percent) const
Did a percent chance succeed?
Definition random.cpp:73
int rangeInclusive(const int start, const int end) const
Random integer in the closed interval [start, end].
Definition random.cpp:42
static Random * inst()
< Convenience 50% coin flip (integer path), readable from QML.
Definition random.cpp:31
void setByte(var8 val, var16 padding=0)
Write a byte at the cursor; advances.
SaveFileIterator * offsetTo(var16 val)
Move the cursor to an absolute offset. Returns this for chaining.
One loaded save: the raw 32 KB bytes, their expanded object tree, and the tools that move between the...
Definition savefile.h:46
SaveFileToolset * toolset
Tools to operate directly on the raw sav file data.
Definition savefile.h:117
SaveFileIterator * iterator()
Returns a unique iterator that's setup to iterate over the raw sav file data.
Definition savefile.cpp:53
var8e var8
Everyday 8-bit alias. Exact (not "fastest") to dodge the pointer-width bug noted above.
Definition types.h:124
qmlCppOwned() – protect Q_INVOKABLE QObject returns from QML's GC.
static T * qmlCppOwned(T *obj)
Hand QML CppOwnership of a C++-owned QObject returned from a Q_INVOKABLE.