Pokered Save Editor 2
Pokemon Red & Blue save file editor - Qt 6 C++/QML
Loading...
Searching...
No Matches
spritedata.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 <QDebug>
24
25#include "spritedata.h"
26#include "../../savefile.h"
29#include <pse-db/sprites.h>
35#include <pse-db/mapsdb.h>
36#include <pse-db/trainers.h>
37#include <pse-db/itemsdb.h>
38#include <pse-db/pokemon.h>
39#include <pse-common/random.h>
40
42{
45};
46
48{
49 return Random::inst()->rangeInclusive(0, 3) * 4;
50}
51
53{
54 // Lets not randomize in the possibility of no-collision
55 return Random::inst()->rangeInclusive(0xFE, 0xFF);
56}
57
59{
60 // Lets not randomize such restricted movment
61 var8 ret = Random::inst()->rangeInclusive(0, 10);
62
63 // Ensure we don't get a specialized movement value
64 // I have no idea where these sprites are or on what map so I'd hate to intend
65 // for them to move but don't or very far, randomization is about being a
66 // bit fun
67 while(ret == UpDown ||
68 ret == LeftRight ||
69 ret == Down ||
70 ret == Up ||
71 ret == Left ||
72 ret == Right)
73 ret = Random::inst()->rangeInclusive(0, 10);
74
75 return ret;
76}
77
79{
80 var8 ret[2] = {
81 0x00,
82 0x80
83 };
84
85 return ret[Random::inst()->rangeExclusive(0, 2)];
86}
87
88SpriteData::SpriteData(bool blankNPC, SaveFile* saveFile, var8 index)
89{
90 load(blankNPC, saveFile, index);
91}
92
94{
95 load(entry);
96}
97
99
100void SpriteData::load(bool blankNPC, SaveFile* saveFile, var8 index)
101{
102 if(saveFile == nullptr)
103 return reset(blankNPC);
104
105 // Grab sprite data 1 and 2 which applies to all sprites
106 loadSpriteData1(saveFile, index);
107 loadSpriteData2(saveFile, index);
108
109 // If this isn't the player sprite then load additional non-player data
110 // and check to see if it's missable
111 if (index > 0) {
112 loadSpriteDataNPC(saveFile, index);
113 checkMissable(saveFile, index);
114 }
115}
116
118{
119 reset(true);
120
121 pictureID = spriteData->getToSprite()->ind;
123
124 mapX = spriteData->adjustedX();
125 mapXChanged();
126
127 mapY = spriteData->adjustedY();
128 mapYChanged();
129
130 if(spriteData->getMove() == "Stay")
132 else
135
136 textID = spriteData->getText();
138
139 if(spriteData->getFace() == "Down")
141 else if(spriteData->getFace() == "Left")
143 else if(spriteData->getFace() == "None")
145 else if(spriteData->getFace() == "Right")
147 else if(spriteData->getFace() == "Up")
149
151
152 // Set Missable
153 if(spriteData->getMissable() >= 0) {
154 missableIndex = spriteData->getMissable();
156 }
157
158 if(spriteData->getRange() >= 0) {
159 rangeDirByte = spriteData->getRange();
161 }
162
163 // Because this is a string, it got incorrectly placed into "face"
164 // It's actually a number representing 0x10 and probably needs to go into
165 // range instead
166 else if(spriteData->getFace() == "Boulder Movement Byte 2") {
169 }
170
171 if(spriteData->type() == MapDBEntrySprite::SpriteType::TRAINER) {
172 auto spriteDataTrainer = (MapDBEntrySpriteTrainer*)spriteData;
173 trainerClassOrItemID = spriteDataTrainer->getToTrainer()->ind;
175
176 trainerSetID = spriteDataTrainer->getTeam();
178 }
179 else if(spriteData->type() == MapDBEntrySprite::SpriteType::ITEM) {
180 // There's two weird sprite data entries in gen1, both are named "0"
181 // Not the number 0, an actual string consisting of a zero. The sprite
182 // reference is also invalid. If we spot it we can't load any data from the
183 // sprite
184 auto spriteDataItem = (MapDBEntrySpriteItem*)spriteData;
185
186 // As stated above, stop here if the item is named "0"
187 if(spriteDataItem->getItem() == "0") {
188 return;
189 }
190
191 trainerClassOrItemID = spriteDataItem->getToItem()->getInd();
193 }
194 else if(spriteData->type() == MapDBEntrySprite::SpriteType::POKEMON) {
195 auto spriteDataMon = (MapDBEntrySpritePokemon*)spriteData;
196 trainerClassOrItemID = spriteDataMon->getToPokemon()->ind;
198
199 trainerSetID = spriteDataMon->getLevel();
201 }
202}
203
205{
206 auto it = saveFile->iterator()->offsetTo((0x10 * index) + 0x2D2C);
207
208 pictureID = it->getByte();
210
211 movementStatus = it->getByte();
213
214 imageIndex = it->getByte();
216
217 yStepVector = it->getByte();
219
220 yPixels = it->getByte();
222
223 xStepVector = it->getByte();
225
226 xPixels = it->getByte();
228
229 intraAnimationFrameCounter = it->getByte();
231
232 animFrameCounter = it->getByte();
234
235 faceDir = it->getByte();
237
238 delete it;
239}
240
242{
243 auto it = saveFile->iterator()->offsetTo((0x10 * index) + 0x2E2C);
244
245 walkAnimationCounter = it->getByte(1);
247
248 yDisp = it->getByte();
249 yDispChanged();
250
251 xDisp = it->getByte();
252 xDispChanged();
253
254 mapY = it->getByte();
255 mapYChanged();
256
257 mapX = it->getByte();
258 mapXChanged();
259
260 movementByte = it->getByte();
262
263 grassPriority = it->getByte();
265
266 movementDelay = it->getByte(5);
268
269 imageBaseOffset = it->getByte();
271
272 delete it;
273}
274
276{
277 index--;
278
279 // Init missable index to non-player value
280 missableIndex = -1;
282
283 auto it = saveFile->iterator()->offsetTo((2 * index) + 0x2790);
284 rangeDirByte = it->getByte();
286
287 textID = it->getByte();
289
290 it->offsetTo((2 * index) + 0x27B0);
291 trainerClassOrItemID = it->getByte();
293
294 trainerSetID = it->getByte();
296
297 delete it;
298}
299
301{
302 auto it = saveFile->iterator();
303
304 for (var8 i = 0; i < 17; i++) {
305 it->offsetTo((0x2 * i) + 0x287A);
306 var8 mId = it->getByte();
307 var8 mIndex = it->getByte();
308
309 // Stop when reach list terminator
310 if (mId == 0xFF)
311 break;
312
313 // Skip if the id doesn't match this sprite
314 if (mId != index)
315 continue;
316
317 // Save bit index this missable refers to and add to missable
318 // array for quick reference
319 this->missableIndex = mIndex;
320 this->missableIndexChanged();
321 break;
322 }
323}
324
325void SpriteData::save(SaveFile* saveFile, var8 index)
326{
327 // Grab sprite data 1 and 2 which applies to all sprites
328 saveSpriteData1(saveFile, index);
329 saveSpriteData2(saveFile, index);
330
331 // If this isn't the player sprite then save NPC data
332 if (index > 0) {
333 saveSpriteDataNPC(saveFile, index);
334 }
335}
336
338{
339 auto it = saveFile->iterator()->offsetTo((0x10 * index) + 0x2D2C);
340 it->setByte(pictureID);
341 it->setByte(movementStatus);
342 it->setByte(imageIndex);
343 it->setByte(yStepVector);
344 it->setByte(yPixels);
345 it->setByte(xStepVector);
346 it->setByte(xPixels);
347 it->setByte(intraAnimationFrameCounter);
348 it->setByte(animFrameCounter);
349 it->setByte(faceDir);
350
351 delete it;
352}
353
355{
356 auto it = saveFile->iterator()->offsetTo((0x10 * index) + 0x2E2C);
358 it->setByte(yDisp);
359 it->setByte(xDisp);
360 it->setByte(mapY);
361 it->setByte(mapX);
362 it->setByte(movementByte);
363 it->setByte(grassPriority);
364 it->setByte(movementDelay, 5);
365 it->setByte(imageBaseOffset);
366 delete it;
367}
368
370{
371 // Correct index, this data starts sprite 0 at sprite 1
372 index--;
373 auto it = saveFile->iterator()->offsetTo((2 * index) + 0x2790);
374 it->setByte(*rangeDirByte);
375 it->setByte(*textID);
376
377 it->offsetTo((2 * index) + 0x27B0);
378 it->setByte(*trainerClassOrItemID);
379 it->setByte(*trainerSetID);
380 delete it;
381}
382
383void SpriteData::saveMissables(SaveFile* saveFile, QVector<SpriteData*> spriteData)
384{
385 auto it = saveFile->iterator();
386
387 it->offsetTo(0x287A);
388 for (var8 i = 0; i < spriteData.size() && i < 16; i++) {
389 auto val = spriteData[i];
390
391 // Skip all sprites that aren't missables
392 if(!val->missableIndex || *val->missableIndex < 0)
393 continue;
394
395 // Save sprite index that's missable
396 // Don't correct index, this data starts at sprite 1
397 it->setByte(i);
398
399 // Save missable index this sprite connects to
400 it->setByte(*val->missableIndex);
401 }
402
403 it->setByte(0xFF);
404 delete it;
405}
406
408{
409 if(rangeDirByte)
410 return *rangeDirByte;
411
412 return -1;
413}
414
416{
417 rangeDirByte = val;
419}
420
426
428{
429 if(textID)
430 return *textID;
431
432 return -1;
433}
434
436{
437 textID = val;
439}
440
442{
443 textID.reset();
445}
446
448{
450 return *trainerClassOrItemID;
451
452 return -1;
453}
454
460
466
468{
469 if(trainerSetID)
470 return *trainerSetID;
471
472 return -1;
473}
474
476{
477 trainerSetID = val;
479}
480
486
488{
489 if(missableIndex)
490 return *missableIndex;
491
492 return -1;
493}
494
496{
497 missableIndex = val;
499}
500
506
508{
509 return yStepVector;
510}
511
513{
514 yStepVector = val;
516}
517
519{
520 return xStepVector;
521}
522
524{
525 xStepVector = val;
527}
528
529void SpriteData::reset(bool blankNPC)
530{
531 pictureID = 0;
533
536
537 imageIndex = 0xFF;
539
542
543 yDisp = 0x8;
544 yDispChanged();
545
546 xDisp = 0x8;
547 xDispChanged();
548
549 mapY = 4;
550 mapYChanged();
551
552 mapX = 4;
553 mapXChanged();
554
557
560
561 yStepVector = 0;
563
564 yPixels = 0;
566
567 xStepVector = 0;
569
570 xPixels = 0;
572
575
578
579 walkAnimationCounter = 0x10; // Could also be zero, not important
581
582 movementDelay = 0;
584
585 imageBaseOffset = 0;
587
588 if(!blankNPC) {
589 rangeDirByte.reset();
590 textID.reset();
591 trainerClassOrItemID.reset();
592 trainerSetID.reset();
593 }
594 else {
595 rangeDirByte = 0;
596 textID = 0;
598 trainerSetID = 0;
599 }
600
605
606 missableIndex.reset();
608}
609
610/*
611 * After soem expirementation with my old editor I learned that:
612 * X & Y pixel values don't do anything, only the X & Y map does
613 * The X & Y map values are exactly +4 off each of normal positioning
614 * Literally all the animation details can be zero and work just fine
615 * Frame Index seems to do better at 255
616 * X&Y Track have to be at 8 each
617 * Outdoor sprites will be glitchy, I've played around with pre-loaded sprites
618 * and many other variables but can't really find a solution as to why.
619 *
620 * Outside of that it doesn't seem to matter at all what most all of these
621 * values are set to. There is no getting around the outdoor sprites being
622 * glitchy though. I've tried everything I can think of, even changing tileset
623 * type to "indoor" but it's not the end of the world.
624 */
625QVector<SpriteData*> SpriteData::randomizeAll(QVector<MapDBEntrySprite*> mapSprites)
626{
627 // Prepare return value
628 QVector<SpriteData*> sprites;
629
630 // Add blank player sprite, very important for it to be at pos 0
631 // Player sprite actually has a lot of data but none of it's used at all by
632 // the game
633 sprites.append(new SpriteData(false));
634
635 // Make first pass and get all sprite positions on map
636 QVector<TmpSpritePos*> tmpPos;
637
638 for(auto entry : mapSprites) {
639
640 // Don't note any positions of boulder sprites, leave them as they are
641 // with nobody placed on top of them. The map could be unplayable otherwise.
642 if(entry->getSprite() == "Boulder")
643 continue;
644
645 auto tmp = new TmpSpritePos;
646 tmp->x = entry->adjustedX();
647 tmp->y = entry->adjustedY();
648 tmpPos.append(tmp);
649 }
650
651 // Now create new sprites from data and randomize their contents
652 for(auto entry : mapSprites) {
653
654 // New Sprite from map data
655 auto tmp = new SpriteData(entry);
656 sprites.append(tmp);
657
658 // If boulders are changed at all then the player cannot progress, leave
659 // boulders the way they are. Do this only for randomizeAll, if the player
660 // wants to randomize a specific one, even if it breaks gameplay, let them
661 // do it
662 if(entry->getSprite() == "Boulder")
663 continue;
664
665 // Randomize newly created sprite
666 tmp->randomize(&tmpPos);
667 }
668
669 return sprites;
670}
671
672void SpriteData::randomize(QVector<TmpSpritePos*>* tmpPos)
673{
674 // Randomize coordinates if coord list is provided
675 if(tmpPos != nullptr) {
676
677 // Pull random coordinates
678 var8 rndPos = Random::inst()->rangeExclusive(0, tmpPos->size());
679 auto rndCoords = tmpPos->at(rndPos);
680
681 // Change coords
682 mapX = rndCoords->x;
683 mapXChanged();
684
685 mapY = rndCoords->y;
686 mapYChanged();
687
688 // Remove chosen random coordinate
689 delete rndCoords;
690 tmpPos->removeAt(rndPos);
691 }
692
693 // Get a random sprite picture and change it
694 auto picture =
695 SpritesDB::inst()->getStore().at(Random::inst()->rangeExclusive(0, SpritesDB::inst()->getStoreSize()));
696
697 pictureID = picture->ind;
699
700 // Get a random facing direction and movement
703
706
707 // Without absurdly more complex coding there's no way to tell if the sprite
708 // is in grass or not so we just give it a random value.
711}
712
714{
715 reset();
716
717 if(spriteData == nullptr)
718 return;
719
720 load(spriteData);
721}
722
723QVector<SpriteData*> SpriteData::setToAll(QVector<MapDBEntrySprite*> spriteSigns)
724{
725 // Prepare return value
726 QVector<SpriteData*> sprites;
727
728 for(auto entry : spriteSigns) {
729
730 // New Sprite from map data
731 auto tmp = new SpriteData;
732 sprites.append(tmp);
733 tmp->setTo(entry);
734 }
735
736 return sprites;
737}
738
740{
741 return SpritesDB::inst()->getIndAt(QString::number(pictureID));
742}
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 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
SaveFileIterator * iterator()
Returns a unique iterator that's setup to iterate over the raw sav file data.
Definition savefile.cpp:53
void movementStatusChanged()
void loadSpriteData1(SaveFile *saveFile, var8 index)
Expand the first sprite-data table for index.
int grassPriority
(0x80 in grass, 0x00 otherwise) - Prioritizing grass drawn around sprite
Definition spritedata.h:309
void movementDelayChanged()
void setMissableIndex(int val)
int movementByte
(0xFF not moving, 0xFE random movements, others move without collision)
Definition spritedata.h:306
void load(bool blankNPC=false, SaveFile *saveFile=nullptr, var8 index=0)
std::optional< int > missableIndex
Sprite data that applies to all non-player missable sprites (All sprites that aren't sprite #0 and ha...
Definition spritedata.h:377
void yStepVectorChanged()
int getTrainerClassOrItemID()
std::optional< int > textID
Text id when this sprite is interacted with.
Definition spritedata.h:361
void xPixelsChanged()
void setTrainerSetID(int val)
void setXStepVector(int val)
void saveSpriteData2(SaveFile *saveFile, var8 index)
Flatten the second sprite-data table.
void saveSpriteData1(SaveFile *saveFile, var8 index)
Flatten the first sprite-data table.
void randomize(QVector< TmpSpritePos * > *tmpPos)
Randomize this sprite (avoiding tmpPos clashes).
virtual ~SpriteData()
void resetMissableIndex()
Clear the optional missableIndex.
SpriteData(bool blankNPC=false, SaveFile *saveFile=nullptr, var8 index=0)
Create a blank sprite or load one from a map.
int getTextID()
void setTrainerClassOrItemID(int val)
int movementDelay
Delay until next movement, counts downward and flags movementStatus ready once reached.
Definition spritedata.h:342
void setRangeDirByte(int val)
void movementByteChanged()
void mapYChanged()
void faceDirChanged()
std::optional< int > trainerSetID
Trainer data id.
Definition spritedata.h:367
void textIDChanged()
int getMissableIndex()
void mapXChanged()
void imageBaseOffsetChanged()
void trainerClassOrItemIDChanged()
void xStepVectorChanged()
void animFrameCounterChanged()
void setTextID(int val)
void save(SaveFile *saveFile, var8 index)
void loadSpriteData2(SaveFile *saveFile, var8 index)
Expand the second sprite-data table for index.
int imageBaseOffset
Used to help compute imageIndex based on vram.
Definition spritedata.h:345
int yDisp
Important to set to 0x8 Keep sprites from wandering too far however it's noted that it's bugged to be...
Definition spritedata.h:293
int yPixels
Screen position in pixels aligned to 4 pixels offset from the grid (To appear centered).
Definition spritedata.h:326
void loadSpriteDataNPC(SaveFile *saveFile, var8 index)
Expand the NPC-only sprite data for index.
int getRangeDirByte()
void reset(bool blankNPC=false)
Blank this sprite (optionally as a blank NPC).
void setYStepVector(int val)
void resetTextID()
Clear the optional textID.
std::optional< int > rangeDirByte
How far a walking sprite can wander, or if still the facing direction A walking sprite having a value...
Definition spritedata.h:358
void xDispChanged()
int mapY
These are very important to set, just usual coordinates for map placement +4 Coordinate 0,...
Definition spritedata.h:302
int getYStepVector()
void walkAnimationCounterChanged()
static QVector< SpriteData * > setToAll(QVector< MapDBEntrySprite * > spriteSigns)
Build sprites from a map's sprite list.
var8 xStepVector
Definition spritedata.h:322
var8 yStepVector
These actually don't really matter and can be zero I'm not sure if the game even uses these values in...
Definition spritedata.h:321
static void saveMissables(SaveFile *saveFile, QVector< SpriteData * > spriteData)
void setTo(MapDBEntrySprite *spriteData)
Copy values from a map-defined sprite.
static QVector< SpriteData * > randomizeAll(QVector< MapDBEntrySprite * > mapSprites)
Randomize a whole map's sprites.
int getTrainerSetID()
void saveSpriteDataNPC(SaveFile *saveFile, var8 index)
Flatten the NPC-only sprite data.
void resetTrainerClassOrItemID()
Clear the optional trainerClassOrItemID.
protected::void pictureIDChanged()
int movementStatus
(0: uninitialized, 1: ready, 2: delayed, 3: moving) Advised to use ready
Definition spritedata.h:281
void yDispChanged()
int getXStepVector()
void missableIndexChanged()
std::optional< int > trainerClassOrItemID
If this is an item sprite, the item id, otherwise the trainer class.
Definition spritedata.h:364
void resetTrainerSetID()
Clear the optional trainerSetID.
int animFrameCounter
Animation frame counter.
Definition spritedata.h:334
int walkAnimationCounter
Tracks movement & wandering, sprites are given 0x10 and it's decremented.
Definition spritedata.h:338
int faceDir
(0: down, 4: up, 8: left, $c: right)
Definition spritedata.h:288
SpriteDBEntry * toSprite()
Resolve this sprite to its DB entry.
int intraAnimationFrameCounter
Counter that helps delay between animation frames so things aren't so instant and fast.
Definition spritedata.h:331
void grassPriorityChanged()
int pictureID
Sprite data that applies to all sprites.
Definition spritedata.h:277
void intraAnimationFrameCounterChanged()
void imageIndexChanged()
void resetRangeDirByte()
Clear the optional rangeDirByte.
void rangeDirByteChanged()
int imageIndex
Basically, in the sprite sheet strip, which "pane" or "tile" is it at 0xFF if not on the screen.
Definition spritedata.h:285
void yPixelsChanged()
void trainerSetIDChanged()
void checkMissable(SaveFile *saveFile, var8 index)
Resolve whether this sprite is a missable.
static SpritesDB * inst()
< Number of sprites.
Definition sprites.cpp:36
SpriteDBEntry * getIndAt(const QString &key) const
Sprite by name key (for QML).
Definition sprites.cpp:52
const QVector< SpriteDBEntry * > getStore() const
All sprites.
Definition sprites.cpp:42
var8e var8
Everyday 8-bit alias. Exact (not "fastest") to dodge the pointer-width bug noted above.
Definition types.h:124
A map sprite that is a pick-up item (type ITEM).
A map sprite that is a static, battleable Pokemon (type POKEMON).
A map sprite that is a battleable trainer (type TRAINER).
A map's sprite definition – base class for the four sprite kinds.
virtual SpriteType type() const
The sprite kind (overridden by subclasses).
const QString getMove() const
const QString getFace() const
SpriteDBEntry * getToSprite() const
One sprite definition: its name/picture-id and the maps that use it.
Definition sprites.h:38
var8 ind
Picture id.
Definition sprites.h:43
static var8 random()
A random valid facing.
static var8 random()
A random grass value.
static var8 random()
A random mobility value.
@ Ready
Ready (advised value).
Definition spritedata.h:39
static var8 random()
A random movement pattern.