Pokered Save Editor 2
Pokemon Red & Blue save file editor - Qt 6 C++/QML
Loading...
Searching...
No Matches
playerbasics.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
23
24#include "./player.h"
25#include "./playerbasics.h"
26#include "./playerpokemon.h"
27#include "../savefileexpanded.h"
28#include "../storage.h"
34#include "../../savefile.h"
37#include <pse-db/names.h>
38#include <pse-db/pokemon.h>
39#include <pse-common/random.h>
40
42{
43 load(saveFile);
44}
45
47
49{
50 reset();
51
52 if(saveFile == nullptr) {
53 return;
54 }
55
56 file = saveFile;
57
58 auto toolset = saveFile->toolset;
59 auto it = saveFile->iterator();
60
61 playerName = toolset->getStr(0x2598, 0xB, 7+1);
63
64 playerID = toolset->getWord(0x2605);
66
67 money = toolset->getBCD(0x25F3, 3);
69
70 coins = toolset->getBCD(0x2850, 2);
72
73 // We only pull from the first duplicate, there are 2 identical sets
74 // of badges in the save file
75 it->offsetTo(0x2602);
76
77 // Load and copy over badges
78 bool tmpBadges[8] = {
79 it->getBit(1, 0),
80 it->getBit(1, 1),
81 it->getBit(1, 2),
82 it->getBit(1, 3),
83 it->getBit(1, 4),
84 it->getBit(1, 5),
85 it->getBit(1, 6),
86 it->getBit(1, 7)
87 };
88
89 for(var8 i = 0; i < 8; i++)
90 badges[i] = tmpBadges[i];
91
93
94 playerStarter = toolset->getByte(0x29C3);
96
97 delete it;
98}
99
101{
102 auto toolset = saveFile->toolset;
103
104 toolset->setStr(0x2598, 0xB, 7+1, playerName);
105 toolset->setWord(0x2605, playerID);
106 toolset->setBCD(0x25F3, 3, money);
107 toolset->setBCD(0x2850, 2, coins);
108 toolset->setByte(0x29C3, playerStarter);
109
110 // Badges have to be duplicated on the save file
111 setBadges(saveFile, 0x2602);
112 setBadges(saveFile, 0x29D6);
113}
114
116{
117 file = nullptr;
118
119 playerName = "";
121
122 playerID = 0;
124
125 money = 0;
126 moneyChanged();
127
128 coins = 0;
129 coinsChanged();
130
131 for(var8 i = 0; i < 8; i++)
132 badges[i] = false;
133
135
136 playerStarter = 0;
138}
139
141{
142 reset();
143
144 // Random name and ID
147
148 playerID = Random::inst()->rangeInclusive(0x0000, 0xFFFF);
150
151 // Figure out random money and coins that are reasonable
152 // We want a minimum of 100 money and 0 coins and a maximum of the chosen
153 // maximum
154 money = Random::inst()->rangeInclusive(100, 6000);
155 moneyChanged();
156
157 coins = Random::inst()->rangeInclusive(0, 100);
158 coinsChanged();
159
160 // Zero out all badges first
161 for(var8 i = 0; i < 8; i++)
162 badges[i] = false;
163
164 // Gen 1 progression is strictly linear - you earn the badges in gym order.
165 // So rather than a random bitmask (which could grant a later badge without
166 // the earlier ones), award a random *count* of badges earned linearly from
167 // the first badge (Boulder). Always at least the first badge.
168 var8 badgesEarned = Random::inst()->rangeInclusive(1, 8);
169 for(var8 i = 0; i < badgesEarned; i++)
170 badges[i] = true;
171
173
175}
176
178{
179 // Determine a random starter
180 var8 starter[3] = {
181 PokemonDB::inst()->getIndAt("Bulbasaur")->ind, // Bulbasaur
182 PokemonDB::inst()->getIndAt("Charmander")->ind, // Charmander
183 PokemonDB::inst()->getIndAt("Squirtle")->ind // Squirtle
184 };
185
186 var8 pick = Random::inst()->rangeExclusive(0, 3);
187
188 playerStarter = starter[pick];
190}
191
197
199{
200 money = Random::inst()->rangeExclusive(0, 999999);
201 moneyChanged();
202}
203
205{
206 playerID = Random::inst()->rangeExclusive(0x0000, 0xFFFF);
208}
209
211{
212 // Prepare iterator
213 auto it = saveFile->iterator()->offsetTo(offset);
214
215 // Save Badges
216 it->setBit(1, 0, badges[0]);
217 it->setBit(1, 1, badges[1]);
218 it->setBit(1, 2, badges[2]);
219 it->setBit(1, 3, badges[3]);
220 it->setBit(1, 4, badges[4]);
221 it->setBit(1, 5, badges[5]);
222 it->setBit(1, 6, badges[6]);
223 it->setBit(1, 7, badges[7]);
224
225 delete it;
226}
227
232
234{
235 // How many badges are actually earned (the header's contract). This was a stub
236 // returning maxBadges unconditionally -- so it always read "8" regardless of the
237 // real badge bits. Count the set flags instead. (Found by tst_synthetic_fixtures
238 // 2026-06-13: a 0-badge save reported 8.)
239 int count = 0;
240 for(var8 i = 0; i < maxBadges; i++)
241 if(badges[i])
242 count++;
243 return count;
244}
245
247{
248 return badges[ind];
249}
250
251void PlayerBasics::badgeSet(int ind, bool val)
252{
253 badges[ind] = val;
255}
256
258{
259 // No-op when nothing actually changed. This is important on three counts:
260 // 1. Fidelity — we must NOT rewrite any Pokémon's OT bytes unless the name
261 // truly changed (only-touch-what-you-were-told-to).
262 // 2. Performance — getNonTradeMons() scans the whole party + every storage
263 // box; doing that on a redundant set (e.g. the editor's value round-trip)
264 // is what made name editing hang.
265 // 3. It breaks the QML two-way bind's feedback loop cleanly.
266 if(val == playerName)
267 return;
268
269 // Capture which mons are "owned" (OT == the OLD name) BEFORE we change it, so
270 // traded mons (different OT) are left untouched. Callers must set the name in
271 // one atomic step — never character-by-character — or an intermediate value
272 // could momentarily match a traded mon's OT and sweep it into this set.
273 auto nonTradeMons = getNonTradeMons();
274
275 // Change Player Name
276 playerName = val;
277
278 // Fix owned mons to reflect the new OT Name
279 fixNonTradeMons(nonTradeMons);
280
281 // Announce change
283}
284
286{
287 // See fullSetPlayerName — same guard, same reasoning (OT ID instead of name).
288 if(id == playerID)
289 return;
290
291 // Get List of Non-Trade Mons (owned == OT ID matches the OLD id)
292 auto nonTradeMons = getNonTradeMons();
293
294 // Change Player ID
295 playerID = id;
296
297 // Fix owned mons to reflect new OT ID
298 fixNonTradeMons(nonTradeMons);
299
300 // Announce change
302}
303
305{
306 return playerName;
307}
308
310{
311 return playerID;
312}
313
314QVector<PokemonBox*> PlayerBasics::getNonTradeMons()
315{
316 // Do nothing if file is invalid
317 if(file == nullptr)
318 return QVector<PokemonBox*>();
319
320 // List of mons to fix
321 QVector<PokemonBox*> monsToFix;
322
323 // Add all party members to fix
324 for(auto el : file->dataExpanded->player->pokemon->pokemon) {
325 if(!el->hasTradeStatus(this))
326 monsToFix.append(el);
327 }
328
329 // Add all box mons to fix
330 for(int i = 0; i < maxPokemonBoxes; i++) {
331
332 // Go through each box
333 auto box = file->dataExpanded->storage->boxAt(i);
334 if(box == nullptr)
335 continue;
336
337 // Go through all the Pokemon in each box
338 for(auto el : box->pokemon) {
339 if(!el->hasTradeStatus(this))
340 monsToFix.append(el);
341 }
342 }
343
344 return monsToFix;
345}
346
347void PlayerBasics::fixNonTradeMons(QVector<PokemonBox*> mons)
348{
349 for(auto el : mons)
350 el->changeOtData(true, this);
351}
QString randomExample()
A random string from the list.
NamesPlayer * player() const
The player-name source (backs player).
Definition names.cpp:35
static Names * inst()
< Random player-name source.
Definition names.cpp:29
void save(SaveFile *saveFile)
Flatten the trainer basics back to the save.
void moneyChanged()
void randomizeMoney()
Randomize just the money.
QString getPlayerName()
Current trainer name (backs the property READ).
void playerIDChanged()
void fullSetPlayerName(QString val)
Set name AND fix non-trade mons' OT name.
int coins
Casino coins.
void randomize()
Randomize the trainer basics (constrained).
QVector< PokemonBox * > getNonTradeMons()
All party/box mons whose OT is this trainer (not traded in).
unsigned int money
Money value.
int playerID
Trainer ID (backs the property).
SaveFile * file
Owning save (used by the full-set helpers to reach the mons).
int getPlayerId()
Current trainer ID (backs the property READ).
QString playerName
Trainer name (backs the property).
void randomizeStarter()
Randomize just the starter.
void randomizeCoins()
Randomize just the coin count.
bool badgeAt(int ind)
Is badge ind set?
void playerStarterChanged()
int badgeCount()
How many badges are set.
void coinsChanged()
void fullSetPlayerId(int id)
Set ID AND fix non-trade mons' OT ID.
void badgesChanged()
PlayerBasics(SaveFile *saveFile=nullptr)
< Trainer name; writing it full-sets non-trade mons (see note above).
void reset()
Blank the trainer basics.
protected::void playerNameChanged()
void fixNonTradeMons(QVector< PokemonBox * > mons)
Rewrite those mons' OT name/ID to match this trainer.
void badgeSet(int ind, bool val)
Set/clear badge ind.
void randomizeID()
Randomize just the trainer ID.
int playerStarter
Chosen starter species.
void load(SaveFile *saveFile=nullptr)
Expand the trainer basics from the save.
void setBadges(SaveFile *saveFile, var16 offset)
Write the badge bits at offset.
bool badges[maxBadges]
Per-badge owned flags, indexed by Badges::Badges_.
PokemonDBEntry * toStarter()
Resolve playerStarter to its DB entry.
virtual ~PlayerBasics()
static PokemonDB * inst()
< Number of species.
Definition pokemon.cpp:183
PokemonDBEntry * getIndAt(const QString &key) const
Species by name key (for QML).
Definition pokemon.cpp:199
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
int rangeExclusive(const int start, const int end) const
Random integer in the half-open interval [start, end).
Definition random.cpp:53
void setBit(var8 size, var8 bit, bool value, bool reverse=false)
Set a bit at the cursor.
SaveFileIterator * offsetTo(var16 val)
Move the cursor to an absolute offset. Returns this for chaining.
void setStr(var16 addr, var16 size, var8 maxLen, QString str)
Sets a string to the sav file, converted from UTF-8 to in-game font encoding.
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
var16e var16
Everyday 16-bit alias. Exact width to avoid the "fastest" widening bug.
Definition types.h:125
constexpr var8 maxBadges
Number of Gym badges.
constexpr var8 maxPokemonBoxes
Total PC boxes (12).
Definition storage.h:35
One species' complete static data – the richest entry in the db layer.
Definition pokemon.h:98
var8 ind
Internal species index.
Definition pokemon.h:104