User prompt
Un detalle con el Tree (ninguna unidad lo cruza, salvo los Jinetes de Dragón y Manakates los pasan de largo como si fuese terreno normal) Y quiero un marco que rodee lo que no sea el mapa del juego, para darle mejor estética
User prompt
Usa estas reglas explícitas por favor: el daño base es 1; si el atacante es Bow y el objetivo es Dragon Rider, el daño pasa a 2 (solo esta bonificación, no se acumula con otras), y si el atacante es Mago y el objetivo es Acorazado, el daño pasa a 2; en cualquier otro enfrentamiento de Bow o Mago contra otra unidad infligen solo 1. Además, prohíbe el apilamiento: cada casilla puede contener como máximo una unidad; al mover enemigos, no pueden entrar ni terminar su movimiento en casillas ocupadas, deben elegir la casilla libre válida más cercana a su objetivo; si todas las casillas adyacentes válidas están ocupadas, la unidad no se mueve ese turno.
User prompt
El bow inflige 1 punto de daño normalmente, pero si ataca a un Dragon Rider inflige 2. El mago también inflige 1 punto de daño normalmente, pero si ataca a un Acorazado inflige 2. Contra todas las demás unidades, ambos hacen únicamente 1 de daño.
User prompt
No, el bow sigue sin hacerle 2 de daño al dragon rider, lo mismo va para el mago contra los acorazados
User prompt
El bow debe hacerle un más de daño extra al atacar al Dragon Rider (o sea, le hace 2), lo mismo el mago contra el acorazado (el mago le hace 2 de daño a esa unidad), contra los demás es solo 1 de daño
User prompt
No, no funciona, mejor que el Manakete ataque de 1-2 casillas como hace el mago, y pueda atravesar el agua y los árboles como si fuese terreno normal.
User prompt
Los arqueros (bows) solo deben marcar en rojo las casillas que estén exactamente a 2 cuadros de distancia, dejando vacío el rango de 1. Los manaketes solo deben marcar en rojo las casillas que estén a 2 o 3 cuadros de distancia, dejando vacío el rango de 1.
User prompt
Para los bows, el rango de ataque debe marcar en rojo únicamente las casillas que estén exactamente a 2 de distancia desde el borde del rango de movimiento, dejando vacío el cuadro de 1 de distancia. Para los manaketes, el rango de ataque debe marcar en rojo únicamente las casillas que estén a 2 o 3 casillas de distancia desde el borde del rango de movimiento, dejando vacío el cuadro de 1 de distancia. Por cierto, los bow hace +1 de daño a los Dragon Rider, y los Magos hacen +1 a los acorazados
User prompt
Quiero que las unidades puedan pasar a través de las unidades del mismo bando al moverse y adelantarlas, pero no pueden compartir la misma cuadrícula al mismo tiempo ni pueden atravesar las unidades de otro bando. Y las unidades enemigas deben tener también el attackRangeTile y healRangeTile Y al seleccionar la unidad para ver su información, la imagen de su sprite tapa la información, hazlo más a la izquierda (en base a la longitud del nombre de la unidad, así no lo tapa), y más grande el rectángulo de la información para que abarque los datos y la imagen de la unidad.
User prompt
El highlightTile debe ser un cuadro azul, segundo, el attackRangeTile del bow se debe ver en con un espacio vacío de por medio, ya que la unidad solo ataca 2 casillas delante de él, no una y dos, lo mismo el Manakete, ataca a partir de 2-3 casillas delante suyo, no 1-2.
User prompt
Muestra el rango de movimiento de la unidad en azul, y luego dibuja un contorno rojo que marque todas las casillas alcanzables de ataque, calculadas a partir del borde externo del rango de movimiento (no alrededor de la unidad, sino sumando un cuadro más según el alcance del arma: 1, 1–2, solo 2, o 2–3). Estas casillas rojas indican dónde debe estar un enemigo para poder ser atacado, y en el caso de los enemigos se muestran igual para que el jugador sepa dónde no pararse. Para los Healers, el contorno debe mostrarse en verde en lugar de rojo, ya que su alcance es de curación.
User prompt
Cuadro que indica su casilla máxima de ataque un cuadro más luego de las casillas de movimiento, no alrededor de la unidad, como indicando que ahí debe estar una unidad para atacarla (y las unidades enemigas deben tener lo mismo, así sabes donde no pararte), a los Healers su cuadro de ataque es verde, porque en realidad es de curación. Segundo, el panel que muestra la información de la unidad seleccionada está mal, la imagen de la unidad tapa la información, así que mueve la imagen de la unidad más a la izquierda, que no quede encima de la información, y un poco más grande el sprite de la información.
User prompt
La casilla de ataque es fuera de las casillas de movimiento, no alrededor de la unidad, como indicando que ahí debe estar una unidad para atacarla (y las unidades enemigas deben tener lo mismo, así sabes donde no pararte), a los Healers su cuadro de ataque es verde, porque en realidad es de curación. Segundo, el panel que muestra la información de la unidad seleccionada está mal, la imagen de la unidad tapa la información, así que mueve la imagen de la unidad más a la izquierda, además de ampliarla un poco y quitarle el filtro azul o rojo.
User prompt
Bien, ahora quiero una casilla roja que, al momento de clicar una unidad, muestre su área de ataque alrededor del área máxima de movimiento. Ejemplo, una unidad que ataque 1 casilla a su alrededor, muestre una casilla roja que indica donde puede atacar si llega al máximo de su área. Y quiero que, al clicar una unidad, se vea en la esquina superior derecha su clase, HP y daño, así como una imagen de la unidad al lado, así quitas las letras de las unidades en el mapa.
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot read properties of null (reading 'getMovementRange')' in or related to this line: 'var maxRange = unit.getMovementRange();' Line Number: 648
User prompt
Que el highlightTile sea un cuadrado rojo Segundo, terreno normal debajo de los árboles, que se ve raro
User prompt
Que no se vea el gridTile, hazlo transparente, que no deja ver el terreno, segundo, aún no me sale la opción de atacar, ignorar o moverme cuando tengo una unidad enemiga en mi rango de ataque, que salga al primer clic mejor, ya al moverme me salga solo atacar o ignorar cuando tenga un enemigo en mi rango de ataque.
Code edit (1 edits merged)
Please save this source code
User prompt
Terreno abajo, que no se ve el río, ni el puente ni los árboles. Luego, añade 2 más de movimiento a cada unidad, que al hacer clic por segunda vez una unidad que tenga a un enemigo dentro de su rango de ataque, salga la opción de Atacar, Ignorar o Moverse (con esta última se moverá la unidad de lugar como siempre). Por último, quiero una unidad más llamada Manakete, la cual se moverá 4 casillas y solo puede atacar de 2 a 3 casillas, no una.
User prompt
Acomoda bien las capas, que los árboles se ven raros, no se ven las casillas de movimiento al clicar las unidades, así que acomoda todo
User prompt
Sin querer quité el sprite del terreno normal, vuélvelo a poner en su sitio, eso sí, que los sprites de terreno (normal, río y puente) no se superpongan, sino que se respete su posición en el mapa, de lo contrario se ven unos debajo de otros y se ve mal, los árboles se quedan como están
User prompt
Quita cuadros de río para hacerlos cuadros de puente (de 2 de horizontal y 3 de vertical) 2 puentes a lados opuestos del mapa.
User prompt
¿Y los 2 puentes? solo veo el río, así las unidades terrestres no van a pasar; y los indicadores de unidades deben estar abajo, ya que el botón de pausa no deja verlos
User prompt
De nuevo, te explicaré el puente ( en horizontal cuadro de río, cuadro de puente, cuadro de puente, de ahí 9 de río, cuadro de puente, cuadro de puente, y cuadro de río, y de ahí en vertical los copias hasta que sea de 3 en vertical), y que no haya árboles en frente de un puente, y mis unidades son en la esquina inferior derecha en vista de espejo a las unidades enemigas que estarán en la esquina superior derecha
User prompt
Ensancha algo más el río (3 de vertical) y pon los puentes en el río como dije (de 2 de horizontal por 3 de vertical), y quiero mis unidades aliadas al extremo derecho del mapa. Por cierto, el filtro de color que diferencia mis unidades de las enemigas (rojo enemigos y azul aliados) hazlo más opaco en el sprite y sin fondo, además de que una unidad aliada movida se vuelva gris, para así saber cuales ya se movieron, así como un contador de Unidades aliadas y otro de enemigas (8/8 rojo para enemigos y azul para aliados, reduciéndose en una cada que se pierda una unidad: 7/8, 6/8...) ↪💡 Consider importing and using the following plugins: @upit/tween.v1
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var GridTile = Container.expand(function (gridX, gridY) {
var self = Container.call(this);
self.gridX = gridX;
self.gridY = gridY;
self.unit = null;
self.hasTerrain = false;
var tileGraphics = self.attachAsset('gridTile', {
anchorX: 0.5,
anchorY: 0.5
});
var highlightGraphics = self.attachAsset('highlightTile', {
anchorX: 0.5,
anchorY: 0.5
});
highlightGraphics.alpha = 0;
var attackGraphics = self.attachAsset('attackTile', {
anchorX: 0.5,
anchorY: 0.5
});
attackGraphics.alpha = 0;
self.showHighlight = function () {
highlightGraphics.alpha = 0.4;
attackGraphics.alpha = 0;
};
self.showAttackRange = function () {
attackGraphics.alpha = 0.5;
highlightGraphics.alpha = 0;
};
self.hideHighlights = function () {
highlightGraphics.alpha = 0;
attackGraphics.alpha = 0;
};
self.terrainType = 'normal'; // normal, tree, water, bridge
self.addTerrain = function (terrainType) {
if (!self.hasTerrain) {
self.terrainType = terrainType || 'normal';
var assetName = 'terrain';
if (terrainType === 'tree') assetName = 'tree';else if (terrainType === 'water') assetName = 'water';else if (terrainType === 'bridge') assetName = 'bridge';
var terrainGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
terrainGraphics.alpha = 0.8;
self.hasTerrain = true;
}
};
return self;
});
var Unit = Container.expand(function (type, team, weaponType) {
var self = Container.call(this);
self.type = type;
self.team = team;
self.weaponType = weaponType;
self.gridX = 0;
self.gridY = 0;
self.hp = 2;
self.maxHp = self.hp;
self.attack = 1;
self.defense = 1;
self.hasMoved = false;
self.hasAttacked = false;
var unitGraphics;
if (type === 'commander') {
unitGraphics = self.attachAsset('commander', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (type === 'rider') {
unitGraphics = self.attachAsset('rider', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (type === 'dragonRider') {
unitGraphics = self.attachAsset('dragonRider', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (type === 'healer') {
unitGraphics = self.attachAsset('healer', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (type === 'armored') {
unitGraphics = self.attachAsset('armored', {
anchorX: 0.5,
anchorY: 0.5
});
} else {
// Use weapon type as sprite
if (weaponType === 'sword') {
unitGraphics = self.attachAsset('sword', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (weaponType === 'bow') {
unitGraphics = self.attachAsset('bow', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (weaponType === 'magic') {
unitGraphics = self.attachAsset('magic', {
anchorX: 0.5,
anchorY: 0.5
});
} else {
unitGraphics = self.attachAsset('sword', {
anchorX: 0.5,
anchorY: 0.5
});
}
}
// Add team color filter directly to unit graphics (more opaque, no background)
if (team === 'player') {
unitGraphics.tint = 0x5555FF; // Blue tint for player units
} else {
unitGraphics.tint = 0xFF5555; // Red tint for enemy units
}
// Add commander crown
if (type === 'commander') {
var crown = new Text2('♛', {
size: 25,
fill: 0xFFD700
});
crown.anchor.set(1, 1);
crown.x = 50;
crown.y = 50;
self.addChild(crown);
}
var weaponText = new Text2(weaponType === 'sword' ? 'S' : weaponType === 'bow' ? 'B' : weaponType === 'magic' ? 'M' : weaponType === 'rider' ? 'R' : weaponType === 'dragonRider' ? 'D' : weaponType === 'healer' ? 'H' : weaponType === 'armored' ? 'A' : '?', {
size: 30,
fill: 0xFFFFFF
});
weaponText.anchor.set(0.5, 0.5);
weaponText.y = -10;
self.addChild(weaponText);
var hpText = new Text2(self.hp.toString(), {
size: 20,
fill: 0xFFFFFF
});
hpText.anchor.set(0.5, 0.5);
hpText.y = 20;
self.addChild(hpText);
self.updateHpDisplay = function () {
hpText.setText(self.hp.toString());
if (self.hp <= 0) {
unitGraphics.alpha = 0.3;
}
};
self.takeDamage = function (damage) {
var actualDamage = Math.max(1, damage - self.defense);
if (grid[self.gridY][self.gridX].hasTerrain) {
actualDamage = Math.max(1, actualDamage - 1);
}
self.hp -= actualDamage;
self.updateHpDisplay();
return self.hp <= 0;
};
self.getRange = function () {
if (self.weaponType === 'sword') return 1;
if (self.weaponType === 'bow') return 2;
if (self.weaponType === 'magic') return 2;
if (self.weaponType === 'rider') return 1;
if (self.weaponType === 'dragonRider') return 1;
if (self.weaponType === 'healer') return 1;
if (self.weaponType === 'armored') return 1;
return 1;
};
self.getMovementRange = function () {
if (self.weaponType === 'rider' || self.weaponType === 'dragonRider') return 3;
if (self.weaponType === 'armored') return 1;
return 2; // Default movement for sword, bow, magic, healer
};
self.canAttack = function (targetX, targetY) {
var distance = Math.abs(self.gridX - targetX) + Math.abs(self.gridY - targetY);
if (self.weaponType === 'healer') return false; // Healer cannot attack
if (self.weaponType === 'bow') {
return distance === 2; // Bow can only attack at exactly range 2, not adjacent
}
return distance <= self.getRange();
};
self.canHeal = function (targetX, targetY) {
if (self.weaponType !== 'healer') return false;
var distance = Math.abs(self.gridX - targetX) + Math.abs(self.gridY - targetY);
return distance === 1; // Healer can heal adjacent allies
};
self.heal = function (target) {
if (target.hp < target.maxHp) {
target.hp += 1;
if (target.hp > target.maxHp) target.hp = target.maxHp;
target.updateHpDisplay();
return true;
}
return false;
};
self.resetTurn = function () {
self.hasMoved = false;
self.hasAttacked = false;
unitGraphics.alpha = 1.0;
// Restore original team color
if (self.team === 'player') {
unitGraphics.tint = 0x5555FF; // Blue tint for player units
} else {
unitGraphics.tint = 0xFF5555; // Red tint for enemy units
}
};
self.endTurn = function () {
self.hasMoved = true;
self.hasAttacked = true;
unitGraphics.alpha = 0.6;
// Add gray tint for moved allied units
if (self.team === 'player') {
unitGraphics.tint = 0x888888; // Gray tint for moved player units
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2d2d2d
});
/****
* Game Code
****/
var GRID_SIZE = 15;
var TILE_SIZE = 128;
var grid = [];
var playerUnits = [];
var enemyUnits = [];
var selectedUnit = null;
var originalUnitPosition = null; // Store original position when moving
var selectedEnemyUnit = null; // Track selected enemy for movement display
var currentTurn = 'player';
var gameState = 'playing';
var turnPhase = 'select'; // 'select', 'move', 'chooseAction', 'attack'
// UI
var turnText = new Text2('Player Turn', {
size: 60,
fill: 0xFFFFFF
});
turnText.anchor.set(0.5, 0);
LK.gui.top.addChild(turnText);
var phaseText = new Text2('Select Unit', {
size: 40,
fill: 0xFFFFFF
});
phaseText.anchor.set(0.5, 0);
phaseText.y = 80;
LK.gui.top.addChild(phaseText);
// Unit counters
var playerCounter = new Text2('8/8', {
size: 35,
fill: 0x5555FF
});
playerCounter.anchor.set(0, 1);
playerCounter.x = 20;
playerCounter.y = -120;
LK.gui.bottomLeft.addChild(playerCounter);
var enemyCounter = new Text2('8/8', {
size: 35,
fill: 0xFF5555
});
enemyCounter.anchor.set(1, 1);
enemyCounter.x = -20;
enemyCounter.y = -120;
LK.gui.bottomRight.addChild(enemyCounter);
var endTurnBtn = new Text2('End Turn', {
size: 50,
fill: 0x00FF00
});
endTurnBtn.anchor.set(0.5, 1);
LK.gui.bottom.addChild(endTurnBtn);
// botones dinámicos de acción
var attackBtn = null;
var ignoreBtn = null;
function showActionButtons() {
// Hide end turn button when showing action buttons
endTurnBtn.alpha = 0;
endTurnBtn.interactive = false;
if (selectedUnit.weaponType === 'healer') {
// Check if there are valid heal targets
var hasHealTargets = false;
for (var ty = 0; ty < GRID_SIZE; ty++) {
for (var tx = 0; tx < GRID_SIZE; tx++) {
if (selectedUnit.canHeal(tx, ty) && grid[ty][tx].unit && grid[ty][tx].unit.team === selectedUnit.team && grid[ty][tx].unit.hp < grid[ty][tx].unit.maxHp) {
hasHealTargets = true;
break;
}
}
if (hasHealTargets) break;
}
if (hasHealTargets) {
attackBtn = new Text2('Heal', {
size: 50,
fill: 0x00FF00
});
attackBtn.anchor.set(0.5, 1);
attackBtn.y = -60;
LK.gui.bottom.addChild(attackBtn);
attackBtn.interactive = true;
attackBtn.down = function () {
turnPhase = 'heal';
phaseText.setText('Choose ally to heal');
showHealOptions(selectedUnit);
removeActionButtons();
};
}
} else {
attackBtn = new Text2('Attack', {
size: 50,
fill: 0xFF0000
});
attackBtn.anchor.set(0.5, 1);
attackBtn.y = -60;
LK.gui.bottom.addChild(attackBtn);
attackBtn.interactive = true;
attackBtn.down = function () {
turnPhase = 'attack';
phaseText.setText('Choose enemy to attack');
showAttackOptions(selectedUnit);
removeActionButtons();
};
}
ignoreBtn = new Text2('Ignore', {
size: 50,
fill: 0xFFFF00
});
ignoreBtn.anchor.set(0.5, 1);
ignoreBtn.y = -10;
LK.gui.bottom.addChild(ignoreBtn);
ignoreBtn.interactive = true;
ignoreBtn.down = function () {
selectedUnit.endTurn();
turnPhase = 'select';
phaseText.setText('Select Unit');
selectedUnit = null;
originalUnitPosition = null;
clearHighlights();
removeActionButtons();
// Check if all player units have moved
checkAutoEndTurn();
};
}
function removeActionButtons() {
// Show end turn button again
if (currentTurn === 'player') {
endTurnBtn.alpha = 1;
endTurnBtn.interactive = true;
}
if (attackBtn) {
LK.gui.bottom.removeChild(attackBtn);
attackBtn = null;
}
if (ignoreBtn) {
LK.gui.bottom.removeChild(ignoreBtn);
ignoreBtn = null;
}
}
// Initialize grid
function initializeGrid() {
var startX = (2048 - GRID_SIZE * TILE_SIZE) / 2;
var startY = (2732 - GRID_SIZE * TILE_SIZE) / 2;
for (var y = 0; y < GRID_SIZE; y++) {
grid[y] = [];
for (var x = 0; x < GRID_SIZE; x++) {
var tile = new GridTile(x, y);
tile.x = startX + x * TILE_SIZE + TILE_SIZE / 2;
tile.y = startY + y * TILE_SIZE + TILE_SIZE / 2;
grid[y][x] = tile;
game.addChild(tile);
}
}
// Generate terrain
generateTerrain();
}
// Initialize units
function initializeUnits() {
// Player units (bottom-right corner)
var playerUnit1 = new Unit('hero', 'player', 'sword');
placeUnit(playerUnit1, 13, 13);
playerUnits.push(playerUnit1);
var playerUnit2 = new Unit('hero', 'player', 'bow');
placeUnit(playerUnit2, 11, 13);
playerUnits.push(playerUnit2);
var playerUnit3 = new Unit('hero', 'player', 'magic');
placeUnit(playerUnit3, 9, 13);
playerUnits.push(playerUnit3);
var playerRider = new Unit('rider', 'player', 'rider');
placeUnit(playerRider, 14, 12);
playerUnits.push(playerRider);
var playerDragonRider = new Unit('dragonRider', 'player', 'dragonRider');
placeUnit(playerDragonRider, 8, 12);
playerUnits.push(playerDragonRider);
var playerHealer = new Unit('healer', 'player', 'healer');
placeUnit(playerHealer, 10, 14);
playerUnits.push(playerHealer);
var playerArmored = new Unit('armored', 'player', 'armored');
placeUnit(playerArmored, 14, 13);
playerUnits.push(playerArmored);
var playerCommander = new Unit('commander', 'player', 'sword');
placeUnit(playerCommander, 12, 14);
playerUnits.push(playerCommander);
// Enemy units (top-left corner)
var enemyUnit1 = new Unit('hero', 'enemy', 'sword');
placeUnit(enemyUnit1, 1, 1);
enemyUnits.push(enemyUnit1);
var enemyUnit2 = new Unit('hero', 'enemy', 'bow');
placeUnit(enemyUnit2, 3, 1);
enemyUnits.push(enemyUnit2);
var enemyUnit3 = new Unit('hero', 'enemy', 'magic');
placeUnit(enemyUnit3, 5, 1);
enemyUnits.push(enemyUnit3);
var enemyRider = new Unit('rider', 'enemy', 'rider');
placeUnit(enemyRider, 0, 2);
enemyUnits.push(enemyRider);
var enemyDragonRider = new Unit('dragonRider', 'enemy', 'dragonRider');
placeUnit(enemyDragonRider, 6, 2);
enemyUnits.push(enemyDragonRider);
var enemyHealer = new Unit('healer', 'enemy', 'healer');
placeUnit(enemyHealer, 4, 0);
enemyUnits.push(enemyHealer);
var enemyArmored = new Unit('armored', 'enemy', 'armored');
placeUnit(enemyArmored, 0, 1);
enemyUnits.push(enemyArmored);
var enemyCommander = new Unit('commander', 'enemy', 'sword');
placeUnit(enemyCommander, 2, 0);
enemyUnits.push(enemyCommander);
}
function generateTerrain() {
// Add water river in middle of map (rows 6, 7, 8 - 3 tiles wide)
for (var y = 6; y <= 8; y++) {
for (var x = 0; x < GRID_SIZE; x++) {
grid[y][x].addTerrain('water');
}
}
// Add bridges: horizontal pattern (river-bridge-bridge then 9 river tiles then bridge-bridge-river)
// Bridge 1: columns 1-2, rows 6-8
for (var y = 6; y <= 8; y++) {
for (var x = 1; x <= 2; x++) {
grid[y][x].addTerrain('bridge');
}
}
// Bridge 2: columns 12-13, rows 6-8
for (var y = 6; y <= 8; y++) {
for (var x = 12; x <= 13; x++) {
grid[y][x].addTerrain('bridge');
}
}
// Add some trees scattered around (avoiding bridge areas)
var treePositions = [{
x: 4,
y: 4
}, {
x: 6,
y: 5
}, {
x: 9,
y: 4
}, {
x: 11,
y: 5
}, {
x: 3,
y: 10
}, {
x: 5,
y: 11
}, {
x: 8,
y: 10
}, {
x: 10,
y: 11
}, {
x: 7,
y: 3
}, {
x: 14,
y: 10
}, {
x: 0,
y: 12
}, {
x: 14,
y: 4
}];
for (var i = 0; i < treePositions.length; i++) {
var pos = treePositions[i];
grid[pos.y][pos.x].addTerrain('tree');
}
}
function placeUnit(unit, x, y) {
unit.gridX = x;
unit.gridY = y;
unit.x = grid[y][x].x;
unit.y = grid[y][x].y;
grid[y][x].unit = unit;
game.addChild(unit);
}
function clearHighlights() {
for (var y = 0; y < GRID_SIZE; y++) {
for (var x = 0; x < GRID_SIZE; x++) {
grid[y][x].hideHighlights();
}
}
}
function showMovementOptions(unit) {
// Use pathfinding to check reachable tiles considering terrain
var reachableTiles = getReachableTiles(unit);
for (var i = 0; i < reachableTiles.length; i++) {
var tile = reachableTiles[i];
grid[tile.y][tile.x].showHighlight();
}
}
function showAttackOptions(unit) {
for (var y = 0; y < GRID_SIZE; y++) {
for (var x = 0; x < GRID_SIZE; x++) {
if (unit.canAttack(x, y) && grid[y][x].unit && grid[y][x].unit.team !== unit.team && grid[y][x].unit.hp > 0) {
grid[y][x].showAttackRange();
}
}
}
}
function showHealOptions(unit) {
for (var y = 0; y < GRID_SIZE; y++) {
for (var x = 0; x < GRID_SIZE; x++) {
if (unit.canHeal(x, y) && grid[y][x].unit && grid[y][x].unit.team === unit.team && grid[y][x].unit.hp < grid[y][x].unit.maxHp) {
grid[y][x].showAttackRange();
}
}
}
}
function getReachableTiles(unit) {
var reachableTiles = [];
var maxRange = unit.getMovementRange();
var visited = {};
var queue = [{
x: unit.gridX,
y: unit.gridY,
cost: 0
}];
visited[unit.gridX + ',' + unit.gridY] = true;
while (queue.length > 0) {
var current = queue.shift();
// Check all 4 directions
var directions = [{
x: 0,
y: 1
}, {
x: 0,
y: -1
}, {
x: 1,
y: 0
}, {
x: -1,
y: 0
}];
for (var d = 0; d < directions.length; d++) {
var dir = directions[d];
var newX = current.x + dir.x;
var newY = current.y + dir.y;
var key = newX + ',' + newY;
if (newX < 0 || newX >= GRID_SIZE || newY < 0 || newY >= GRID_SIZE) continue;
if (visited[key]) continue;
if (grid[newY][newX].unit) continue;
var moveCost = getMoveCost(unit, newX, newY);
if (moveCost === -1) continue; // Cannot move to this tile
var newCost = current.cost + moveCost;
if (newCost <= maxRange) {
visited[key] = true;
queue.push({
x: newX,
y: newY,
cost: newCost
});
if (newX !== unit.gridX || newY !== unit.gridY) {
reachableTiles.push({
x: newX,
y: newY,
cost: newCost
});
}
}
}
}
return reachableTiles;
}
function getMoveCost(unit, x, y) {
var tile = grid[y][x];
// Water - only dragon riders can cross
if (tile.terrainType === 'water' && unit.weaponType !== 'dragonRider') {
return -1; // Cannot move
}
// Tree - riders cannot cross, others get movement cost
if (tile.terrainType === 'tree') {
if (unit.weaponType === 'rider') {
return -1; // Cannot move
}
if (unit.weaponType === 'dragonRider') {
return 1; // Normal cost
}
return unit.getMovementRange(); // Ends movement for turn
}
// Normal terrain and bridges
return 1;
}
function showEnemyMovementOptions(unit) {
var reachableTiles = getReachableTiles(unit);
for (var i = 0; i < reachableTiles.length; i++) {
var tile = reachableTiles[i];
grid[tile.y][tile.x].showHighlight();
}
}
function moveUnit(unit, targetX, targetY) {
grid[unit.gridY][unit.gridX].unit = null;
unit.gridX = targetX;
unit.gridY = targetY;
grid[targetY][targetX].unit = unit;
tween(unit, {
x: grid[targetY][targetX].x,
y: grid[targetY][targetX].y
}, {
duration: 300
});
clearHighlights();
}
function isValidMove(unit, x, y) {
if (x < 0 || x >= GRID_SIZE || y < 0 || y >= GRID_SIZE) return false;
if (grid[y][x].unit) return false;
var distance = Math.abs(unit.gridX - x) + Math.abs(unit.gridY - y);
if (distance > unit.getMovementRange()) return false;
// Check terrain restrictions
var targetTile = grid[y][x];
if (targetTile.terrainType === 'water') {
// Only dragon riders can cross water
if (unit.weaponType !== 'dragonRider') return false;
}
return true;
}
function getTileFromPosition(screenX, screenY) {
var startX = (2048 - GRID_SIZE * TILE_SIZE) / 2;
var startY = (2732 - GRID_SIZE * TILE_SIZE) / 2;
var gridX = Math.floor((screenX - startX) / TILE_SIZE);
var gridY = Math.floor((screenY - startY) / TILE_SIZE);
if (gridX >= 0 && gridX < GRID_SIZE && gridY >= 0 && gridY < GRID_SIZE) {
return {
x: gridX,
y: gridY
};
}
return null;
}
// Game click handler
game.down = function (x, y, obj) {
var tilePos = getTileFromPosition(x, y);
if (!tilePos) return;
var clickedTile = grid[tilePos.y][tilePos.x];
if (turnPhase === 'select') {
// Handle enemy unit selection for viewing movement
if (clickedTile.unit && clickedTile.unit.team !== currentTurn) {
if (selectedEnemyUnit === clickedTile.unit) {
// Clicking same enemy again - hide movement
selectedEnemyUnit = null;
clearHighlights();
} else {
// Clicking different enemy - show movement
selectedEnemyUnit = clickedTile.unit;
clearHighlights();
showEnemyMovementOptions(selectedEnemyUnit);
}
return;
}
// Clear enemy selection when clicking elsewhere
if (selectedEnemyUnit) {
selectedEnemyUnit = null;
clearHighlights();
}
// Handle player unit selection
if (clickedTile.unit && clickedTile.unit.team === currentTurn && !clickedTile.unit.hasMoved) {
selectedUnit = clickedTile.unit;
originalUnitPosition = {
x: selectedUnit.gridX,
y: selectedUnit.gridY
};
turnPhase = 'move';
phaseText.setText('Choose destination');
showMovementOptions(selectedUnit);
}
} else if (turnPhase === 'move') {
// Cancel move selection by clicking anywhere
if (!isValidMove(selectedUnit, tilePos.x, tilePos.y)) {
selectedUnit = null;
originalUnitPosition = null;
turnPhase = 'select';
phaseText.setText('Select Unit');
clearHighlights();
return;
}
if (isValidMove(selectedUnit, tilePos.x, tilePos.y)) {
moveUnit(selectedUnit, tilePos.x, tilePos.y);
LK.setTimeout(function () {
if (!selectedUnit) return;
if (selectedUnit.weaponType === 'healer') {
var hasHealTargets = false;
for (var ty = 0; ty < GRID_SIZE; ty++) {
for (var tx = 0; tx < GRID_SIZE; tx++) {
if (selectedUnit.canHeal(tx, ty) && grid[ty][tx].unit && grid[ty][tx].unit.team === selectedUnit.team && grid[ty][tx].unit.hp < grid[ty][tx].unit.maxHp) {
hasHealTargets = true;
break;
}
}
if (hasHealTargets) break;
}
if (hasHealTargets) {
turnPhase = 'chooseAction';
phaseText.setText('Heal or Ignore');
showActionButtons();
} else {
selectedUnit.endTurn();
turnPhase = 'select';
phaseText.setText('Select Unit');
selectedUnit = null;
originalUnitPosition = null;
clearHighlights();
checkAutoEndTurn();
}
} else {
var hasAttackTargets = false;
for (var ty = 0; ty < GRID_SIZE; ty++) {
for (var tx = 0; tx < GRID_SIZE; tx++) {
if (selectedUnit.canAttack(tx, ty) && grid[ty][tx].unit && grid[ty][tx].unit.team !== selectedUnit.team && grid[ty][tx].unit.hp > 0) {
hasAttackTargets = true;
break;
}
}
if (hasAttackTargets) break;
}
if (hasAttackTargets) {
turnPhase = 'chooseAction';
phaseText.setText('Attack or Ignore');
showActionButtons();
} else {
selectedUnit.endTurn();
turnPhase = 'select';
phaseText.setText('Select Unit');
selectedUnit = null;
originalUnitPosition = null;
clearHighlights();
checkAutoEndTurn();
}
}
}, 350);
}
} else if (turnPhase === 'heal') {
if (clickedTile.unit && clickedTile.unit.team === selectedUnit.team && selectedUnit.canHeal(tilePos.x, tilePos.y)) {
var target = clickedTile.unit;
selectedUnit.heal(target);
selectedUnit.endTurn();
turnPhase = 'select';
phaseText.setText('Select Unit');
selectedUnit = null;
originalUnitPosition = null;
clearHighlights();
checkAutoEndTurn();
} else {
// Clicked elsewhere during heal - return to original position
if (originalUnitPosition && selectedUnit) {
moveUnit(selectedUnit, originalUnitPosition.x, originalUnitPosition.y);
LK.setTimeout(function () {
if (selectedUnit) {
turnPhase = 'move';
phaseText.setText('Choose destination');
showMovementOptions(selectedUnit);
}
}, 350);
}
}
} else if (turnPhase === 'attack') {
if (clickedTile.unit && clickedTile.unit.team !== selectedUnit.team && selectedUnit.canAttack(tilePos.x, tilePos.y)) {
var target = clickedTile.unit;
// Add attack shake animation
if (!selectedUnit) return;
var originalX = selectedUnit.x;
tween(selectedUnit, {
x: originalX + 10
}, {
duration: 100,
onFinish: function onFinish() {
if (!selectedUnit) return;
tween(selectedUnit, {
x: originalX - 10
}, {
duration: 100,
onFinish: function onFinish() {
if (!selectedUnit) return;
tween(selectedUnit, {
x: originalX
}, {
duration: 100
});
}
});
}
});
var isDead = target.takeDamage(selectedUnit.attack);
if (isDead) {
grid[target.gridY][target.gridX].unit = null;
tween(target, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
target.destroy();
}
});
if (target.team === 'player') {
var index = playerUnits.indexOf(target);
if (index > -1) playerUnits.splice(index, 1);
updateUnitCounters();
// Check if player commander was defeated
if (target.type === 'commander') {
LK.showGameOver();
return;
}
} else {
var index = enemyUnits.indexOf(target);
if (index > -1) enemyUnits.splice(index, 1);
updateUnitCounters();
// Check if enemy commander was defeated
if (target.type === 'commander') {
LK.showYouWin();
return;
}
}
}
selectedUnit.endTurn();
turnPhase = 'select';
phaseText.setText('Select Unit');
selectedUnit = null;
originalUnitPosition = null;
clearHighlights();
checkAutoEndTurn();
} else {
// Clicked elsewhere during attack - return to original position
if (originalUnitPosition && selectedUnit) {
moveUnit(selectedUnit, originalUnitPosition.x, originalUnitPosition.y);
LK.setTimeout(function () {
if (selectedUnit) {
turnPhase = 'move';
phaseText.setText('Choose destination');
showMovementOptions(selectedUnit);
}
}, 350);
}
}
}
};
// Enemy AI functions
function getValidMovesForUnit(unit) {
return getReachableTiles(unit);
}
function updateUnitCounters() {
var alivePlayerUnits = 0;
var aliveEnemyUnits = 0;
for (var i = 0; i < playerUnits.length; i++) {
if (playerUnits[i].hp > 0) alivePlayerUnits++;
}
for (var i = 0; i < enemyUnits.length; i++) {
if (enemyUnits[i].hp > 0) aliveEnemyUnits++;
}
playerCounter.setText(alivePlayerUnits + '/8');
enemyCounter.setText(aliveEnemyUnits + '/8');
}
function findAttackTargets(unit) {
var targets = [];
for (var y = 0; y < GRID_SIZE; y++) {
for (var x = 0; x < GRID_SIZE; x++) {
if (unit.canAttack(x, y) && grid[y][x].unit && grid[y][x].unit.team !== unit.team && grid[y][x].unit.hp > 0) {
targets.push({
unit: grid[y][x].unit,
x: x,
y: y
});
}
}
}
return targets;
}
function checkAutoEndTurn() {
if (currentTurn !== 'player') return;
var allMoved = true;
for (var i = 0; i < playerUnits.length; i++) {
if (!playerUnits[i].hasMoved && playerUnits[i].hp > 0) {
allMoved = false;
break;
}
}
if (allMoved) {
// Auto end turn
LK.setTimeout(function () {
for (var i = 0; i < playerUnits.length; i++) {
playerUnits[i].resetTurn();
}
currentTurn = 'enemy';
turnText.setText('Enemy Turn');
phaseText.setText('Enemy AI thinking...');
LK.setTimeout(executeEnemyAI, 1000);
}, 500);
}
}
function executeEnemyAI() {
if (currentTurn !== 'enemy') return;
// Hide end turn button during enemy turn
endTurnBtn.alpha = 0;
endTurnBtn.interactive = false;
var currentEnemyIndex = 0;
function processNextEnemy() {
if (currentEnemyIndex >= enemyUnits.length) {
// All enemies processed, end turn
for (var i = 0; i < enemyUnits.length; i++) {
enemyUnits[i].resetTurn();
}
currentTurn = 'player';
turnText.setText('Player Turn');
selectedUnit = null;
selectedEnemyUnit = null;
turnPhase = 'select';
phaseText.setText('Select Unit');
clearHighlights();
removeActionButtons();
// Show end turn button again for player turn
endTurnBtn.alpha = 1;
endTurnBtn.interactive = true;
return;
}
var enemy = enemyUnits[currentEnemyIndex];
currentEnemyIndex++;
if (enemy.hasMoved || enemy.hp <= 0) {
processNextEnemy();
return;
}
// Check if enemy can attack from current position
var currentTargets = findAttackTargets(enemy);
if (currentTargets.length > 0) {
// Attack the first available target
var target = currentTargets[0].unit;
// Add attack shake animation
var originalX = enemy.x;
tween(enemy, {
x: originalX + 10
}, {
duration: 100,
onFinish: function onFinish() {
tween(enemy, {
x: originalX - 10
}, {
duration: 100,
onFinish: function onFinish() {
tween(enemy, {
x: originalX
}, {
duration: 100
});
}
});
}
});
var isDead = target.takeDamage(enemy.attack);
if (isDead) {
grid[target.gridY][target.gridX].unit = null;
tween(target, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
target.destroy();
}
});
if (target.team === 'player') {
var index = playerUnits.indexOf(target);
if (index > -1) playerUnits.splice(index, 1);
updateUnitCounters();
// Check if player commander was defeated
if (target.type === 'commander') {
LK.showGameOver();
return;
}
}
}
enemy.endTurn();
LK.setTimeout(processNextEnemy, 500);
return;
}
// Try to move closer to player units
var validMoves = getValidMovesForUnit(enemy);
if (validMoves.length > 0) {
// Find the move that gets closest to any player unit
var bestMove = null;
var bestScore = Infinity;
for (var m = 0; m < validMoves.length; m++) {
var move = validMoves[m];
var minDistanceToPlayer = Infinity;
for (var p = 0; p < playerUnits.length; p++) {
if (playerUnits[p].hp <= 0) continue;
var dist = Math.abs(move.x - playerUnits[p].gridX) + Math.abs(move.y - playerUnits[p].gridY);
minDistanceToPlayer = Math.min(minDistanceToPlayer, dist);
}
if (minDistanceToPlayer < bestScore) {
bestScore = minDistanceToPlayer;
bestMove = move;
}
}
if (bestMove) {
moveUnit(enemy, bestMove.x, bestMove.y);
LK.setTimeout(function () {
// Check if can attack after moving
var targetsAfterMove = findAttackTargets(enemy);
if (targetsAfterMove.length > 0) {
var target = targetsAfterMove[0].unit;
// Add attack shake animation
var originalX = enemy.x;
tween(enemy, {
x: originalX + 10
}, {
duration: 100,
onFinish: function onFinish() {
tween(enemy, {
x: originalX - 10
}, {
duration: 100,
onFinish: function onFinish() {
tween(enemy, {
x: originalX
}, {
duration: 100
});
}
});
}
});
var isDead = target.takeDamage(enemy.attack);
if (isDead) {
grid[target.gridY][target.gridX].unit = null;
tween(target, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
target.destroy();
}
});
if (target.team === 'player') {
var index = playerUnits.indexOf(target);
if (index > -1) playerUnits.splice(index, 1);
updateUnitCounters();
// Check if player commander was defeated
if (target.type === 'commander') {
LK.showGameOver();
return;
}
}
}
}
enemy.endTurn();
LK.setTimeout(processNextEnemy, 500);
}, 400);
return;
}
}
// No valid moves, end this unit's turn
enemy.endTurn();
LK.setTimeout(processNextEnemy, 300);
}
processNextEnemy();
}
// End turn button
endTurnBtn.interactive = true;
endTurnBtn.down = function () {
if (currentTurn === 'player') {
for (var i = 0; i < playerUnits.length; i++) {
playerUnits[i].resetTurn();
}
currentTurn = 'enemy';
turnText.setText('Enemy Turn');
phaseText.setText('Enemy AI thinking...');
LK.setTimeout(executeEnemyAI, 1000);
} else {
for (var i = 0; i < enemyUnits.length; i++) {
enemyUnits[i].resetTurn();
}
currentTurn = 'player';
turnText.setText('Player Turn');
}
selectedUnit = null;
selectedEnemyUnit = null;
originalUnitPosition = null;
turnPhase = 'select';
phaseText.setText('Select Unit');
clearHighlights();
removeActionButtons();
};
// Initialize game
initializeGrid();
initializeUnits();
updateUnitCounters(); ===================================================================
--- original.js
+++ change.js
@@ -262,20 +262,20 @@
var playerCounter = new Text2('8/8', {
size: 35,
fill: 0x5555FF
});
-playerCounter.anchor.set(0, 0);
+playerCounter.anchor.set(0, 1);
playerCounter.x = 20;
-playerCounter.y = 20;
-LK.gui.topLeft.addChild(playerCounter);
+playerCounter.y = -120;
+LK.gui.bottomLeft.addChild(playerCounter);
var enemyCounter = new Text2('8/8', {
size: 35,
fill: 0xFF5555
});
-enemyCounter.anchor.set(1, 0);
+enemyCounter.anchor.set(1, 1);
enemyCounter.x = -20;
-enemyCounter.y = 20;
-LK.gui.topRight.addChild(enemyCounter);
+enemyCounter.y = -120;
+LK.gui.bottomRight.addChild(enemyCounter);
var endTurnBtn = new Text2('End Turn', {
size: 50,
fill: 0x00FF00
});
@@ -443,16 +443,16 @@
for (var x = 0; x < GRID_SIZE; x++) {
grid[y][x].addTerrain('water');
}
}
- // Add bridges: horizontal pattern (river-bridge-bridge-river...bridge-bridge-river)
+ // Add bridges: horizontal pattern (river-bridge-bridge then 9 river tiles then bridge-bridge-river)
// Bridge 1: columns 1-2, rows 6-8
for (var y = 6; y <= 8; y++) {
for (var x = 1; x <= 2; x++) {
grid[y][x].addTerrain('bridge');
}
}
- // Bridge 2: columns 12-13, rows 6-8
+ // Bridge 2: columns 12-13, rows 6-8
for (var y = 6; y <= 8; y++) {
for (var x = 12; x <= 13; x++) {
grid[y][x].addTerrain('bridge');
}
Un arbusto frondoso estilo animado. In-Game asset. 2d. High contrast. No shadows
Un cuadrado perfecto, sin ningún contorno, gris y con textura de piedra. In-Game asset. 2d. High contrast. No shadows
Un joven comandante de porte noble, con cabello plateado profundo ligeramente desordenado y una diadema dorada. Sus ojos son grandes y dorados. Viste una armadura de placas negro adornada con bordes dorados, con hombreras firmes y un broche rojo brillante en el pecho. Lleva una capa roja que cae sobre su hombro izquierdo. Su expresión es seria y firme. Estilo chibi fullbody In-Game asset. 2d. High contrast. No shadows
Un guerrero acorazado en estilo chibi de cuerpo completo, de cuerpo bajo y ancho, con una gran armadura pesada de hierro gris oscuro que cubre casi todo su cuerpo. Un casco de hierro de aspecto rudo, dejando ver un bigote italiano chistoso y exagerado. La armadura es muy voluminosa, con hombreras redondeadas, brazales gruesos y botas metálicas, transmitiendo la idea de fortaleza e impenetrabilidad. Sostiene un enorme hacha de batalla de doble filo, desproporcionada en estilo chibi, que enfatiza su poder de ataque. Su expresión es ruda pero cómica gracias al bigote, dándole un aire intimidante y gracioso a la vez.. In-Game asset. 2d. High contrast. No shadows
Una sanadora en estilo chibi/cuerpo completo, una chica joven de físico algo marcado por el esfuerzo y la vida dura. Viste ropas tribales, va descalza. Su cabello es negro, largo y despeinado, cayendo con un aire descuidado pero natural. Porta un bastón improvisado hecho de una rama de árbol torcida, aún con algunas hojas pegadas, lo que resalta su carácter rústico y tribal. En su piel se notan tatuajes o marcas tribales, además de collares o pulseras hechos de hueso, madera o cuentas simples, que refuerzan su conexión espiritual con la naturaleza. Su expresión es serena y determinada, mostrando que a pesar de su apariencia austera y primitiva, es una guía y protectora para su gente.. In-Game asset. 2d. High contrast. No shadows
Una jinete de wyvern en estilo chibi/cuerpo completo. El wyvern es grande y de color morado, con alas extendidas y expresión fiera. La jinete es una joven guerrera con armadura ligera de torso en color verde con detalles aqua, con hombreras y guantes. Lleva pantalones blancos y botas verdes altas. Su cabello es corto estilo bob, de color aqua, y tiene ojos azules brillantes. Montada sobre el wyvern, sostiene con una mano las riendas y con la otra una lanza larga.. In-Game asset. 2d. High contrast. No shadows
Un manakete en estilo chibi/cuerpo completo. Se ve como un niño pequeño con cabello blanco desordenado. Sus alas de dragón, morado oscura y membranosas, están enrolladas alrededor de su cuerpo como si fueran una capucha, dándole un aire misterioso. Viste ropa casual y simple, algo holgada, que oculta sus verdaderos atributos. En sus brazos lleva vendas mal puestas, apenas cubriendo las escamas moradas de dragón que intentan asomarse bajo ellas, garras en las manos. En cada mejilla tiene tres marcas visibles como arañazos, y un colmillo visible. Su expresión es inocente pero enigmática, transmitiendo que no es un simple niño. El estilo es chibi pero con un aire ligeramente oscuro y mágico.. In-Game asset. 2d. High contrast. No shadows
Un caballero jinete en estilo chibi/cuerpo completo. Es un hombre delgado de aproximadamente 50 años, con barba de chivo bien marcada y expresión seria pero orgullosa. Lleva una armadura roja con detalles plateados brillantes, elegante y clásica. Porta una larga lanza de justas en una mano y un gran escudo ovalado en la otra, el cual está muy decorado con símbolos y adornos llamativos. Está montado sobre un caballo robusto y elegante, en pose firme y heroica. El estilo es chibi, pero mantiene la imponencia de un caballero veterano.. In-Game asset. 2d. High contrast. No shadows
Un arquero en estilo chibi/cuerpo completo. Es un joven de cabello castaño y tez blanca, con un aire humilde y campesino. Lleva ropa sencilla de campesino, con un parche en el ojo derecho que le da un toque rudo. Porta un arco de color dorado brillante y un carcaj lleno de flechas que lleva a modo de cinturón en la cintura. En la cabeza luce un sombrero vueltiao típico, ancho y llamativo. Está en pose de combate ligera, como apuntando o sosteniendo el arco listo para disparar, manteniendo el estilo simpático y expresivo de un chibi.. In-Game asset. 2d. High contrast. No shadows
Una maga en estilo chibi/cuerpo completo. Es una joven de unos 20 años, de cabello negro largo y suelto, con piel blanca pálida y ojeras marcadas que le dan un aire misterioso. Viste una capa morada que cubre su espalda y cae con elegancia, con guantes a juego del mismo color. Su atuendo consiste en una camiseta blanca sin mangas, pantalón negro ajustado y protecciones en los codos y rodillas para un toque práctico. En el cuello lleva un collar con un amuleto en forma de ojo dorado brillante, que resalta como símbolo mágico. Su expresión es seria y enigmática, transmitiendo el aire de una hechicera experimentada a pesar de su juventud.. In-Game asset. 2d. High contrast. No shadows
Un espadachín en estilo chibi/cuerpo completo. Es un joven de cabello negro algo desordenado, piel clara y expresión confiada, con una sonrisa desafiante. Viste un abrigo largo rojo hasta los tobillos, abierto al frente, con bordes dorados y un cinturón que ajusta a la cintura. Bajo el abrigo lleva una camiseta oscura, pantalones grises ajustados y botas rojas con detalles dorados. En su cintura lleva correas y fundas decorativas. Porta una gran espada de hoja plateada con brillo metálico y detalles en rojo, con la empuñadura dorada. Su pose transmite seguridad y carisma, como si estuviera listo para un combate pero también disfrutara de la situación.. In-Game asset. 2d. High contrast. No shadows
Una espadachina en estilo chibi/cuerpo completo. Es una mujer samurái de unos 30 años con cabello largo y marrón, atado ligeramente hacia atrás para que no estorbe en combate. Lleva una banda negra que cubre sus ojos. Su vestimenta es una túnica japonesa de batalla color azul cielo, de corte práctico y elegante, con detalles sutiles que insinúan resistencia. Viste unas botas japonesas de doble zanco (geta alta) que le dan un aire tradicional. Porta una katana brillante de hoja plateada y empuñadura simple, sostenida con firmeza en una mano, mientras la otra reposa relajada. Su expresión es calmada y disciplinada, transmitiendo seguridad y experiencia en combate.. In-Game asset. 2d. High contrast. No shadows
Efecto de Impacto sin letras. In-Game asset. 2d. High contrast. No shadows
Un destello verde blancuzco. In-Game asset. 2d. High contrast. No shadows