Refactored targetting system
This commit is contained in:
parent
8be89b1825
commit
d3f4cffde8
11
TODO.md
11
TODO.md
|
@ -36,26 +36,26 @@ Battle
|
|||
------
|
||||
|
||||
* Add a voluntary retreat option
|
||||
* Add scroll buttons when there are too many actions
|
||||
* Remove dead ships from ship list and play order
|
||||
* Add quick animation of playing ship indicator, on ship change
|
||||
* Toggle bar/text display in power section of action bar
|
||||
* Fix ship's active effect radius pushing the tooltip far from the ship
|
||||
* Display a hint when a move-fire simulation failed (cannot enter exclusion area for example)
|
||||
* Display effects description instead of attribute changes
|
||||
* Display radius and power usage hints for area effects on action icon hover + add confirmation?
|
||||
* End the battle as soon as victory or defeat condition is detected (do not wait for the turn to end)
|
||||
* Show a cooldown indicator on move action icon, if the simulation would cause the engine to overheat
|
||||
* Mark action icons unavailable next turn, if if will overheat
|
||||
* Any displayed info should be based on a ship copy stored in ArenaShip, and in sync with current log index (not the game state ship)
|
||||
* Add engine trail effect, and sound
|
||||
* Fix targetting not resetting on current cursor location when using keyboard shortcuts
|
||||
* Allow to skip animations, and allow no animation mode
|
||||
* Find incentives to move from starting position (permanent drones or anomalies?)
|
||||
* Add a "loot all" button (on the character sheet or outcome dialog?)
|
||||
* Do not focus on ship while targetting for area effects (dissociate hover and target)
|
||||
* Mark targetting in error when target is refused by the action (there is already an arrow for this)
|
||||
* Repair drone has its activation effect sometimes displayed as permanent effect on ships in the radius
|
||||
* Merge identical sticky effects
|
||||
* Allow to undo last moves
|
||||
* Add a battle log display
|
||||
* Allow to move targetting indicator with arrow keys
|
||||
* Trigger targetting mode for all actions (even for damage protector or shield transfer)
|
||||
* Add targetting shortcuts for "previous target", "next enemy" and "next ally"
|
||||
|
||||
Ships models and equipments
|
||||
|
@ -96,6 +96,7 @@ Common UI
|
|||
Technical
|
||||
---------
|
||||
|
||||
* Run jasmine tests in random order by default, and fix problems
|
||||
* Pack all images in atlases
|
||||
* Pack sounds
|
||||
|
||||
|
|
BIN
graphics/exported/battle/hud/simulator-failed.png
Normal file
BIN
graphics/exported/battle/hud/simulator-failed.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.6 KiB |
Binary file not shown.
|
@ -80,11 +80,11 @@ module TS.SpaceTac.Specs {
|
|||
stats.processLog(battle.log, battle.fleets[0]);
|
||||
expect(stats.stats).toEqual({});
|
||||
|
||||
battle.log.add(new ActionAppliedEvent(attacker, new BaseAction("nop", "nop", false), null, 4));
|
||||
battle.log.add(new ActionAppliedEvent(attacker, new BaseAction("nop", "nop"), null, 4));
|
||||
stats.processLog(battle.log, battle.fleets[0], true);
|
||||
expect(stats.stats).toEqual({ "Power used": [4, 0] });
|
||||
|
||||
battle.log.add(new ActionAppliedEvent(defender, new BaseAction("nop", "nop", false), null, 2));
|
||||
battle.log.add(new ActionAppliedEvent(defender, new BaseAction("nop", "nop"), null, 2));
|
||||
stats.processLog(battle.log, battle.fleets[0], true);
|
||||
expect(stats.stats).toEqual({ "Power used": [4, 2] });
|
||||
})
|
||||
|
|
|
@ -43,7 +43,7 @@ module TS.SpaceTac {
|
|||
effects: BaseEffect[] = []
|
||||
|
||||
// Action available when equipped
|
||||
action = new BaseAction("nothing", "Do nothing", false)
|
||||
action: BaseAction | null = null
|
||||
|
||||
// Equipment wear due to usage in battles (will lower the sell price)
|
||||
wear = 0
|
||||
|
@ -158,9 +158,11 @@ module TS.SpaceTac {
|
|||
parts.push(["When equipped:"].concat(this.effects.map(effect => "• " + effect.getDescription())).join("\n"));
|
||||
}
|
||||
|
||||
let action_desc = this.action.getEffectsDescription();
|
||||
if (action_desc != "") {
|
||||
parts.push(action_desc);
|
||||
if (this.action) {
|
||||
let action_desc = this.action.getEffectsDescription();
|
||||
if (action_desc != "") {
|
||||
parts.push(action_desc);
|
||||
}
|
||||
}
|
||||
|
||||
return parts.length > 0 ? parts.join("\n\n") : "does nothing";
|
||||
|
|
|
@ -150,8 +150,9 @@ module TS.SpaceTac.Specs {
|
|||
|
||||
it("does nothing if trying to move in the same spot", function () {
|
||||
let [ship, simulator, action] = simpleWeaponCase();
|
||||
let result = simulator.simulateAction(ship.listEquipment(SlotType.Engine)[0].action, new Target(ship.arena_x, ship.arena_y, null));
|
||||
expect(result.success).toBe(true);
|
||||
let move_action = nn(ship.listEquipment(SlotType.Engine)[0].action)
|
||||
let result = simulator.simulateAction(move_action, new Target(ship.arena_x, ship.arena_y, null));
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.need_move).toBe(false);
|
||||
expect(result.need_fire).toBe(false);
|
||||
expect(result.parts).toEqual([]);
|
||||
|
|
|
@ -155,11 +155,14 @@ module TS.SpaceTac {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (move_target && arenaDistance(move_target, this.ship.location) < 0.000001) {
|
||||
result.need_move = false;
|
||||
}
|
||||
|
||||
// Check move AP
|
||||
if (result.need_move && move_target) {
|
||||
let engine = this.findBestEngine();
|
||||
if (engine) {
|
||||
if (engine && engine.action) {
|
||||
result.total_move_ap = engine.action.getActionPointsUsage(this.ship, move_target);
|
||||
result.can_move = ap > 0;
|
||||
result.can_end_move = result.total_move_ap <= ap;
|
||||
|
@ -172,15 +175,17 @@ module TS.SpaceTac {
|
|||
}
|
||||
|
||||
// Check action AP
|
||||
if (!(action instanceof MoveAction)) {
|
||||
if (action instanceof MoveAction) {
|
||||
result.success = result.need_move && result.can_move;
|
||||
} else {
|
||||
result.need_fire = true;
|
||||
result.total_fire_ap = action.getActionPointsUsage(this.ship, target);
|
||||
result.can_fire = result.total_fire_ap <= ap;
|
||||
result.fire_location = target;
|
||||
result.parts.push({ action: action, target: target, ap: result.total_fire_ap, possible: (!result.need_move || result.can_end_move) && result.can_fire });
|
||||
result.success = true;
|
||||
}
|
||||
|
||||
result.success = true;
|
||||
result.complete = (!result.need_move || result.can_end_move) && (!result.need_fire || result.can_fire);
|
||||
|
||||
return result;
|
||||
|
|
|
@ -241,7 +241,7 @@ module TS.SpaceTac.Specs {
|
|||
ship.startTurn();
|
||||
expect(action.activated).toBe(false);
|
||||
|
||||
let result = action.apply(ship, null);
|
||||
let result = action.apply(ship);
|
||||
expect(result).toBe(true, "Could not be applied");
|
||||
expect(action.activated).toBe(true);
|
||||
|
||||
|
@ -252,11 +252,11 @@ module TS.SpaceTac.Specs {
|
|||
expect(action.activated).toBe(false);
|
||||
|
||||
expect(battle.log.events).toEqual([
|
||||
new ActionAppliedEvent(ship, action, null, 0),
|
||||
new ActionAppliedEvent(ship, action, Target.newFromShip(ship), 0),
|
||||
new ToggleEvent(ship, action, true),
|
||||
new ActiveEffectsEvent(ship, [], [], [new AttributeEffect("power_capacity", 1)]),
|
||||
new ValueChangeEvent(ship, new ShipAttribute("power capacity", 1), 1),
|
||||
new ActionAppliedEvent(ship, action, null, 0),
|
||||
new ActionAppliedEvent(ship, action, Target.newFromShip(ship), 0),
|
||||
new ToggleEvent(ship, action, false),
|
||||
new ActiveEffectsEvent(ship, [], [], []),
|
||||
new ValueChangeEvent(ship, new ShipAttribute("power capacity", 0), -1),
|
||||
|
@ -274,7 +274,7 @@ module TS.SpaceTac.Specs {
|
|||
let shield = ship1.addSlot(SlotType.Shield).attach(new Equipment(SlotType.Shield));
|
||||
shield.action = new ToggleAction(shield, 0, 15, [new AttributeEffect("shield_capacity", 5)]);
|
||||
battle.playing_ship = ship1;
|
||||
shield.action.apply(ship1, null);
|
||||
shield.action.apply(ship1);
|
||||
|
||||
expect(ship1.getAttribute("shield_capacity")).toBe(5);
|
||||
expect(ship2.getAttribute("shield_capacity")).toBe(5);
|
||||
|
|
|
@ -137,7 +137,7 @@ module TS.SpaceTac {
|
|||
let slots = [SlotType.Engine, SlotType.Power, SlotType.Hull, SlotType.Shield, SlotType.Weapon];
|
||||
slots.forEach(slot => {
|
||||
this.listEquipment(slot).forEach(equipment => {
|
||||
if (equipment.action.code != "nothing") {
|
||||
if (equipment.action) {
|
||||
actions.push(equipment.action)
|
||||
}
|
||||
});
|
||||
|
@ -338,7 +338,7 @@ module TS.SpaceTac {
|
|||
// Reset toggle actions state
|
||||
this.listEquipment().forEach(equipment => {
|
||||
if (equipment.action instanceof ToggleAction && equipment.action.activated) {
|
||||
equipment.action.apply(this, null);
|
||||
equipment.action.apply(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ module TS.SpaceTac {
|
|||
describe("BaseAction", function () {
|
||||
it("check if equipment can be used with remaining AP", function () {
|
||||
var equipment = new Equipment(SlotType.Hull);
|
||||
var action = new BaseAction("test", "Test", false, equipment);
|
||||
var action = new BaseAction("test", "Test", equipment);
|
||||
spyOn(action, "getActionPointsUsage").and.returnValue(3);
|
||||
var ship = new Ship();
|
||||
ship.addSlot(SlotType.Hull).attach(equipment);
|
||||
|
@ -28,7 +28,7 @@ module TS.SpaceTac {
|
|||
|
||||
it("check if equipment can be used with overheat", function () {
|
||||
let equipment = new Equipment();
|
||||
let action = new BaseAction("test", "Test", false, equipment);
|
||||
let action = new BaseAction("test", "Test", equipment);
|
||||
let ship = new Ship();
|
||||
|
||||
expect(action.checkCannotBeApplied(ship)).toBe(null);
|
||||
|
@ -71,11 +71,13 @@ module TS.SpaceTac {
|
|||
TestTools.setShipAP(ship, 10);
|
||||
let power = ship.listEquipment(SlotType.Power)[0];
|
||||
let equipment = new Equipment(SlotType.Weapon);
|
||||
let action = new BaseAction("test", "Test", false, equipment);
|
||||
let action = new BaseAction("test", "Test", equipment);
|
||||
|
||||
spyOn(action, "checkTarget").and.callFake((ship: Ship, target: Target) => target);
|
||||
|
||||
expect(power.wear).toBe(0);
|
||||
expect(equipment.wear).toBe(0);
|
||||
action.apply(ship, null);
|
||||
action.apply(ship);
|
||||
|
||||
expect(power.wear).toBe(1);
|
||||
expect(equipment.wear).toBe(1);
|
||||
|
|
|
@ -1,4 +1,20 @@
|
|||
module TS.SpaceTac {
|
||||
/**
|
||||
* Targetting mode for an action.
|
||||
*
|
||||
* This is a hint as to what type of target is required for this action.
|
||||
*/
|
||||
export enum ActionTargettingMode {
|
||||
// Apply immediately on the ship owning the action, without confirmation
|
||||
SELF,
|
||||
// Apply on the ship owning the action, with a confirmation
|
||||
SELF_CONFIRM,
|
||||
// Apply on one selected ship
|
||||
SHIP,
|
||||
// Apply on a space area
|
||||
SPACE
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for a battle action.
|
||||
*
|
||||
|
@ -11,40 +27,56 @@ module TS.SpaceTac {
|
|||
// Human-readable name
|
||||
name: string
|
||||
|
||||
// Boolean at true if the action needs a target
|
||||
needs_target: boolean
|
||||
|
||||
// Equipment that triggers this action
|
||||
equipment: Equipment | null
|
||||
|
||||
// Create the action
|
||||
constructor(code: string, name: string, needs_target: boolean, equipment: Equipment | null = null) {
|
||||
constructor(code: string, name: string, equipment: Equipment | null = null) {
|
||||
this.code = code;
|
||||
this.name = name;
|
||||
this.needs_target = needs_target;
|
||||
this.equipment = equipment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the relevent cooldown for this action
|
||||
*/
|
||||
get cooldown(): Cooldown {
|
||||
return this.equipment ? this.equipment.cooldown : new Cooldown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the targetting mode
|
||||
*/
|
||||
getTargettingMode(ship: Ship): ActionTargettingMode {
|
||||
if (this.getBlastRadius(ship)) {
|
||||
return ActionTargettingMode.SPACE;
|
||||
} else if (this.getRangeRadius(ship)) {
|
||||
return ActionTargettingMode.SHIP;
|
||||
} else {
|
||||
return ActionTargettingMode.SELF_CONFIRM;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a default target for this action
|
||||
*/
|
||||
getDefaultTarget(ship: Ship): Target {
|
||||
return Target.newFromShip(ship);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of turns this action is unavailable, because of overheating
|
||||
*/
|
||||
getCooldownDuration(estimated = false): number {
|
||||
if (this.equipment) {
|
||||
return estimated ? this.equipment.cooldown.cooling : this.equipment.cooldown.heat;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
let cooldown = this.cooldown;
|
||||
return estimated ? this.cooldown.cooling : this.cooldown.heat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of remaining uses before overheat, infinity if there is no overheat
|
||||
*/
|
||||
getUsesBeforeOverheat(): number {
|
||||
if (this.equipment) {
|
||||
return this.equipment.cooldown.getRemainingUses();
|
||||
} else {
|
||||
return Infinity;
|
||||
}
|
||||
return this.cooldown.getRemainingUses();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -71,7 +103,7 @@ module TS.SpaceTac {
|
|||
}
|
||||
|
||||
// Check cooldown
|
||||
if (this.equipment && !this.equipment.cooldown.canUse()) {
|
||||
if (!this.cooldown.canUse()) {
|
||||
return "overheated";
|
||||
}
|
||||
|
||||
|
@ -93,40 +125,43 @@ module TS.SpaceTac {
|
|||
return 0;
|
||||
}
|
||||
|
||||
// Method to check if a target is applicable for this action
|
||||
// Will call checkLocationTarget or checkShipTarget by default
|
||||
checkTarget(ship: Ship, target: Target | null): Target | null {
|
||||
/**
|
||||
* Check if a target is suitable for this action
|
||||
*
|
||||
* Will call checkLocationTarget or checkShipTarget by default
|
||||
*/
|
||||
checkTarget(ship: Ship, target: Target): Target | null {
|
||||
if (this.checkCannotBeApplied(ship)) {
|
||||
return null;
|
||||
} else if (target) {
|
||||
} else {
|
||||
if (target.ship) {
|
||||
return this.checkShipTarget(ship, target);
|
||||
} else {
|
||||
return this.checkLocationTarget(ship, target);
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Method to reimplement to check if a space target is applicable
|
||||
// Method to reimplement to check if a space target is suitable
|
||||
// Must return null if the target can't be applied, an altered target, or the original target
|
||||
checkLocationTarget(ship: Ship, target: Target): Target | null {
|
||||
protected checkLocationTarget(ship: Ship, target: Target): Target | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Method to reimplement to check if a ship target is applicable
|
||||
// Method to reimplement to check if a ship target is suitable
|
||||
// Must return null if the target can't be applied, an altered target, or the original target
|
||||
checkShipTarget(ship: Ship, target: Target): Target | null {
|
||||
protected checkShipTarget(ship: Ship, target: Target): Target | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Apply an action, returning true if it was successful
|
||||
apply(ship: Ship, target: Target | null): boolean {
|
||||
/**
|
||||
* Apply an action, returning true if it was successful
|
||||
*/
|
||||
apply(ship: Ship, target = this.getDefaultTarget(ship)): boolean {
|
||||
let reject = this.checkCannotBeApplied(ship);
|
||||
if (reject == null) {
|
||||
let checked_target = this.checkTarget(ship, target);
|
||||
if (!checked_target && this.needs_target) {
|
||||
if (!checked_target) {
|
||||
console.warn("Action rejected - invalid target", ship, this, target);
|
||||
return false;
|
||||
}
|
||||
|
@ -140,10 +175,10 @@ module TS.SpaceTac {
|
|||
if (this.equipment) {
|
||||
this.equipment.addWear(1);
|
||||
ship.listEquipment(SlotType.Power).forEach(equipment => equipment.addWear(1));
|
||||
|
||||
this.equipment.cooldown.use();
|
||||
}
|
||||
|
||||
this.cooldown.use();
|
||||
|
||||
let battle = ship.getBattle();
|
||||
if (battle) {
|
||||
battle.log.add(new ActionAppliedEvent(ship, this, checked_target, cost));
|
||||
|
@ -157,8 +192,10 @@ module TS.SpaceTac {
|
|||
}
|
||||
}
|
||||
|
||||
// Method to reimplement to apply a action
|
||||
protected customApply(ship: Ship, target: Target | null) {
|
||||
/**
|
||||
* Method to reimplement to apply the action
|
||||
*/
|
||||
protected customApply(ship: Ship, target: Target): void {
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -9,7 +9,6 @@ module TS.SpaceTac {
|
|||
expect(action.code).toEqual("deploy-testdrone");
|
||||
expect(action.name).toEqual("Deploy");
|
||||
expect(action.equipment).toBe(equipment);
|
||||
expect(action.needs_target).toBe(true);
|
||||
});
|
||||
|
||||
it("allows to deploy in range", function () {
|
||||
|
|
|
@ -24,7 +24,7 @@ module TS.SpaceTac {
|
|||
equipment: Equipment;
|
||||
|
||||
constructor(equipment: Equipment, power = 1, deploy_distance = 0, lifetime = 0, effect_radius = 0, effects: BaseEffect[] = []) {
|
||||
super("deploy-" + equipment.code, "Deploy", true, equipment);
|
||||
super("deploy-" + equipment.code, "Deploy", equipment);
|
||||
|
||||
this.power = power;
|
||||
this.deploy_distance = deploy_distance;
|
||||
|
|
|
@ -3,25 +3,26 @@ module TS.SpaceTac.Specs {
|
|||
it("can't be applied to non-playing ship", () => {
|
||||
spyOn(console, "warn").and.stub();
|
||||
|
||||
var battle = Battle.newQuickRandom();
|
||||
var action = new EndTurnAction();
|
||||
let battle = Battle.newQuickRandom();
|
||||
let action = new EndTurnAction();
|
||||
|
||||
expect(action.checkCannotBeApplied(battle.play_order[0])).toBe(null);
|
||||
expect(action.checkCannotBeApplied(battle.play_order[1])).toBe("ship not playing");
|
||||
|
||||
var result = action.apply(battle.play_order[1], null);
|
||||
let ship = battle.play_order[1];
|
||||
let result = action.apply(battle.play_order[1]);
|
||||
expect(result).toBe(false);
|
||||
|
||||
expect(console.warn).toHaveBeenCalledWith("Action rejected - ship not playing", battle.play_order[1], action, null);
|
||||
expect(console.warn).toHaveBeenCalledWith("Action rejected - ship not playing", ship, action, Target.newFromShip(ship));
|
||||
});
|
||||
|
||||
it("ends turn when applied", () => {
|
||||
var battle = Battle.newQuickRandom();
|
||||
var action = new EndTurnAction();
|
||||
let battle = Battle.newQuickRandom();
|
||||
let action = new EndTurnAction();
|
||||
|
||||
expect(battle.playing_ship_index).toBe(0);
|
||||
|
||||
var result = action.apply(battle.play_order[0], null);
|
||||
let result = action.apply(battle.play_order[0], Target.newFromShip(battle.play_order[0]));
|
||||
expect(result).toBe(true);
|
||||
expect(battle.playing_ship_index).toBe(1);
|
||||
});
|
||||
|
|
|
@ -2,18 +2,28 @@ module TS.SpaceTac {
|
|||
// Action to end the ship's turn
|
||||
export class EndTurnAction extends BaseAction {
|
||||
constructor() {
|
||||
super("endturn", "End ship's turn", false);
|
||||
super("endturn", "End ship's turn");
|
||||
}
|
||||
|
||||
protected customApply(ship: Ship, target: Target) {
|
||||
ship.endTurn();
|
||||
if (target.ship == ship) {
|
||||
ship.endTurn();
|
||||
|
||||
let battle = ship.getBattle();
|
||||
if (battle) {
|
||||
battle.advanceToNextShip();
|
||||
let battle = ship.getBattle();
|
||||
if (battle) {
|
||||
battle.advanceToNextShip();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected checkShipTarget(ship: Ship, target: Target): Target | null {
|
||||
return target.ship == ship ? target : null;
|
||||
}
|
||||
|
||||
getTargettingMode(ship: Ship): ActionTargettingMode {
|
||||
return ship.getValue("power") ? ActionTargettingMode.SELF_CONFIRM : ActionTargettingMode.SELF;
|
||||
}
|
||||
|
||||
getEffectsDescription(): string {
|
||||
return "End the current ship's turn.\nWill also generate power and cool down equipments.";
|
||||
}
|
||||
|
|
|
@ -9,10 +9,6 @@ module TS.SpaceTac {
|
|||
expect(action.code).toEqual("fire-testweapon");
|
||||
expect(action.name).toEqual("Fire");
|
||||
expect(action.equipment).toBe(equipment);
|
||||
expect(action.needs_target).toBe(true);
|
||||
|
||||
action = new FireWeaponAction(equipment, 4, 0, 10);
|
||||
expect(action.needs_target).toBe(false);
|
||||
});
|
||||
|
||||
it("applies effects to alive ships in blast radius", function () {
|
||||
|
@ -49,35 +45,37 @@ module TS.SpaceTac {
|
|||
let ship2 = new Ship();
|
||||
ship2.setArenaPosition(150, 10);
|
||||
let weapon = TestTools.addWeapon(ship1, 1, 0, 100, 30);
|
||||
let action = nn(weapon.action);
|
||||
|
||||
let target = weapon.action.checkTarget(ship1, new Target(150, 10));
|
||||
let target = action.checkTarget(ship1, new Target(150, 10));
|
||||
expect(target).toEqual(new Target(150, 10));
|
||||
|
||||
target = weapon.action.checkTarget(ship1, Target.newFromShip(ship2));
|
||||
target = action.checkTarget(ship1, Target.newFromShip(ship2));
|
||||
expect(target).toEqual(new Target(150, 10));
|
||||
|
||||
ship1.setArenaPosition(30, 10);
|
||||
|
||||
target = weapon.action.checkTarget(ship1, Target.newFromShip(ship2));
|
||||
target = action.checkTarget(ship1, Target.newFromShip(ship2));
|
||||
expect(target).toEqual(new Target(130, 10));
|
||||
|
||||
ship1.setArenaPosition(0, 10);
|
||||
|
||||
target = weapon.action.checkTarget(ship1, Target.newFromShip(ship2));
|
||||
target = action.checkTarget(ship1, Target.newFromShip(ship2));
|
||||
expect(target).toEqual(new Target(100, 10));
|
||||
});
|
||||
|
||||
it("rotates toward the target", function () {
|
||||
let ship = new Ship();
|
||||
let weapon = TestTools.addWeapon(ship, 1, 0, 100, 30);
|
||||
let action = nn(weapon.action);
|
||||
spyOn(action, "checkTarget").and.callFake((ship: Ship, target: Target) => target);
|
||||
expect(ship.arena_angle).toEqual(0);
|
||||
|
||||
let result = weapon.action.apply(ship, Target.newFromLocation(10, 20));
|
||||
let result = action.apply(ship, Target.newFromLocation(10, 20));
|
||||
expect(result).toBe(true);
|
||||
expect(ship.arena_angle).toBeCloseTo(1.107, 0.001);
|
||||
|
||||
weapon.action.needs_target = false;
|
||||
result = weapon.action.apply(ship, null);
|
||||
result = action.apply(ship, Target.newFromShip(ship));
|
||||
expect(result).toBe(true);
|
||||
expect(ship.arena_angle).toBeCloseTo(1.107, 0.001);
|
||||
});
|
||||
|
|
|
@ -6,22 +6,22 @@ module TS.SpaceTac {
|
|||
*/
|
||||
export class FireWeaponAction extends BaseAction {
|
||||
// Power consumption
|
||||
power: number;
|
||||
power: number
|
||||
|
||||
// Maximal range of the weapon
|
||||
range: number
|
||||
|
||||
// Blast radius
|
||||
blast: number;
|
||||
blast: number
|
||||
|
||||
// Effects applied on hit
|
||||
effects: BaseEffect[];
|
||||
// Effects applied on target
|
||||
effects: BaseEffect[]
|
||||
|
||||
// Equipment cannot be null
|
||||
equipment: Equipment;
|
||||
equipment: Equipment
|
||||
|
||||
constructor(equipment: Equipment, power = 1, range = 0, blast = 0, effects: BaseEffect[] = [], name = "Fire") {
|
||||
super("fire-" + equipment.code, name, range > 0, equipment);
|
||||
super("fire-" + equipment.code, name, equipment);
|
||||
|
||||
this.power = power;
|
||||
this.range = range;
|
||||
|
@ -29,6 +29,23 @@ module TS.SpaceTac {
|
|||
this.blast = blast;
|
||||
}
|
||||
|
||||
getDefaultTarget(ship: Ship): Target {
|
||||
if (this.range == 0) {
|
||||
return Target.newFromShip(ship);
|
||||
} else {
|
||||
let battle = ship.getBattle();
|
||||
if (battle) {
|
||||
let harmful = any(this.effects, effect => !effect.isBeneficial());
|
||||
let player = ship.getPlayer();
|
||||
let ships = imaterialize(harmful ? battle.ienemies(player, true) : ifilter(battle.iallies(player, true), iship => iship != ship));
|
||||
let nearest = minBy(ships, iship => arenaDistance(ship.location, iship.location));
|
||||
return Target.newFromShip(nearest);
|
||||
} else {
|
||||
return Target.newFromShip(ship);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getActionPointsUsage(ship: Ship, target: Target | null): number {
|
||||
return this.power;
|
||||
}
|
||||
|
@ -51,8 +68,8 @@ module TS.SpaceTac {
|
|||
}
|
||||
|
||||
checkShipTarget(ship: Ship, target: Target): Target | null {
|
||||
if (target.ship && ship.getPlayer() === target.ship.getPlayer()) {
|
||||
// No friendly fire
|
||||
if (this.range > 0 && ship == target.ship) {
|
||||
// No self fire
|
||||
return null;
|
||||
} else {
|
||||
// Check if target is in range
|
||||
|
@ -80,13 +97,10 @@ module TS.SpaceTac {
|
|||
return result;
|
||||
}
|
||||
|
||||
protected customApply(ship: Ship, target: Target | null) {
|
||||
if (!target) {
|
||||
// Self-target
|
||||
target = Target.newFromShip(ship);
|
||||
} else {
|
||||
protected customApply(ship: Ship, target: Target) {
|
||||
if (arenaDistance(ship.location, target) > 0.000001) {
|
||||
// Face the target
|
||||
ship.rotate(Target.newFromShip(ship).getAngleTo(target), first(ship.listEquipment(SlotType.Engine), () => true));
|
||||
ship.rotate(arenaAngle(ship.location, target), first(ship.listEquipment(SlotType.Engine), () => true));
|
||||
}
|
||||
|
||||
// Fire event
|
||||
|
|
|
@ -14,13 +14,21 @@ module TS.SpaceTac {
|
|||
maneuvrability_factor: number
|
||||
|
||||
constructor(equipment: Equipment, distance_per_power = 0, safety_distance = 120, maneuvrability_factor = 0) {
|
||||
super("move", "Move", true, equipment);
|
||||
super("move", "Move", equipment);
|
||||
|
||||
this.distance_per_power = distance_per_power;
|
||||
this.safety_distance = safety_distance;
|
||||
this.maneuvrability_factor = maneuvrability_factor;
|
||||
}
|
||||
|
||||
getTargettingMode(ship: Ship): ActionTargettingMode {
|
||||
return ActionTargettingMode.SPACE;
|
||||
}
|
||||
|
||||
getDefaultTarget(ship: Ship): Target {
|
||||
return Target.newFromLocation(ship.arena_x + Math.cos(ship.arena_angle) * 100, ship.arena_y + Math.sin(ship.arena_angle) * 100);
|
||||
}
|
||||
|
||||
checkCannotBeApplied(ship: Ship, remaining_ap: number | null = null): string | null {
|
||||
let base = super.checkCannotBeApplied(ship, Infinity);
|
||||
if (base) {
|
||||
|
@ -38,13 +46,13 @@ module TS.SpaceTac {
|
|||
}
|
||||
}
|
||||
|
||||
getActionPointsUsage(ship: Ship, target: Target): number {
|
||||
if (target === null) {
|
||||
getActionPointsUsage(ship: Ship, target: Target | null): number {
|
||||
if (target) {
|
||||
let distance = Target.newFromShip(ship).getDistanceTo(target);
|
||||
return Math.ceil(distance / this.getDistanceByActionPoint(ship));
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
||||
var distance = Target.newFromShip(ship).getDistanceTo(target);
|
||||
return Math.ceil(distance / this.getDistanceByActionPoint(ship));
|
||||
}
|
||||
|
||||
getRangeRadius(ship: Ship): number {
|
||||
|
|
|
@ -21,7 +21,7 @@ module TS.SpaceTac {
|
|||
activated = false
|
||||
|
||||
constructor(equipment: Equipment, power = 1, radius = 0, effects: BaseEffect[] = [], name = "(De)activate") {
|
||||
super("toggle-" + equipment.code, name, false, equipment);
|
||||
super("toggle-" + equipment.code, name, equipment);
|
||||
|
||||
this.power = power;
|
||||
this.radius = radius;
|
||||
|
@ -40,6 +40,10 @@ module TS.SpaceTac {
|
|||
return this.radius;
|
||||
}
|
||||
|
||||
checkShipTarget(ship: Ship, target: Target): Target | null {
|
||||
return (ship == target.ship) ? target : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of ships in range to be affected
|
||||
*/
|
||||
|
|
|
@ -63,7 +63,7 @@ module TS.SpaceTac {
|
|||
}
|
||||
|
||||
// End the ship turn
|
||||
this.applyAction(new EndTurnAction(), null);
|
||||
this.applyAction(new EndTurnAction(), Target.newFromShip(ship));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -71,7 +71,7 @@ module TS.SpaceTac {
|
|||
*
|
||||
* This should be the only real interaction point with battle state
|
||||
*/
|
||||
private applyAction(action: BaseAction, target: Target | null): boolean {
|
||||
private applyAction(action: BaseAction, target: Target): boolean {
|
||||
return action.apply(this.ship, target);
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ module TS.SpaceTac.Specs {
|
|||
TestTools.setShipHP(ship2, 30, 30);
|
||||
ship3.setArenaPosition(0, 15);
|
||||
TestTools.setShipHP(ship3, 30, 30);
|
||||
let maneuver = new Maneuver(ship1, weapon.action, Target.newFromLocation(0, 0));
|
||||
let maneuver = new Maneuver(ship1, nn(weapon.action), Target.newFromLocation(0, 0));
|
||||
expect(maneuver.effects).toEqual([
|
||||
[ship1, new DamageEffect(50)],
|
||||
[ship2, new DamageEffect(50)]
|
||||
|
@ -43,6 +43,7 @@ module TS.SpaceTac.Specs {
|
|||
let battle = new Battle();
|
||||
let ship = battle.fleets[0].addShip();
|
||||
let engine = TestTools.addEngine(ship, 500);
|
||||
let move = nn(engine.action);
|
||||
TestTools.setShipAP(ship, 10);
|
||||
let drone = new Drone(ship);
|
||||
drone.effects = [new AttributeEffect("maneuvrability", 1)];
|
||||
|
@ -51,11 +52,11 @@ module TS.SpaceTac.Specs {
|
|||
drone.radius = 50;
|
||||
battle.addDrone(drone);
|
||||
|
||||
let maneuver = new Maneuver(ship, engine.action, Target.newFromLocation(40, 30));
|
||||
let maneuver = new Maneuver(ship, move, Target.newFromLocation(40, 30));
|
||||
expect(maneuver.getFinalLocation()).toEqual(jasmine.objectContaining({ x: 40, y: 30 }));
|
||||
expect(maneuver.effects).toEqual([]);
|
||||
|
||||
maneuver = new Maneuver(ship, engine.action, Target.newFromLocation(100, 30));
|
||||
maneuver = new Maneuver(ship, move, Target.newFromLocation(100, 30));
|
||||
expect(maneuver.getFinalLocation()).toEqual(jasmine.objectContaining({ x: 100, y: 30 }));
|
||||
expect(maneuver.effects).toEqual([[ship, new AttributeEffect("maneuvrability", 1)]]);
|
||||
});
|
||||
|
|
|
@ -7,7 +7,7 @@ module TS.SpaceTac.Specs {
|
|||
constructor(score: number) {
|
||||
let battle = new Battle();
|
||||
let ship = battle.fleets[0].addShip();
|
||||
super(ship, new BaseAction("nothing", "Do nothing", true), new Target(0, 0));
|
||||
super(ship, new BaseAction("nothing", "Do nothing"), new Target(0, 0));
|
||||
this.score = score;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,10 +17,10 @@ module TS.SpaceTac.Specs {
|
|||
let weapon2 = TestTools.addWeapon(ship0a, 15);
|
||||
result = imaterialize(TacticalAIHelpers.produceDirectShots(ship0a, battle));
|
||||
expect(result.length).toBe(4);
|
||||
expect(result).toContain(new Maneuver(ship0a, weapon1.action, Target.newFromShip(ship1a)));
|
||||
expect(result).toContain(new Maneuver(ship0a, weapon1.action, Target.newFromShip(ship1b)));
|
||||
expect(result).toContain(new Maneuver(ship0a, weapon2.action, Target.newFromShip(ship1a)));
|
||||
expect(result).toContain(new Maneuver(ship0a, weapon2.action, Target.newFromShip(ship1b)));
|
||||
expect(result).toContain(new Maneuver(ship0a, nn(weapon1.action), Target.newFromShip(ship1a)));
|
||||
expect(result).toContain(new Maneuver(ship0a, nn(weapon1.action), Target.newFromShip(ship1b)));
|
||||
expect(result).toContain(new Maneuver(ship0a, nn(weapon2.action), Target.newFromShip(ship1a)));
|
||||
expect(result).toContain(new Maneuver(ship0a, nn(weapon2.action), Target.newFromShip(ship1b)));
|
||||
});
|
||||
|
||||
it("produces random moves inside a grid", function () {
|
||||
|
@ -39,10 +39,10 @@ module TS.SpaceTac.Specs {
|
|||
|
||||
result = imaterialize(TacticalAIHelpers.produceRandomMoves(ship, battle, 2, 1, new SkewedRandomGenerator([0.5], true)));
|
||||
expect(result).toEqual([
|
||||
new Maneuver(ship, engine.action, Target.newFromLocation(25, 25)),
|
||||
new Maneuver(ship, engine.action, Target.newFromLocation(75, 25)),
|
||||
new Maneuver(ship, engine.action, Target.newFromLocation(25, 75)),
|
||||
new Maneuver(ship, engine.action, Target.newFromLocation(75, 75)),
|
||||
new Maneuver(ship, nn(engine.action), Target.newFromLocation(25, 25)),
|
||||
new Maneuver(ship, nn(engine.action), Target.newFromLocation(75, 25)),
|
||||
new Maneuver(ship, nn(engine.action), Target.newFromLocation(25, 75)),
|
||||
new Maneuver(ship, nn(engine.action), Target.newFromLocation(75, 75)),
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -68,8 +68,8 @@ module TS.SpaceTac.Specs {
|
|||
|
||||
result = imaterialize(TacticalAIHelpers.produceInterestingBlastShots(ship, battle));
|
||||
expect(result).toEqual([
|
||||
new Maneuver(ship, weapon.action, Target.newFromLocation(600, 0)),
|
||||
new Maneuver(ship, weapon.action, Target.newFromLocation(600, 0)),
|
||||
new Maneuver(ship, nn(weapon.action), Target.newFromLocation(600, 0)),
|
||||
new Maneuver(ship, nn(weapon.action), Target.newFromLocation(600, 0)),
|
||||
]);
|
||||
|
||||
let enemy3 = battle.fleets[1].addShip();
|
||||
|
@ -77,8 +77,8 @@ module TS.SpaceTac.Specs {
|
|||
|
||||
result = imaterialize(TacticalAIHelpers.produceInterestingBlastShots(ship, battle));
|
||||
expect(result).toEqual([
|
||||
new Maneuver(ship, weapon.action, Target.newFromLocation(600, 0)),
|
||||
new Maneuver(ship, weapon.action, Target.newFromLocation(600, 0)),
|
||||
new Maneuver(ship, nn(weapon.action), Target.newFromLocation(600, 0)),
|
||||
new Maneuver(ship, nn(weapon.action), Target.newFromLocation(600, 0)),
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -86,29 +86,30 @@ module TS.SpaceTac.Specs {
|
|||
let battle = new Battle();
|
||||
let ship = battle.fleets[0].addShip();
|
||||
let weapon = TestTools.addWeapon(ship, 50, 5, 100);
|
||||
let action = nn(weapon.action);
|
||||
let engine = TestTools.addEngine(ship, 25);
|
||||
|
||||
let maneuver = new Maneuver(ship, new BaseAction("fake", "Nothing", false), new Target(0, 0), 0);
|
||||
let maneuver = new Maneuver(ship, new BaseAction("fake", "Nothing"), new Target(0, 0), 0);
|
||||
expect(TacticalAIHelpers.evaluateTurnCost(ship, battle, maneuver)).toBe(-1);
|
||||
|
||||
maneuver = new Maneuver(ship, weapon.action, Target.newFromLocation(100, 0), 0);
|
||||
maneuver = new Maneuver(ship, action, Target.newFromLocation(100, 0), 0);
|
||||
expect(TacticalAIHelpers.evaluateTurnCost(ship, battle, maneuver)).toBe(-Infinity);
|
||||
|
||||
TestTools.setShipAP(ship, 4);
|
||||
maneuver = new Maneuver(ship, weapon.action, Target.newFromLocation(100, 0), 0);
|
||||
maneuver = new Maneuver(ship, action, Target.newFromLocation(100, 0), 0);
|
||||
expect(TacticalAIHelpers.evaluateTurnCost(ship, battle, maneuver)).toBe(-Infinity);
|
||||
|
||||
TestTools.setShipAP(ship, 10);
|
||||
maneuver = new Maneuver(ship, weapon.action, Target.newFromLocation(100, 0), 0);
|
||||
maneuver = new Maneuver(ship, action, Target.newFromLocation(100, 0), 0);
|
||||
expect(TacticalAIHelpers.evaluateTurnCost(ship, battle, maneuver)).toBe(0.5); // 5 power remaining on 10
|
||||
|
||||
maneuver = new Maneuver(ship, weapon.action, Target.newFromLocation(110, 0), 0);
|
||||
maneuver = new Maneuver(ship, action, Target.newFromLocation(110, 0), 0);
|
||||
expect(TacticalAIHelpers.evaluateTurnCost(ship, battle, maneuver)).toBe(0.4); // 4 power remaining on 10
|
||||
|
||||
maneuver = new Maneuver(ship, weapon.action, Target.newFromLocation(140, 0), 0);
|
||||
maneuver = new Maneuver(ship, action, Target.newFromLocation(140, 0), 0);
|
||||
expect(TacticalAIHelpers.evaluateTurnCost(ship, battle, maneuver)).toBe(0.3); // 3 power remaining on 10
|
||||
|
||||
maneuver = new Maneuver(ship, weapon.action, Target.newFromLocation(310, 0), 0);
|
||||
maneuver = new Maneuver(ship, action, Target.newFromLocation(310, 0), 0);
|
||||
expect(TacticalAIHelpers.evaluateTurnCost(ship, battle, maneuver)).toBe(-1); // can't do in one turn
|
||||
});
|
||||
|
||||
|
@ -119,18 +120,18 @@ module TS.SpaceTac.Specs {
|
|||
let engine = TestTools.addEngine(ship, 50);
|
||||
let weapon = TestTools.addWeapon(ship, 10, 2, 100, 10);
|
||||
|
||||
let maneuver = new Maneuver(ship, weapon.action, Target.newFromLocation(0, 0));
|
||||
let maneuver = new Maneuver(ship, nn(weapon.action), Target.newFromLocation(0, 0));
|
||||
expect(TacticalAIHelpers.evaluateIdling(ship, battle, maneuver)).toEqual(-0.3);
|
||||
|
||||
maneuver = new Maneuver(ship, engine.action, Target.newFromLocation(0, 0));
|
||||
maneuver = new Maneuver(ship, nn(engine.action), Target.newFromLocation(0, 0));
|
||||
expect(TacticalAIHelpers.evaluateIdling(ship, battle, maneuver)).toEqual(-0.5);
|
||||
|
||||
ship.setValue("power", 2);
|
||||
|
||||
maneuver = new Maneuver(ship, weapon.action, Target.newFromLocation(0, 0));
|
||||
maneuver = new Maneuver(ship, nn(weapon.action), Target.newFromLocation(0, 0));
|
||||
expect(TacticalAIHelpers.evaluateIdling(ship, battle, maneuver)).toEqual(0.5);
|
||||
|
||||
maneuver = new Maneuver(ship, engine.action, Target.newFromLocation(0, 0));
|
||||
maneuver = new Maneuver(ship, nn(engine.action), Target.newFromLocation(0, 0));
|
||||
expect(TacticalAIHelpers.evaluateIdling(ship, battle, maneuver)).toEqual(0);
|
||||
});
|
||||
|
||||
|
@ -138,6 +139,7 @@ module TS.SpaceTac.Specs {
|
|||
let battle = new Battle();
|
||||
let ship = battle.fleets[0].addShip();
|
||||
let weapon = TestTools.addWeapon(ship, 50, 5, 500, 100);
|
||||
let action = nn(weapon.action);
|
||||
|
||||
let enemy1 = battle.fleets[1].addShip();
|
||||
enemy1.setArenaPosition(250, 0);
|
||||
|
@ -147,15 +149,15 @@ module TS.SpaceTac.Specs {
|
|||
TestTools.setShipHP(enemy2, 25, 0);
|
||||
|
||||
// no enemies hurt
|
||||
let maneuver = new Maneuver(ship, weapon.action, Target.newFromLocation(100, 0));
|
||||
let maneuver = new Maneuver(ship, action, Target.newFromLocation(100, 0));
|
||||
expect(TacticalAIHelpers.evaluateEnemyHealth(ship, battle, maneuver)).toBeCloseTo(0, 8);
|
||||
|
||||
// one enemy loses half-life
|
||||
maneuver = new Maneuver(ship, weapon.action, Target.newFromLocation(180, 0));
|
||||
maneuver = new Maneuver(ship, action, Target.newFromLocation(180, 0));
|
||||
expect(TacticalAIHelpers.evaluateEnemyHealth(ship, battle, maneuver)).toBeCloseTo(0.1666666666, 8);
|
||||
|
||||
// one enemy loses half-life, the other one is dead
|
||||
maneuver = new Maneuver(ship, weapon.action, Target.newFromLocation(280, 0));
|
||||
maneuver = new Maneuver(ship, action, Target.newFromLocation(280, 0));
|
||||
expect(TacticalAIHelpers.evaluateEnemyHealth(ship, battle, maneuver)).toBeCloseTo(0.6666666666, 8);
|
||||
});
|
||||
|
||||
|
@ -166,7 +168,7 @@ module TS.SpaceTac.Specs {
|
|||
TestTools.setShipAP(ship, 10);
|
||||
let weapon = TestTools.addWeapon(ship, 100, 1, 100, 10);
|
||||
|
||||
let maneuver = new Maneuver(ship, weapon.action, Target.newFromLocation(200, 0), 0.5);
|
||||
let maneuver = new Maneuver(ship, nn(weapon.action), Target.newFromLocation(200, 0), 0.5);
|
||||
expect(maneuver.simulation.move_location.x).toBeCloseTo(100.5, 1);
|
||||
expect(maneuver.simulation.move_location.y).toBe(0);
|
||||
expect(TacticalAIHelpers.evaluateClustering(ship, battle, maneuver)).toEqual(0);
|
||||
|
@ -188,21 +190,22 @@ module TS.SpaceTac.Specs {
|
|||
let battle = new Battle(undefined, undefined, 200, 100);
|
||||
let ship = battle.fleets[0].addShip();
|
||||
let weapon = TestTools.addWeapon(ship, 1, 1, 400);
|
||||
let action = nn(weapon.action);
|
||||
|
||||
ship.setArenaPosition(0, 0);
|
||||
let maneuver = new Maneuver(ship, weapon.action, new Target(0, 0), 0);
|
||||
let maneuver = new Maneuver(ship, action, new Target(0, 0), 0);
|
||||
expect(TacticalAIHelpers.evaluatePosition(ship, battle, maneuver)).toEqual(-1);
|
||||
|
||||
ship.setArenaPosition(100, 0);
|
||||
maneuver = new Maneuver(ship, weapon.action, new Target(0, 0), 0);
|
||||
maneuver = new Maneuver(ship, action, new Target(0, 0), 0);
|
||||
expect(TacticalAIHelpers.evaluatePosition(ship, battle, maneuver)).toEqual(-1);
|
||||
|
||||
ship.setArenaPosition(100, 10);
|
||||
maneuver = new Maneuver(ship, weapon.action, new Target(0, 0), 0);
|
||||
maneuver = new Maneuver(ship, action, new Target(0, 0), 0);
|
||||
expect(TacticalAIHelpers.evaluatePosition(ship, battle, maneuver)).toEqual(-0.6);
|
||||
|
||||
ship.setArenaPosition(100, 50);
|
||||
maneuver = new Maneuver(ship, weapon.action, new Target(0, 0), 0);
|
||||
maneuver = new Maneuver(ship, action, new Target(0, 0), 0);
|
||||
expect(TacticalAIHelpers.evaluatePosition(ship, battle, maneuver)).toEqual(1);
|
||||
});
|
||||
|
||||
|
@ -211,7 +214,7 @@ module TS.SpaceTac.Specs {
|
|||
let ship = battle.fleets[0].addShip();
|
||||
let weapon = TestTools.addWeapon(ship, 1, 1, 400);
|
||||
|
||||
let maneuver = new Maneuver(ship, weapon.action, new Target(0, 0));
|
||||
let maneuver = new Maneuver(ship, nn(weapon.action), new Target(0, 0));
|
||||
expect(TacticalAIHelpers.evaluateOverheat(ship, battle, maneuver)).toEqual(0);
|
||||
|
||||
weapon.cooldown.configure(1, 1);
|
||||
|
|
|
@ -33,6 +33,7 @@ module TS.SpaceTac.Equipments {
|
|||
let ship1 = battle.fleets[0].addShip();
|
||||
ship1.upgradeSkill("skill_time", 3);
|
||||
let protector = ship1.addSlot(SlotType.Weapon).attach(new DamageProtector().generate(1));
|
||||
let action = nn(protector.action);
|
||||
TestTools.setShipAP(ship1, 10);
|
||||
let ship2 = battle.fleets[0].addShip();
|
||||
let ship3 = battle.fleets[0].addShip();
|
||||
|
@ -41,10 +42,7 @@ module TS.SpaceTac.Equipments {
|
|||
ship3.setArenaPosition(800, 0);
|
||||
battle.playing_ship = ship1;
|
||||
ship1.playing = true;
|
||||
expect(ship1.getAvailableActions()).toEqual([
|
||||
protector.action,
|
||||
new EndTurnAction()
|
||||
]);
|
||||
expect(ship1.getAvailableActions()).toEqual([action, new EndTurnAction()]);
|
||||
|
||||
TestTools.setShipHP(ship1, 100, 0);
|
||||
TestTools.setShipHP(ship2, 100, 0);
|
||||
|
@ -57,7 +55,7 @@ module TS.SpaceTac.Equipments {
|
|||
expect(ship2.getValue("hull")).toEqual(90);
|
||||
expect(ship3.getValue("hull")).toEqual(90);
|
||||
|
||||
let result = protector.action.apply(ship1, null);
|
||||
let result = action.apply(ship1);
|
||||
expect(result).toBe(true);
|
||||
expect((<ToggleAction>protector.action).activated).toBe(true);
|
||||
|
||||
|
@ -68,7 +66,7 @@ module TS.SpaceTac.Equipments {
|
|||
expect(ship2.getValue("hull")).toEqual(82);
|
||||
expect(ship3.getValue("hull")).toEqual(80);
|
||||
|
||||
result = protector.action.apply(ship1, null);
|
||||
result = action.apply(ship1);
|
||||
expect(result).toBe(true);
|
||||
expect((<ToggleAction>protector.action).activated).toBe(false);
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ module TS.SpaceTac.Equipments {
|
|||
expect(target.sticky_effects).toEqual([]);
|
||||
|
||||
// Attribute is immediately limited
|
||||
equipment.action.apply(ship, Target.newFromShip(target));
|
||||
nn(equipment.action).apply(ship, Target.newFromShip(target));
|
||||
|
||||
expect(target.values.power.get()).toBe(3);
|
||||
expect(target.sticky_effects).toEqual([
|
||||
|
|
|
@ -41,7 +41,7 @@ module TS.SpaceTac.Equipments {
|
|||
battle.playing_ship = ship;
|
||||
battle.play_order = [ship];
|
||||
TestTools.setShipAP(ship, 10);
|
||||
let result = equipment.action.apply(ship, new Target(5, 5, null));
|
||||
let result = nn(equipment.action).apply(ship, new Target(5, 5, null));
|
||||
expect(result).toBe(true);
|
||||
|
||||
expect(battle.drones.length).toBe(1);
|
||||
|
|
|
@ -44,7 +44,7 @@ module TS.SpaceTac.Equipments {
|
|||
|
||||
var template = new Equipments.SubMunitionMissile();
|
||||
var equipment = template.generate(1);
|
||||
let action = <FireWeaponAction>equipment.action;
|
||||
let action = <FireWeaponAction>nn(equipment.action);
|
||||
action.range = 5;
|
||||
action.blast = 1.5;
|
||||
(<DamageEffect>action.effects[0]).base = 20;
|
||||
|
@ -65,11 +65,11 @@ module TS.SpaceTac.Equipments {
|
|||
|
||||
// Fire at a ship
|
||||
var target = Target.newFromShip(enemy1);
|
||||
expect(equipment.action.checkCannotBeApplied(ship)).toBe(null);
|
||||
equipment.action.apply(ship, target);
|
||||
expect(action.checkCannotBeApplied(ship)).toBe(null);
|
||||
action.apply(ship, target);
|
||||
checkHP(50, 10, 50, 10, 50, 10);
|
||||
expect(battle.log.events.length).toBe(5);
|
||||
expect(battle.log.events[0]).toEqual(new ActionAppliedEvent(ship, equipment.action, Target.newFromLocation(1, 0), 4));
|
||||
expect(battle.log.events[0]).toEqual(new ActionAppliedEvent(ship, action, Target.newFromLocation(1, 0), 4));
|
||||
expect(battle.log.events[1]).toEqual(new FireEvent(ship, equipment, Target.newFromLocation(1, 0)));
|
||||
expect(battle.log.events[2]).toEqual(new DamageEvent(ship, 0, 20));
|
||||
expect(battle.log.events[3]).toEqual(new DamageEvent(enemy1, 0, 20));
|
||||
|
@ -80,11 +80,11 @@ module TS.SpaceTac.Equipments {
|
|||
|
||||
// Fire in space
|
||||
target = Target.newFromLocation(2.4, 0);
|
||||
expect(equipment.action.checkCannotBeApplied(ship)).toBe(null);
|
||||
equipment.action.apply(ship, target);
|
||||
expect(action.checkCannotBeApplied(ship)).toBe(null);
|
||||
action.apply(ship, target);
|
||||
checkHP(50, 10, 40, 0, 40, 0);
|
||||
expect(battle.log.events.length).toBe(4);
|
||||
expect(battle.log.events[0]).toEqual(new ActionAppliedEvent(ship, equipment.action, target, 4));
|
||||
expect(battle.log.events[0]).toEqual(new ActionAppliedEvent(ship, action, target, 4));
|
||||
expect(battle.log.events[1]).toEqual(new FireEvent(ship, equipment, target));
|
||||
expect(battle.log.events[2]).toEqual(new DamageEvent(enemy1, 10, 10));
|
||||
expect(battle.log.events[3]).toEqual(new DamageEvent(enemy2, 10, 10));
|
||||
|
@ -94,11 +94,11 @@ module TS.SpaceTac.Equipments {
|
|||
|
||||
// Fire far away
|
||||
target = Target.newFromLocation(5, 0);
|
||||
expect(equipment.action.checkCannotBeApplied(ship)).toBe(null);
|
||||
equipment.action.apply(ship, target);
|
||||
expect(action.checkCannotBeApplied(ship)).toBe(null);
|
||||
action.apply(ship, target);
|
||||
checkHP(50, 10, 40, 0, 40, 0);
|
||||
expect(battle.log.events.length).toBe(2);
|
||||
expect(battle.log.events[0]).toEqual(new ActionAppliedEvent(ship, equipment.action, target, 4));
|
||||
expect(battle.log.events[0]).toEqual(new ActionAppliedEvent(ship, action, target, 4));
|
||||
expect(battle.log.events[1]).toEqual(new FireEvent(ship, equipment, target));
|
||||
});
|
||||
});
|
||||
|
|
|
@ -87,10 +87,14 @@ module TS.SpaceTac.UI {
|
|||
|
||||
// Initialize
|
||||
this.updateActiveStatus(true);
|
||||
this.updateCooldownStatus();
|
||||
this.updateCooldownStatus(0);
|
||||
}
|
||||
|
||||
// Process a click event on the action icon
|
||||
/**
|
||||
* Process a click event on the action icon
|
||||
*
|
||||
* This will enter the action's targetting mode, waiting for a target or confirmation to apply the action
|
||||
*/
|
||||
processClick(): void {
|
||||
if (!this.bar.interactive) {
|
||||
return;
|
||||
|
@ -110,28 +114,29 @@ module TS.SpaceTac.UI {
|
|||
this.bar.actionEnded();
|
||||
this.bar.actionStarted();
|
||||
|
||||
// Update range hint
|
||||
if (this.battleview.arena.range_hint && this.action instanceof MoveAction) {
|
||||
this.battleview.arena.range_hint.update(this.ship, this.action);
|
||||
}
|
||||
|
||||
// Set the selected state
|
||||
this.setSelected(true);
|
||||
|
||||
if (this.action.needs_target) {
|
||||
let mode = this.action.getTargettingMode(this.ship);
|
||||
if (mode == ActionTargettingMode.SELF || mode == ActionTargettingMode.SELF_CONFIRM) {
|
||||
// Apply immediately on the ship
|
||||
// TODO Handle confirm
|
||||
this.processSelection(Target.newFromShip(this.ship));
|
||||
} else {
|
||||
let sprite = this.battleview.arena.findShipSprite(this.ship);
|
||||
if (sprite) {
|
||||
// Switch to targetting mode (will apply action when a target is selected)
|
||||
this.targetting = this.battleview.enterTargettingMode(this.action);
|
||||
this.targetting = this.battleview.enterTargettingMode(this.action, mode);
|
||||
}
|
||||
} else {
|
||||
// No target needed, apply action immediately
|
||||
this.processSelection(null);
|
||||
}
|
||||
}
|
||||
|
||||
// Called when a target is selected
|
||||
processSelection(target: Target | null): void {
|
||||
/**
|
||||
* Called when a target is selected
|
||||
*
|
||||
* This will effectively apply the action
|
||||
*/
|
||||
processSelection(target: Target): void {
|
||||
if (this.action.apply(this.ship, target)) {
|
||||
this.bar.actionEnded();
|
||||
}
|
||||
|
@ -157,7 +162,7 @@ module TS.SpaceTac.UI {
|
|||
}
|
||||
|
||||
// Update the cooldown status
|
||||
updateCooldownStatus(): void {
|
||||
updateCooldownStatus(animate = 300): void {
|
||||
let remaining = this.action.getUsesBeforeOverheat();
|
||||
if (this.selected && remaining == 1) {
|
||||
// will overheat, hint at the cooldown time
|
||||
|
@ -165,21 +170,21 @@ module TS.SpaceTac.UI {
|
|||
this.battleview.changeImage(this.cooldown, "battle-actionbar-icon-cooldown");
|
||||
this.cooldown.scale.set(0.7);
|
||||
this.cooldown_count.text = `${cooldown}`;
|
||||
this.battleview.animations.setVisible(this.cooldown, true, 300);
|
||||
this.battleview.animations.setVisible(this.cooldown, true, animate);
|
||||
} else if (remaining == 0) {
|
||||
// overheated, show cooldown time
|
||||
let cooldown = this.action.getCooldownDuration(false);
|
||||
this.battleview.changeImage(this.cooldown, "battle-actionbar-icon-cooldown");
|
||||
this.cooldown.scale.set(1);
|
||||
this.cooldown_count.text = `${cooldown}`;
|
||||
this.battleview.animations.setVisible(this.cooldown, true, 300);
|
||||
this.battleview.animations.setVisible(this.cooldown, true, animate);
|
||||
} else if (this.action instanceof ToggleAction && this.action.activated) {
|
||||
this.battleview.changeImage(this.cooldown, "battle-actionbar-icon-toggled");
|
||||
this.cooldown.scale.set(1);
|
||||
this.cooldown_count.text = "";
|
||||
this.battleview.animations.setVisible(this.cooldown, true, 300);
|
||||
this.battleview.animations.setVisible(this.cooldown, true, animate);
|
||||
} else {
|
||||
this.battleview.animations.setVisible(this.cooldown, false, 300);
|
||||
this.battleview.animations.setVisible(this.cooldown, false, animate);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,9 +4,9 @@ module TS.SpaceTac.UI {
|
|||
*
|
||||
* This is the area in the BattleView that will display ships with their real positions
|
||||
*/
|
||||
export class Arena extends Phaser.Group {
|
||||
export class Arena {
|
||||
// Link to battleview
|
||||
battleview: BattleView
|
||||
view: BattleView
|
||||
|
||||
// Boundaries of the arena
|
||||
boundaries: IBounded = { x: 112, y: 132, width: 1808, height: 948 }
|
||||
|
@ -32,6 +32,7 @@ module TS.SpaceTac.UI {
|
|||
private playing: ArenaShip | null
|
||||
|
||||
// Layer for particles
|
||||
container: Phaser.Group
|
||||
layer_garbage: Phaser.Group
|
||||
layer_hints: Phaser.Group
|
||||
layer_drones: Phaser.Group
|
||||
|
@ -39,79 +40,101 @@ module TS.SpaceTac.UI {
|
|||
layer_weapon_effects: Phaser.Group
|
||||
layer_targetting: Phaser.Group
|
||||
|
||||
// Create a graphical arena for ship sprites to fight in a 2D space
|
||||
constructor(battleview: BattleView) {
|
||||
super(battleview.game);
|
||||
// Callbacks to receive cursor events
|
||||
callbacks_hover: ((location: ArenaLocation | null, ship: Ship | null) => void)[] = []
|
||||
callbacks_click: (() => void)[] = []
|
||||
|
||||
this.battleview = battleview;
|
||||
// Create a graphical arena for ship sprites to fight in a 2D space
|
||||
constructor(view: BattleView, container?: Phaser.Group) {
|
||||
this.view = view;
|
||||
this.playing = null;
|
||||
this.hovered = null;
|
||||
this.range_hint = new RangeHint(this);
|
||||
|
||||
this.position.set(this.boundaries.x, this.boundaries.y);
|
||||
this.container = container || new Phaser.Group(view.game, undefined, "arena");
|
||||
this.container.position.set(this.boundaries.x, this.boundaries.y);
|
||||
|
||||
this.init();
|
||||
this.setupMouseCapture();
|
||||
|
||||
this.layer_garbage = this.container.add(new Phaser.Group(view.game, undefined, "garbage"));
|
||||
this.layer_hints = this.container.add(new Phaser.Group(view.game, undefined, "hints"));
|
||||
this.layer_drones = this.container.add(new Phaser.Group(view.game, undefined, "drones"));
|
||||
this.layer_ships = this.container.add(new Phaser.Group(view.game, undefined, "ships"));
|
||||
this.layer_weapon_effects = this.container.add(new Phaser.Group(view.game, undefined, "effects"));
|
||||
this.layer_targetting = this.container.add(new Phaser.Group(view.game, undefined, "targetting"));
|
||||
|
||||
this.range_hint.setLayer(this.layer_hints);
|
||||
this.addShipSprites();
|
||||
|
||||
this.container.onDestroy.add(() => this.destroy());
|
||||
}
|
||||
|
||||
/**
|
||||
* Move to a specific layer
|
||||
*/
|
||||
moveToLayer(layer: Phaser.Group): void {
|
||||
layer.add(this.container);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the mouse capture for targetting events
|
||||
*/
|
||||
setupMouseCapture() {
|
||||
let battleview = this.battleview;
|
||||
let view = this.view;
|
||||
|
||||
var background = new Phaser.Button(battleview.game, 0, 0, "battle-arena-background");
|
||||
var background = new Phaser.Button(view.game, 0, 0, "battle-arena-background");
|
||||
background.name = "mouse-capture";
|
||||
background.scale.set(this.boundaries.width / background.width, this.boundaries.height / background.height);
|
||||
this.mouse_capture = background;
|
||||
|
||||
// Capture clicks on background
|
||||
background.onInputUp.add(() => {
|
||||
battleview.cursorClicked();
|
||||
this.callbacks_click.forEach(callback => callback());
|
||||
});
|
||||
background.onInputOut.add(() => {
|
||||
battleview.targetting.setTarget(null);
|
||||
this.callbacks_hover.forEach(callback => callback(null, null));
|
||||
});
|
||||
|
||||
// Watch mouse move to capture hovering over background
|
||||
this.input_callback = this.game.input.addMoveCallback((pointer: Phaser.Pointer) => {
|
||||
this.input_callback = this.view.input.addMoveCallback((pointer: Phaser.Pointer) => {
|
||||
var point = new Phaser.Point();
|
||||
if (battleview.game.input.hitTest(background, pointer, point)) {
|
||||
battleview.cursorInSpace(point.x * background.scale.x, point.y * background.scale.y);
|
||||
if (view.input.hitTest(background, pointer, point)) {
|
||||
let location = new ArenaLocation(point.x * background.scale.x, point.y * background.scale.y);
|
||||
let ship = this.getShip(location);
|
||||
this.callbacks_hover.forEach(callback => callback(location, ship));
|
||||
}
|
||||
}, null);
|
||||
|
||||
this.add(this.mouse_capture);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.input_callback) {
|
||||
this.game.input.deleteMoveCallback(this.input_callback);
|
||||
this.input_callback = null;
|
||||
}
|
||||
super.destroy();
|
||||
this.container.add(this.mouse_capture);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize state (create sprites)
|
||||
* Get the ship under a cursor location
|
||||
*/
|
||||
init(): void {
|
||||
this.setupMouseCapture();
|
||||
getShip(location: ArenaLocation): Ship | null {
|
||||
let nearest = minBy(this.ship_sprites, sprite => arenaDistance(location, sprite.ship.location));
|
||||
if (nearest && arenaDistance(location, nearest) < 50) {
|
||||
return nearest.ship;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
this.layer_garbage = this.add(new Phaser.Group(this.game));
|
||||
this.layer_hints = this.add(new Phaser.Group(this.game));
|
||||
this.layer_drones = this.add(new Phaser.Group(this.game));
|
||||
this.layer_ships = this.add(new Phaser.Group(this.game));
|
||||
this.layer_weapon_effects = this.add(new Phaser.Group(this.game));
|
||||
this.layer_targetting = this.add(new Phaser.Group(this.game));
|
||||
|
||||
this.range_hint.setLayer(this.layer_hints);
|
||||
this.addShipSprites();
|
||||
/**
|
||||
* Call when the arena is destroyed to properly remove input handlers
|
||||
*/
|
||||
destroy() {
|
||||
if (this.input_callback) {
|
||||
this.view.input.deleteMoveCallback(this.input_callback);
|
||||
this.input_callback = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the sprites for all ships
|
||||
*/
|
||||
addShipSprites() {
|
||||
iforeach(this.battleview.battle.iships(), ship => {
|
||||
iforeach(this.view.battle.iships(), ship => {
|
||||
let sprite = new ArenaShip(this, ship);
|
||||
this.layer_ships.add(sprite);
|
||||
this.ship_sprites.push(sprite);
|
||||
|
@ -129,16 +152,18 @@ module TS.SpaceTac.UI {
|
|||
return base.filter(ship => arenaInside(ship, area, border_inclusive));
|
||||
}
|
||||
|
||||
// Get the current MainUI instance
|
||||
getGame(): MainUI {
|
||||
return this.battleview.gameui;
|
||||
/**
|
||||
* Get the current MainUI instance
|
||||
*/
|
||||
get game(): MainUI {
|
||||
return this.view.gameui;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current battle displayed
|
||||
*/
|
||||
getBattle(): Battle {
|
||||
return this.battleview.battle;
|
||||
return this.view.battle;
|
||||
}
|
||||
|
||||
// Remove a ship sprite
|
||||
|
@ -210,7 +235,7 @@ module TS.SpaceTac.UI {
|
|||
*/
|
||||
addDrone(drone: Drone, animate = true): number {
|
||||
if (!this.findDrone(drone)) {
|
||||
let sprite = new ArenaDrone(this.battleview, drone);
|
||||
let sprite = new ArenaDrone(this.view, drone);
|
||||
let angle = Math.atan2(drone.y - drone.owner.arena_y, drone.x - drone.owner.arena_x);
|
||||
this.layer_drones.add(sprite);
|
||||
this.drone_sprites.push(sprite);
|
||||
|
@ -219,7 +244,7 @@ module TS.SpaceTac.UI {
|
|||
sprite.position.set(drone.owner.arena_x, drone.owner.arena_y);
|
||||
sprite.sprite.rotation = drone.owner.arena_angle;
|
||||
let move_duration = Animations.moveInSpace(sprite, drone.x, drone.y, angle, sprite.sprite);
|
||||
this.game.tweens.create(sprite.radius).from({ alpha: 0 }, 500, Phaser.Easing.Cubic.In, true, move_duration);
|
||||
this.view.tweens.create(sprite.radius).from({ alpha: 0 }, 500, Phaser.Easing.Cubic.In, true, move_duration);
|
||||
|
||||
return move_duration + 500;
|
||||
} else {
|
||||
|
@ -254,9 +279,9 @@ module TS.SpaceTac.UI {
|
|||
setTacticalMode(active: boolean): void {
|
||||
this.ship_sprites.forEach(sprite => sprite.setHovered(active, true));
|
||||
this.drone_sprites.forEach(drone => drone.setTacticalMode(active));
|
||||
this.battleview.animations.setVisible(this.layer_garbage, !active, 200);
|
||||
if (this.battleview.background) {
|
||||
this.battleview.animations.setVisible(this.battleview.background, !active, 200);
|
||||
this.view.animations.setVisible(this.layer_garbage, !active, 200);
|
||||
if (this.view.background) {
|
||||
this.view.animations.setVisible(this.view.background, !active, 200);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ module TS.SpaceTac.UI {
|
|||
enemy: boolean
|
||||
|
||||
// Ship sprite
|
||||
sprite: Phaser.Button
|
||||
sprite: Phaser.Image
|
||||
|
||||
// Statis effect
|
||||
stasis: Phaser.Image
|
||||
|
@ -44,7 +44,7 @@ module TS.SpaceTac.UI {
|
|||
constructor(parent: Arena, ship: Ship) {
|
||||
super(parent.game);
|
||||
this.arena = parent;
|
||||
this.battleview = parent.battleview;
|
||||
this.battleview = parent.view;
|
||||
|
||||
this.ship = ship;
|
||||
this.enemy = this.ship.getPlayer() != this.battleview.player;
|
||||
|
@ -60,8 +60,7 @@ module TS.SpaceTac.UI {
|
|||
this.setPlaying(false);
|
||||
|
||||
// Add ship sprite
|
||||
let info = this.battleview.getImageInfo(`ship-${ship.model.code}-sprite`);
|
||||
this.sprite = new Phaser.Button(this.game, 0, 0, info.key, undefined, undefined, info.frame, info.frame);
|
||||
this.sprite = this.battleview.newImage(`ship-${ship.model.code}-sprite`)
|
||||
this.sprite.rotation = ship.arena_angle;
|
||||
this.sprite.anchor.set(0.5, 0.5);
|
||||
this.sprite.scale.set(0.4);
|
||||
|
@ -121,13 +120,6 @@ module TS.SpaceTac.UI {
|
|||
this.updateActiveEffects();
|
||||
this.updateEffectsRadius();
|
||||
|
||||
// Handle input on ship sprite
|
||||
UITools.setHoverClick(this.sprite,
|
||||
() => this.battleview.cursorOnShip(ship),
|
||||
() => this.battleview.cursorOffShip(ship),
|
||||
() => this.battleview.cursorClicked()
|
||||
);
|
||||
|
||||
// Set location
|
||||
if (this.battleview.battle.turn == 1 && ship.alive && ship.fleet.player === this.battleview.player) {
|
||||
this.position.set(ship.arena_x - 500 * Math.cos(ship.arena_angle), ship.arena_y - 500 * Math.sin(ship.arena_angle));
|
||||
|
|
|
@ -4,78 +4,66 @@ module TS.SpaceTac.UI.Specs {
|
|||
describe("BattleView", function () {
|
||||
let testgame = setupBattleview();
|
||||
|
||||
it("forwards events in targetting mode", function () {
|
||||
it("handles ship hovering to display tooltip", function () {
|
||||
let battleview = testgame.battleview;
|
||||
expect(battleview.ship_hovered).toBeNull("initial state");
|
||||
|
||||
let ship = nn(battleview.battle.playing_ship);
|
||||
battleview.cursorHovered(ship.location, ship);
|
||||
expect(battleview.ship_hovered).toBe(ship, "ship1 hovered");
|
||||
|
||||
ship = nn(battleview.battle.play_order[1]);
|
||||
battleview.cursorHovered(ship.location, ship);
|
||||
expect(battleview.ship_hovered).toBe(ship, "ship2 hovered");
|
||||
|
||||
battleview.cursorHovered(new ArenaLocation(0, 0), null);
|
||||
expect(battleview.ship_hovered).toBeNull("out");
|
||||
|
||||
battleview.cursorOnShip(ship);
|
||||
expect(battleview.ship_hovered).toBe(ship, "force on");
|
||||
|
||||
battleview.cursorOffShip(battleview.battle.play_order[2]);
|
||||
expect(battleview.ship_hovered).toBe(ship, "force off on wrong ship");
|
||||
|
||||
battleview.cursorOffShip(ship);
|
||||
expect(battleview.ship_hovered).toBeNull("force off");
|
||||
});
|
||||
|
||||
it("forwards cursor hovering and click to targetting", function () {
|
||||
let battleview = testgame.battleview;
|
||||
expect(battleview.targetting.active).toBe(false);
|
||||
battleview.setInteractionEnabled(true);
|
||||
|
||||
spyOn(battleview.targetting, "validate").and.stub();
|
||||
|
||||
battleview.cursorInSpace(5, 5);
|
||||
|
||||
expect(battleview.targetting.active).toBe(false);
|
||||
|
||||
// Enter targetting mode
|
||||
let weapon = TestTools.addWeapon(nn(battleview.battle.playing_ship), 10);
|
||||
battleview.enterTargettingMode(weapon.action);
|
||||
|
||||
battleview.enterTargettingMode(nn(weapon.action), ActionTargettingMode.SPACE);
|
||||
expect(battleview.targetting.active).toBe(true);
|
||||
|
||||
// Forward selection in space
|
||||
battleview.cursorInSpace(8, 4);
|
||||
|
||||
battleview.cursorHovered(new ArenaLocation(5, 8), null);
|
||||
expect(battleview.targetting.target).toEqual(Target.newFromLocation(5, 8));
|
||||
expect(battleview.ship_hovered).toBeNull();
|
||||
expect(battleview.targetting.target).toEqual(Target.newFromLocation(8, 4));
|
||||
|
||||
// Process a click on space
|
||||
let ship = battleview.battle.play_order[3];
|
||||
battleview.cursorHovered(ship.location, ship);
|
||||
expect(battleview.targetting.target).toEqual(Target.newFromLocation(ship.arena_x, ship.arena_y));
|
||||
expect(battleview.ship_hovered).toBe(ship);
|
||||
|
||||
spyOn(battleview.targetting, "validate").and.stub();
|
||||
|
||||
expect(battleview.targetting.validate).toHaveBeenCalledTimes(0);
|
||||
battleview.cursorClicked();
|
||||
expect(battleview.targetting.validate).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Forward ship hovering
|
||||
battleview.cursorOnShip(battleview.battle.play_order[0]);
|
||||
|
||||
expect(battleview.ship_hovered).toEqual(battleview.battle.play_order[0]);
|
||||
expect(battleview.targetting.target).toEqual(Target.newFromShip(battleview.battle.play_order[0]));
|
||||
|
||||
// Don't leave a ship we're not hovering
|
||||
battleview.cursorOffShip(battleview.battle.play_order[1]);
|
||||
|
||||
expect(battleview.ship_hovered).toEqual(battleview.battle.play_order[0]);
|
||||
expect(battleview.targetting.target).toEqual(Target.newFromShip(battleview.battle.play_order[0]));
|
||||
|
||||
// Don't move in space while on ship
|
||||
battleview.cursorInSpace(1, 3);
|
||||
|
||||
expect(battleview.ship_hovered).toEqual(battleview.battle.play_order[0]);
|
||||
expect(battleview.targetting.target).toEqual(Target.newFromShip(battleview.battle.play_order[0]));
|
||||
|
||||
// Process a click on ship
|
||||
battleview.cursorClicked();
|
||||
|
||||
// Leave the ship
|
||||
battleview.cursorOffShip(battleview.battle.play_order[0]);
|
||||
|
||||
expect(battleview.ship_hovered).toBeNull();
|
||||
expect(battleview.targetting.target).toBeNull();
|
||||
|
||||
// Quit targetting
|
||||
battleview.exitTargettingMode();
|
||||
|
||||
expect(battleview.targetting.active).toBe(false);
|
||||
|
||||
// Events process normally
|
||||
battleview.cursorInSpace(8, 4);
|
||||
expect(battleview.ship_hovered).toBeNull();
|
||||
battleview.cursorOnShip(battleview.battle.play_order[0]);
|
||||
expect(battleview.ship_hovered).toEqual(battleview.battle.play_order[0]);
|
||||
|
||||
// Quit twice don't do anything
|
||||
battleview.exitTargettingMode();
|
||||
battleview.cursorHovered(new ArenaLocation(5, 8), null);
|
||||
expect(battleview.targetting.target).toBeNull();
|
||||
});
|
||||
|
||||
it("allows to choose an action and a target with shortcut keys", function () {
|
||||
let battleview = testgame.battleview;
|
||||
battleview.setInteractionEnabled(true);
|
||||
let action_icon = nn(first(battleview.action_bar.action_icons, icon => icon.action.needs_target));
|
||||
let action_icon = battleview.action_bar.action_icons[0];
|
||||
|
||||
expect(battleview.targetting.active).toBe(false);
|
||||
expect(battleview.action_bar.hasActionSelected()).toBe(false);
|
||||
|
@ -83,7 +71,7 @@ module TS.SpaceTac.UI.Specs {
|
|||
expect(battleview.action_bar.hasActionSelected()).toBe(true);
|
||||
expect(battleview.targetting.active).toBe(true);
|
||||
expect(battleview.targetting.action).toBe(action_icon.action);
|
||||
expect(battleview.targetting.target).toBe(null);
|
||||
expect(battleview.targetting.target).toEqual(action_icon.action.getDefaultTarget(action_icon.ship));
|
||||
battleview.numberPressed(3);
|
||||
expect(battleview.targetting.active).toBe(true);
|
||||
expect(battleview.targetting.action).toBe(action_icon.action);
|
||||
|
|
|
@ -94,9 +94,10 @@ module TS.SpaceTac.UI {
|
|||
this.background = new Phaser.Image(game, 0, 0, "battle-background", 0);
|
||||
this.layer_background.add(this.background);
|
||||
|
||||
// Add arena (local map)
|
||||
this.arena = new Arena(this);
|
||||
this.layer_arena.add(this.arena);
|
||||
// Add arena (local battlefield map)
|
||||
this.arena = new Arena(this, this.layer_arena);
|
||||
this.arena.callbacks_hover.push(bound(this, "cursorHovered"));
|
||||
this.arena.callbacks_click.push(bound(this, "cursorClicked"));
|
||||
|
||||
// Add UI elements
|
||||
this.action_bar = new ActionBar(this);
|
||||
|
@ -106,7 +107,7 @@ module TS.SpaceTac.UI {
|
|||
this.layer_sheets.add(this.character_sheet);
|
||||
|
||||
// Targetting info
|
||||
this.targetting = new Targetting(this, this.action_bar, this.toggle_tactical_mode);
|
||||
this.targetting = new Targetting(this, this.action_bar, this.toggle_tactical_mode, this.arena.range_hint);
|
||||
this.targetting.moveToLayer(this.arena.layer_targetting);
|
||||
|
||||
// BGM
|
||||
|
@ -178,30 +179,43 @@ module TS.SpaceTac.UI {
|
|||
}
|
||||
}
|
||||
|
||||
// Method called when cursor starts hovering over a ship (or its icon)
|
||||
/**
|
||||
* Method called when the arena cursor is hovered
|
||||
*/
|
||||
cursorHovered(location: ArenaLocation | null, ship: Ship | null) {
|
||||
if (this.targetting.active) {
|
||||
this.targetting.setTargetFromLocation(location);
|
||||
}
|
||||
|
||||
if (ship && this.ship_hovered != ship) {
|
||||
// TODO if targetting is active, this may hide targetting info with the tooltip
|
||||
this.cursorOnShip(ship);
|
||||
} else if (!ship && this.ship_hovered) {
|
||||
this.cursorOffShip(this.ship_hovered);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method called when cursor starts hovering over a ship (or its icon)
|
||||
*/
|
||||
cursorOnShip(ship: Ship): void {
|
||||
if (!this.targetting.active || ship.alive) {
|
||||
if (ship.alive) {
|
||||
this.setShipHovered(ship);
|
||||
}
|
||||
}
|
||||
|
||||
// Method called when cursor stops hovering over a ship (or its icon)
|
||||
/**
|
||||
* Method called when cursor stops hovering over a ship (or its icon)
|
||||
*/
|
||||
cursorOffShip(ship: Ship): void {
|
||||
if (this.ship_hovered === ship) {
|
||||
this.setShipHovered(null);
|
||||
}
|
||||
}
|
||||
|
||||
// Method called when cursor moves in space
|
||||
cursorInSpace(x: number, y: number): void {
|
||||
if (!this.ship_hovered) {
|
||||
if (this.targetting.active) {
|
||||
this.targetting.setTarget(Target.newFromLocation(x, y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Method called when cursor has been clicked (in space or on a ship)
|
||||
/**
|
||||
* Method called when cursor has been clicked (in space or on a ship)
|
||||
*/
|
||||
cursorClicked(): void {
|
||||
if (this.targetting.active) {
|
||||
this.targetting.validate();
|
||||
|
@ -211,7 +225,9 @@ module TS.SpaceTac.UI {
|
|||
}
|
||||
}
|
||||
|
||||
// Set the currently hovered ship
|
||||
/**
|
||||
* Set the currently hovered ship
|
||||
*/
|
||||
setShipHovered(ship: Ship | null): void {
|
||||
this.ship_hovered = ship;
|
||||
this.arena.setShipHovered(ship);
|
||||
|
@ -222,14 +238,6 @@ module TS.SpaceTac.UI {
|
|||
} else {
|
||||
this.ship_tooltip.hide();
|
||||
}
|
||||
|
||||
if (this.targetting.active) {
|
||||
if (ship) {
|
||||
this.targetting.setTarget(Target.newFromShip(ship));
|
||||
} else {
|
||||
this.targetting.setTarget(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enable or disable the global player interaction
|
||||
|
@ -248,12 +256,12 @@ module TS.SpaceTac.UI {
|
|||
|
||||
// Enter targetting mode
|
||||
// While in this mode, the Targetting object will receive hover and click events, and handle them
|
||||
enterTargettingMode(action: BaseAction): Targetting | null {
|
||||
enterTargettingMode(action: BaseAction, mode: ActionTargettingMode): Targetting | null {
|
||||
if (!this.interacting) {
|
||||
return null;
|
||||
}
|
||||
|
||||
this.targetting.setAction(action);
|
||||
this.targetting.setAction(action, mode);
|
||||
return this.targetting;
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ module TS.SpaceTac.UI {
|
|||
private height: number
|
||||
|
||||
constructor(arena: Arena) {
|
||||
this.view = arena.battleview;
|
||||
this.view = arena.view;
|
||||
|
||||
let boundaries = arena.getBoundaries();
|
||||
this.width = boundaries.width;
|
||||
|
|
|
@ -2,8 +2,15 @@ module TS.SpaceTac.UI.Specs {
|
|||
describe("Targetting", function () {
|
||||
let testgame = setupBattleview();
|
||||
|
||||
function newTargetting(): Targetting {
|
||||
return new Targetting(testgame.battleview,
|
||||
testgame.battleview.action_bar,
|
||||
testgame.battleview.toggle_tactical_mode,
|
||||
testgame.battleview.arena.range_hint);
|
||||
}
|
||||
|
||||
it("draws simulation parts", function () {
|
||||
let targetting = new Targetting(testgame.battleview, testgame.battleview.action_bar, new Toggle());
|
||||
let targetting = newTargetting();
|
||||
|
||||
let ship = nn(testgame.battleview.battle.playing_ship);
|
||||
ship.setArenaPosition(10, 20);
|
||||
|
@ -14,7 +21,7 @@ module TS.SpaceTac.UI.Specs {
|
|||
let drawvector = spyOn(targetting, "drawVector").and.stub();
|
||||
|
||||
let part = {
|
||||
action: weapon.action,
|
||||
action: nn(weapon.action),
|
||||
target: new Target(50, 30),
|
||||
ap: 5,
|
||||
possible: true
|
||||
|
@ -27,15 +34,15 @@ module TS.SpaceTac.UI.Specs {
|
|||
expect(drawvector).toHaveBeenCalledTimes(2);
|
||||
expect(drawvector).toHaveBeenCalledWith(0x8e8e8e, 10, 20, 50, 30, 0);
|
||||
|
||||
targetting.setAction(engine.action);
|
||||
part.action = engine.action;
|
||||
targetting.action = engine.action;
|
||||
part.action = nn(engine.action);
|
||||
targetting.drawPart(part, true, null);
|
||||
expect(drawvector).toHaveBeenCalledTimes(3);
|
||||
expect(drawvector).toHaveBeenCalledWith(0xe09c47, 10, 20, 50, 30, 12);
|
||||
});
|
||||
|
||||
it("updates impact indicators on ships inside the blast radius", function () {
|
||||
let targetting = new Targetting(testgame.battleview, testgame.battleview.action_bar, new Toggle());
|
||||
let targetting = newTargetting();
|
||||
let ship = nn(testgame.battleview.battle.playing_ship);
|
||||
|
||||
let collect = spyOn(testgame.battleview.battle, "collectShipsInCircle").and.returnValues(
|
||||
|
@ -71,7 +78,7 @@ module TS.SpaceTac.UI.Specs {
|
|||
});
|
||||
|
||||
it("updates graphics from simulation", function () {
|
||||
let targetting = new Targetting(testgame.battleview, testgame.battleview.action_bar, new Toggle());
|
||||
let targetting = newTargetting();
|
||||
let ship = nn(testgame.battleview.battle.playing_ship);
|
||||
|
||||
let engine = TestTools.addEngine(ship, 8000);
|
||||
|
@ -90,8 +97,8 @@ module TS.SpaceTac.UI.Specs {
|
|||
result.need_fire = true;
|
||||
result.can_fire = true;
|
||||
result.parts = [
|
||||
{ action: engine.action, target: Target.newFromLocation(80, 20), ap: 1, possible: true },
|
||||
{ action: weapon.action, target: Target.newFromLocation(156, 65), ap: 5, possible: true }
|
||||
{ action: nn(engine.action), target: Target.newFromLocation(80, 20), ap: 1, possible: true },
|
||||
{ action: nn(weapon.action), target: Target.newFromLocation(156, 65), ap: 5, possible: true }
|
||||
]
|
||||
targetting.simulation = result;
|
||||
});
|
||||
|
|
|
@ -12,6 +12,7 @@ module TS.SpaceTac.UI {
|
|||
ship: Ship | null = null
|
||||
action: BaseAction | null = null
|
||||
target: Target | null = null
|
||||
mode: ActionTargettingMode
|
||||
simulation = new MoveFireResult()
|
||||
|
||||
// Movement projector
|
||||
|
@ -25,15 +26,17 @@ module TS.SpaceTac.UI {
|
|||
|
||||
// Collaborators to update
|
||||
actionbar: ActionBar
|
||||
range_hint: RangeHint
|
||||
tactical_mode: ToggleClient
|
||||
|
||||
// Access to the parent view
|
||||
view: BaseView
|
||||
|
||||
constructor(view: BaseView, actionbar: ActionBar, tactical_mode: Toggle) {
|
||||
constructor(view: BaseView, actionbar: ActionBar, tactical_mode: Toggle, range_hint: RangeHint) {
|
||||
this.view = view;
|
||||
this.actionbar = actionbar;
|
||||
this.tactical_mode = tactical_mode.manipulate("targetting");
|
||||
this.range_hint = range_hint;
|
||||
|
||||
this.container = view.add.group();
|
||||
|
||||
|
@ -155,6 +158,9 @@ module TS.SpaceTac.UI {
|
|||
this.fire_arrow.visible = false;
|
||||
this.move_ghost.visible = false;
|
||||
|
||||
let from = simulation.need_fire ? simulation.move_location : this.ship.location;
|
||||
let angle = Math.atan2(this.target.y - from.y, this.target.x - from.x);
|
||||
|
||||
if (simulation.success) {
|
||||
let previous: MoveFirePart | null = null;
|
||||
simulation.parts.forEach(part => {
|
||||
|
@ -162,9 +168,6 @@ module TS.SpaceTac.UI {
|
|||
previous = part;
|
||||
});
|
||||
|
||||
let from = simulation.need_fire ? simulation.move_location : this.ship.location;
|
||||
let angle = Math.atan2(this.target.y - from.y, this.target.x - from.x);
|
||||
|
||||
if (simulation.need_move) {
|
||||
this.move_ghost.visible = true;
|
||||
this.move_ghost.position.set(simulation.move_location.x, simulation.move_location.y);
|
||||
|
@ -194,17 +197,46 @@ module TS.SpaceTac.UI {
|
|||
this.fire_impact.visible = false;
|
||||
this.fire_arrow.visible = false;
|
||||
}
|
||||
|
||||
this.container.visible = true;
|
||||
} else {
|
||||
// TODO Display error
|
||||
this.container.visible = false;
|
||||
this.drawVector(0x888888, this.ship.arena_x, this.ship.arena_y, this.target.x, this.target.y);
|
||||
this.fire_arrow.position.set(this.target.x, this.target.y);
|
||||
this.fire_arrow.rotation = angle;
|
||||
this.view.changeImage(this.fire_arrow, "battle-hud-simulator-failed");
|
||||
this.fire_arrow.visible = true;
|
||||
this.fire_blast.visible = false;
|
||||
}
|
||||
this.container.visible = true;
|
||||
} else {
|
||||
this.container.visible = false;
|
||||
}
|
||||
|
||||
// Toggle tactical mode
|
||||
this.tactical_mode(bool(this.action));
|
||||
|
||||
// Toggle range hint
|
||||
if (this.ship && this.action) {
|
||||
if (this.simulation.need_move) {
|
||||
if (this.simulation.success) {
|
||||
let last_move = first(acopy(this.simulation.parts).reverse(), part => part.action instanceof MoveAction);
|
||||
if (last_move) {
|
||||
this.range_hint.update(this.ship, last_move.action);
|
||||
} else {
|
||||
this.range_hint.clear();
|
||||
}
|
||||
} else {
|
||||
let engine = new MoveFireSimulator(this.ship).findBestEngine();
|
||||
if (engine && engine.action) {
|
||||
this.range_hint.update(this.ship, engine.action);
|
||||
} else {
|
||||
this.range_hint.clear();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.range_hint.update(this.ship, this.action);
|
||||
}
|
||||
} else {
|
||||
this.range_hint.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -222,19 +254,44 @@ module TS.SpaceTac.UI {
|
|||
/**
|
||||
* Set the current targetting action, or null to stop targetting
|
||||
*/
|
||||
setAction(action: BaseAction | null): void {
|
||||
setAction(action: BaseAction | null, mode?: ActionTargettingMode): void {
|
||||
if (action && action.equipment && action.equipment.attached_to && action.equipment.attached_to.ship) {
|
||||
this.ship = action.equipment.attached_to.ship;
|
||||
this.action = action;
|
||||
this.mode = (typeof mode == "undefined") ? action.getTargettingMode(this.ship) : mode;
|
||||
|
||||
this.view.changeImage(this.move_ghost, `ship-${this.ship.model.code}-sprite`);
|
||||
this.move_ghost.scale.set(0.4);
|
||||
|
||||
this.setTarget(action.getDefaultTarget(this.ship));
|
||||
} else {
|
||||
this.ship = null;
|
||||
this.action = null;
|
||||
|
||||
this.setTarget(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the target according to a hovered arena location
|
||||
*
|
||||
* This will apply the current targetting mode, to assist the player
|
||||
*/
|
||||
setTargetFromLocation(location: ArenaLocation | null): void {
|
||||
if (location && this.ship) {
|
||||
let battle = this.ship.getBattle();
|
||||
if (this.mode == ActionTargettingMode.SHIP && battle) {
|
||||
let targets = imaterialize(battle.iships(true));
|
||||
let nearest = minBy(targets, ship => arenaDistance(ship.location, location));
|
||||
this.setTarget(Target.newFromShip(nearest ? nearest : this.ship));
|
||||
} else if (this.mode == ActionTargettingMode.SPACE) {
|
||||
this.setTarget(Target.newFromLocation(location.x, location.y));
|
||||
} else {
|
||||
this.setTarget(Target.newFromShip(this.ship));
|
||||
}
|
||||
} else {
|
||||
this.setTarget(null);
|
||||
}
|
||||
this.target = null;
|
||||
this.update();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -39,10 +39,10 @@ module TS.SpaceTac.UI {
|
|||
private effect: Function
|
||||
|
||||
constructor(arena: Arena, ship: Ship, target: Target, weapon: Equipment) {
|
||||
this.ui = arena.getGame();
|
||||
this.ui = arena.game;
|
||||
this.arena = arena;
|
||||
this.view = arena.battleview;
|
||||
this.timer = arena.battleview.timer;
|
||||
this.view = arena.view;
|
||||
this.timer = arena.view.timer;
|
||||
this.layer = arena.layer_weapon_effects;
|
||||
this.ship = ship;
|
||||
this.target = target;
|
||||
|
@ -161,7 +161,7 @@ module TS.SpaceTac.UI {
|
|||
missile.rotation = arenaAngle(this.source, this.destination);
|
||||
this.layer.add(missile);
|
||||
|
||||
let blast_radius = this.weapon.action.getBlastRadius(this.ship);
|
||||
let blast_radius = this.weapon.action ? this.weapon.action.getBlastRadius(this.ship) : 0;
|
||||
|
||||
let projectile_duration = arenaDistance(this.source, this.destination) * 1.5;
|
||||
let tween = this.ui.tweens.create(missile);
|
||||
|
|
Loading…
Reference in a new issue