Passed
Push — depfu/update/npm/lodash-4.17.1... ( 152c97 )
by
unknown
04:58
created

PokemonBox.newPokemon   D

Complexity

Conditions 12

Size

Total Lines 55
Code Lines 49

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 12
eloc 49
dl 0
loc 55
rs 4.8
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like PokemonBox.newPokemon often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
import { SaveFileIterator } from './../SaveFileIterator';
2
import { SaveFileService } from './../../savefile.service';
3
import {Pokemon} from '../../../../assets/data/pokemon';
4
import { GameDataService } from '../../gameData.service';
5
import { PokemonDBService } from '../../pokemonDB.service';
6
7
// @ts-ignore
8
const _ = window.require("lodash");
9
10
export class PokemonBox {
11
    constructor(saveFile?: SaveFileService,
12
        startOffset?: number,
13
        nicknameStartOffset?: number,
14
        otNameStartOffset?: number,
15
        index?: number,
16
        recordSize?: number) {
17
18
        this.gd = new GameDataService();
19
        this.pkmnArr = this.gd.file("pokemon").data;
20
21
        if (saveFile !== undefined) {
22
            this.load(
23
                saveFile as SaveFileService,
24
                startOffset as number,
25
                nicknameStartOffset as number,
26
                otNameStartOffset as number,
27
                index as number,
28
                recordSize as number
29
            );
30
        }
31
32
        // Pokemon box data structure complete, Ready for Pokemon Party to
33
        // takeover
34
    }
35
36
    // Creates a new Pokemon of a random starter-like species without a nickname
37
    // and not traded. Depending on the species everything else is filled out
38
    // accordingly such as the chosen species type, catch rate, and initial moves
39
    // the level is level 5
40
    // The random species chosen is a base evolution species that's not legendary
41
    static newPokemon(file: SaveFileService, pdb: PokemonDBService, gd: GameDataService) {
42
        const pkmn = new PokemonBox();
43
        const pkmnList = gd.file("randomPkmn").data;
44
        const pkmnData = pdb.pokemon[_.snakeCase(pkmnList[_.random(0, pkmnList.length, false)])];
45
46
        pkmn.species = pkmnData.ind;
47
        pkmn.level = 5;
48
49
        if(pkmnData.type1 !== undefined)
50
            pkmn.type1 = pkmnData.type1.ind;
51
52
        if(pkmnData.type2 !== undefined)
53
            pkmn.type2 = pkmnData.type2.ind;
54
55
        if(pkmn.type1 == pkmn.type2)
56
            pkmn.type2 = 0xFF;
57
58
        for(let i = 0; i < 4; i++) {
59
            if(pkmnData.initial == undefined)
60
                continue;
61
62
            pkmn.moves[i].moveID = (pkmnData.initial[i]) ? pkmnData.initial[i].ind : 0;
63
            pkmn.moves[i].pp = (pkmnData.initial[i]) ? pkmnData.initial[i].pp : 0;
64
            pkmn.moves[i].ppUp = 0;
65
        }
66
67
        pkmn.otID = file.fileDataExpanded.player.basics.playerID;
68
        pkmn.otName = file.fileDataExpanded.player.basics.playerName;
69
70
        pkmn.dv.attack = _.random(0, 15, false);
71
        pkmn.dv.defense = _.random(0, 15, false);
72
        pkmn.dv.speed = _.random(0, 15, false);
73
        pkmn.dv.special = _.random(0, 15, false);
74
75
        let nickname = pkmnData.name.toUpperCase();
76
        if(nickname == "NIDORAN<F>")
77
            nickname = "NIDORAN<f>"
78
        else if(nickname == "NIDORAN<M>")
79
            nickname = "NIDORAN<m>"
80
81
        pkmn.nickname = nickname;
82
83
        if(pkmnData.catchRate !== undefined)
84
            pkmn.catchRate = pkmnData.catchRate;
85
86
        pkmn.hp = pkmn.hpStat;
87
        pkmn.updateExp();
88
89
        return pkmn;
90
    }
91
92
    public load(saveFile: SaveFileService,
93
        startOffset: number,
94
        nicknameStartOffset: number,
95
        otNameStartOffset: number,
96
        index: number,
97
98
        // Unless overridden, the record size for box data is 0x21
99
        recordSize: number = 0x21): SaveFileIterator {
100
101
        // Grab Pokemon Records
102
        // These are globally cached so theres no memory issues
103
        // this.pkmnArr = saveFile.gd.file("pokemon").data;
104
105
        // Calculate record offset
106
        const offset = (recordSize * index) + startOffset;
107
108
        const it: SaveFileIterator = saveFile.iterator.offsetTo(offset);
109
110
        this.species = it.getByte();
111
        this.hp = it.getWord();
112
        this.level = it.getByte();
113
114
        this.status = it.getByte();
115
116
        this.type1 = it.getByte();
117
        this.type2 = it.getByte();
118
119
        // Don't duplicate type 1 to type 2, fill type 2 only if it's different
120
        // Also mark if it was explicitly marked no in-game
121
        if (this.type2 === 0xFF) {
122
            this.type2ExplicitNo = true;
123
        } else if (this.type1 === this.type2) {
124
            this.type2 = 0xFF;
125
        }
126
127
        this.catchRate = it.getByte();
128
129
        // Save offset to restore later
130
        it.push();
131
132
        // Temporarily save moves for later
133
        const moves = [];
134
        for (let i = 0; i < 4; i++) {
135
            const moveID = it.getByte();
136
            moves.push(moveID);
137
        }
138
139
        // Restore offset to start of moves and move past the moves
140
        it.pop().offsetBy(0x4);
141
142
        this.otID = it.getHex(2, false);
143
144
        // Exp is 3 bytes so it's a bit tricky
145
        const expRaw = it.getRange(3);
146
        this.exp = expRaw[0];
147
        this.exp <<= 8;
148
        this.exp |= expRaw[1];
149
        this.exp <<= 8;
150
        this.exp |= expRaw[2];
151
152
        this.hpExp = it.getWord();
153
        this.attackExp = it.getWord();
154
        this.defenseExp = it.getWord();
155
        this.speedExp = it.getWord();
156
        this.specialExp = it.getWord();
157
158
        const dvTotal = it.getWord();
159
        this.dv = {
160
            attack: (dvTotal & 0xF000) >> 12,
161
            defense: (dvTotal & 0x0F00) >> 8,
162
            speed: (dvTotal & 0x00F0) >> 4,
163
            special: dvTotal & 0x000F
164
        };
165
166
        it.push();
167
168
        // Next gather PP
169
        const ppList = [];
170
        for (let i = 0; i < moves.length; i++) {
171
            ppList.push(it.getByte());
172
        }
173
174
        // Combine together in moves
175
        this.moves = [];
176
        for (let i = 0; i < moves.length; i++) {
177
            const moveID = moves[i];
178
            const pp = ppList[i];
179
            this.moves.push({
180
                moveID,
181
                pp: pp & 0b00111111,
182
                ppUp: (pp & 0b11000000) >> 6
183
            });
184
        }
185
186
        // Restore back to before PP and move past PP, save iterator to class
187
        // because PokemonParty may pick it up and continue since it extends
188
        it.pop().offsetBy(0x4);
189
190
        // Now we must gather the OT names and Pokemon names whihc were poorly
191
        // implemented in sometimes arbitrary spots outside of the data sructure
192
        const otNameOffset = (index * 0xB) + otNameStartOffset;
193
        this.otName = saveFile.getStr(otNameOffset, 0xB, 7);
194
195
        const nicknameOffset = (index * 0xB) + nicknameStartOffset;
196
        this.nickname = saveFile.getStr(nicknameOffset, 0xB, 10);
197
198
        return it;
199
    }
200
201
    public save(saveFile: SaveFileService,
202
        startOffset: number,
203
        speciesStartOffset: number | null,
204
        nicknameStartOffset: number,
205
        otNameStartOffset: number,
206
        index: number,
207
        recordSize: number = 0x21): SaveFileIterator {
208
209
        // Retrieve stored internals
210
        const offset = (recordSize * index) + startOffset;
211
        const it: SaveFileIterator = saveFile.iterator.offsetTo(offset);
212
        const otNameOffset = (index * 0xB) + otNameStartOffset;
213
        const nicknameOffset = (index * 0xB) + nicknameStartOffset;
214
215
        // Add species to species list if exists
216
        if(speciesStartOffset !== null) {
217
            const speciesOffset = index + speciesStartOffset;
218
            saveFile.setByte(speciesOffset, this.species);
219
        }
220
221
        // Re-save back
222
        it.setByte(this.species);
223
        it.setWord(this.hp);
224
225
        // Don't save level to BoxData if this is in the party
226
        // This honors the global don't touch policy
227
        // which is don't touch any bits that don't need to be changed
228
        if (recordSize === 0x21) {
229
            it.setByte(this.level);
230
        } else {
231
            it.inc();
232
        }
233
234
        it.setByte(this.status);
235
        it.setByte(this.type1);
236
237
        // If type 2 explicit no then just write what's in type 2
238
        if (this.type2ExplicitNo) {
239
            it.setByte(this.type2);
240
241
            // If type 2 is not explicitly no but implicitly no (Type 1 and 2 were marked the same)
242
            // save it as type 1
243
        } else if (this.type2 === 0xFF) {
244
            it.setByte(this.type1);
245
246
            // Else just save type 2
247
        } else {
248
            it.setByte(this.type2);
249
        }
250
251
        it.setByte(this.catchRate);
252
253
        for (let i = 0; i < 4; i++) {
254
            it.setByte(this.moves[i].moveID);
255
        }
256
257
        it.setHex(2, this.otID, false);
258
259
        const exp = this.exp;
260
261
        it.setByte((exp & 0xFF0000) >> 16);
262
        it.setByte((exp & 0x00FF00) >> 8);
263
        it.setByte(exp & 0x0000FF);
264
265
        it.setWord(this.hpExp);
266
        it.setWord(this.attackExp);
267
        it.setWord(this.defenseExp);
268
        it.setWord(this.speedExp);
269
        it.setWord(this.specialExp);
270
271
        let dv = 0;
272
        dv |= (this.dv.attack << 12);
273
        dv |= (this.dv.defense << 8);
274
        dv |= (this.dv.speed << 4);
275
        dv |= this.dv.special;
276
        it.setWord(dv);
277
278
        for (let i = 0; i < 4; i++) {
279
            const move = this.moves[i];
280
            const ppCombined = (move.ppUp << 6) | move.pp;
281
            it.setByte(ppCombined);
282
        }
283
284
        saveFile.setStr(otNameOffset, 0xB, 10, this.otName);
285
        saveFile.setStr(nicknameOffset, 0xB, 10, this.nickname);
286
287
        return it;
288
    }
289
290
    // Is this a valid Pokemon? (Is it even in the Pokedex?)
291
    // If not returns false, otherwise returns Pokemon Record
292
    public get isValidPokemon() {
293
        // Get Pokemon Record
294
        // The Pokemon Array is organized by species ID with 1 top entry missing
295
        // thus offset by 1 accordingly
296
        let record = this.pkmnArr[this.species - 1];
297
298
        // Check it's a valid Pokemon (not glitch)
299
        if(record == undefined || record.pokedex == null || record.pokedex == undefined)
300
            return false;
301
302
        return record;
303
    }
304
305
    // Correctly Converts level to Pokemon Exp
306
    public levelToExp(level: number = this.level): number {
307
        let record = this.isValidPokemon;
308
        let exp = 0;
309
310
        // Proceed only if it's valid
311
        if(record === false)
312
            return exp;
313
314
        // Obtain it's growth rate and calculate accordingly it's exp for the given level
315
        const gr = record.growthRate;
316
317
        // Growth Rate 0: Medium Fast
318
        if(gr == 0)
319
            exp = Math.pow(level, 3);
320
321
        // Growth Rate 3: Medium Slow
322
        else if(gr == 3)
323
            exp = (1.2 * Math.pow(level, 3)) - (15 * Math.pow(level, 2)) + (100*level) - 140;
324
325
        // Growth Rate 4: Fast
326
        else if(gr == 4)
327
            exp = (4 * Math.pow(level, 3)) / 5
328
329
        // Growth Rate 5: Slow
330
        else if(gr == 5)
331
            exp = (5 * Math.pow(level, 3)) / 4
332
333
        // Return EXP
334
        return Math.floor(exp);
335
    }
336
337
    public get expStart() {
338
        if(this.isValidPokemon === false)
339
            return this.exp;
340
341
        return this.levelToExp((this.level < 100) ? this.level : 100);
342
    }
343
344
    public get expEnd() {
345
        if(this.isValidPokemon === false)
346
            return this.exp;
347
348
        return this.levelToExp((this.level < 100) ? this.level + 1 : 100) - 1;
349
    }
350
351
    // Percent of current exp to level between 0-1
352
    public get expPercent() {
353
        if(this.isValidPokemon === false)
354
            return 0;
355
356
        if(this.level >= 100)
357
            return 1;
358
359
        const exp = this.exp - this.expStart;
360
        const expEnd = this.expEnd - this.expStart;
361
362
        // Return percentage
363
        return exp / expEnd;
364
    }
365
366
    // Resets EXP to start of current level and current species
367
    public updateExp() {
368
        if(this.isValidPokemon === false)
369
            return;
370
371
        this.exp = this.expStart;
372
    }
373
374
    // Gets HP DV based on other DV's
375
    public get hpDV() {
376
        let hpDv = 0;
377
378
        if((this.dv.attack % 2) !== 0)
379
            hpDv |= 8;
380
381
        if((this.dv.defense % 2) !== 0)
382
            hpDv |= 4;
383
384
        if((this.dv.speed % 2) !== 0)
385
            hpDv |= 2;
386
387
        if((this.dv.special % 2) !== 0)
388
            hpDv |= 1;
389
390
        return hpDv;
391
    }
392
393
    public get hpStat() {
394
        const record = this.isValidPokemon;
395
396
        // Proceed only if it's valid
397
        if(record === false || record.baseHp == undefined)
398
            return 1;
399
400
        return Math.floor((((record.baseHp+this.hpDV)*2+Math.floor(Math.floor(Math.sqrt(this.hpExp))/4))*this.level)/100) + this.level + 10;
401
    }
402
403
    public getNonHPStat(statName: string, dv: number, ev: number) {
404
        const record = this.isValidPokemon;
405
406
        // Proceed only if it's valid
407
        if(record === false)
408
            return 0;
409
410
        const baseStat = record[statName] as number;
411
        return Math.floor((((baseStat+dv)*2+Math.floor(Math.floor(Math.sqrt(ev))/4))*this.level)/100) + 5;
412
    }
413
414
    public get attackStat() {
415
        return this.getNonHPStat("baseAttack", this.dv.attack, this.attackExp);
416
    }
417
418
    public get defenseStat() {
419
        return this.getNonHPStat("baseDefense", this.dv.defense, this.defenseExp);
420
    }
421
422
    public get speedStat() {
423
        return this.getNonHPStat("baseSpeed", this.dv.speed, this.speedExp);
424
    }
425
426
    public get specialStat() {
427
        return this.getNonHPStat("baseSpecial", this.dv.special, this.specialExp);
428
    }
429
430
    private pkmnArr: Pokemon[] = [];
431
432
    public species = 0;
433
    public hp = 0;
434
    public level = 0;
435
436
    // Pokemon Status codes are a bit weird
437
    // The game programatically allows one, more, or all status afflections to
438
    // be active at any given time however the game only ever uses one at a time
439
    // and never makes use of more than one at the same time despite the fact
440
    // the codes there to make it happen
441
442
    // Raw status byte
443
    // The only used numbers would ever be at any time via normal gameplay are
444
    // 0 - No status
445
    // 1-7 - Sleep Turns Left
446
    // 8 - Poisoned
447
    // 16 - Burned
448
    // 32 - Frozen
449
    // 64 - Paralyzed
450
    public status = 0;
451
452
    public type1 = 0;
453
    public type2 = 0;
454
455
    // Sometimes type 2 is a duplicate of type 1 and
456
    // sometimes it's explicitly 0xFF, this is which one
457
    public type2ExplicitNo = false;
458
459
    public catchRate = 0;
460
    public moves: {
461
        moveID: number;
462
        pp: number;
463
        ppUp: number;
464
    }[] = [{
465
        moveID: 0,
466
        pp: 0,
467
        ppUp: 0
468
    }, {
469
        moveID: 0,
470
        pp: 0,
471
        ppUp: 0
472
    }, {
473
        moveID: 0,
474
        pp: 0,
475
        ppUp: 0
476
    }, {
477
        moveID: 0,
478
        pp: 0,
479
        ppUp: 0
480
    }];
481
    public otID = '0000';
482
    public exp = 0;
483
    public hpExp = 0;
484
    public attackExp = 0;
485
    public defenseExp = 0;
486
    public speedExp = 0;
487
    public specialExp = 0;
488
    public dv = {
489
        attack: 0 as number,
490
        defense: 0 as number,
491
        speed: 0 as number,
492
        special: 0 as number
493
    };
494
    public otName = '';
495
    public nickname = '';
496
    public gd: GameDataService;
497
}
498