Pokered Save Editor 2
Pokemon Red & Blue save file editor - Qt 6 C++/QML
Loading...
Searching...
No Matches
fontsdb.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2019 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#include <QVector>
23#include <QJsonArray>
24#include <QQmlEngine>
25#include <pse-common/utility.h>
26
27#include "./fontsdb.h"
28#include "./util/gamedata.h"
29#include "./util/fontsearch.h"
31
33{
34 static bool once = false;
35 if(once)
36 return;
37
38 // Grab Event Pokemon Data
39 auto jsonData = GameData::inst()->json("font");
40
41 // Go through each event Pokemon
42 for(QJsonValue jsonEntry : jsonData.array())
43 {
44 // Create a new event Pokemon entry
45 auto entry = new FontDBEntry(jsonEntry);
46
47 // Add to array
48 store.append(entry);
49 }
50
51 once = true;
52}
53
55{
56 static bool once = false;
57 if(once)
58 return;
59
60 for(auto entry : store)
61 {
62 // Index name and index
63 ind.insert(entry->name, entry);
64 ind.insert("ind"+QString::number(entry->ind), entry);
65 }
66
67 once = true;
68}
69
70void FontsDB::qmlProtect(const QQmlEngine* const engine) const
71{
72 Utility::qmlProtectUtil(this, engine);
73 for(auto el : store)
74 el->qmlProtect(engine);
75}
76
77void FontsDB::qmlRegister() const
78{
79 static bool once = false;
80 if(once)
81 return;
82
83 qmlRegisterUncreatableType<FontsDB>("PSE.DB.FontsDB", 1, 0, "FontsDB", "Can't instantiate in QML");
84 once = true;
85}
86
87FontsDB::FontsDB()
88{
89 qmlRegister();
90 load();
91}
92
94{
95 return new FontSearch();
96}
97
98QScopedPointer<FontSearch, QScopedPointerDeleteLater> FontsDB::search() const
99{
100 return QScopedPointer<FontSearch, QScopedPointerDeleteLater>(
101 new FontSearch());
102}
103
104// Converts a string filled with english typable in-game text code
105// representations to raw in-game code
106// If fed strings not in the representation list, the unknown characters will
107// be ignored thus possibly corrupting output
108// Possibly very slow
109const QVector<int> FontsDB::convertToCode(QString str, int maxLen, const bool autoEnd) const
110{
111 // Code string to return
112 QVector<int> code;
113
114 // Last code
115 int lastCode = 0;
116
117 // Auto Ending takes up a character, remove 1 from max length if we auto-end
118 if(autoEnd)
119 maxLen -= 1;
120
121 // loop through all string characters
122 while (str.length() != 0) {
123
124 bool match = false;
125
126 // Loop through all 255 font characters
127 // This is what makes it slow and why this is complicated
128 // "a" maps to "a" in-game but "<m>" maps to "male symbol" in-game
129 // "a<m>" maps to 2 characters in-game not 4. When we find a symbol we have
130 // to remove all the characters in the string before proceeding.
131 for (int i = 0; i < store.length(); i++) {
132
133 // Find a starting match
134 // Ignore this character if none are found
135 FontDBEntry* transPair = store.at(i);
136 if (!str.startsWith(transPair->name))
137 continue;
138
139 // Match was found
140 match = true;
141
142 // Slice off match from string start
143 str = str.mid(transPair->name.length());
144
145 // Append code to code array and set last code
146 code.append(transPair->ind);
147 lastCode = transPair->ind;
148
149 // Break early
150 break;
151 }
152
153 // If no match then strip unknown character and continue
154 if (match == false)
155 str = str.mid(1);
156
157 // Stop here if code array is at max bytes or a stop code was manually
158 // set (0x50)
159 if (code.length() >= maxLen || lastCode == 0x50)
160 break;
161 }
162
163 // In-Game font marks the end-of-line to be 0x50
164 // if auto-end is requested, append an 0x50
165 if(autoEnd)
166 code.append(0x50);
167
168 // Return finished product
169 return code;
170}
171
172// Much easier and faster, just expand the in-game code to it's english
173// representation directly
174const QString FontsDB::convertFromCode(const QVector<int> codes, const int maxLen) const
175{
176 // Prepare empty string
177 QString eng = "";
178
179 for (int i = 0; i < codes.length(); i++) {
180 int code = codes[i];
181
182 // Don't include the end terminator
183 // stop here if there is one
184 if (code == 0x50)
185 break;
186
187 // If the code is invalid then also stop here
188 if(ind.value("ind"+QString::number(code), nullptr) == nullptr)
189 continue;
190
191 // Append to end of string the typable equivelant
192 eng += ind.value("ind"+QString::number(code))->name;
193
194 // Stop here if we've reached max length
195 if (i >= maxLen)
196 break;
197 }
198
199 // Return string
200 return eng;
201}
202
203// Converts an english format string to code represented as how it would be
204// in-game
205const QString FontsDB::expandStr(const QString msg, const int maxLen, const QString rival, const QString player) const
206{
207 // Convert string to char codes
208 // Very expensive
209 QVector<int> charCodes = convertToCode(msg, maxLen, true);
210
211 // First we go through and stop or react on some control codes
212 for(int i = 0; i < charCodes.length(); i++) {
213 // Grab a code
214 int code = charCodes[i];
215
216 // <page> => stop & end
217 // Verified: It awaits for you to continue by pressing any key to advance
218 // dialog
219 if(code == 73) {
220 charCodes = charCodes.mid(0, i);
221 break;
222 }
223
224 // <cont_> => stop & end
225 // Verified: It awaits for you to continue by pressing any key to advance
226 // dialog.
227 else if(code == 75) {
228 charCodes = charCodes.mid(0, i);
229 break;
230 }
231
232 // <autocont> => cut off up til now and keep rest
233 // Verified: It doesn't await for you to continue and just advances
234 // dialog.
235 else if(code == 76) {
236 charCodes = charCodes.mid(i+1, -1);
237 i = 0;
238 }
239
240 // <end> => stop & end
241 // Verified: Ends dialog
242 else if(code == 80) {
243 charCodes = charCodes.mid(0, i);
244 break;
245 }
246
247 // <para> => stop & end
248 // Verified: It awaits for you to continue by pressing any key to advance
249 // dialog.
250 else if(code == 81) {
251 charCodes = charCodes.mid(0, i);
252 break;
253 }
254
255 // <cont> => stop & end
256 // Verified: It awaits for you to continue by pressing any key to advance
257 // dialog.
258 else if(code == 85) {
259 charCodes = charCodes.mid(0, i);
260 break;
261 }
262
263 // <done> => cut off up til now and keep rest
264 // Verified: More or less does <autocont>
265 else if(code == 87) {
266 charCodes = charCodes.mid(i+1, -1);
267 }
268
269 // <prompt> => cut off up til now and keep rest
270 // Verified: More or less does <para>
271 else if(code == 88) {
272 charCodes = charCodes.mid(0, i);
273 break;
274 }
275 }
276
277 int lineCount = 1;
278
279 // Now begin processing it, we have to expand the expansive char codes first
280 for(int i = 0; i < charCodes.length(); i++) {
281
282 // Grab a code
283 int code = charCodes[i];
284
285 // Expand if any of the codes are present
286 // <pkmn> => <pk><mn>
287 if (code == 0x4A) {
288 splice(charCodes, "<pk><mn>", i);
289 }
290 // <player> => RED
291 else if (code == 0x52) {
292 splice(charCodes, player, i);
293 }
294 // <rival> => BLUE
295 else if (code == 0x53) {
296 splice(charCodes, rival, i);
297 }
298 // <poke> => POK<e>
299 else if (code == 0x54) {
300 splice(charCodes, "POK<e>", i);
301 }
302 // <......> => <...><...>
303 else if (code == 0x56) {
304 splice(charCodes, "<...><...>", i);
305 }
306 // <targ> => CHARIZARD
307 else if (code == 0x59) {
308 splice(charCodes, "CHARIZARD", i);
309 }
310 // <user> => Enemy BLASTOISE
311 else if (code == 0x5A) {
312 splice(charCodes, "Enemy BLASTOISE", i);
313 }
314 // <pc> -> PC
315 else if (code == 0x5B) {
316 splice(charCodes, "PC", i);
317 }
318 // <tm> -> TM
319 else if (code == 0x5C) {
320 splice(charCodes, "TM", i);
321 }
322 // <trainer> => TRAINER
323 else if (code == 0x5D) {
324 splice(charCodes, "TRAINER", i);
325 }
326 // <rocket>
327 else if (code == 0x5E) {
328 splice(charCodes, "ROCKET", i);
329 }
330
331 // <next> => move to next line
332 // Verified: Moves down a line
333 else if(code == 78) {
334 lineCount++;
335 if(lineCount > 2) { // Allow 1 past end of line
336 charCodes = charCodes.mid(0, i);
337 break;
338 }
339 }
340
341 // <line> => move to next line
342 // Verified: Moves down a line
343 else if(code == 79) {
344 lineCount++;
345 if(lineCount > 2) { // Allow 1 past end of line
346 charCodes = charCodes.mid(0, i);
347 break;
348 }
349 }
350
351 // <dex> => Add a period
352 // Verified: Does different things, mostly just adds a period, sometimes
353 // other stuff like restarting the line
354 else if(code == 95) {
355 splice(charCodes, ".", i);
356 }
357 }
358
359 // Return as a converted string, also create line-breaks
360 auto ret = convertFromCode(charCodes, 255);
361 ret = ret.replace("<next>", "\n");
362 ret = ret.replace("<line>", "\n");
363
364 return ret;
365}
366
368{
369 static FontsDB* _inst = new FontsDB();
370 return _inst;
371}
372
373const QVector<FontDBEntry*> FontsDB::getStore() const
374{
375 return store;
376}
377
378const QHash<QString, FontDBEntry*> FontsDB::getInd() const
379{
380 return ind;
381}
382
384{
385 return store.size();
386}
387
388FontDBEntry* FontsDB::getStoreAt(const int ind) const
389{
390 if(ind < 0 || ind >= store.size())
391 return nullptr;
392
393 return store.at(ind);
394}
395
396FontDBEntry* FontsDB::getIndAt(const QString val) const
397{
398 return ind.value(val, nullptr);
399}
400
402{
403 // Font values start at 1, to get requested value, offset by 1
404 // Check for negatives as well
405 ind--;
406 if(ind >= store.size() ||
407 ind < 0)
408 return nullptr;
409
410 return store.at(ind);
411}
412
414{
415 return const_cast<FontDBEntry*>(getStoreByVal(ind));
416}
417
419{
420 return getStoreSize();
421}
422
423int FontsDB::countSizeOf(const QString val) const
424{
425 // Ignore max size and ending byte
426 return convertToCode(val, 255, false).size();
427}
428
429int FontsDB::countSizeOfExpanded(const QString val) const
430{
431 // Gives length of expanded string in tiles with maximum player and rival
432 // names
433 return convertToCode(
434 expandStr(val, 255, "1234567", "1234567"), 255, false).size();
435}
436
437void FontsDB::splice(QVector<int>& out, const QString in, const int position) const
438{
439 auto codes = convertToCode(in, 255, false);
440
441 // Replace the tile at `position` with its expansion. The original code MUST be
442 // removed first — leaving it in place makes expandStr re-expand the same
443 // variable tile forever (infinite-loop hang, e.g. on "next random example").
444 out.remove(position);
445 for(int i = 0; i < codes.size(); i++)
446 out.insert(position + i, codes.at(i));
447}
A chainable filter ("finder") over the font glyphs.
Definition fontsearch.h:40
const QHash< QString, FontDBEntry * > getInd() const
Name->glyph index.
Definition fontsdb.cpp:378
FontDBEntry * getIndAt(const QString val) const
Glyph by name key (for QML).
Definition fontsdb.cpp:396
static FontsDB * inst()
< Number of font glyphs.
Definition fontsdb.cpp:367
FontSearch * searchRaw() const
Raw finder for QML (caller must delete; see note).
Definition fontsdb.cpp:93
FontDBEntry * getStoreAt(const int ind) const
Glyph by store index (for QML).
Definition fontsdb.cpp:388
void load()
Load glyphs from JSON.
Definition fontsdb.cpp:32
const QVector< int > convertToCode(QString str, int maxLen=11, const bool autoEnd=true) const
String -> font codes (see note; expensive).
Definition fontsdb.cpp:109
void index()
Build the name->glyph index.
Definition fontsdb.cpp:54
int countSizeOfExpanded(const QString val) const
Rendered length after expandStr (expensive).
Definition fontsdb.cpp:429
FontDBEntry * fontAt(int ind) const
Alias for getStoreAt (1-based-friendly UI accessor).
Definition fontsdb.cpp:413
int countSizeOf(const QString val) const
Rendered length of val (expensive).
Definition fontsdb.cpp:423
int getStoreSize() const
Glyph count.
Definition fontsdb.cpp:383
const QString convertFromCode(const QVector< int > codes, const int maxLen=11) const
Font codes -> string (fast).
Definition fontsdb.cpp:174
void qmlProtect(const QQmlEngine *const engine) const
Pin to C++ ownership.
Definition fontsdb.cpp:70
int fontCount() const
Alias for getStoreSize.
Definition fontsdb.cpp:418
const QString expandStr(const QString msg, const int maxLen, const QString rival, const QString player) const
Expand English text to in-game form (substitutes rival/player).
Definition fontsdb.cpp:205
const FontDBEntry * getStoreByVal(int ind) const
Glyph by its font-code value.
Definition fontsdb.cpp:401
const QVector< FontDBEntry * > getStore() const
All glyphs.
Definition fontsdb.cpp:373
QScopedPointer< FontSearch, QScopedPointerDeleteLater > search() const
C++-owned finder (smart pointer).
Definition fontsdb.cpp:98
const QJsonDocument json(const QString filename) const
Parsed document for filename.
Definition gamedata.cpp:45
static GameData * inst()
The process-wide GameData singleton.
Definition gamedata.cpp:71
static void qmlProtectUtil(const QObject *const obj, const QQmlEngine *const engine)
Pin obj to C++ ownership so the QML engine never garbage-collects it.
Definition utility.cpp:63
One in-game font character: its code, output text, and classification flags.
Definition fontdbentry.h:43
QString name
Definition fontdbentry.h:84