Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ const template = {
weather: {
enabled: true,
},
cpm: {
enabled: true,
}
translations: {
enabled: true,
options: {
Expand Down
105 changes: 105 additions & 0 deletions docs/devWrapper-outline.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# devWrapper.ts - Script Outline

## Purpose

Development script that fetches the latest Pokémon GO game master data and generates a structured masterfile with various Pokémon GO data entities (Pokémon, moves, items, invasions, etc.)

---

## Main Flow

### 1. Data Fetching

- Fetches latest game master JSON from PokeMiners GitHub repository
- Saves raw data to `./latest.json` for reference

### 2. Command-Line Argument Processing

- `--pokeapi-staging`: Uses PokeAPI staging environment
- `--pokeapi`: Uses PokeAPI production (or static cached data if neither flag)
- `--raw`: Generates raw data format
- `--test`: Enables test mode for file output
- `--invasions`: Generates Team Rocket invasion data

### 3. Data Generation

- Calls the `generate()` function from `src/index.ts`
- Passes configuration options based on command-line flags
- Either uses PokeAPI (live or staging) or static cached files:
- `static/baseStats.json` - Base stats data
- `static/tempEvos.json` - Temporary evolution data
- `static/types.json` - Type effectiveness data
- Times the generation process

### 4. Test Mode File Output

When `--test` flag is present:

#### a. Invasion Data

- If `--invasions` flag: Writes `invasions.json` with Team Rocket invasion data

#### b. PokeAPI Cache Update

- If using PokeAPI: Updates static cache files with fresh data:
- `static/baseStats.json`
- `static/tempEvos.json`
- `static/types.json`
- Removes PokeAPI data from final output

#### c. Masterfile Output

- Writes complete generated data to `./masterfile.json`

### 5. Completion

- Logs generation time
- Handles errors
- Confirms successful generation

---

## Key Features

- **Flexible data sources**: Can use live PokeAPI or cached static data
- **Modular output**: Different flags control what data is generated
- **Development-friendly**: Timing, error handling, and formatted JSON output
- **Cache management**: Updates static files when using live PokeAPI
- **Test mode**: Prevents accidental production runs without explicit flag

## Usage Examples

```bash
# Generate with default settings (using static cache)
yarn generate

# Generate using live PokeAPI data
yarn pokeapi

# Generate raw format data
yarn raw

# Generate invasion data
yarn invasions
```

## Command-Line Flags

| Flag | Description |
| ------------------- | ------------------------------------------- |
| `--test` | Enable test mode (required for file output) |
| `--pokeapi` | Use PokeAPI production environment |
| `--pokeapi-staging` | Use PokeAPI staging environment |
| `--raw` | Generate raw data format |
| `--invasions` | Generate Team Rocket invasion data |

## Output Files

| File | Condition | Description |
| ----------------------- | ------------------------------ | ------------------------------------ |
| `latest.json` | Always | Raw game master data from PokeMiners |
| `masterfile.json` | `--test` flag | Complete generated masterfile |
| `invasions.json` | `--test` + `--invasions` flags | Team Rocket invasion data |
| `static/baseStats.json` | `--test` + `--pokeapi*` flags | Pokemon base stats cache |
| `static/tempEvos.json` | `--test` + `--pokeapi*` flags | Temporary evolutions cache |
| `static/types.json` | `--test` + `--pokeapi*` flags | Type effectiveness cache |
12 changes: 12 additions & 0 deletions src/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,18 @@ const baseTemplate: FullTemplate = {
quests: true,
},
},
cpm: {
enabled: true,
options: {
keys: {
main: 'level',
},
},
template: {
level: true,
multiplier: true,
},
},
}

export default baseTemplate
48 changes: 48 additions & 0 deletions src/classes/Cpm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import type { AllCpm } from '../typings/dataTypes'
import type { NiaMfObj } from '../typings/general'
import Masterfile from './Masterfile'

export default class Cpm extends Masterfile {
parsedCpm: AllCpm

constructor() {
super()
this.parsedCpm = {}
}

addCpm(object: NiaMfObj) {
const playerLevel = object.data.playerLevel
if (playerLevel?.cpMultiplier) {
const tempCpm: { level: number; multiplier: number }[] = []

// First, generate all whole level values
for (let i = 0; i < playerLevel.cpMultiplier.length; i++) {
const wholeLevel = i + 1

tempCpm.push({
level: wholeLevel,
multiplier: playerLevel.cpMultiplier[i],
})

const halfLevel = i + 1.5
const cpmCurrent = playerLevel.cpMultiplier[i]
const cpmNext = playerLevel.cpMultiplier[i + 1]

if (cpmNext) {
// Calculate half-level CPM using: sqrt((CPM(n)^2 + CPM(n+1)^2) / 2)
const cpmHalf = Math.sqrt((cpmCurrent ** 2 + cpmNext ** 2) / 2)

tempCpm.push({
level: halfLevel,
multiplier: cpmHalf,
})
}
}

// Add to parsedCpm with consistent key format (n.0 or n.5)
for (const entry of tempCpm) {
this.parsedCpm[entry.level.toFixed(1)] = entry
}
}
}
}
10 changes: 10 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import base from './base'
import ApkReader from './classes/Apk'
import Invasions from './classes/Invasion'
import Cpm from './classes/Cpm'
import Items from './classes/Item'
import LocationCards from './classes/LocationCards'
import Masterfile from './classes/Masterfile'
Expand Down Expand Up @@ -48,6 +49,7 @@ export async function generate({
questRewardTypes,
invasions,
weather,
cpm,
translations,
raids,
routeTypes,
Expand All @@ -64,6 +66,7 @@ export async function generate({
const AllInvasions = new Invasions(invasions.options)
const AllTypes = new Types()
const AllWeather = new Weather()
const AllCpm = new Cpm()
const AllTranslations = new Translations(
translations.options,
translationApkUrl,
Expand Down Expand Up @@ -353,6 +356,8 @@ export async function generate({
AllPokemon.addExtendedStats(data[i])
} else if (data[i].data.locationCardSettings) {
AllLocationCards.addLocationCard(data[i])
} else if (data[i].data.playerLevel) {
AllCpm.addCpm(data[i])
}
}
}
Expand Down Expand Up @@ -637,6 +642,11 @@ export async function generate({
? AllMisc.teams
: AllMisc.templater(AllMisc.teams, teams)
}
if (cpm.enabled) {
final[cpm.options.topLevelName || "cpm"] = raw
? AllCpm.parsedCpm
: AllCpm.templater(AllCpm.parsedCpm, cpm)
}

if (test && pokeApi === true) {
final.AllPokeApi = AllPokeApi
Expand Down
8 changes: 8 additions & 0 deletions src/typings/dataTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,13 @@ export interface AllForms {
[id: string]: SingleForm
}

export interface AllCpm {
[level: string]: {
level?: number
multiplier?: number
}
}

export interface SinglePokemon extends SingleForm {
pokedexId?: number
pokemonName?: string
Expand Down Expand Up @@ -206,6 +213,7 @@ export interface FinalResult {
moves?: AllMoves
types?: AllTypes
weather?: AllWeather
cpm?: AllCpm
questRewardTypes?: AllQuests
questConditions?: AllQuests
locationCards?: AllLocationCards
Expand Down
9 changes: 9 additions & 0 deletions src/typings/general.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,15 @@ export interface NiaMfObj {
cardType?: string
vfxAddress?: string
}
playerLevel?: {
rankNum: number[]
requiredExperience: number[]
cpMultiplier: number[]
maxEggPlayerLevel: number
maxEncounterPlayerLevel: number
maxQuestEncounterPlayerLevel: number
extendedPlayerLevelThreshold: number
}
}
}

Expand Down
10 changes: 10 additions & 0 deletions src/typings/inputs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,11 @@ export interface TranslationsTemplate {
quests?: boolean
}

export interface CpmTemplate {
level?: boolean
multiplier?: boolean
}

export interface Input {
url?: string
translationApkUrl?: string
Expand Down Expand Up @@ -376,6 +381,11 @@ export interface FullTemplate {
options: Options
template: MiscProto | keyof MiscProto
}
cpm?: {
enabled?: boolean
options: Options
template: CpmTemplate | string
}
}

export type Locales = [
Expand Down
66 changes: 66 additions & 0 deletions tests/cpm.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
const { generate } = require('../dist/index')

jest.setTimeout(30_000)

describe('CPM Generation', () => {
let cpmData

beforeAll(async () => {
const data = await generate({ raw: true })
cpmData = data.cpm
});

test('generates CPM data', () => {
expect(cpmData).toBeDefined()
expect(Object.keys(cpmData).length).toBeGreaterThan(0)
})

test('has correct whole level values', () => {
// Level 1
expect(cpmData["1.0"]).toBeDefined()
expect(cpmData["1.0"].level).toBe(1)
expect(cpmData["1.0"].multiplier).toBe(0.094)

// Level 40
expect(cpmData["40.0"]).toBeDefined()
expect(cpmData["40.0"].level).toBe(40)
expect(cpmData["40.0"].multiplier).toBe(0.7903)

// Level 55
expect(cpmData["55.0"]).toBeDefined()
expect(cpmData["55.0"].level).toBe(55)
expect(cpmData["55.0"].multiplier).toBe(0.8653)
});

test('calculates half level values correctly', () => {
// Level 1.5
expect(cpmData["1.5"]).toBeDefined()
expect(cpmData["1.5"].level).toBe(1.5)
expect(cpmData["1.5"].multiplier).toBeCloseTo(0.1351374, 6)

// Level 40.5
expect(cpmData["40.5"]).toBeDefined()
expect(cpmData["40.5"].level).toBe(40.5)
});

test('keys are in correct format (n.0 or n.5)', () => {
const keys = Object.keys(cpmData)
keys.forEach((key) => {
expect(key).toMatch(/^\d+\.(0|5)$/)
})
})

test('levels are in sequential order', () => {
const keys = Object.keys(cpmData)
const levels = keys.map((k) => parseFloat(k))

for (let i = 1; i < levels.length; i++) {
expect(levels[i]).toBeGreaterThan(levels[i - 1])
}
})

test('has expected total number of entries', () => {
// 80 whole levels + 79 half levels = 159 total
expect(Object.keys(cpmData).length).toBe(159)
})
})