5d20e43f-cfcc-4489-bd25-9c8b412f3386
This commit is contained in:
372
src/core/pokemon_table.cpp
Normal file
372
src/core/pokemon_table.cpp
Normal file
@@ -0,0 +1,372 @@
|
||||
#include "core/pokemon_table.h"
|
||||
#include "../thirdParty/rapidjson/document.h"
|
||||
#include "../thirdParty/rapidjson/filereadstream.h"
|
||||
#include "../thirdParty/rapidjson/error/en.h"
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
|
||||
namespace PokEng {
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
// Global Pokemon table instance
|
||||
std::unique_ptr<PokemonTable> g_pokemonTable;
|
||||
|
||||
bool PokemonTable::loadFromDataDirectory() {
|
||||
// Clear any existing data
|
||||
clear();
|
||||
|
||||
// Get the path to the data directory relative to the executable
|
||||
fs::path dataDir = fs::current_path() / "data" / "pokemon";
|
||||
|
||||
// If not found, try relative to the build directory (common in CMake builds)
|
||||
if (!fs::exists(dataDir)) {
|
||||
dataDir = fs::current_path() / ".." / "data" / "pokemon";
|
||||
}
|
||||
|
||||
// If still not found, try a few more common locations
|
||||
if (!fs::exists(dataDir)) {
|
||||
dataDir = fs::current_path() / "data" / "pokemon";
|
||||
if (!fs::exists(dataDir)) {
|
||||
std::cerr << "Error: Could not find Pokemon data directory at: " << dataDir << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> jsonFiles;
|
||||
|
||||
// Find all JSON files in the pokemon data directory
|
||||
for (const auto& entry : fs::directory_iterator(dataDir)) {
|
||||
if (entry.is_regular_file() && entry.path().extension() == ".json") {
|
||||
jsonFiles.push_back(entry.path().string());
|
||||
}
|
||||
}
|
||||
|
||||
if (jsonFiles.empty()) {
|
||||
std::cerr << "Error: No Pokemon JSON files found in: " << dataDir << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sort files to ensure consistent loading order
|
||||
std::sort(jsonFiles.begin(), jsonFiles.end());
|
||||
|
||||
std::cout << "Found " << jsonFiles.size() << " Pokemon data files to load" << std::endl;
|
||||
|
||||
return loadFromFiles(jsonFiles);
|
||||
}
|
||||
|
||||
bool PokemonTable::loadFromFiles(const std::vector<std::string>& filePaths) {
|
||||
clear();
|
||||
|
||||
bool success = true;
|
||||
size_t totalPokemonLoaded = 0;
|
||||
|
||||
for (const auto& filePath : filePaths) {
|
||||
if (!parsePokemonFile(filePath)) {
|
||||
std::cerr << "Failed to parse Pokemon file: " << filePath << std::endl;
|
||||
success = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Count Pokemon in this file (rough estimate)
|
||||
std::ifstream file(filePath);
|
||||
if (file.is_open()) {
|
||||
std::string content((std::istreambuf_iterator<char>(file)),
|
||||
std::istreambuf_iterator<char>());
|
||||
size_t pokemonCount = std::count(content.begin(), content.end(), '{');
|
||||
totalPokemonLoaded += pokemonCount / 2; // Rough estimate
|
||||
}
|
||||
}
|
||||
|
||||
if (success && !m_pokemonSpecies.empty()) {
|
||||
std::cout << "Successfully loaded " << size() << " Pokemon species" << std::endl;
|
||||
std::cout << "Max Pokemon ID: " << getMaxId() << std::endl;
|
||||
}
|
||||
|
||||
return success && !m_pokemonSpecies.empty();
|
||||
}
|
||||
|
||||
bool PokemonTable::parsePokemonFile(const std::string& filePath) {
|
||||
using namespace rapidjson;
|
||||
|
||||
// Open the file
|
||||
FILE* fp = fopen(filePath.c_str(), "rb");
|
||||
if (!fp) {
|
||||
std::cerr << "Error: Could not open file: " << filePath << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create a file read stream
|
||||
char readBuffer[65536];
|
||||
FileReadStream is(fp, readBuffer, sizeof(readBuffer));
|
||||
|
||||
// Parse the JSON document
|
||||
Document doc;
|
||||
doc.ParseStream(is);
|
||||
|
||||
// Check for parse errors
|
||||
if (doc.HasParseError()) {
|
||||
std::cerr << "JSON parse error in file " << filePath << ": "
|
||||
<< GetParseError_En(doc.GetParseError()) << std::endl;
|
||||
fclose(fp);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Close the file
|
||||
fclose(fp);
|
||||
|
||||
// Verify it's an array
|
||||
if (!doc.IsArray()) {
|
||||
std::cerr << "Error: Expected JSON array in file: " << filePath << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse each Pokemon in the array
|
||||
for (const auto& pokemonValue : doc.GetArray()) {
|
||||
if (!pokemonValue.IsObject()) {
|
||||
std::cerr << "Warning: Skipping non-object entry in " << filePath << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
PokemonSpecies species = parsePokemonJson(&pokemonValue);
|
||||
|
||||
// Ensure the vector is large enough
|
||||
if (species.id >= m_pokemonSpecies.size()) {
|
||||
m_pokemonSpecies.resize(species.id + 1);
|
||||
}
|
||||
|
||||
// Store the Pokemon
|
||||
m_pokemonSpecies[species.id] = species;
|
||||
|
||||
// Update the name-to-ID mapping
|
||||
m_nameToIdMap[species.name] = species.id;
|
||||
|
||||
// Update max ID
|
||||
if (species.id > m_maxId) {
|
||||
m_maxId = species.id;
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Error parsing Pokemon in " << filePath << ": " << e.what() << std::endl;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
PokemonSpecies PokemonTable::parsePokemonJson(const void* pokemonJson) {
|
||||
using namespace rapidjson;
|
||||
|
||||
const Value* pokemonValue = static_cast<const Value*>(pokemonJson);
|
||||
PokemonSpecies species;
|
||||
|
||||
// Parse ID
|
||||
if (pokemonValue->HasMember("id") && (*pokemonValue)["id"].IsUint()) {
|
||||
species.id = static_cast<uint16_t>((*pokemonValue)["id"].GetUint());
|
||||
} else {
|
||||
throw std::runtime_error("Missing or invalid 'id' field");
|
||||
}
|
||||
|
||||
// Parse name
|
||||
if (pokemonValue->HasMember("name") && (*pokemonValue)["name"].IsString()) {
|
||||
species.name = (*pokemonValue)["name"].GetString();
|
||||
} else {
|
||||
throw std::runtime_error("Missing or invalid 'name' field");
|
||||
}
|
||||
|
||||
// Parse base stats
|
||||
if (pokemonValue->HasMember("stats") && (*pokemonValue)["stats"].IsObject()) {
|
||||
species.base_stats = parseBaseStats(&(*pokemonValue)["stats"]);
|
||||
} else {
|
||||
throw std::runtime_error("Missing or invalid 'stats' field");
|
||||
}
|
||||
|
||||
// Parse types
|
||||
if (pokemonValue->HasMember("types") && (*pokemonValue)["types"].IsArray()) {
|
||||
species.types = parseTypes(&(*pokemonValue)["types"]);
|
||||
} else {
|
||||
throw std::runtime_error("Missing or invalid 'types' field");
|
||||
}
|
||||
|
||||
return species;
|
||||
}
|
||||
|
||||
BaseStats PokemonTable::parseBaseStats(const void* statsJson) {
|
||||
using namespace rapidjson;
|
||||
|
||||
const Value* statsValue = static_cast<const Value*>(statsJson);
|
||||
BaseStats baseStats;
|
||||
|
||||
// Helper function to safely get stat value
|
||||
auto getStatValue = [](const Value& obj, const char* key) -> uint8_t {
|
||||
if (obj.HasMember(key) && obj[key].IsUint()) {
|
||||
return static_cast<uint8_t>(std::min(obj[key].GetUint(), 255u));
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
baseStats.hp = getStatValue(*statsValue, "hp");
|
||||
baseStats.attack = getStatValue(*statsValue, "attack");
|
||||
baseStats.defense = getStatValue(*statsValue, "defense");
|
||||
baseStats.sp_attack = getStatValue(*statsValue, "special-attack");
|
||||
baseStats.sp_defense = getStatValue(*statsValue, "special-defense");
|
||||
baseStats.speed = getStatValue(*statsValue, "speed");
|
||||
|
||||
return baseStats;
|
||||
}
|
||||
|
||||
PokemonTypes PokemonTable::parseTypes(const void* typesJson) {
|
||||
using namespace rapidjson;
|
||||
|
||||
const Value* typesValue = static_cast<const Value*>(typesJson);
|
||||
PokemonTypes types;
|
||||
|
||||
if (typesValue->Empty()) {
|
||||
return types; // Return empty types
|
||||
}
|
||||
|
||||
// Get the first type (primary)
|
||||
if (typesValue->Size() > 0) {
|
||||
const auto& firstType = (*typesValue)[0];
|
||||
if (firstType.IsString()) {
|
||||
std::string typeStr = firstType.GetString();
|
||||
auto type = TypeUtils::stringToType(typeStr);
|
||||
if (type) {
|
||||
types.setPrimary(*type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get the second type (secondary) if it exists
|
||||
if (typesValue->Size() > 1) {
|
||||
const auto& secondType = (*typesValue)[1];
|
||||
if (secondType.IsString()) {
|
||||
std::string typeStr = secondType.GetString();
|
||||
auto type = TypeUtils::stringToType(typeStr);
|
||||
if (type) {
|
||||
types.setSecondary(*type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return types;
|
||||
}
|
||||
|
||||
|
||||
|
||||
const PokemonSpecies* PokemonTable::getPokemon(uint16_t id) const {
|
||||
if (id == 0 || id >= m_pokemonSpecies.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
return &m_pokemonSpecies[id];
|
||||
}
|
||||
|
||||
const PokemonSpecies* PokemonTable::getPokemonByName(std::string_view name) const {
|
||||
auto it = m_nameToIdMap.find(std::string(name));
|
||||
if (it == m_nameToIdMap.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
return getPokemon(it->second);
|
||||
}
|
||||
|
||||
bool PokemonTable::hasPokemon(uint16_t id) const {
|
||||
return id > 0 && id < m_pokemonSpecies.size() && m_pokemonSpecies[id].id != 0;
|
||||
}
|
||||
|
||||
size_t PokemonTable::size() const {
|
||||
// Count non-empty entries (ID != 0)
|
||||
return std::count_if(m_pokemonSpecies.begin(), m_pokemonSpecies.end(),
|
||||
[](const PokemonSpecies& species) { return species.id != 0; });
|
||||
}
|
||||
|
||||
bool PokemonTable::empty() const {
|
||||
return m_pokemonSpecies.empty() || size() == 0;
|
||||
}
|
||||
|
||||
uint16_t PokemonTable::getMaxId() const {
|
||||
return m_maxId;
|
||||
}
|
||||
|
||||
void PokemonTable::clear() {
|
||||
m_pokemonSpecies.clear();
|
||||
m_nameToIdMap.clear();
|
||||
m_maxId = 0;
|
||||
}
|
||||
|
||||
const std::vector<PokemonSpecies>& PokemonTable::getAllPokemon() const {
|
||||
return m_pokemonSpecies;
|
||||
}
|
||||
|
||||
bool PokemonTable::validate() const {
|
||||
if (empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that all Pokemon have valid IDs
|
||||
for (uint16_t id = 1; id <= m_maxId; ++id) {
|
||||
const auto* pokemon = getPokemon(id);
|
||||
if (pokemon) {
|
||||
if (pokemon->id != id) {
|
||||
std::cerr << "Validation error: Pokemon at ID " << id
|
||||
<< " has mismatched ID " << pokemon->id << std::endl;
|
||||
return false;
|
||||
}
|
||||
if (pokemon->name.empty()) {
|
||||
std::cerr << "Validation error: Pokemon ID " << id
|
||||
<< " has empty name" << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check name-to-ID mapping consistency
|
||||
for (const auto& pair : m_nameToIdMap) {
|
||||
const auto* pokemon = getPokemon(pair.second);
|
||||
if (!pokemon || pokemon->name != pair.first) {
|
||||
std::cerr << "Validation error: Name-to-ID mapping inconsistency for '"
|
||||
<< pair.first << "'" << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool initializePokemonTable() {
|
||||
if (g_pokemonTable) {
|
||||
std::cout << "Warning: Pokemon table already initialized" << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
g_pokemonTable = std::make_unique<PokemonTable>();
|
||||
|
||||
if (!g_pokemonTable->loadFromDataDirectory()) {
|
||||
std::cerr << "Failed to initialize Pokemon table" << std::endl;
|
||||
g_pokemonTable.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!g_pokemonTable->validate()) {
|
||||
std::cerr << "Pokemon table validation failed" << std::endl;
|
||||
g_pokemonTable.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
std::cout << "Pokemon table initialized successfully with "
|
||||
<< g_pokemonTable->size() << " Pokemon" << std::endl;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void shutdownPokemonTable() {
|
||||
if (g_pokemonTable) {
|
||||
std::cout << "Shutting down Pokemon table" << std::endl;
|
||||
g_pokemonTable.reset();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace PokEng
|
||||
@@ -278,6 +278,9 @@ struct TypeChartInitializer {
|
||||
}
|
||||
};
|
||||
|
||||
// Definition of the static type chart array
|
||||
std::array<std::span<const TypeMultiplier>, static_cast<size_t>(Generation::IX) + 1> TypeUtils::s_typeChart;
|
||||
|
||||
// Static initializer to load type charts at program startup
|
||||
static TypeChartInitializer s_initializer;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user