Spaces:
Running
Running
| /** | |
| * 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); | |
| } | |
| } | |