Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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,
}
Comment thread
captbunzo marked this conversation as resolved.
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",
Comment thread
captbunzo marked this conversation as resolved.
Outdated
},
},
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)
});
Comment thread
captbunzo marked this conversation as resolved.

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)
})
})