/** * Adaptive Tactics - Game Models * Core data structures for units, weapons, tiles, and map data */ /** * Weapon data structure */ export class Weapon { constructor(data) { this.id = data.id; this.name = data.name; this.kind = data.kind; // 'physical', 'magic', or 'heal' this.might = data.might; this.hit = data.hit; this.minRange = data.minRange; this.maxRange = data.maxRange; this.heal = data.heal || 0; } /** * Check if this weapon can reach a given range */ canReach(range) { return range >= this.minRange && range <= this.maxRange; } /** * Check if this is a healing weapon */ isHealingWeapon() { return this.kind === 'heal'; } } /** * Unit data structure */ export class Unit { constructor(data, weapons) { this.id = data.id; this.name = data.name; this.title = data.title || ''; this.sprite = data.sprite; this.spriteBlend = data.spriteBlend || false; this.faction = data.faction || 'player'; // 'player' or 'enemy' // Stats this.hp = data.stats.hp; this.maxHp = data.stats.maxHp || data.stats.hp; this.power = data.stats.power; this.skill = data.stats.skill; this.speed = data.stats.speed; this.def = data.stats.def; this.res = data.stats.res; this.mov = data.stats.mov; // Weapons this.weaponIds = data.weapons || []; this.equippedWeaponId = data.equippedWeapon || this.weaponIds[0]; this.weapons = weapons || {}; // Position (set during battle) this.row = 0; this.col = 0; // State this.acted = false; this.alive = true; } /** * Get the currently equipped weapon */ getEquippedWeapon() { return this.weapons[this.equippedWeaponId] || null; } /** * Get all available weapons */ getAvailableWeapons() { return this.weaponIds.map(id => this.weapons[id]).filter(w => w); } /** * Switch to a different weapon */ switchWeapon(weaponId) { if (this.weaponIds.includes(weaponId)) { this.equippedWeaponId = weaponId; return true; } return false; } /** * Take damage */ takeDamage(amount) { this.hp = Math.max(0, this.hp - amount); if (this.hp === 0) { this.alive = false; } return this.hp; } /** * Heal */ heal(amount) { this.hp = Math.min(this.maxHp, this.hp + amount); return this.hp; } /** * Check if unit is a player unit */ isPlayer() { return this.faction === 'player'; } /** * Check if unit is an enemy */ isEnemy() { return this.faction === 'enemy'; } /** * Get position as [row, col] */ getPosition() { return [this.row, this.col]; } /** * Set position */ setPosition(row, col) { this.row = row; this.col = col; } /** * Get HP percentage */ getHpPercent() { return (this.hp / this.maxHp) * 100; } } /** * Tile data structure */ export class Tile { constructor(row, col, type = 'floor') { this.row = row; this.col = col; this.type = type; // 'floor', 'bush', 'blocked', 'throne' this.unit = null; } /** * Check if tile is passable */ isPassable() { return this.type !== 'blocked'; } /** * Check if tile is occupied */ isOccupied() { return this.unit !== null; } /** * Get terrain avoid bonus */ getAvoidBonus() { switch (this.type) { case 'bush': return 10; case 'throne': return 15; default: return 0; } } /** * Get terrain defense bonus */ getDefBonus() { switch (this.type) { case 'bush': return 1; case 'throne': return 2; default: return 0; } } /** * Check if this is the throne tile */ isThrone() { return this.type === 'throne'; } /** * Place a unit on this tile */ placeUnit(unit) { this.unit = unit; if (unit) { unit.setPosition(this.row, this.col); } } /** * Remove unit from this tile */ removeUnit() { const unit = this.unit; this.unit = null; return unit; } } /** * Map data structure */ export class MapData { constructor(data) { this.id = data.id; this.name = data.name; this.rows = data.rows; this.cols = data.cols; // Store raw tile data for initialization this.tileData = data.tiles; this.playerSpawn = data.player_spawn; this.enemyLayout = data.enemy_layout; this.flexNodes = data.flex_nodes; this.reinforcementEntries = data.reinforcement_entries; // Tiles grid will be built during battle init this.tiles = []; } /** * Initialize the tile grid */ initTiles() { this.tiles = []; // Create all floor tiles first for (let row = 1; row <= this.rows; row++) { const rowTiles = []; for (let col = 1; col <= this.cols; col++) { rowTiles.push(new Tile(row, col, 'floor')); } this.tiles.push(rowTiles); } // Set blocked tiles if (this.tileData.blocked) { for (const [row, col] of this.tileData.blocked) { this.getTile(row, col).type = 'blocked'; } } // Set bush tiles if (this.tileData.bush) { for (const [row, col] of this.tileData.bush) { this.getTile(row, col).type = 'bush'; } } // Set throne tile if (this.tileData.throne) { const [row, col] = this.tileData.throne; this.getTile(row, col).type = 'throne'; } return this.tiles; } /** * Get tile at position (1-indexed) */ getTile(row, col) { if (row < 1 || row > this.rows || col < 1 || col > this.cols) { return null; } return this.tiles[row - 1]?.[col - 1] || null; } /** * Get all tiles as flat array */ getAllTiles() { return this.tiles.flat(); } /** * Check if position is valid */ isValidPosition(row, col) { return row >= 1 && row <= this.rows && col >= 1 && col <= this.cols; } /** * Get Manhattan distance between two positions */ static getDistance(row1, col1, row2, col2) { return Math.abs(row1 - row2) + Math.abs(col1 - col2); } }