557 lines
15 KiB
JavaScript
557 lines
15 KiB
JavaScript
|
|
import { expect } from "@esm-bundle/chai";
|
||
|
|
import sinon from "sinon";
|
||
|
|
import * as THREE from "three";
|
||
|
|
import { GameLoop } from "../../../src/core/GameLoop.js";
|
||
|
|
import { skillRegistry } from "../../../src/managers/SkillRegistry.js";
|
||
|
|
import {
|
||
|
|
createGameLoopSetup,
|
||
|
|
cleanupGameLoop,
|
||
|
|
createRunData,
|
||
|
|
createMockGameStateManagerForCombat,
|
||
|
|
setupCombatUnits,
|
||
|
|
cleanupTurnSystem,
|
||
|
|
} from "./helpers.js";
|
||
|
|
|
||
|
|
describe("Core: GameLoop - Combat Skill Targeting and Execution", function () {
|
||
|
|
this.timeout(30000);
|
||
|
|
|
||
|
|
let gameLoop;
|
||
|
|
let container;
|
||
|
|
let mockGameStateManager;
|
||
|
|
let playerUnit;
|
||
|
|
let enemyUnit;
|
||
|
|
|
||
|
|
beforeEach(async () => {
|
||
|
|
const setup = createGameLoopSetup();
|
||
|
|
gameLoop = setup.gameLoop;
|
||
|
|
container = setup.container;
|
||
|
|
|
||
|
|
gameLoop.stop();
|
||
|
|
if (
|
||
|
|
gameLoop.turnSystem &&
|
||
|
|
typeof gameLoop.turnSystem.reset === "function"
|
||
|
|
) {
|
||
|
|
gameLoop.turnSystem.reset();
|
||
|
|
}
|
||
|
|
|
||
|
|
gameLoop.init(container);
|
||
|
|
mockGameStateManager = createMockGameStateManagerForCombat();
|
||
|
|
gameLoop.gameStateManager = mockGameStateManager;
|
||
|
|
|
||
|
|
const runData = createRunData({
|
||
|
|
squad: [{ id: "u1", classId: "CLASS_VANGUARD" }],
|
||
|
|
});
|
||
|
|
await gameLoop.startLevel(runData, { startAnimation: false });
|
||
|
|
|
||
|
|
const units = setupCombatUnits(gameLoop);
|
||
|
|
playerUnit = units.playerUnit;
|
||
|
|
enemyUnit = units.enemyUnit;
|
||
|
|
|
||
|
|
// Start combat and set player unit as active
|
||
|
|
if (gameLoop.turnSystem) {
|
||
|
|
gameLoop.turnSystem.startCombat([playerUnit, enemyUnit]);
|
||
|
|
// Manually set player unit as active for testing
|
||
|
|
gameLoop.turnSystem.activeUnitId = playerUnit.id;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
afterEach(() => {
|
||
|
|
gameLoop.clearMovementHighlights();
|
||
|
|
gameLoop.clearSpawnZoneHighlights();
|
||
|
|
cleanupTurnSystem(gameLoop);
|
||
|
|
cleanupGameLoop(gameLoop, container);
|
||
|
|
});
|
||
|
|
|
||
|
|
describe("Skill Targeting Validation", () => {
|
||
|
|
it("should only highlight valid targets when entering targeting mode", async () => {
|
||
|
|
// Add a skill to the player unit
|
||
|
|
const skillId = "SKILL_TELEPORT";
|
||
|
|
const skillDef = {
|
||
|
|
id: skillId,
|
||
|
|
name: "Phase Shift",
|
||
|
|
costs: { ap: 2 },
|
||
|
|
targeting: {
|
||
|
|
range: 5,
|
||
|
|
type: "EMPTY",
|
||
|
|
line_of_sight: false,
|
||
|
|
},
|
||
|
|
effects: [{ type: "TELEPORT" }],
|
||
|
|
};
|
||
|
|
|
||
|
|
// Register the skill in the skill registry
|
||
|
|
skillRegistry.skills.set(skillId, skillDef);
|
||
|
|
|
||
|
|
// Add skill to unit actions
|
||
|
|
if (!playerUnit.actions) {
|
||
|
|
playerUnit.actions = [];
|
||
|
|
}
|
||
|
|
playerUnit.actions.push({
|
||
|
|
id: skillId,
|
||
|
|
name: "Phase Shift",
|
||
|
|
costAP: 2,
|
||
|
|
cooldown: 0,
|
||
|
|
});
|
||
|
|
|
||
|
|
// Ensure unit has enough AP
|
||
|
|
playerUnit.currentAP = 10;
|
||
|
|
|
||
|
|
// Enter targeting mode
|
||
|
|
gameLoop.onSkillClicked(skillId);
|
||
|
|
|
||
|
|
// Verify we're in targeting mode
|
||
|
|
expect(gameLoop.combatState).to.equal("TARGETING_SKILL");
|
||
|
|
expect(gameLoop.activeSkillId).to.equal(skillId);
|
||
|
|
|
||
|
|
// Check that highlights were created (valid targets only)
|
||
|
|
if (gameLoop.voxelManager && gameLoop.voxelManager.rangeHighlights) {
|
||
|
|
const highlightCount = gameLoop.voxelManager.rangeHighlights.size;
|
||
|
|
// Should have highlights for valid empty tiles within range
|
||
|
|
expect(highlightCount).to.be.greaterThan(0);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
it("should not highlight invalid targets (out of range)", async () => {
|
||
|
|
const skillId = "SKILL_SHORT_RANGE";
|
||
|
|
const skillDef = {
|
||
|
|
id: skillId,
|
||
|
|
name: "Short Range",
|
||
|
|
costs: { ap: 1 },
|
||
|
|
targeting: {
|
||
|
|
range: 2, // Very short range
|
||
|
|
type: "ENEMY",
|
||
|
|
line_of_sight: true,
|
||
|
|
},
|
||
|
|
effects: [],
|
||
|
|
};
|
||
|
|
|
||
|
|
// Register the skill in the skill registry
|
||
|
|
skillRegistry.skills.set(skillId, skillDef);
|
||
|
|
|
||
|
|
// Add skill to unit actions
|
||
|
|
if (!playerUnit.actions) {
|
||
|
|
playerUnit.actions = [];
|
||
|
|
}
|
||
|
|
playerUnit.actions.push({
|
||
|
|
id: skillId,
|
||
|
|
name: "Short Range",
|
||
|
|
costAP: 1,
|
||
|
|
cooldown: 0,
|
||
|
|
});
|
||
|
|
|
||
|
|
// Place enemy far away
|
||
|
|
const farAwayPos = {
|
||
|
|
x: playerUnit.position.x + 10,
|
||
|
|
y: playerUnit.position.y,
|
||
|
|
z: playerUnit.position.z + 10,
|
||
|
|
};
|
||
|
|
gameLoop.grid.moveUnit(enemyUnit, farAwayPos, { force: true });
|
||
|
|
|
||
|
|
// Enter targeting mode
|
||
|
|
gameLoop.onSkillClicked(skillId);
|
||
|
|
|
||
|
|
// Verify we're in targeting mode
|
||
|
|
expect(gameLoop.combatState).to.equal("TARGETING_SKILL");
|
||
|
|
|
||
|
|
// The far away enemy should not be highlighted
|
||
|
|
// (validation happens when checking individual tiles)
|
||
|
|
expect(gameLoop.activeSkillId).to.equal(skillId);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe("Movement Button Toggle", () => {
|
||
|
|
it("should return to movement mode when movement button is clicked", () => {
|
||
|
|
// First enter skill targeting mode
|
||
|
|
const skillId = "SKILL_TELEPORT";
|
||
|
|
const skillDef = {
|
||
|
|
id: skillId,
|
||
|
|
name: "Phase Shift",
|
||
|
|
costs: { ap: 2 },
|
||
|
|
targeting: {
|
||
|
|
range: 5,
|
||
|
|
type: "EMPTY",
|
||
|
|
line_of_sight: false,
|
||
|
|
},
|
||
|
|
effects: [{ type: "TELEPORT" }],
|
||
|
|
};
|
||
|
|
|
||
|
|
if (gameLoop.skillTargetingSystem) {
|
||
|
|
const registry = gameLoop.skillTargetingSystem.skillRegistry;
|
||
|
|
if (registry instanceof Map) {
|
||
|
|
registry.set(skillId, skillDef);
|
||
|
|
} else if (registry.set) {
|
||
|
|
registry.set(skillId, skillDef);
|
||
|
|
} else {
|
||
|
|
registry[skillId] = skillDef;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!playerUnit.actions) {
|
||
|
|
playerUnit.actions = [];
|
||
|
|
}
|
||
|
|
playerUnit.actions.push({
|
||
|
|
id: skillId,
|
||
|
|
name: "Phase Shift",
|
||
|
|
costAP: 2,
|
||
|
|
cooldown: 0,
|
||
|
|
});
|
||
|
|
|
||
|
|
// Ensure unit has enough AP
|
||
|
|
playerUnit.currentAP = 10;
|
||
|
|
|
||
|
|
gameLoop.onSkillClicked(skillId);
|
||
|
|
expect(gameLoop.combatState).to.equal("TARGETING_SKILL");
|
||
|
|
|
||
|
|
// Click movement button
|
||
|
|
gameLoop.onMovementClicked();
|
||
|
|
|
||
|
|
// Should return to IDLE/movement mode
|
||
|
|
expect(gameLoop.combatState).to.equal("IDLE");
|
||
|
|
expect(gameLoop.activeSkillId).to.be.null;
|
||
|
|
});
|
||
|
|
|
||
|
|
it("should toggle skill off when clicking the same skill again", () => {
|
||
|
|
const skillId = "SKILL_TELEPORT";
|
||
|
|
const skillDef = {
|
||
|
|
id: skillId,
|
||
|
|
name: "Phase Shift",
|
||
|
|
costs: { ap: 2 },
|
||
|
|
targeting: {
|
||
|
|
range: 5,
|
||
|
|
type: "EMPTY",
|
||
|
|
line_of_sight: false,
|
||
|
|
},
|
||
|
|
effects: [{ type: "TELEPORT" }],
|
||
|
|
};
|
||
|
|
|
||
|
|
if (gameLoop.skillTargetingSystem) {
|
||
|
|
const registry = gameLoop.skillTargetingSystem.skillRegistry;
|
||
|
|
if (registry instanceof Map) {
|
||
|
|
registry.set(skillId, skillDef);
|
||
|
|
} else if (registry.set) {
|
||
|
|
registry.set(skillId, skillDef);
|
||
|
|
} else {
|
||
|
|
registry[skillId] = skillDef;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!playerUnit.actions) {
|
||
|
|
playerUnit.actions = [];
|
||
|
|
}
|
||
|
|
playerUnit.actions.push({
|
||
|
|
id: skillId,
|
||
|
|
name: "Phase Shift",
|
||
|
|
costAP: 2,
|
||
|
|
cooldown: 0,
|
||
|
|
});
|
||
|
|
|
||
|
|
// Ensure unit has enough AP
|
||
|
|
playerUnit.currentAP = 10;
|
||
|
|
|
||
|
|
// First click - enter targeting mode
|
||
|
|
gameLoop.onSkillClicked(skillId);
|
||
|
|
expect(gameLoop.combatState).to.equal("TARGETING_SKILL");
|
||
|
|
|
||
|
|
// Second click - should cancel targeting
|
||
|
|
gameLoop.onSkillClicked(skillId);
|
||
|
|
expect(gameLoop.combatState).to.equal("IDLE");
|
||
|
|
expect(gameLoop.activeSkillId).to.be.null;
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe("Hotkey Support", () => {
|
||
|
|
it("should trigger skill when number key is pressed", () => {
|
||
|
|
const skillId = "SKILL_TEST";
|
||
|
|
const skillDef = {
|
||
|
|
id: skillId,
|
||
|
|
name: "Test Skill",
|
||
|
|
costs: { ap: 2 },
|
||
|
|
targeting: {
|
||
|
|
range: 5,
|
||
|
|
type: "ENEMY",
|
||
|
|
line_of_sight: true,
|
||
|
|
},
|
||
|
|
effects: [],
|
||
|
|
};
|
||
|
|
|
||
|
|
// Register the skill in the skill registry
|
||
|
|
skillRegistry.skills.set(skillId, skillDef);
|
||
|
|
|
||
|
|
if (!playerUnit.actions) {
|
||
|
|
playerUnit.actions = [];
|
||
|
|
}
|
||
|
|
playerUnit.actions.push({
|
||
|
|
id: skillId,
|
||
|
|
name: "Test Skill",
|
||
|
|
costAP: 2,
|
||
|
|
cooldown: 0,
|
||
|
|
});
|
||
|
|
|
||
|
|
// Ensure unit has enough AP
|
||
|
|
playerUnit.currentAP = 10;
|
||
|
|
|
||
|
|
// Press number key 1 (first skill)
|
||
|
|
gameLoop.handleKeyInput("Digit1");
|
||
|
|
|
||
|
|
// Should enter targeting mode
|
||
|
|
expect(gameLoop.combatState).to.equal("TARGETING_SKILL");
|
||
|
|
expect(gameLoop.activeSkillId).to.equal(skillId);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("should trigger movement mode when M key is pressed", () => {
|
||
|
|
// First enter skill targeting mode
|
||
|
|
const skillId = "SKILL_TEST";
|
||
|
|
const skillDef = {
|
||
|
|
id: skillId,
|
||
|
|
name: "Test Skill",
|
||
|
|
costs: { ap: 2 },
|
||
|
|
targeting: {
|
||
|
|
range: 5,
|
||
|
|
type: "ENEMY",
|
||
|
|
line_of_sight: true,
|
||
|
|
},
|
||
|
|
effects: [],
|
||
|
|
};
|
||
|
|
|
||
|
|
// Register the skill in the skill registry
|
||
|
|
skillRegistry.skills.set(skillId, skillDef);
|
||
|
|
|
||
|
|
if (!playerUnit.actions) {
|
||
|
|
playerUnit.actions = [];
|
||
|
|
}
|
||
|
|
playerUnit.actions.push({
|
||
|
|
id: skillId,
|
||
|
|
name: "Test Skill",
|
||
|
|
costAP: 2,
|
||
|
|
cooldown: 0,
|
||
|
|
});
|
||
|
|
|
||
|
|
// Ensure unit has enough AP
|
||
|
|
playerUnit.currentAP = 10;
|
||
|
|
|
||
|
|
gameLoop.onSkillClicked(skillId);
|
||
|
|
expect(gameLoop.combatState).to.equal("TARGETING_SKILL");
|
||
|
|
|
||
|
|
// Press M key
|
||
|
|
gameLoop.handleKeyInput("KeyM");
|
||
|
|
|
||
|
|
// Should return to movement mode
|
||
|
|
expect(gameLoop.combatState).to.equal("IDLE");
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe("TELEPORT Effect Execution", () => {
|
||
|
|
it("should teleport unit to target position when TELEPORT effect is executed", async () => {
|
||
|
|
const skillId = "SKILL_TELEPORT";
|
||
|
|
const skillDef = {
|
||
|
|
id: skillId,
|
||
|
|
name: "Phase Shift",
|
||
|
|
costs: { ap: 2 },
|
||
|
|
targeting: {
|
||
|
|
range: 5,
|
||
|
|
type: "EMPTY",
|
||
|
|
line_of_sight: false,
|
||
|
|
},
|
||
|
|
effects: [{ type: "TELEPORT" }],
|
||
|
|
};
|
||
|
|
|
||
|
|
// Register the skill in the skill registry
|
||
|
|
skillRegistry.skills.set(skillId, skillDef);
|
||
|
|
|
||
|
|
// Add skill to unit actions
|
||
|
|
if (!playerUnit.actions) {
|
||
|
|
playerUnit.actions = [];
|
||
|
|
}
|
||
|
|
playerUnit.actions.push({
|
||
|
|
id: skillId,
|
||
|
|
name: "Phase Shift",
|
||
|
|
costAP: 2,
|
||
|
|
cooldown: 0,
|
||
|
|
});
|
||
|
|
|
||
|
|
const originalPos = { ...playerUnit.position };
|
||
|
|
const targetPos = {
|
||
|
|
x: originalPos.x + 3,
|
||
|
|
y: originalPos.y,
|
||
|
|
z: originalPos.z + 3,
|
||
|
|
};
|
||
|
|
|
||
|
|
// Ensure target position is valid and empty
|
||
|
|
if (gameLoop.grid.isOccupied(targetPos)) {
|
||
|
|
// Clear it if occupied
|
||
|
|
const unitAtPos = gameLoop.grid.getUnitAt(targetPos);
|
||
|
|
if (unitAtPos) {
|
||
|
|
gameLoop.grid.removeUnit(unitAtPos);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Ensure target position is walkable (floor at y=0, air at y=1 and y=2)
|
||
|
|
gameLoop.grid.setCell(targetPos.x, 0, targetPos.z, 1); // Floor
|
||
|
|
gameLoop.grid.setCell(targetPos.x, targetPos.y, targetPos.z, 0); // Air at y=1
|
||
|
|
gameLoop.grid.setCell(targetPos.x, targetPos.y + 1, targetPos.z, 0); // Air at y=2 (headroom)
|
||
|
|
|
||
|
|
// Execute the skill
|
||
|
|
await gameLoop.executeSkill(skillId, targetPos);
|
||
|
|
|
||
|
|
// Unit should be at target position
|
||
|
|
expect(playerUnit.position.x).to.equal(targetPos.x);
|
||
|
|
expect(playerUnit.position.z).to.equal(targetPos.z);
|
||
|
|
// Y might be adjusted to walkable level
|
||
|
|
expect(playerUnit.position.y).to.be.a("number");
|
||
|
|
|
||
|
|
// Unit mesh should be updated
|
||
|
|
const mesh = gameLoop.unitMeshes.get(playerUnit.id);
|
||
|
|
if (mesh) {
|
||
|
|
expect(mesh.position.x).to.equal(playerUnit.position.x);
|
||
|
|
expect(mesh.position.z).to.equal(playerUnit.position.z);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
it("should deduct AP when executing TELEPORT skill", async () => {
|
||
|
|
const skillId = "SKILL_TELEPORT";
|
||
|
|
const skillDef = {
|
||
|
|
id: skillId,
|
||
|
|
name: "Phase Shift",
|
||
|
|
costs: { ap: 2 },
|
||
|
|
targeting: {
|
||
|
|
range: 5,
|
||
|
|
type: "EMPTY",
|
||
|
|
line_of_sight: false,
|
||
|
|
},
|
||
|
|
effects: [{ type: "TELEPORT" }],
|
||
|
|
};
|
||
|
|
|
||
|
|
// Register the skill in the skill registry
|
||
|
|
skillRegistry.skills.set(skillId, skillDef);
|
||
|
|
|
||
|
|
if (!playerUnit.actions) {
|
||
|
|
playerUnit.actions = [];
|
||
|
|
}
|
||
|
|
playerUnit.actions.push({
|
||
|
|
id: skillId,
|
||
|
|
name: "Phase Shift",
|
||
|
|
costAP: 2,
|
||
|
|
cooldown: 0,
|
||
|
|
});
|
||
|
|
|
||
|
|
// Ensure unit has enough AP
|
||
|
|
playerUnit.currentAP = 10;
|
||
|
|
const initialAP = playerUnit.currentAP;
|
||
|
|
const targetPos = {
|
||
|
|
x: playerUnit.position.x + 2,
|
||
|
|
y: playerUnit.position.y,
|
||
|
|
z: playerUnit.position.z + 2,
|
||
|
|
};
|
||
|
|
|
||
|
|
// Ensure target is empty
|
||
|
|
if (gameLoop.grid.isOccupied(targetPos)) {
|
||
|
|
const unitAtPos = gameLoop.grid.getUnitAt(targetPos);
|
||
|
|
if (unitAtPos) {
|
||
|
|
gameLoop.grid.removeUnit(unitAtPos);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
await gameLoop.executeSkill(skillId, targetPos);
|
||
|
|
|
||
|
|
// AP should be deducted
|
||
|
|
expect(playerUnit.currentAP).to.equal(initialAP - 2);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("should set cooldown when executing skill", async () => {
|
||
|
|
const skillId = "SKILL_TELEPORT";
|
||
|
|
const skillDef = {
|
||
|
|
id: skillId,
|
||
|
|
name: "Phase Shift",
|
||
|
|
costs: { ap: 2 },
|
||
|
|
cooldown_turns: 4,
|
||
|
|
targeting: {
|
||
|
|
range: 5,
|
||
|
|
type: "EMPTY",
|
||
|
|
line_of_sight: false,
|
||
|
|
},
|
||
|
|
effects: [{ type: "TELEPORT" }],
|
||
|
|
};
|
||
|
|
|
||
|
|
// Register the skill in the skill registry
|
||
|
|
skillRegistry.skills.set(skillId, skillDef);
|
||
|
|
|
||
|
|
if (!playerUnit.actions) {
|
||
|
|
playerUnit.actions = [];
|
||
|
|
}
|
||
|
|
const skillAction = {
|
||
|
|
id: skillId,
|
||
|
|
name: "Phase Shift",
|
||
|
|
costAP: 2,
|
||
|
|
cooldown: 0,
|
||
|
|
};
|
||
|
|
playerUnit.actions.push(skillAction);
|
||
|
|
|
||
|
|
// Ensure unit has enough AP
|
||
|
|
playerUnit.currentAP = 10;
|
||
|
|
|
||
|
|
const targetPos = {
|
||
|
|
x: playerUnit.position.x + 2,
|
||
|
|
y: playerUnit.position.y,
|
||
|
|
z: playerUnit.position.z + 2,
|
||
|
|
};
|
||
|
|
|
||
|
|
// Ensure target is empty
|
||
|
|
if (gameLoop.grid.isOccupied(targetPos)) {
|
||
|
|
const unitAtPos = gameLoop.grid.getUnitAt(targetPos);
|
||
|
|
if (unitAtPos) {
|
||
|
|
gameLoop.grid.removeUnit(unitAtPos);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
await gameLoop.executeSkill(skillId, targetPos);
|
||
|
|
|
||
|
|
// Cooldown should be set
|
||
|
|
expect(skillAction.cooldown).to.equal(1);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe("Skill Targeting State Management", () => {
|
||
|
|
it("should clear highlights when ending turn with skill active", () => {
|
||
|
|
const skillId = "SKILL_TELEPORT";
|
||
|
|
const skillDef = {
|
||
|
|
id: skillId,
|
||
|
|
name: "Phase Shift",
|
||
|
|
costs: { ap: 2 },
|
||
|
|
targeting: {
|
||
|
|
range: 5,
|
||
|
|
type: "EMPTY",
|
||
|
|
line_of_sight: false,
|
||
|
|
},
|
||
|
|
effects: [{ type: "TELEPORT" }],
|
||
|
|
};
|
||
|
|
|
||
|
|
// Register the skill in the skill registry
|
||
|
|
skillRegistry.skills.set(skillId, skillDef);
|
||
|
|
|
||
|
|
if (!playerUnit.actions) {
|
||
|
|
playerUnit.actions = [];
|
||
|
|
}
|
||
|
|
playerUnit.actions.push({
|
||
|
|
id: skillId,
|
||
|
|
name: "Phase Shift",
|
||
|
|
costAP: 2,
|
||
|
|
cooldown: 0,
|
||
|
|
});
|
||
|
|
|
||
|
|
// Ensure unit has enough AP
|
||
|
|
playerUnit.currentAP = 10;
|
||
|
|
|
||
|
|
// Enter targeting mode
|
||
|
|
gameLoop.onSkillClicked(skillId);
|
||
|
|
expect(gameLoop.combatState).to.equal("TARGETING_SKILL");
|
||
|
|
|
||
|
|
// End turn
|
||
|
|
gameLoop.endTurn();
|
||
|
|
|
||
|
|
// Should clear targeting state
|
||
|
|
expect(gameLoop.combatState).to.equal("IDLE");
|
||
|
|
expect(gameLoop.activeSkillId).to.be.null;
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|