文件预览

add.ts

查看 Pogo Pvp 技能包中的文件内容。

文件内容

src/add.ts

// add.ts — /pvp 添加:录入我的宝可梦到 data/my_pokemon.json

import * as fs from 'fs';
import * as path from 'path';
import { findSpeciesId } from './iv';
import { getSpeciesCn } from './mapper';

const DATA_DIR = path.resolve(__dirname, '..', 'data');
const DATA_FILE = path.join(DATA_DIR, 'my_pokemon.json');

export interface MyPokemon {
  name: string;                    // 中文名
  speciesId: string;               // PvPoke 英文 ID
  league: string;                  // "1500" / "2500" / "master"
  iv: [number, number, number];    // [攻IV, 防IV, 血IV]
  cp: number;                      // 当前 CP
  level: number;                   // 当前等级(支持小数)
  moves: string[];                 // 招式列表(可选)
  built: boolean;                  // 是否已培养
  note: string;                    // 备注
  createdAt: string;               // ISO 8601
  updatedAt: string;               // ISO 8601
}

interface AddPokemonInput {
  pokemon: string;                 // 用户输入的中文名
  league: string;                  // 联盟
  iv: [number, number, number];    // IV
  cp: number;                      // CP
  level: number;                   // 等级
  moves?: string[];                // 招式(可选)
  built?: boolean;                 // 是否已培养
  note?: string;                   // 备注
}

/** 解析 IV 字符串 "a/d/h" → [a,d,h] */
export function parseAddIV(input: string): [number, number, number] | null {
  const parts = input.trim().split('/');
  if (parts.length !== 3) return null;
  const a = parseInt(parts[0], 10);
  const d = parseInt(parts[1], 10);
  const s = parseInt(parts[2], 10);
  if (isNaN(a) || isNaN(d) || isNaN(s)) return null;
  if (a < 0 || a > 15 || d < 0 || d > 15 || s < 0 || s > 15) return null;
  return [a, d, s];
}

/** 校验等级(支持小数) */
function validateLevel(input: string): number | null {
  const lv = parseFloat(input);
  if (isNaN(lv)) return null;
  if (lv < 1 || lv > 51) return null;
  return lv;
}

/** 校验 CP */
function validateCP(input: string): number | null {
  const cp = parseInt(input, 10);
  if (isNaN(cp)) return null;
  if (cp < 10) return null;
  return cp;
}

/** 校验联盟 */
function validateLeague(input: string): string | null {
  const map: Record<string, string> = {
    '1500' : '1500', '超级' : '1500', '超级联盟' : '1500',
    '2500' : '2500', '高级' : '2500', '高级联盟' : '2500',
    'master' : 'master', '大师' : 'master', '大师联盟' : 'master', '无限制' : 'master',
    '484' : '484', '小小' : '484', '小小杯' : '484', '幼童' : '484',
  };
  return map[input.trim()] || null;
}

/** 从文件加载宝可梦列表 */
function loadMyPokemon(): MyPokemon[] {
  try {
    if (fs.existsSync(DATA_FILE)) {
      const raw = fs.readFileSync(DATA_FILE, 'utf-8');
      return JSON.parse(raw) as MyPokemon[];
    }
  } catch {
    // 文件损坏则重置
  }
  return [];
}

/** 保存到文件 */
function saveMyPokemon(list: MyPokemon[]): void {
  if (!fs.existsSync(DATA_DIR)) {
    fs.mkdirSync(DATA_DIR, { recursive: true });
  }
  fs.writeFileSync(DATA_FILE, JSON.stringify(list, null, 2), 'utf-8');
}

/**
 * 添加宝可梦
 * 同一 speciesId + league + iv 视为重复 → 覆盖 CP/level/updatedAt
 * 否则新增
 */
export function addPokemon(input: AddPokemonInput): {
  success: boolean;
  message: string;
  pokemon: MyPokemon;
  isUpdate: boolean;
} {
  const now = new Date().toISOString();

  const entry: MyPokemon = {
    name: getSpeciesCn(input.pokemon) || input.pokemon,
    speciesId: input.pokemon,  // 已经是由 findSpeciesId 转换后的 speciesId
    league: input.league,
    iv: input.iv,
    cp: input.cp,
    level: input.level,
    moves: input.moves || [],
    built: input.built || false,
    note: input.note || '',
    createdAt: now,
    updatedAt: now,
  };

  const list = loadMyPokemon();

  // 查找重复:同 speciesId + league + iv(三个条件都匹配)
  const existingIdx = list.findIndex(
    (p) => p.speciesId === entry.speciesId
        && p.league === entry.league
        && p.iv[0] === entry.iv[0]
        && p.iv[1] === entry.iv[1]
        && p.iv[2] === entry.iv[2]
  );

  let isUpdate = false;
  if (existingIdx >= 0) {
    // 覆盖已有
    const existing = list[existingIdx];
    existing.cp = entry.cp;
    existing.level = entry.level;
    if (entry.moves.length > 0) existing.moves = entry.moves;
    existing.note = entry.note || existing.note;
    existing.updatedAt = now;
    entry.createdAt = existing.createdAt;
    isUpdate = true;
    list[existingIdx] = existing;
  } else {
    list.push(entry);
  }

  saveMyPokemon(list);

  return {
    success: true,
    message: isUpdate
      ? `已更新 ${entry.name} (${entry.league})`
      : `已添加 ${entry.name} (${entry.league})`,
    pokemon: existingIdx >= 0 ? list[existingIdx] : entry,
    isUpdate,
  };
}

/**
 * /pvp 添加 命令入口
 * 参数:[宝可梦, 联盟, IV, CP, 等级]
 */
export async function handleAdd(args: string[]): Promise<string> {
  if (args.length < 5) {
    return '用法:/pvp 添加 <宝可梦> <联盟> <攻击/防御/生命> <CP> <等级> [招式1/招式2/招式3]\n示例:/pvp 添加 胖嘟嘟 1500 1/14/14 1498 24.5\n      /pvp 添加 土龙弟弟 1500 0/14/12 1157 27 咬住/岩崩';
  }

  const pokemonInput = args[0];
  const leagueInput = args[1];
  const ivInput = args[2];
  const cpInput = args[3];
  const levelInput = args[4];

  // 校验联盟
  const league = validateLeague(leagueInput);
  if (!league) {
    return `未知联盟: ${leagueInput},支持 1500(超级联盟)/ 2500(高级联盟)/ master(大师联盟)`;
  }

  // 校验 IV
  const iv = parseAddIV(ivInput);
  if (!iv) {
    return `IV 格式错误: ${ivInput},正确格式如 1/14/14`;
  }

  // 校验 CP
  const cp = validateCP(cpInput);
  if (!cp) {
    return `CP 格式错误: ${cpInput}`;
  }

  // 校验 CP 上限
  const cpLimit = league === '1500' ? 1500 : league === '2500' ? 2500 : league === '484' ? 484 : 99999;
  if (cp > cpLimit) {
    return `CP ${cp} 超出 ${league === 'master' ? '大师' : league === '1500' ? '超级' : '高级'}联盟上限 (${cpLimit})`;
  }

  // 校验等级
  const level = validateLevel(levelInput);
  if (!level) {
    return `等级格式错误: ${levelInput},有效范围 1~51`;
  }

  // 查找 speciesId(复用 iv.ts 中的 findSpeciesId)
  const speciesIds = findSpeciesId(pokemonInput);
  if (speciesIds.length === 0) {
    return `未找到宝可梦: ${pokemonInput}`;
  }

  const speciesId = speciesIds[0];

  // 解析配招(可选参数,args[5] 以后是招式,用 / 分隔)
  const movesInput = args.slice(5).join('');
  const moves = movesInput ? movesInput.split('/').filter(m => m.trim().length > 0).map(m => m.trim()) : [];

  // 添加
  const result = addPokemon({
    pokemon: speciesId,
    league,
    iv,
    cp,
    level,
    moves,
  });

  // 读取最新文件内容用于输出
  const fileContent = loadMyPokemon();

  const lines: string[] = [];
  lines.push(`✅ ${result.message}`);
  const movesLine = moves.length > 0 ? ` 招式: ${moves.join('/')}` : '';
  lines.push(`   IV: ${iv[0]}/${iv[1]}/${iv[2]}  CP: ${cp}  Lv: ${level}${movesLine ? ' |' + movesLine : ''}`);
  lines.push('');
  lines.push(`data/my_pokemon.json 当前 ${fileContent.length} 条记录:`);
  for (const p of fileContent) {
    const builtMark = p.built ? '✅' : '⏳';
    const m = p.moves.length > 0 ? ` 招式: ${p.moves.join('/')}` : '';
    lines.push(`   ${builtMark} ${p.name} (${p.league}) IV=${p.iv[0]}/${p.iv[1]}/${p.iv[2]} CP=${p.cp} Lv=${p.level}${m ? ' |' + m : ''}`);
  }

  return lines.join('\n');
}