Pokered Save Editor 2
Pokemon Red & Blue save file editor - Qt 6 C++/QML
Loading...
Searching...
No Matches
pokemonstoragebox.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 "./pokemonstoragebox.h"
24#include "../../qmlownership.h"
25#include "./pokemonbox.h"
26#include "../../savefile.h"
27#include "../savefileexpanded.h"
28#include "../player/player.h"
32#include <pse-common/random.h>
33
36{
37 // Own this box (and, via inheritance, the party PlayerPokemon) in C++ so QML's
38 // GC can never free it. The model exposes boxes to QML through public SLOTS
39 // (getCurBox()/getBox(), callable from QML exactly like Q_INVOKABLE) and via
40 // Storage::boxAt(); a parentless QObject returned that way defaults to
41 // JavaScriptOwnership. If QML GC'd a box, its dtor deleteLater()s EVERY mon it
42 // holds -- so the whole box's mons became dangling regardless of the mons'
43 // own ownership, then a virtual call on a freed mon (e.g. isBoxMon() in
44 // PokemonStorageModel::data) crashed intermittently. Owning the box at birth
45 // closes that at the source (storage still frees boxes itself). The party path
46 // (getBox()->party) never went through the boxAt() wrap, so it was unprotected
47 // until now. See qmlownership.h / qt6-patterns.md.
48 QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
49
50 load(saveFile, boxOffset);
51}
52
54{
55 for(auto mon : pokemon)
56 mon->deleteLater();
57}
58
60{
61 return pokemon.size();
62}
63
65{
66 return maxSize;
67}
68
70{
71 return pokemon.size() >= maxSize;
72}
73
75{
76 return qmlCppOwned(pokemon.at(ind));
77}
78
79bool PokemonStorageBox::pokemonMove(int from, int to)
80{
81 if(pokemon.size() <= 0 ||
82 from == to ||
83 from >= pokemon.size() ||
84 from < 0 ||
85 to >= pokemon.size() ||
86 to < 0)
87 return false;
88
89 // Grab and remove item
90 auto eFrom = pokemon.at(from);
91 pokemon.removeAt(from);
92
93 // Insert it elsewhere
94 pokemon.insert(to, eFrom);
95
96 pokemonMoveChange(from, to);
98
99 return true;
100}
101
103{
104 if(pokemon.size() <= 0 ||
105 ind < 0 ||
106 ind >= pokemon.size())
107 return;
108
109 pokemon.at(ind)->deleteLater();
110 pokemon.removeAt(ind);
113}
114
116{
117 if(pokemon.size() >= maxSize)
118 return;
119
120 auto mon = PokemonBox::newPokemon(PokemonRandom::Random_Starters, file->dataExpanded->player->basics);
121 pokemon.append(mon);
124}
125
127{
128 bool ret = true;
129
130 while(pokemon.size() > 0 && dst->pokemon.size() < dst->pokemonMax()) {
131 if(!relocateOne(dst, 0))
132 ret = false;
133 }
134
135 return ret;
136}
137
139{
140 if(pokemon.size() <= 0 ||
141 ind < 0 ||
142 ind >= pokemon.size() ||
143 dst->pokemon.size() >= dst->pokemonMax())
144 return false;
145
146 auto el = pokemon.at(ind);
147 beforePokemonRelocate(el);
148
149 pokemon.removeAt(ind);
152
153 dst->pokemon.append(el);
154 dst->pokemonInsertChange();
155 dst->pokemonChanged();
156
157 return true;
158}
159
160void PokemonStorageBox::load(SaveFile* saveFile, var16 boxOffset)
161{
162 reset();
163
164 this->file = saveFile;
165
166 if(saveFile == nullptr)
167 return;
168
169 auto toolset = saveFile->toolset;
170
171 // Simply read-in and append new Pokemon in box
172 for (var8 i = 0; i < toolset->getByte(boxOffset) && i < maxSize; i++) {
173 pokemon.append(new PokemonBox(
174 saveFile,
175 boxOffset + 0x16,
176 boxOffset + 0x386,
177 boxOffset + 0x2AA,
178 i));
179
181 }
182
184}
185
186void PokemonStorageBox::save(SaveFile* saveFile, var16 boxOffset)
187{
188 auto toolset = saveFile->toolset;
189
190 // Set box size
191 toolset->setByte(boxOffset, pokemon.size());
192
193 // Save each Pokemon
194 for (var8 i = 0; i < pokemon.size() && i < maxSize; i++) {
195 pokemon.at(i)->save(
196 saveFile,
197 boxOffset + 0x16,
198 boxOffset + 0x1,
199 boxOffset + 0x386,
200 boxOffset + 0x2AA,
201 i
202 );
203 }
204
205 // Mark end of species list if not full box
206 if(pokemon.size() >= maxSize)
207 return;
208
209 var16 speciesOffset = boxOffset + 1 + pokemon.size();
210 toolset->setByte(speciesOffset, 0xFF);
211}
212
214{
215 for(auto mon : pokemon)
216 mon->deleteLater();
217
218 pokemon.clear();
219
222}
223
225{
226 reset();
227
228 // Determine fill amount
229
230 // 65% chance the box will remain empty
231 if(Random::inst()->flipCoin())
232 return;
233
234 // If the box is to be filled, how much so?
235 int maxRange[] = {
236 (int)(maxSize * 0.25),
237 (int)(maxSize * 0.50),
238 (int)(maxSize * 0.75),
239 (int)(maxSize * 1.00)
240 };
241
242 // Start with default of index 1
243 int maxRangeSel = 0;
244
245 // Make it incresingly difficult to proceed to higher indexes
246 if(Random::inst()->chanceSuccess(65))
247 maxRangeSel = 1;
248 else if(Random::inst()->chanceSuccess(65))
249 maxRangeSel = 2;
250 else if(Random::inst()->chanceSuccess(65))
251 maxRangeSel = 3;
252
253 // Get end capacity
254 var8 count = Random::inst()->rangeInclusive(0, maxRange[maxRangeSel]);
255
256 // Insert Pokemon
257 for(var8 i = 0; i < count; i++) {
258 auto tmp = new PokemonBox;
259 tmp->randomize(basics);
260 pokemon.append(tmp);
262 }
263
265}
The trainer's headline values: name, ID, money, coins, badges, starter.
A single Pokemon record – the most property-rich object in the tree.
Definition pokemonbox.h:213
virtual void randomize(PlayerBasics *basics=nullptr)
Randomize this Pokemon (constrained).
static PokemonBox * newPokemon(PokemonRandom::PokemonRandom_ list=PokemonRandom::Random_Starters, PlayerBasics *basics=nullptr)
void pokemonChanged()
Box contents changed.
virtual void randomize(PlayerBasics *basics)
Fill with constrained random mons.
int pokemonMax()
Capacity (maxSize).
virtual void pokemonNew()
Add a fresh mon.
void pokemonInsertChange()
A mon was inserted.
virtual void load(SaveFile *saveFile=nullptr, var16 boxOffset=0)
Expand the box from the save.
virtual bool relocateOne(PokemonStorageBox *dst, int ind)
Move one mon into dst.
QVector< PokemonBox * > pokemon
The stored mons.
void pokemonResetChange()
The box was reset.
virtual void save(SaveFile *saveFile, var16 boxOffset=0)
Flatten the box to the save.
void pokemonMoveChange(int from, int to)
A mon moved slot.
void reset()
Empty the box.
SaveFile * file
Owning save.
bool pokemonMove(int from, int to)
Reorder a mon within the box.
bool relocateAll(PokemonStorageBox *dst)
Move every mon into dst.
bool isFull()
At capacity.
int pokemonCount()
Number of mons present.
PokemonStorageBox(int maxSize=boxMaxPokemon, SaveFile *saveFile=nullptr, var16 boxOffset=0)
< How many mons are in the box.
PokemonBox * pokemonAt(int ind)
Mon at ind (GC-protected return).
void pokemonRemove(int ind)
Remove the mon at ind.
void pokemonRemoveChange(int ind)
A mon was removed.
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(var16 addr, var8 val)
Simply sets a byte.
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
var8e var8
Everyday 8-bit alias. Exact (not "fastest") to dodge the pointer-width bug noted above.
Definition types.h:124
var16e var16
Everyday 16-bit alias. Exact width to avoid the "fastest" widening bug.
Definition types.h:125
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.
@ Random_Starters
A "startery"-feeling Pokemon (non-legendary base evo).
Definition pokemonbox.h:115