diff --git a/package.json b/package.json index 142d280..2373010 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "typescript": "^2.5.3" }, "dependencies": { - "jasmine-core": "^2.8.0", + "jasmine-core": "2.8.0", "parse": "^1.9.2", "phaser": "^2.6.2", "phaser-plugin-scene-graph": "^1.0.4" diff --git a/spacetac b/spacetac index 94dbeb6..170578c 100755 --- a/spacetac +++ b/spacetac @@ -11,5 +11,5 @@ dir=$(dirname $0) test -x "${dir}/.env/bin/nodeenv" || ( virtualenv -p python3 "${dir}/.env" && "${dir}/.env/bin/pip" install --upgrade nodeenv ) test -e "${dir}/.env/node/bin/activate" || "${dir}/.env/bin/nodeenv" --node=6.11.1 --force "${dir}/.env/node" -test -e "${dir}/.env/node/bin/yarn" || "${dir}/.env/node/bin/shim" "${dir}/.env/node/bin/npm" install -g yarn@0.27.5 +test -e "${dir}/.env/node/bin/yarn" || "${dir}/.env/node/bin/shim" "${dir}/.env/node/bin/npm" install -g yarn@1.1.0 PATH="${dir}/.env/node/bin:${PATH}" yarn "$@" diff --git a/src/core/ArenaLocation.spec.ts b/src/core/ArenaLocation.spec.ts index a6739bb..9faebe9 100644 --- a/src/core/ArenaLocation.spec.ts +++ b/src/core/ArenaLocation.spec.ts @@ -1,8 +1,15 @@ module TK.SpaceTac.Specs { describe("ArenaLocation", () => { - it("gets distance and angle between two locations", () => { + it("gets distance and angle between two locations", function () { expect(arenaDistance({ x: 0, y: 0 }, { x: 1, y: 1 })).toBeCloseTo(Math.sqrt(2), 8); expect(arenaAngle({ x: 0, y: 0 }, { x: 1, y: 1 })).toBeCloseTo(Math.PI / 4, 8); - }); + }) + + it("gets an angular distance", function () { + expect(angularDistance(0.5, 1.5)).toBe(1.0); + expect(angularDistance(0.5, 1.5 + Math.PI * 6)).toBeCloseTo(1.0, 0.000001); + expect(angularDistance(0.5, -0.5)).toBe(-1.0); + expect(angularDistance(0.5, -0.3 - Math.PI * 4)).toBeCloseTo(-0.8, 0.000001); + }) }); } diff --git a/src/core/ArenaLocation.ts b/src/core/ArenaLocation.ts index b6b361f..51ae854 100644 --- a/src/core/ArenaLocation.ts +++ b/src/core/ArenaLocation.ts @@ -52,16 +52,23 @@ module TK.SpaceTac { } /** - * Get the normalized angle between two locations + * Get the normalized angle (in radians) between two locations */ - export function arenaAngle(loc1: IArenaLocation, loc2: IArenaLocation) { + export function arenaAngle(loc1: IArenaLocation, loc2: IArenaLocation): number { return Math.atan2(loc2.y - loc1.y, loc2.x - loc1.x); } + /** + * Get the "angular distance" between two angles in radians + */ + export function angularDistance(angle1: number, angle2: number): number { + return (angle2 - angle1) % (Math.PI * 2); + } + /** * Get the normalized distance between two locations */ - export function arenaDistance(loc1: IArenaLocation, loc2: IArenaLocation) { + export function arenaDistance(loc1: IArenaLocation, loc2: IArenaLocation): number { let dx = loc2.x - loc1.x; let dy = loc2.y - loc1.y; return Math.sqrt(dx * dx + dy * dy); @@ -70,7 +77,7 @@ module TK.SpaceTac { /** * Check if a location is inside an area */ - export function arenaInside(loc1: IArenaLocation, loc2: IArenaCircleArea, border_inclusive = true) { + export function arenaInside(loc1: IArenaLocation, loc2: IArenaCircleArea, border_inclusive = true): boolean { let dist = arenaDistance(loc1, loc2); return border_inclusive ? (dist <= loc2.radius) : (dist < loc2.radius); } diff --git a/src/core/Equipment.spec.ts b/src/core/Equipment.spec.ts index 2d41a74..e9a901c 100644 --- a/src/core/Equipment.spec.ts +++ b/src/core/Equipment.spec.ts @@ -47,9 +47,7 @@ module TK.SpaceTac.Specs { let equipment = new Equipment(); expect(equipment.getEffectsDescription()).toEqual("does nothing"); - let action = new FireWeaponAction(equipment, 1, 200, 0, [ - new DamageEffect(50) - ]); + let action = new TriggerAction(equipment, [new DamageEffect(50)], 1, 200, 0); equipment.action = action; expect(equipment.getEffectsDescription()).toEqual("Fire (power usage 1, max range 200km):\n• do 50 damage on target"); diff --git a/src/core/LootQualityModifiers.ts b/src/core/LootQualityModifiers.ts index 0a14b84..ac5de44 100644 --- a/src/core/LootQualityModifiers.ts +++ b/src/core/LootQualityModifiers.ts @@ -64,7 +64,7 @@ module TK.SpaceTac { equipment.effects.forEach(effectFactor); - if (equipment.action instanceof FireWeaponAction) { + if (equipment.action instanceof TriggerAction) { simpleFactor(equipment.action, 'power', true); simpleFactor(equipment.action, 'blast'); simpleFactor(equipment.action, 'range'); diff --git a/src/core/LootTemplate.spec.ts b/src/core/LootTemplate.spec.ts index 0535fab..3a5f77a 100644 --- a/src/core/LootTemplate.spec.ts +++ b/src/core/LootTemplate.spec.ts @@ -95,15 +95,15 @@ module TK.SpaceTac.Specs { it("adds fire actions", function () { let template = new LootTemplate(SlotType.Weapon, "Weapon"); - template.addFireAction(istep(1), istep(100), istep(50), [ + template.addTriggerAction(istep(1), [ new EffectTemplate(new FakeEffect(3), { "fakevalue": istep(8) }) - ]); + ], istep(100), istep(50), istep(10)); let result = template.generate(1); - expect(result.action).toEqual(new FireWeaponAction(result, 1, 100, 50, [new FakeEffect(8)])); + expect(result.action).toEqual(new TriggerAction(result, [new FakeEffect(8)], 1, 100, 50, 10)); result = template.generate(2); - expect(result.action).toEqual(new FireWeaponAction(result, 2, 101, 51, [new FakeEffect(9)])); + expect(result.action).toEqual(new TriggerAction(result, [new FakeEffect(9)], 2, 101, 51, 11)); }); it("adds drone actions", function () { @@ -126,10 +126,10 @@ module TK.SpaceTac.Specs { template.addAttributeEffect("maneuvrability", irepeat(1)); expect(template.hasDamageEffect()).toBe(false); - template.addFireAction(irepeat(1), irepeat(50), irepeat(50), [new EffectTemplate(new BaseEffect("test"), {})]); + template.addTriggerAction(irepeat(1), [new EffectTemplate(new BaseEffect("test"), {})], irepeat(50), irepeat(50)); expect(template.hasDamageEffect()).toBe(false); - template.addFireAction(irepeat(1), irepeat(50), irepeat(50), [new EffectTemplate(new DamageEffect(20), {})]); + template.addTriggerAction(irepeat(1), [new EffectTemplate(new DamageEffect(20), {})], irepeat(50), irepeat(50)); expect(template.hasDamageEffect()).toBe(true); }); }); diff --git a/src/core/LootTemplate.ts b/src/core/LootTemplate.ts index 76cff85..a6327e1 100644 --- a/src/core/LootTemplate.ts +++ b/src/core/LootTemplate.ts @@ -203,12 +203,12 @@ module TK.SpaceTac { } /** - * Add a fire weapon action. + * Add a trigger action. */ - addFireAction(power: LeveledValue, range: LeveledValue, blast: LeveledValue, effects: EffectTemplate[]): void { + addTriggerAction(power: LeveledValue, effects: EffectTemplate[], range: LeveledValue = irepeat(0), blast: LeveledValue = irepeat(0), angle: LeveledValue = irepeat(0)): void { this.base_modifiers.push((equipment, level) => { let reffects = effects.map(effect => effect.generate(level)); - equipment.action = new FireWeaponAction(equipment, resolveForLevel(power, level), resolveForLevel(range, level), resolveForLevel(blast, level), reffects); + equipment.action = new TriggerAction(equipment, reffects, resolveForLevel(power, level), resolveForLevel(range, level), resolveForLevel(blast, level), resolveForLevel(angle, level)); }); } @@ -238,7 +238,7 @@ module TK.SpaceTac { hasDamageEffect(): boolean { let example = this.generate(1); let action = example.action; - if (action instanceof FireWeaponAction || action instanceof DeployDroneAction) { + if (action instanceof TriggerAction || action instanceof DeployDroneAction) { return any(action.effects, effect => effect instanceof DamageEffect || (effect instanceof StickyEffect && effect.base instanceof DamageEffect)); } else { return false; diff --git a/src/core/MoveFireSimulator.spec.ts b/src/core/MoveFireSimulator.spec.ts index 67711cf..2887914 100644 --- a/src/core/MoveFireSimulator.spec.ts +++ b/src/core/MoveFireSimulator.spec.ts @@ -5,7 +5,7 @@ module TK.SpaceTac.Specs { let ship = new Ship(); TestTools.setShipAP(ship, ship_ap); TestTools.addEngine(ship, engine_distance); - let action = new FireWeaponAction(new Equipment(), weapon_ap, distance); + let action = new TriggerAction(new Equipment(), [], weapon_ap, distance); let simulator = new MoveFireSimulator(ship); return [ship, simulator, action]; } diff --git a/src/core/Ship.spec.ts b/src/core/Ship.spec.ts index 061a67c..9b0e839 100644 --- a/src/core/Ship.spec.ts +++ b/src/core/Ship.spec.ts @@ -115,7 +115,7 @@ module TK.SpaceTac.Specs { slot = ship.addSlot(SlotType.Power); equipment = new Equipment(slot.type); - equipment.action = new FireWeaponAction(equipment); + equipment.action = new TriggerAction(equipment); slot.attach(equipment); actions = ship.getAvailableActions(); diff --git a/src/core/Ship.ts b/src/core/Ship.ts index a84ab94..e79e48c 100644 --- a/src/core/Ship.ts +++ b/src/core/Ship.ts @@ -436,7 +436,7 @@ module TK.SpaceTac { let start = copy(this.location); let area_effects = imaterialize(this.iToggleActions(true)); - let old_impacted_ships = area_effects.map(action => action.getAffectedShips(this)); + let old_impacted_ships = area_effects.map(action => action.getImpactedShips(this, Target.newFromShip(this))); let old_area_effects = this.getActiveEffects().area; if (engine) { @@ -450,7 +450,7 @@ module TK.SpaceTac { this.addBattleEvent(new MoveEvent(this, start, copy(this.location), engine)); } - let new_impacted_ships = area_effects.map(action => action.getAffectedShips(this)); + let new_impacted_ships = area_effects.map(action => action.getImpactedShips(this, Target.newFromShip(this))); let diff_impacted_ships = flatten(zip(old_impacted_ships, new_impacted_ships).map(([a, b]) => disjunctunion(a, b))); let new_area_effects = this.getActiveEffects().area; if (disjunctunion(old_area_effects, new_area_effects).length > 0) { diff --git a/src/core/TestTools.ts b/src/core/TestTools.ts index 0aa2c95..c182c5e 100644 --- a/src/core/TestTools.ts +++ b/src/core/TestTools.ts @@ -47,9 +47,9 @@ module TK.SpaceTac { /** * Add a weapon to a ship */ - static addWeapon(ship: Ship, damage = 100, power_usage = 1, max_distance = 100, blast = 0): Equipment { + static addWeapon(ship: Ship, damage = 100, power_usage = 1, max_distance = 100, blast = 0, angle = 0): Equipment { var equipment = ship.addSlot(SlotType.Weapon).attach(new Equipment(SlotType.Weapon)); - equipment.action = new FireWeaponAction(equipment, power_usage, max_distance, blast, [new DamageEffect(damage)], "Test Weapon"); + equipment.action = new TriggerAction(equipment, [new DamageEffect(damage)], power_usage, max_distance, blast, angle, "Test Weapon"); return equipment; } diff --git a/src/core/actions/BaseAction.spec.ts b/src/core/actions/BaseAction.spec.ts index a63665f..0efcdce 100644 --- a/src/core/actions/BaseAction.spec.ts +++ b/src/core/actions/BaseAction.spec.ts @@ -82,24 +82,5 @@ module TK.SpaceTac { expect(power.wear).toBe(1); expect(equipment.wear).toBe(1); }) - - it("guesses targetting mode", function () { - let ship = new Ship(); - let action = new BaseAction("test", "Test"); - expect(action.getTargettingMode(ship)).toEqual(ActionTargettingMode.SELF_CONFIRM); - - action = new BaseAction("test", "Test"); - spyOn(action, "getRangeRadius").and.returnValue(50); - expect(action.getTargettingMode(ship)).toEqual(ActionTargettingMode.SHIP); - - action = new BaseAction("test", "Test"); - spyOn(action, "getRangeRadius").and.returnValue(50); - spyOn(action, "getBlastRadius").and.returnValue(20); - expect(action.getTargettingMode(ship)).toEqual(ActionTargettingMode.SPACE); - - action = new BaseAction("test", "Test"); - spyOn(action, "getBlastRadius").and.returnValue(20); - expect(action.getTargettingMode(ship)).toEqual(ActionTargettingMode.SURROUNDINGS); - }) }); } diff --git a/src/core/actions/BaseAction.ts b/src/core/actions/BaseAction.ts index 7ad58ed..56e6b74 100644 --- a/src/core/actions/BaseAction.ts +++ b/src/core/actions/BaseAction.ts @@ -50,20 +50,7 @@ module TK.SpaceTac { * Get the targetting mode */ getTargettingMode(ship: Ship): ActionTargettingMode { - let blast = this.getBlastRadius(ship); - let range = this.getRangeRadius(ship); - - if (blast) { - if (range) { - return ActionTargettingMode.SPACE; - } else { - return ActionTargettingMode.SURROUNDINGS; - } - } else if (range) { - return ActionTargettingMode.SHIP; - } else { - return ActionTargettingMode.SELF_CONFIRM; - } + return ActionTargettingMode.SELF; } /** @@ -119,7 +106,11 @@ module TK.SpaceTac { return null; } - // Get the number of action points the action applied to a target would use + /** + * Get the number of action points the action applied to a target would use + * + * If target is null, an estimated cost is returned. + */ getActionPointsUsage(ship: Ship, target: Target | null): number { return 0; } @@ -129,9 +120,25 @@ module TK.SpaceTac { return 0; } - // Get the effect area radius of this action - getBlastRadius(ship: Ship): number { - return 0; + /** + * Filter a list of ships to return only those impacted by this action + * + * This may be used as an indicator for helping the player in targetting, or to effectively apply the effects + */ + filterImpactedShips(source: IArenaLocation, target: Target, ships: Ship[]): Ship[] { + return []; + } + + /** + * Get a list of ships impacted by this action + */ + getImpactedShips(ship: Ship, target: Target, source: IArenaLocation = ship.location): Ship[] { + let battle = ship.getBattle(); + if (battle) { + return this.filterImpactedShips(source, target, imaterialize(battle.iships(true))); + } else { + return []; + } } /** diff --git a/src/core/actions/DeployDroneAction.spec.ts b/src/core/actions/DeployDroneAction.spec.ts index 0e1a815..61cc2bd 100644 --- a/src/core/actions/DeployDroneAction.spec.ts +++ b/src/core/actions/DeployDroneAction.spec.ts @@ -1,5 +1,3 @@ -/// - module TK.SpaceTac { describe("DeployDroneAction", function () { it("stores useful information", function () { diff --git a/src/core/actions/DeployDroneAction.ts b/src/core/actions/DeployDroneAction.ts index 4efac47..5058866 100644 --- a/src/core/actions/DeployDroneAction.ts +++ b/src/core/actions/DeployDroneAction.ts @@ -33,6 +33,10 @@ module TK.SpaceTac { this.effects = effects; } + getTargettingMode(ship: Ship): ActionTargettingMode { + return ActionTargettingMode.SPACE; + } + getActionPointsUsage(ship: Ship, target: Target | null): number { return this.power; } @@ -41,8 +45,8 @@ module TK.SpaceTac { return this.deploy_distance; } - getBlastRadius(ship: Ship): number { - return this.effect_radius; + filterImpactedShips(source: ArenaLocation, target: Target, ships: Ship[]): Ship[] { + return ships.filter(ship => arenaDistance(ship.location, target) <= this.effect_radius); } checkLocationTarget(ship: Ship, target: Target): Target { diff --git a/src/core/actions/ToggleAction.spec.ts b/src/core/actions/ToggleAction.spec.ts index e69de29..c05e0c2 100644 --- a/src/core/actions/ToggleAction.spec.ts +++ b/src/core/actions/ToggleAction.spec.ts @@ -0,0 +1,31 @@ +module TK.SpaceTac { + describe("ToggleAction", function () { + it("returns correct targetting mode", function () { + let action = new ToggleAction(new Equipment(), 1, 0, []); + expect(action.getTargettingMode(new Ship())).toBe(ActionTargettingMode.SELF_CONFIRM); + + action.activated = true; + expect(action.getTargettingMode(new Ship())).toBe(ActionTargettingMode.SELF_CONFIRM); + + action = new ToggleAction(new Equipment(), 1, 50, []); + expect(action.getTargettingMode(new Ship())).toBe(ActionTargettingMode.SURROUNDINGS); + + action.activated = true; + expect(action.getTargettingMode(new Ship())).toBe(ActionTargettingMode.SELF_CONFIRM); + }) + + it("collects impacted ships", function () { + let action = new ToggleAction(new Equipment(), 1, 50, []); + let battle = new Battle(); + let ship1 = battle.fleets[0].addShip(); + ship1.setArenaPosition(0, 0); + let ship2 = battle.fleets[0].addShip(); + ship2.setArenaPosition(0, 30); + let ship3 = battle.fleets[0].addShip(); + ship3.setArenaPosition(0, 60); + + let result = action.getImpactedShips(ship1, Target.newFromShip(ship1)); + expect(result).toEqual([ship1, ship2]); + }); + }) +} diff --git a/src/core/actions/ToggleAction.ts b/src/core/actions/ToggleAction.ts index 11992f1..e803c96 100644 --- a/src/core/actions/ToggleAction.ts +++ b/src/core/actions/ToggleAction.ts @@ -29,10 +29,10 @@ module TK.SpaceTac { } getTargettingMode(ship: Ship): ActionTargettingMode { - if (this.activated) { - return ActionTargettingMode.SELF; + if (this.activated || !this.radius) { + return ActionTargettingMode.SELF_CONFIRM; } else { - return super.getTargettingMode(ship); + return ActionTargettingMode.SURROUNDINGS; } } @@ -44,30 +44,20 @@ module TK.SpaceTac { return 0; } - getBlastRadius(ship: Ship): number { - return this.radius; + filterImpactedShips(source: ArenaLocation, target: Target, ships: Ship[]): Ship[] { + return ships.filter(ship => arenaDistance(ship.location, source) <= 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 - */ - getAffectedShips(ship: Ship): Ship[] { - let target = Target.newFromShip(ship); - let radius = this.getBlastRadius(ship); - let battle = ship.getBattle(); - return (radius && battle) ? battle.collectShipsInCircle(target, radius, true) : ((target.ship && target.ship.alive) ? [target.ship] : []); - } - /** * Collect the effects applied by this action */ - getEffects(ship: Ship): [Ship, BaseEffect][] { + getEffects(ship: Ship, target: Target, source = ship.location): [Ship, BaseEffect][] { let result: [Ship, BaseEffect][] = []; - let ships = this.getAffectedShips(ship); + let ships = this.getImpactedShips(ship, target, source); ships.forEach(ship => { this.effects.forEach(effect => result.push([ship, effect])); }); @@ -77,7 +67,7 @@ module TK.SpaceTac { protected customApply(ship: Ship, target: Target) { this.activated = !this.activated; ship.addBattleEvent(new ToggleEvent(ship, this, this.activated)); - this.getAffectedShips(ship).forEach(iship => iship.setActiveEffectsChanged()); + this.getImpactedShips(ship, target).forEach(iship => iship.setActiveEffectsChanged()); } getEffectsDescription(): string { diff --git a/src/core/actions/FireWeaponAction.spec.ts b/src/core/actions/TriggerAction.spec.ts similarity index 57% rename from src/core/actions/FireWeaponAction.spec.ts rename to src/core/actions/TriggerAction.spec.ts index 81fd4d9..730b5da 100644 --- a/src/core/actions/FireWeaponAction.spec.ts +++ b/src/core/actions/TriggerAction.spec.ts @@ -1,15 +1,13 @@ -/// - module TK.SpaceTac { - describe("FireWeaponAction", function () { + describe("TriggerAction", function () { it("constructs correctly", function () { let equipment = new Equipment(SlotType.Weapon, "testweapon"); - let action = new FireWeaponAction(equipment, 4, 30, 10); + let action = new TriggerAction(equipment, [], 4, 30, 10); expect(action.code).toEqual("fire-testweapon"); expect(action.name).toEqual("Fire"); expect(action.equipment).toBe(equipment); - }); + }) it("applies effects to alive ships in blast radius", function () { let fleet = new Fleet(); @@ -17,7 +15,7 @@ module TK.SpaceTac { let equipment = new Equipment(SlotType.Weapon, "testweapon"); let effect = new BaseEffect("testeffect"); let mock_apply = spyOn(effect, "applyOnShip").and.stub(); - let action = new FireWeaponAction(equipment, 5, 100, 10, [effect]); + let action = new TriggerAction(equipment, [effect], 5, 100, 10); TestTools.setShipAP(ship, 10); @@ -37,7 +35,7 @@ module TK.SpaceTac { action.apply(ship, Target.newFromLocation(50, 50)); expect(mock_apply).toHaveBeenCalledTimes(1); expect(mock_apply).toHaveBeenCalledWith(ship2, ship); - }); + }) it("transforms ship target in location target, when the weapon has blast radius", function () { let ship1 = new Ship(); @@ -62,7 +60,46 @@ module TK.SpaceTac { target = action.checkTarget(ship1, Target.newFromShip(ship2)); expect(target).toEqual(new Target(100, 10)); - }); + }) + + it("lists impacted ships", function () { + let ship1 = new Ship(null, "S1"); + ship1.setArenaPosition(10, 50); + let ship2 = new Ship(null, "S2"); + ship2.setArenaPosition(40, 60); + let ship3 = new Ship(null, "S3"); + ship3.setArenaPosition(0, 30); + let ships = [ship1, ship2, ship3]; + + let action = new TriggerAction(new Equipment(), [], 1, 50); + expect(action.filterImpactedShips({ x: 0, y: 0 }, Target.newFromShip(ship2), ships)).toEqual([ship2]); + expect(action.filterImpactedShips({ x: 0, y: 0 }, Target.newFromLocation(10, 50), ships)).toEqual([]); + + action = new TriggerAction(new Equipment(), [], 1, 50, 40); + expect(action.filterImpactedShips({ x: 0, y: 0 }, Target.newFromLocation(20, 20), ships)).toEqual([ship1, ship3]); + + action = new TriggerAction(new Equipment(), [], 1, 100, 0, 30); + expect(action.filterImpactedShips({ x: 0, y: 51 }, Target.newFromLocation(30, 50), ships)).toEqual([ship1, ship2]); + }) + + it("guesses targetting mode", function () { + let ship = new Ship(); + let equ = new Equipment(); + let action = new TriggerAction(equ, []); + expect(action.getTargettingMode(ship)).toEqual(ActionTargettingMode.SELF_CONFIRM, "self"); + + action = new TriggerAction(equ, [], 1, 50); + expect(action.getTargettingMode(ship)).toEqual(ActionTargettingMode.SHIP, "ship"); + + action = new TriggerAction(equ, [], 1, 50, 20); + expect(action.getTargettingMode(ship)).toEqual(ActionTargettingMode.SPACE, "blast"); + + action = new TriggerAction(equ, [], 1, 0, 20); + expect(action.getTargettingMode(ship)).toEqual(ActionTargettingMode.SURROUNDINGS, "surroundings"); + + action = new TriggerAction(equ, [], 1, 50, 0, 15); + expect(action.getTargettingMode(ship)).toEqual(ActionTargettingMode.SPACE, "angle"); + }) it("rotates toward the target", function () { let ship = new Ship(); @@ -78,6 +115,6 @@ module TK.SpaceTac { result = action.apply(ship, Target.newFromShip(ship)); expect(result).toBe(true); expect(ship.arena_angle).toBeCloseTo(1.107, 0.001); - }); + }) }); } diff --git a/src/core/actions/FireWeaponAction.ts b/src/core/actions/TriggerAction.ts similarity index 60% rename from src/core/actions/FireWeaponAction.ts rename to src/core/actions/TriggerAction.ts index af298d2..3487ff2 100644 --- a/src/core/actions/FireWeaponAction.ts +++ b/src/core/actions/TriggerAction.ts @@ -2,31 +2,35 @@ module TK.SpaceTac { /** - * Action to fire a weapon on another ship, or in space + * Action to trigger an equipment (for example a weapon), with an optional target */ - export class FireWeaponAction extends BaseAction { + export class TriggerAction extends BaseAction { // Power consumption power: number - // Maximal range of the weapon + // Maximal range of the weapon (distance to target) range: number - // Blast radius + // Radius around the target that will be impacted blast: number + // Angle of the area between the source and the target that will be impacted + angle: number + // Effects applied on target effects: BaseEffect[] // Equipment cannot be null equipment: Equipment - constructor(equipment: Equipment, power = 1, range = 0, blast = 0, effects: BaseEffect[] = [], name = range ? "Fire" : "Trigger") { + constructor(equipment: Equipment, effects: BaseEffect[] = [], power = 1, range = 0, blast = 0, angle = 0, name = range ? "Fire" : "Trigger") { super("fire-" + equipment.code, name, equipment); this.power = power; this.range = range; this.effects = effects; this.blast = blast; + this.angle = angle; } getDefaultTarget(ship: Ship): Target { @@ -46,6 +50,24 @@ module TK.SpaceTac { } } + getTargettingMode(ship: Ship): ActionTargettingMode { + if (this.blast) { + if (this.range) { + return ActionTargettingMode.SPACE; + } else { + return ActionTargettingMode.SURROUNDINGS; + } + } else if (this.range) { + if (this.angle) { + return ActionTargettingMode.SPACE; + } else { + return ActionTargettingMode.SHIP; + } + } else { + return ActionTargettingMode.SELF_CONFIRM; + } + } + getActionPointsUsage(ship: Ship, target: Target | null): number { return this.power; } @@ -54,12 +76,20 @@ module TK.SpaceTac { return this.range; } - getBlastRadius(ship: Ship): number { - return this.blast; + filterImpactedShips(source: ArenaLocation, target: Target, ships: Ship[]): Ship[] { + if (this.blast) { + return ships.filter(ship => arenaDistance(ship.location, target) <= this.blast); + } else if (this.angle) { + let angle = arenaAngle(source, target); + let maxangle = (this.angle * 0.5) * Math.PI / 180; + return ships.filter(ship => arenaDistance(source, ship.location) <= this.range && Math.abs(angularDistance(arenaAngle(source, ship.location), angle)) < maxangle); + } else { + return ships.filter(ship => target.ship === ship); + } } checkLocationTarget(ship: Ship, target: Target): Target | null { - if (target && this.blast > 0) { + if (target && (this.blast > 0 || this.angle > 0)) { target = target.constraintInRange(ship.arena_x, ship.arena_y, this.range); return target; } else { @@ -73,7 +103,7 @@ module TK.SpaceTac { return null; } else { // Check if target is in range - if (this.blast > 0) { + if (this.blast > 0 || this.angle > 0) { return this.checkLocationTarget(ship, new Target(target.x, target.y)); } else if (target.isInRange(ship.arena_x, ship.arena_y, this.range)) { return target; @@ -86,11 +116,9 @@ module TK.SpaceTac { /** * Collect the effects applied by this action */ - getEffects(ship: Ship, target: Target): [Ship, BaseEffect][] { + getEffects(ship: Ship, target: Target, source = ship.location): [Ship, BaseEffect][] { let result: [Ship, BaseEffect][] = []; - let blast = this.getBlastRadius(ship); - let battle = ship.getBattle(); - let ships = (blast && battle) ? battle.collectShipsInCircle(target, blast, true) : ((target.ship && target.ship.alive) ? [target.ship] : []); + let ships = this.getImpactedShips(ship, target, source); ships.forEach(ship => { this.effects.forEach(effect => result.push([ship, effect])); }); @@ -126,7 +154,16 @@ module TK.SpaceTac { let desc = `${this.name} (${info.join(", ")})`; let effects = this.effects.map(effect => { - let suffix = this.blast ? `in ${this.blast}km radius` : (this.range ? "on target" : "on self"); + let suffix: string; + if (this.blast) { + suffix = `in ${this.blast}km radius`; + } else if (this.angle) { + suffix = `in ${this.angle}° arc`; + } else if (this.range) { + suffix = "on target"; + } else { + suffix = "on self"; + } return "• " + effect.getDescription() + " " + suffix; }); return `${desc}:\n${effects.join("\n")}`; diff --git a/src/core/ai/Maneuver.ts b/src/core/ai/Maneuver.ts index e4003d4..1a07340 100644 --- a/src/core/ai/Maneuver.ts +++ b/src/core/ai/Maneuver.ts @@ -78,7 +78,7 @@ module TK.SpaceTac { let result: [Ship, BaseEffect][] = []; // Effects of weapon - if (this.action instanceof FireWeaponAction) { + if (this.action instanceof TriggerAction) { result = result.concat(this.action.getEffects(this.ship, this.target)); } else if (this.action instanceof DeployDroneAction) { let ships = this.battle.collectShipsInCircle(this.target, this.action.effect_radius, true); diff --git a/src/core/ai/TacticalAIHelpers.ts b/src/core/ai/TacticalAIHelpers.ts index 07e8880..fd0885e 100644 --- a/src/core/ai/TacticalAIHelpers.ts +++ b/src/core/ai/TacticalAIHelpers.ts @@ -71,7 +71,7 @@ module TK.SpaceTac { */ static produceDirectShots(ship: Ship, battle: Battle): TacticalProducer { let enemies = ifilter(battle.iships(), iship => iship.alive && iship.getPlayer() !== ship.getPlayer()); - let weapons = ifilter(getPlayableActions(ship), action => action instanceof FireWeaponAction); + let weapons = ifilter(getPlayableActions(ship), action => action instanceof TriggerAction); return imap(icombine(enemies, weapons), ([enemy, weapon]) => new Maneuver(ship, weapon, Target.newFromShip(enemy))); } @@ -91,11 +91,11 @@ module TK.SpaceTac { */ static produceInterestingBlastShots(ship: Ship, battle: Battle): TacticalProducer { // TODO Work with groups of 3, 4 ... - let weapons = ifilter(getPlayableActions(ship), action => action instanceof FireWeaponAction && action.blast > 0); + let weapons = >ifilter(getPlayableActions(ship), action => action instanceof TriggerAction && action.blast > 0); let enemies = battle.ienemies(ship.getPlayer(), true); // FIXME This produces duplicates (x, y) and (y, x) let couples = ifilter(icombine(enemies, enemies), ([e1, e2]) => e1 != e2); - let candidates = ifilter(icombine(weapons, couples), ([weapon, [e1, e2]]) => Target.newFromShip(e1).getDistanceTo(Target.newFromShip(e2)) < weapon.getBlastRadius(ship) * 2); + let candidates = ifilter(icombine(weapons, couples), ([weapon, [e1, e2]]) => Target.newFromShip(e1).getDistanceTo(Target.newFromShip(e2)) < weapon.blast * 2); let result = imap(candidates, ([weapon, [e1, e2]]) => new Maneuver(ship, weapon, Target.newFromLocation((e1.arena_x + e2.arena_x) / 2, (e1.arena_y + e2.arena_y) / 2))); return result; } @@ -104,8 +104,8 @@ module TK.SpaceTac { * Produce random blast weapon shots, on a grid. */ static produceRandomBlastShots(ship: Ship, battle: Battle): TacticalProducer { - let weapons = ifilter(getPlayableActions(ship), action => action instanceof FireWeaponAction && action.blast > 0); - let candidates = ifilter(icombine(weapons, scanArena(battle)), ([weapon, location]) => (weapon).getEffects(ship, location).length > 0); + let weapons = ifilter(getPlayableActions(ship), action => action instanceof TriggerAction && action.blast > 0); + let candidates = ifilter(icombine(weapons, scanArena(battle)), ([weapon, location]) => (weapon).getEffects(ship, location).length > 0); let result = imap(candidates, ([weapon, location]) => new Maneuver(ship, weapon, location)); return result; } @@ -149,7 +149,7 @@ module TK.SpaceTac { let lost = ship.getValue("power") - maneuver.getPowerUsage() + ship.getAttribute("power_generation") - ship.getAttribute("power_capacity"); if (lost > 0) { return -lost / ship.getAttribute("power_capacity"); - } else if (maneuver.action instanceof FireWeaponAction || maneuver.action instanceof DeployDroneAction) { + } else if (maneuver.action instanceof TriggerAction || maneuver.action instanceof DeployDroneAction) { if (maneuver.effects.length == 0) { return -1; } else { diff --git a/src/core/equipments/Generators.spec.ts b/src/core/equipments/Generators.spec.ts index 3cb4021..e46e8f3 100644 --- a/src/core/equipments/Generators.spec.ts +++ b/src/core/equipments/Generators.spec.ts @@ -49,9 +49,7 @@ module TK.SpaceTac.Equipments { new AttributeEffect("power_capacity", 5), new AttributeEffect("power_generation", 4), ]); - expect(equipment.action).toEqual(new FireWeaponAction(equipment, 1, 0, 0, [ - new CooldownEffect(1, 1) - ])); + expect(equipment.action).toEqual(new TriggerAction(equipment, [new CooldownEffect(1, 1)])); expect(equipment.price).toEqual(420); equipment = template.generate(2); @@ -60,9 +58,7 @@ module TK.SpaceTac.Equipments { new AttributeEffect("power_capacity", 6), new AttributeEffect("power_generation", 4), ]); - expect(equipment.action).toEqual(new FireWeaponAction(equipment, 1, 0, 0, [ - new CooldownEffect(1, 1) - ])); + expect(equipment.action).toEqual(new TriggerAction(equipment, [new CooldownEffect(1, 1)])); expect(equipment.price).toEqual(1470); equipment = template.generate(3); @@ -71,9 +67,7 @@ module TK.SpaceTac.Equipments { new AttributeEffect("power_capacity", 6), new AttributeEffect("power_generation", 5), ]); - expect(equipment.action).toEqual(new FireWeaponAction(equipment, 1, 0, 0, [ - new CooldownEffect(1, 1) - ])); + expect(equipment.action).toEqual(new TriggerAction(equipment, [new CooldownEffect(1, 1)])); expect(equipment.price).toEqual(3570); equipment = template.generate(10); @@ -82,9 +76,7 @@ module TK.SpaceTac.Equipments { new AttributeEffect("power_capacity", 13), new AttributeEffect("power_generation", 12), ]); - expect(equipment.action).toEqual(new FireWeaponAction(equipment, 7, 0, 0, [ - new CooldownEffect(4, 7) - ])); + expect(equipment.action).toEqual(new TriggerAction(equipment, [new CooldownEffect(4, 7)], 7)); expect(equipment.price).toEqual(47670); }) }) diff --git a/src/core/equipments/Generators.ts b/src/core/equipments/Generators.ts index 9d9e92b..ad11d2f 100644 --- a/src/core/equipments/Generators.ts +++ b/src/core/equipments/Generators.ts @@ -19,7 +19,7 @@ module TK.SpaceTac.Equipments { this.setSkillsRequirements({ "skill_time": leveled(1, 1.7), "skill_gravity": leveled(0.3, 0.4) }); this.addAttributeEffect("power_capacity", leveled(5.5, 0.5)); this.addAttributeEffect("power_generation", leveled(4, 0.5)); - this.addFireAction(leveled(1, 0.4), irepeat(0), irepeat(0), [ + this.addTriggerAction(leveled(1, 0.4), [ new EffectTemplate(new CooldownEffect(), { cooling: leveled(1, 0.2), maxcount: leveled(1, 0.4) }) ]) } diff --git a/src/core/equipments/Hulls.spec.ts b/src/core/equipments/Hulls.spec.ts index cafa830..a9ac804 100644 --- a/src/core/equipments/Hulls.spec.ts +++ b/src/core/equipments/Hulls.spec.ts @@ -69,9 +69,7 @@ module TK.SpaceTac.Equipments { new AttributeEffect("hull_capacity", 60), new AttributeEffect("precision", 2), ]); - expect(equipment.action).toEqual(new FireWeaponAction(equipment, 1, 0, 0, [ - new ValueEffect("hull", 60) - ])) + expect(equipment.action).toEqual(new TriggerAction(equipment, [new ValueEffect("hull", 60)])); expect(equipment.cooldown).toEqual(new Cooldown(1, 4)); expect(equipment.price).toEqual(250); diff --git a/src/core/equipments/Hulls.ts b/src/core/equipments/Hulls.ts index d2e8761..c52be0a 100644 --- a/src/core/equipments/Hulls.ts +++ b/src/core/equipments/Hulls.ts @@ -27,7 +27,7 @@ module TK.SpaceTac.Equipments { this.setSkillsRequirements({ "skill_quantum": leveled(1, 2) }); this.addAttributeEffect("hull_capacity", leveled(60)); this.addAttributeEffect("precision", leveled(2)); - this.addFireAction(leveled(1, 0.1), irepeat(0), irepeat(0), [ + this.addTriggerAction(leveled(1, 0.1), [ new EffectTemplate(new ValueEffect("hull"), { value: leveled(60) }) ]) this.setCooldown(irepeat(1), irepeat(4)); diff --git a/src/core/equipments/PowerDepleter.spec.ts b/src/core/equipments/PowerDepleter.spec.ts index 5f05d5f..211d768 100644 --- a/src/core/equipments/PowerDepleter.spec.ts +++ b/src/core/equipments/PowerDepleter.spec.ts @@ -5,75 +5,31 @@ module TK.SpaceTac.Equipments { let equipment = template.generate(1); expect(equipment.requirements).toEqual({ "skill_antimatter": 1 }); - expect(equipment.action).toEqual(new FireWeaponAction(equipment, 4, 460, 0, [ + expect(equipment.action).toEqual(new TriggerAction(equipment, [ new StickyEffect(new AttributeLimitEffect("power_capacity", 3), 2, true) - ])); + ], 4, 460, 0)); expect(equipment.price).toEqual(100); equipment = template.generate(2); expect(equipment.requirements).toEqual({ "skill_antimatter": 2 }); - expect(equipment.action).toEqual(new FireWeaponAction(equipment, 4, 490, 0, [ + expect(equipment.action).toEqual(new TriggerAction(equipment, [ new StickyEffect(new AttributeLimitEffect("power_capacity", 3), 2, true) - ])); + ], 4, 490, 0)); expect(equipment.price).toEqual(350); equipment = template.generate(3); expect(equipment.requirements).toEqual({ "skill_antimatter": 4 }); - expect(equipment.action).toEqual(new FireWeaponAction(equipment, 4, 526, 0, [ + expect(equipment.action).toEqual(new TriggerAction(equipment, [ new StickyEffect(new AttributeLimitEffect("power_capacity", 3), 2, true) - ])); + ], 4, 526, 0)); expect(equipment.price).toEqual(850); equipment = template.generate(10); expect(equipment.requirements).toEqual({ "skill_antimatter": 25 }); - expect(equipment.action).toEqual(new FireWeaponAction(equipment, 4, 946, 0, [ + expect(equipment.action).toEqual(new TriggerAction(equipment, [ new StickyEffect(new AttributeLimitEffect("power_capacity", 3), 2, true) - ])); + ], 4, 946, 0)); expect(equipment.price).toEqual(11350); }); - - it("limits target's AP", () => { - var template = new PowerDepleter(); - var equipment = template.generate(1); - - var ship = new Ship(); - var target = new Ship(); - TestTools.setShipAP(ship, 50); - TestTools.setShipAP(target, 7, 2); - - expect(target.values.power.get()).toBe(7); - expect(target.sticky_effects).toEqual([]); - - // Attribute is immediately limited - nn(equipment.action).apply(ship, Target.newFromShip(target)); - - expect(target.values.power.get()).toBe(3); - expect(target.sticky_effects).toEqual([ - new StickyEffect(new AttributeLimitEffect("power_capacity", 3), 2, true, false) - ]); - - // Attribute is limited for two turns, and prevents AP recovery - target.values.power.set(6); - target.recoverActionPoints(); - target.startTurn(); - - expect(target.values.power.get()).toBe(3); - expect(target.sticky_effects).toEqual([ - new StickyEffect(new AttributeLimitEffect("power_capacity", 3), 1, true, false) - ]); - - target.endTurn(); - target.recoverActionPoints(); - expect(target.values.power.get()).toBe(3); - target.startTurn(); - - expect(target.sticky_effects).toEqual([]); - - // Effect vanished, so AP recovery happens - target.endTurn(); - - expect(target.values.power.get()).toBe(5); - expect(target.sticky_effects).toEqual([]); - }); }); } diff --git a/src/core/equipments/PowerDepleter.ts b/src/core/equipments/PowerDepleter.ts index 36a1cf9..38ccd6e 100644 --- a/src/core/equipments/PowerDepleter.ts +++ b/src/core/equipments/PowerDepleter.ts @@ -7,9 +7,9 @@ module TK.SpaceTac.Equipments { this.setSkillsRequirements({ "skill_antimatter": leveled(1, 1.5) }); this.setCooldown(irepeat(2), irepeat(3)); - this.addFireAction(irepeat(4), leveled(460, 30), irepeat(0), [ + this.addTriggerAction(irepeat(4), [ new StickyEffectTemplate(new AttributeLimitEffect("power_capacity"), { "value": irepeat(3) }, irepeat(2)) - ]); + ], leveled(460, 30)); } } } diff --git a/src/core/equipments/RawWeapons.spec.ts b/src/core/equipments/RawWeapons.spec.ts index 6a504e4..802741c 100644 --- a/src/core/equipments/RawWeapons.spec.ts +++ b/src/core/equipments/RawWeapons.spec.ts @@ -5,25 +5,25 @@ module TK.SpaceTac.Equipments { let equipment = template.generate(1); expect(equipment.requirements).toEqual({ "skill_materials": 1 }); - expect(equipment.action).toEqual(new FireWeaponAction(equipment, 3, 500, 0, [new DamageEffect(30, 20)])); + expect(equipment.action).toEqual(new TriggerAction(equipment, [new DamageEffect(30, 20)], 3, 400, 0)); expect(equipment.price).toEqual(100); expect(equipment.cooldown).toEqual(new Cooldown(2, 2)); equipment = template.generate(2); expect(equipment.requirements).toEqual({ "skill_materials": 2 }); - expect(equipment.action).toEqual(new FireWeaponAction(equipment, 3, 512, 0, [new DamageEffect(42, 28)])); + expect(equipment.action).toEqual(new TriggerAction(equipment, [new DamageEffect(42, 28)], 3, 412, 0)); expect(equipment.price).toEqual(350); expect(equipment.cooldown).toEqual(new Cooldown(2, 2)); equipment = template.generate(3); expect(equipment.requirements).toEqual({ "skill_materials": 4 }); - expect(equipment.action).toEqual(new FireWeaponAction(equipment, 3, 526, 0, [new DamageEffect(56, 37)])); + expect(equipment.action).toEqual(new TriggerAction(equipment, [new DamageEffect(56, 37)], 3, 426, 0)); expect(equipment.price).toEqual(850); expect(equipment.cooldown).toEqual(new Cooldown(2, 2)); equipment = template.generate(10); expect(equipment.requirements).toEqual({ "skill_materials": 23 }); - expect(equipment.action).toEqual(new FireWeaponAction(equipment, 3, 694, 0, [new DamageEffect(224, 149)])); + expect(equipment.action).toEqual(new TriggerAction(equipment, [new DamageEffect(224, 149)], 3, 594, 0)); expect(equipment.price).toEqual(11350); expect(equipment.cooldown).toEqual(new Cooldown(2, 2)); }); @@ -33,25 +33,25 @@ module TK.SpaceTac.Equipments { let equipment = template.generate(1); expect(equipment.requirements).toEqual({ "skill_materials": 1, "skill_photons": 1 }); - expect(equipment.action).toEqual(new FireWeaponAction(equipment, 4, 500, 150, [new DamageEffect(26, 4)])); + expect(equipment.action).toEqual(new TriggerAction(equipment, [new DamageEffect(26, 4)], 4, 500, 150)); expect(equipment.cooldown).toEqual(new Cooldown(1, 0)); expect(equipment.price).toEqual(163); equipment = template.generate(2); expect(equipment.requirements).toEqual({ "skill_materials": 2, "skill_photons": 1 }); - expect(equipment.action).toEqual(new FireWeaponAction(equipment, 4, 520, 155, [new DamageEffect(28, 5)])); + expect(equipment.action).toEqual(new TriggerAction(equipment, [new DamageEffect(28, 5)], 4, 520, 155)); expect(equipment.cooldown).toEqual(new Cooldown(1, 0)); expect(equipment.price).toEqual(570); equipment = template.generate(3); expect(equipment.requirements).toEqual({ "skill_materials": 3, "skill_photons": 2 }); - expect(equipment.action).toEqual(new FireWeaponAction(equipment, 4, 544, 161, [new DamageEffect(30, 6)])); + expect(equipment.action).toEqual(new TriggerAction(equipment, [new DamageEffect(30, 6)], 4, 544, 161)); expect(equipment.cooldown).toEqual(new Cooldown(1, 0)); expect(equipment.price).toEqual(1385); equipment = template.generate(10); expect(equipment.requirements).toEqual({ "skill_materials": 20, "skill_photons": 13 }); - expect(equipment.action).toEqual(new FireWeaponAction(equipment, 4, 824, 231, [new DamageEffect(58, 20)])); + expect(equipment.action).toEqual(new TriggerAction(equipment, [new DamageEffect(58, 20)], 4, 824, 231)); expect(equipment.cooldown).toEqual(new Cooldown(1, 0)); expect(equipment.price).toEqual(18500); }); @@ -60,26 +60,26 @@ module TK.SpaceTac.Equipments { let template = new ProkhorovLaser(); let equipment = template.generate(1); - expect(equipment.requirements).toEqual({ "skill_photons": 1 }); - expect(equipment.action).toEqual(new FireWeaponAction(equipment, 5, 0, 250, [new DamageEffect(20, 25)])); + expect(equipment.requirements).toEqual({ "skill_photons": 1, "skill_quantum": 1 }); + expect(equipment.action).toEqual(new TriggerAction(equipment, [new DamageEffect(20, 25)], 5, 300, 0, 40)); expect(equipment.cooldown).toEqual(new Cooldown(1, 1)); expect(equipment.price).toEqual(152); equipment = template.generate(2); - expect(equipment.requirements).toEqual({ "skill_antimatter": 1, "skill_photons": 2 }); - expect(equipment.action).toEqual(new FireWeaponAction(equipment, 5, 0, 260, [new DamageEffect(28, 35)])); + expect(equipment.requirements).toEqual({ "skill_antimatter": 1, "skill_photons": 2, "skill_quantum": 2 }); + expect(equipment.action).toEqual(new TriggerAction(equipment, [new DamageEffect(28, 35)], 5, 310, 0, 42)); expect(equipment.cooldown).toEqual(new Cooldown(1, 1)); expect(equipment.price).toEqual(532); equipment = template.generate(3); - expect(equipment.requirements).toEqual({ "skill_antimatter": 1, "skill_photons": 3 }); - expect(equipment.action).toEqual(new FireWeaponAction(equipment, 5, 0, 272, [new DamageEffect(37, 47)])); + expect(equipment.requirements).toEqual({ "skill_antimatter": 1, "skill_photons": 4, "skill_quantum": 3 }); + expect(equipment.action).toEqual(new TriggerAction(equipment, [new DamageEffect(37, 47)], 5, 322, 0, 44)); expect(equipment.cooldown).toEqual(new Cooldown(1, 1)); expect(equipment.price).toEqual(1292); equipment = template.generate(10); - expect(equipment.requirements).toEqual({ "skill_antimatter": 11, "skill_photons": 22 }); - expect(equipment.action).toEqual(new FireWeaponAction(equipment, 5, 0, 412, [new DamageEffect(149, 187)])); + expect(equipment.requirements).toEqual({ "skill_antimatter": 11, "skill_photons": 23, "skill_quantum": 20 }); + expect(equipment.action).toEqual(new TriggerAction(equipment, [new DamageEffect(149, 187)], 5, 462, 0, 72)); expect(equipment.cooldown).toEqual(new Cooldown(1, 1)); expect(equipment.price).toEqual(17252); }); diff --git a/src/core/equipments/RawWeapons.ts b/src/core/equipments/RawWeapons.ts index caa383d..35a4017 100644 --- a/src/core/equipments/RawWeapons.ts +++ b/src/core/equipments/RawWeapons.ts @@ -7,9 +7,9 @@ module TK.SpaceTac.Equipments { this.setSkillsRequirements({ "skill_materials": leveled(1, 1.4) }); this.setCooldown(irepeat(2), irepeat(2)); - this.addFireAction(irepeat(3), leveled(500, 12), irepeat(0), [ + this.addTriggerAction(irepeat(3), [ new EffectTemplate(new DamageEffect(), { base: leveled(30), span: leveled(20) }) - ]); + ], leveled(400, 12)); } } @@ -19,9 +19,9 @@ module TK.SpaceTac.Equipments { this.setSkillsRequirements({ "skill_materials": leveled(1, 1.2), "skill_photons": leveled(1, 0.8) }); this.setCooldown(irepeat(1), irepeat(0)); - this.addFireAction(irepeat(4), leveled(500, 20), leveled(150, 5), [ + this.addTriggerAction(irepeat(4), [ new EffectTemplate(new DamageEffect(), { base: leveled(26, 2), span: leveled(4, 1) }) - ]); + ], leveled(500, 20), leveled(150, 5)); } } @@ -30,12 +30,11 @@ module TK.SpaceTac.Equipments { super(SlotType.Weapon, "Prokhorov Laser", "Powerful mid-range perforating laser, using antimatter to contain the tremendous photonic energy", 152); // TODO increased damage to hull - // TODO cone targetting - this.setSkillsRequirements({ "skill_antimatter": leveled(0.3, 0.7), "skill_photons": leveled(1, 1.3) }); + this.setSkillsRequirements({ "skill_antimatter": leveled(0.3, 0.7), "skill_quantum": leveled(1, 1.2), "skill_photons": leveled(1, 1.4) }); this.setCooldown(irepeat(1), irepeat(1)); - this.addFireAction(irepeat(5), irepeat(0), leveled(250, 10), [ + this.addTriggerAction(irepeat(5), [ new EffectTemplate(new DamageEffect(), { base: leveled(20), span: leveled(25) }) - ]); + ], leveled(300, 10), irepeat(0), leveled(40, 2)); } } } diff --git a/src/core/equipments/ShieldTransfer.spec.ts b/src/core/equipments/ShieldTransfer.spec.ts index 803a27c..5a004bb 100644 --- a/src/core/equipments/ShieldTransfer.spec.ts +++ b/src/core/equipments/ShieldTransfer.spec.ts @@ -6,30 +6,30 @@ module TK.SpaceTac.Equipments { let equipment = template.generate(1); expect(equipment.requirements).toEqual({ "skill_gravity": 2 }); expect(equipment.cooldown).toEqual(new Cooldown(3, 3)); - expect(equipment.action).toEqual(new FireWeaponAction(equipment, 3, 0, 250, [ + expect(equipment.action).toEqual(new TriggerAction(equipment, [ new ValueTransferEffect("shield", -40) - ])); + ], 3, 0, 250)); equipment = template.generate(2); expect(equipment.requirements).toEqual({ "skill_gravity": 3 }); expect(equipment.cooldown).toEqual(new Cooldown(3, 3)); - expect(equipment.action).toEqual(new FireWeaponAction(equipment, 3, 0, 270, [ + expect(equipment.action).toEqual(new TriggerAction(equipment, [ new ValueTransferEffect("shield", -44) - ])); + ], 3, 0, 270)); equipment = template.generate(3); expect(equipment.requirements).toEqual({ "skill_gravity": 5 }); expect(equipment.cooldown).toEqual(new Cooldown(3, 3)); - expect(equipment.action).toEqual(new FireWeaponAction(equipment, 3, 0, 294, [ + expect(equipment.action).toEqual(new TriggerAction(equipment, [ new ValueTransferEffect("shield", -49) - ])); + ], 3, 0, 294)); equipment = template.generate(10); expect(equipment.requirements).toEqual({ "skill_gravity": 26 }); expect(equipment.cooldown).toEqual(new Cooldown(3, 3)); - expect(equipment.action).toEqual(new FireWeaponAction(equipment, 3, 0, 574, [ + expect(equipment.action).toEqual(new TriggerAction(equipment, [ new ValueTransferEffect("shield", -105) - ])); + ], 3, 0, 574)); }) }) } diff --git a/src/core/equipments/ShieldTransfer.ts b/src/core/equipments/ShieldTransfer.ts index 5ea3a92..c8f8f3b 100644 --- a/src/core/equipments/ShieldTransfer.ts +++ b/src/core/equipments/ShieldTransfer.ts @@ -7,9 +7,9 @@ module TK.SpaceTac.Equipments { this.setSkillsRequirements({ "skill_gravity": leveled(2, 1.5) }); this.setCooldown(irepeat(3), irepeat(3)); - this.addFireAction(irepeat(3), irepeat(0), leveled(250, 20), [ + this.addTriggerAction(irepeat(3), [ new EffectTemplate(new ValueTransferEffect("shield"), { "amount": leveled(-40, -4) }) - ]); + ], irepeat(0), leveled(250, 20)); } } } diff --git a/src/core/equipments/Shields.spec.ts b/src/core/equipments/Shields.spec.ts index 8a0eed8..72700a2 100644 --- a/src/core/equipments/Shields.spec.ts +++ b/src/core/equipments/Shields.spec.ts @@ -32,7 +32,7 @@ module TK.SpaceTac.Equipments { expect(equipment.effects).toEqual([ new AttributeEffect("shield_capacity", 60), ]); - expect(equipment.action).toEqual(new FireWeaponAction(equipment, 2, 0, 300, [new RepelEffect(100)])); + expect(equipment.action).toEqual(new TriggerAction(equipment, [new RepelEffect(100)], 2, 0, 300)); expect(equipment.price).toEqual(140); equipment = template.generate(2); @@ -40,7 +40,7 @@ module TK.SpaceTac.Equipments { expect(equipment.effects).toEqual([ new AttributeEffect("shield_capacity", 84), ]); - expect(equipment.action).toEqual(new FireWeaponAction(equipment, 2, 0, 310, [new RepelEffect(105)])); + expect(equipment.action).toEqual(new TriggerAction(equipment, [new RepelEffect(105)], 2, 0, 310)); expect(equipment.price).toEqual(490); equipment = template.generate(3); @@ -48,7 +48,7 @@ module TK.SpaceTac.Equipments { expect(equipment.effects).toEqual([ new AttributeEffect("shield_capacity", 112), ]); - expect(equipment.action).toEqual(new FireWeaponAction(equipment, 2, 0, 322, [new RepelEffect(111)])); + expect(equipment.action).toEqual(new TriggerAction(equipment, [new RepelEffect(111)], 2, 0, 322)); expect(equipment.price).toEqual(1190); equipment = template.generate(10); @@ -56,7 +56,7 @@ module TK.SpaceTac.Equipments { expect(equipment.effects).toEqual([ new AttributeEffect("shield_capacity", 448), ]); - expect(equipment.action).toEqual(new FireWeaponAction(equipment, 2, 0, 462, [new RepelEffect(181)])); + expect(equipment.action).toEqual(new TriggerAction(equipment, [new RepelEffect(181)], 2, 0, 462)); expect(equipment.price).toEqual(15890); }); diff --git a/src/core/equipments/Shields.ts b/src/core/equipments/Shields.ts index 1668342..b35c4ce 100644 --- a/src/core/equipments/Shields.ts +++ b/src/core/equipments/Shields.ts @@ -16,9 +16,9 @@ module TK.SpaceTac.Equipments { this.setSkillsRequirements({ "skill_gravity": leveled(2, 3) }); this.addAttributeEffect("shield_capacity", leveled(60)); - this.addFireAction(irepeat(2), irepeat(0), leveled(300, 10), [ + this.addTriggerAction(irepeat(2), [ new EffectTemplate(new RepelEffect(), { value: leveled(100, 5) }) - ]); + ], irepeat(0), leveled(300, 10)); } } diff --git a/src/ui/battle/ActionTooltip.spec.ts b/src/ui/battle/ActionTooltip.spec.ts index 09e5d99..3f24679 100644 --- a/src/ui/battle/ActionTooltip.spec.ts +++ b/src/ui/battle/ActionTooltip.spec.ts @@ -12,7 +12,7 @@ module TK.SpaceTac.UI.Specs { let action1 = new MoveAction(new Equipment()); nn(action1.equipment).name = "Engine"; action1.name = "Move"; - let action2 = new FireWeaponAction(new Equipment(), 2, 50, 0, [new DamageEffect(12)]); + let action2 = new TriggerAction(new Equipment(), [new DamageEffect(12)], 2, 50, 0); nn(action2.equipment).name = "Weapon"; action2.name = "Fire"; let action3 = new EndTurnAction(); diff --git a/src/ui/battle/ActionTooltip.ts b/src/ui/battle/ActionTooltip.ts index 5e31100..2690d48 100644 --- a/src/ui/battle/ActionTooltip.ts +++ b/src/ui/battle/ActionTooltip.ts @@ -51,7 +51,7 @@ module TK.SpaceTac.UI { let description = action.getEffectsDescription(); if (description) { - filler.addText(0, 150, description, "#ffffff", 14); + filler.addText(30, 170, description, "#ffffff", 16); } let shortcut = ""; diff --git a/src/ui/battle/Targetting.spec.ts b/src/ui/battle/Targetting.spec.ts index f7b754a..5f0ce18 100644 --- a/src/ui/battle/Targetting.spec.ts +++ b/src/ui/battle/Targetting.spec.ts @@ -44,37 +44,32 @@ module TK.SpaceTac.UI.Specs { it("updates impact indicators on ships inside the blast radius", function () { let targetting = newTargetting(); let ship = nn(testgame.battleview.battle.playing_ship); + let impacts = targetting.impact_indicators; + let action = new TriggerAction(new Equipment(), [], 1, 0, 50); - let collect = spyOn(testgame.battleview.battle, "collectShipsInCircle").and.returnValues( + let collect = spyOn(action, "getImpactedShips").and.returnValues( [new Ship(), new Ship(), new Ship()], [new Ship(), new Ship()], []); - targetting.updateImpactIndicators(ship, new Target(20, 10), 50); + targetting.updateImpactIndicators(impacts, ship, action, new Target(20, 10)); expect(collect).toHaveBeenCalledTimes(1); - expect(collect).toHaveBeenCalledWith(new Target(20, 10), 50, true); - expect(targetting.fire_impact.children.length).toBe(3); - expect(targetting.fire_impact.visible).toBe(true); + expect(collect).toHaveBeenCalledWith(ship, new Target(20, 10), ship.location); + expect(targetting.impact_indicators.children.length).toBe(3); + expect(targetting.impact_indicators.visible).toBe(true); - targetting.updateImpactIndicators(ship, new Target(20, 11), 50); + targetting.updateImpactIndicators(impacts, ship, action, new Target(20, 11)); expect(collect).toHaveBeenCalledTimes(2); - expect(collect).toHaveBeenCalledWith(new Target(20, 11), 50, true); - expect(targetting.fire_impact.children.length).toBe(2); - expect(targetting.fire_impact.visible).toBe(true); + expect(collect).toHaveBeenCalledWith(ship, new Target(20, 11), ship.location); + expect(targetting.impact_indicators.children.length).toBe(2); + expect(targetting.impact_indicators.visible).toBe(true); - let target = Target.newFromShip(new Ship()); - targetting.updateImpactIndicators(ship, target, 0); - - expect(collect).toHaveBeenCalledTimes(2); - expect(targetting.fire_impact.children.length).toBe(1); - expect(targetting.fire_impact.visible).toBe(true); - - targetting.updateImpactIndicators(ship, new Target(20, 12), 50); + targetting.updateImpactIndicators(impacts, ship, action, new Target(20, 12)); expect(collect).toHaveBeenCalledTimes(3); - expect(collect).toHaveBeenCalledWith(new Target(20, 12), 50, true); - expect(targetting.fire_impact.visible).toBe(false); + expect(collect).toHaveBeenCalledWith(ship, new Target(20, 12), ship.location); + expect(targetting.impact_indicators.visible).toBe(false); }) it("updates graphics from simulation", function () { @@ -109,8 +104,8 @@ module TK.SpaceTac.UI.Specs { expect(targetting.fire_arrow.visible).toBe(true); expect(targetting.fire_arrow.position).toEqual(jasmine.objectContaining({ x: 156, y: 65 })); expect(targetting.fire_arrow.rotation).toBeCloseTo(0.534594, 5); - expect(targetting.fire_blast.visible).toBe(true); - expect(targetting.fire_blast.position).toEqual(jasmine.objectContaining({ x: 156, y: 65 })); + expect(targetting.impact_area.visible).toBe(true); + expect(targetting.impact_area.position).toEqual(jasmine.objectContaining({ x: 156, y: 65 })); expect(targetting.move_ghost.visible).toBe(true); expect(targetting.move_ghost.position).toEqual(jasmine.objectContaining({ x: 80, y: 20 })); expect(targetting.move_ghost.rotation).toBeCloseTo(0.534594, 5); diff --git a/src/ui/battle/Targetting.ts b/src/ui/battle/Targetting.ts index 99c306a..0104dbf 100644 --- a/src/ui/battle/Targetting.ts +++ b/src/ui/battle/Targetting.ts @@ -15,14 +15,14 @@ module TK.SpaceTac.UI { mode: ActionTargettingMode simulation = new MoveFireResult() - // Movement projector + // Move and fire lines drawn_info: Phaser.Graphics move_ghost: Phaser.Image - - // Fire projector fire_arrow: Phaser.Image - fire_blast: Phaser.Image - fire_impact: Phaser.Group + + // Impact area + impact_area: Phaser.Graphics + impact_indicators: Phaser.Group // Collaborators to update actionbar: ActionBar @@ -50,17 +50,16 @@ module TK.SpaceTac.UI { this.fire_arrow = this.view.newImage("battle-hud-simulator-ok"); this.fire_arrow.anchor.set(1, 0.5); this.fire_arrow.visible = false; - this.fire_impact = new Phaser.Group(view.game); - this.fire_impact.visible = false; - this.fire_blast = new Phaser.Image(view.game, 0, 0, "battle-arena-blast"); - this.fire_blast.anchor.set(0.5, 0.5); - this.fire_blast.visible = false; + this.impact_indicators = new Phaser.Group(view.game); + this.impact_indicators.visible = false; + this.impact_area = new Phaser.Graphics(view.game); + this.impact_area.visible = false; - this.container.add(this.fire_impact); - this.container.add(this.fire_blast); + this.container.add(this.impact_indicators); + this.container.add(this.impact_area); this.container.add(this.drawn_info); - this.container.add(this.fire_arrow); this.container.add(this.move_ghost); + this.container.add(this.fire_arrow); } /** @@ -118,31 +117,58 @@ module TK.SpaceTac.UI { } /** - * Update impact indicators + * Update impact indicators (highlighting impacted ships) */ - updateImpactIndicators(ship: Ship, target: Target, radius: number): void { - let ships: Ship[]; - if (radius) { - let battle = ship.getBattle(); - if (battle) { - ships = battle.collectShipsInCircle(target, radius, true); - } else { - ships = []; - } - } else { - ships = target.ship ? [target.ship] : []; - } - + updateImpactIndicators(impacts: Phaser.Group, ship: Ship, action: BaseAction, target: Target, source: IArenaLocation = ship.location): void { + let ships = action.getImpactedShips(ship, target, source); if (ships.length) { - this.fire_impact.removeAll(true); + // TODO differential + impacts.removeAll(true); ships.forEach(iship => { let indicator = this.view.newImage("battle-hud-ship-impacted", iship.arena_x, iship.arena_y); indicator.anchor.set(0.5); - this.fire_impact.add(indicator); + impacts.add(indicator); }); - this.fire_impact.visible = true; + impacts.visible = true; } else { - this.fire_impact.visible = false; + impacts.visible = false; + } + } + + /** + * Update impact graphics (area display) + */ + updateImpactArea(area: Phaser.Graphics, action: BaseAction): void { + area.clear(); + + let color = 0; + let radius = 0; + let angle = 0; + if (action instanceof TriggerAction) { + color = 0x90481e; + if (action.angle) { + angle = (action.angle * 0.5) * Math.PI / 180; + radius = action.range; + } else { + radius = action.blast; + } + } else if (action instanceof DeployDroneAction) { + color = 0xe9f2f9; + radius = action.effect_radius; + } else if (action instanceof ToggleAction) { + color = 0xd3e448; + radius = action.radius; + } + + if (radius) { + area.lineStyle(2, 0x90481e, 0.6); + area.beginFill(0x90481e, 0.2); + if (angle) { + area.arc(0, 0, radius, angle, -angle, true); + } else { + area.drawCircle(0, 0, radius * 2); + } + area.endFill(); } } @@ -177,24 +203,24 @@ module TK.SpaceTac.UI { } if (simulation.need_fire) { - let blast = this.action.getBlastRadius(this.ship); - if (blast) { - this.fire_blast.position.set(this.target.x, this.target.y); - this.fire_blast.scale.set(blast * 2 / 365); - this.fire_blast.alpha = simulation.can_fire ? 1 : 0.5; - this.fire_blast.visible = true; + if (this.action instanceof TriggerAction && this.action.angle) { + this.impact_area.position.set(simulation.move_location.x, simulation.move_location.y); + this.impact_area.rotation = arenaAngle(simulation.move_location, simulation.fire_location); } else { - this.fire_blast.visible = false; + this.impact_area.position.set(this.target.x, this.target.y); } - this.updateImpactIndicators(this.ship, this.target, blast); + this.impact_area.alpha = simulation.can_fire ? 1 : 0.5; + this.impact_area.visible = true; + + this.updateImpactIndicators(this.impact_indicators, this.ship, this.action, this.target, this.simulation.move_location); this.fire_arrow.position.set(this.target.x, this.target.y); this.fire_arrow.rotation = angle; this.view.changeImage(this.fire_arrow, simulation.complete ? "battle-hud-simulator-ok" : "battle-hud-simulator-power"); this.fire_arrow.visible = true; } else { - this.fire_blast.visible = false; - this.fire_impact.visible = false; + this.impact_area.visible = false; + this.impact_indicators.visible = false; this.fire_arrow.visible = false; } } else { @@ -203,7 +229,7 @@ module TK.SpaceTac.UI { 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.impact_area.visible = false; } this.container.visible = true; } else { @@ -270,6 +296,8 @@ module TK.SpaceTac.UI { this.view.changeImage(this.move_ghost, `ship-${this.ship.model.code}-sprite`); this.move_ghost.scale.set(0.4); + this.updateImpactArea(this.impact_area, this.action); + this.setTarget(action.getDefaultTarget(this.ship)); } else { this.ship = null; diff --git a/src/ui/battle/WeaponEffect.ts b/src/ui/battle/WeaponEffect.ts index 765da7f..578e943 100644 --- a/src/ui/battle/WeaponEffect.ts +++ b/src/ui/battle/WeaponEffect.ts @@ -161,7 +161,7 @@ module TK.SpaceTac.UI { missile.rotation = arenaAngle(this.source, this.destination); this.layer.add(missile); - let blast_radius = this.weapon.action ? this.weapon.action.getBlastRadius(this.ship) : 0; + let blast_radius = (this.weapon.action instanceof TriggerAction) ? this.weapon.action.blast : 0; let projectile_duration = arenaDistance(this.source, this.destination) * 1.5; let tween = this.ui.tweens.create(missile); @@ -185,7 +185,7 @@ module TK.SpaceTac.UI { }); tween.start(); - if (blast_radius > 0 && this.weapon.action instanceof FireWeaponAction) { + if (blast_radius > 0 && this.weapon.action instanceof TriggerAction) { if (any(this.weapon.action.effects, effect => effect instanceof DamageEffect)) { let ships = this.arena.getShipsInCircle(new ArenaCircleArea(this.destination.x, this.destination.y, blast_radius)); ships.forEach(sprite => { diff --git a/yarn.lock b/yarn.lock index 1ee9809..1e46572 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1315,7 +1315,7 @@ istanbul@0.4.5, istanbul@^0.4.0: which "^1.1.1" wordwrap "^1.0.0" -jasmine-core@^2.8.0, jasmine-core@~2.8.0: +jasmine-core@2.8.0, jasmine-core@~2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.8.0.tgz#bcc979ae1f9fd05701e45e52e65d3a5d63f1a24e"