Simplified drone system
This commit is contained in:
parent
cbb860b1ec
commit
ee1cbc9c14
|
@ -125,11 +125,8 @@ the end of turn (it will only start cooling down after being overheated).
|
||||||
|
|
||||||
Drones are static objects, deployed by ships, that apply effects in a circular zone around themselves.
|
Drones are static objects, deployed by ships, that apply effects in a circular zone around themselves.
|
||||||
|
|
||||||
A drone lasts for a given number of battle cycles. For example, if there are 8 ships in play, a 2-cycles
|
Drones activate between two ship turns. At each activation, the drone effects are applied to any ship
|
||||||
drone will try to activate 16 times, before being destroyed.
|
in the surrounding zone. A drone will live for a given number of activations, before being destroyed.
|
||||||
|
|
||||||
All drones activate between two ship turns. At each activation, the drone effects are applied to any ship
|
|
||||||
in the surrounding zone, except if less than a battle cycle passed since last activation for this ship.
|
|
||||||
|
|
||||||
Drones are fully autonomous, and once deployed, are not controlled by their owner ship.
|
Drones are fully autonomous, and once deployed, are not controlled by their owner ship.
|
||||||
|
|
||||||
|
|
2
TODO
2
TODO
|
@ -27,7 +27,7 @@
|
||||||
* Actions: fix targetting not resetting when using keyboard shortcuts
|
* Actions: fix targetting not resetting when using keyboard shortcuts
|
||||||
* Suspend AI operation when the game is paused (window not focused)
|
* Suspend AI operation when the game is paused (window not focused)
|
||||||
* Add actions with cost dependent of distance (like current move actions)
|
* Add actions with cost dependent of distance (like current move actions)
|
||||||
* Keep move actions out of arena borders
|
* Keep move and drone actions out of arena borders
|
||||||
* Find incentives to move from starting position
|
* Find incentives to move from starting position
|
||||||
* Outcome: add battle statistics and/or honors
|
* Outcome: add battle statistics and/or honors
|
||||||
* Outcome: disable the loot button if there is no loot
|
* Outcome: disable the loot button if there is no loot
|
||||||
|
|
|
@ -53,45 +53,6 @@ module TS.SpaceTac {
|
||||||
expect(effect.getApplyCalls()).toEqual([ship1, ship2]);
|
expect(effect.getApplyCalls()).toEqual([ship1, ship2]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("maintains ship application countdown", function () {
|
|
||||||
let battle = new Battle();
|
|
||||||
spyOn(battle, "getCycleLength").and.returnValue(7);
|
|
||||||
let ship = new Ship(battle.fleets[0]);
|
|
||||||
let drone = new Drone(ship, "test", 2);
|
|
||||||
|
|
||||||
expect(drone.getShipCountdown(ship)).toBe(0);
|
|
||||||
drone.startShipCountdown(ship);
|
|
||||||
expect(drone.getShipCountdown(ship)).toBe(7);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("applies at most once per battle cycle", function () {
|
|
||||||
let battle = new Battle();
|
|
||||||
let ship1 = new Ship(battle.fleets[0], "ship1");
|
|
||||||
ship1.setArenaPosition(0, 0);
|
|
||||||
let ship2 = new Ship(battle.fleets[1], "ship2");
|
|
||||||
ship2.setArenaPosition(100, 100);
|
|
||||||
battle.throwInitiative();
|
|
||||||
expect(battle.getCycleLength()).toEqual(2);
|
|
||||||
|
|
||||||
let [drone, effect] = newTestDrone(2, 2, 8, ship1);
|
|
||||||
expect(effect.getApplyCalls()).toEqual([]);
|
|
||||||
|
|
||||||
drone.activate();
|
|
||||||
expect(effect.getApplyCalls()).toEqual([ship1]);
|
|
||||||
|
|
||||||
drone.activate();
|
|
||||||
expect(effect.getApplyCalls()).toEqual([]);
|
|
||||||
|
|
||||||
drone.activate();
|
|
||||||
expect(effect.getApplyCalls()).toEqual([ship1]);
|
|
||||||
|
|
||||||
drone.activate();
|
|
||||||
expect(effect.getApplyCalls()).toEqual([]);
|
|
||||||
|
|
||||||
drone.activate();
|
|
||||||
expect(effect.getApplyCalls()).toEqual([ship1]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("signals the need for destruction after its lifetime", function () {
|
it("signals the need for destruction after its lifetime", function () {
|
||||||
let battle = new Battle();
|
let battle = new Battle();
|
||||||
let owner = new Ship(battle.fleets[0]);
|
let owner = new Ship(battle.fleets[0]);
|
||||||
|
|
|
@ -23,14 +23,11 @@ module TS.SpaceTac {
|
||||||
// Effects to apply
|
// Effects to apply
|
||||||
effects: BaseEffect[] = [];
|
effects: BaseEffect[] = [];
|
||||||
|
|
||||||
// Cycle countdown for ships
|
|
||||||
countdown: [Ship, number][] = [];
|
|
||||||
|
|
||||||
constructor(owner: Ship, code = "drone", base_duration = 1) {
|
constructor(owner: Ship, code = "drone", base_duration = 1) {
|
||||||
this.battle = owner.getBattle() || new Battle();
|
this.battle = owner.getBattle() || new Battle();
|
||||||
this.owner = owner;
|
this.owner = owner;
|
||||||
this.code = code;
|
this.code = code;
|
||||||
this.duration = base_duration * this.battle.getCycleLength();
|
this.duration = base_duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -44,42 +41,11 @@ module TS.SpaceTac {
|
||||||
return `For ${this.duration} activation${this.duration > 1 ? "s" : ""}:\n${effects}`;
|
return `For ${this.duration} activation${this.duration > 1 ? "s" : ""}:\n${effects}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get countdown until next activation for a given ship
|
|
||||||
*/
|
|
||||||
getShipCountdown(ship: Ship): number {
|
|
||||||
let countdown = 0;
|
|
||||||
this.countdown.forEach(([iship, icountdown]) => {
|
|
||||||
if (iship === ship) {
|
|
||||||
countdown = icountdown;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return countdown;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start the countdown for a given ship
|
|
||||||
*/
|
|
||||||
startShipCountdown(ship: Ship): void {
|
|
||||||
let found = false;
|
|
||||||
this.countdown = this.countdown.map(([iship, countdown]): [Ship, number] => {
|
|
||||||
if (iship === ship) {
|
|
||||||
found = true;
|
|
||||||
return [iship, this.battle.getCycleLength()];
|
|
||||||
} else {
|
|
||||||
return [iship, countdown];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (!found) {
|
|
||||||
this.countdown.push([ship, this.battle.getCycleLength()]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the list of affected ships.
|
* Get the list of affected ships.
|
||||||
*/
|
*/
|
||||||
getAffectedShips(): Ship[] {
|
getAffectedShips(): Ship[] {
|
||||||
let ships = ifilter(this.battle.iships(), ship => ship.alive && ship.isInCircle(this.x, this.y, this.radius) && this.getShipCountdown(ship) == 0);
|
let ships = ifilter(this.battle.iships(), ship => ship.alive && ship.isInCircle(this.x, this.y, this.radius));
|
||||||
return imaterialize(ships);
|
return imaterialize(ships);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,7 +61,6 @@ module TS.SpaceTac {
|
||||||
}
|
}
|
||||||
|
|
||||||
ships.forEach(ship => {
|
ships.forEach(ship => {
|
||||||
this.startShipCountdown(ship);
|
|
||||||
this.effects.forEach(effect => effect.applyOnShip(ship));
|
this.effects.forEach(effect => effect.applyOnShip(ship));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -107,8 +72,6 @@ module TS.SpaceTac {
|
||||||
activate(log = true) {
|
activate(log = true) {
|
||||||
this.apply(this.getAffectedShips(), log);
|
this.apply(this.getAffectedShips(), log);
|
||||||
|
|
||||||
this.countdown = this.countdown.map(([ship, countdown]): [Ship, number] => [ship, countdown - 1]).filter(([ship, countdown]) => countdown > 0);
|
|
||||||
|
|
||||||
this.duration--;
|
this.duration--;
|
||||||
if (this.duration == 0) {
|
if (this.duration == 0) {
|
||||||
this.battle.removeDrone(this, log);
|
this.battle.removeDrone(this, log);
|
||||||
|
|
|
@ -30,7 +30,6 @@ module TS.SpaceTac {
|
||||||
let ship = battle.fleets[0].addShip();
|
let ship = battle.fleets[0].addShip();
|
||||||
ship.setArenaPosition(0, 0);
|
ship.setArenaPosition(0, 0);
|
||||||
battle.playing_ship = ship;
|
battle.playing_ship = ship;
|
||||||
spyOn(battle, "getCycleLength").and.returnValue(4);
|
|
||||||
TestTools.setShipAP(ship, 3);
|
TestTools.setShipAP(ship, 3);
|
||||||
let equipment = new Equipment(SlotType.Weapon, "testdrone");
|
let equipment = new Equipment(SlotType.Weapon, "testdrone");
|
||||||
let action = new DeployDroneAction(equipment, 2, 8, 2, 4, [new DamageEffect(50)]);
|
let action = new DeployDroneAction(equipment, 2, 8, 2, 4, [new DamageEffect(50)]);
|
||||||
|
@ -44,7 +43,7 @@ module TS.SpaceTac {
|
||||||
|
|
||||||
let drone = battle.drones[0];
|
let drone = battle.drones[0];
|
||||||
expect(drone.code).toEqual("testdrone");
|
expect(drone.code).toEqual("testdrone");
|
||||||
expect(drone.duration).toEqual(8);
|
expect(drone.duration).toEqual(2);
|
||||||
expect(drone.owner).toBe(ship);
|
expect(drone.owner).toBe(ship);
|
||||||
expect(drone.x).toEqual(5);
|
expect(drone.x).toEqual(5);
|
||||||
expect(drone.y).toEqual(0);
|
expect(drone.y).toEqual(0);
|
||||||
|
|
|
@ -65,7 +65,7 @@ module TS.SpaceTac {
|
||||||
}
|
}
|
||||||
|
|
||||||
getEffectsDescription(): string {
|
getEffectsDescription(): string {
|
||||||
let desc = `Deploy drone for ${this.lifetime} cycle${this.lifetime > 1 ? "s" : ""} (power usage ${this.power}, max range ${this.deploy_distance}km)`;
|
let desc = `Deploy drone for ${this.lifetime} activation${this.lifetime > 1 ? "s" : ""} (power usage ${this.power}, max range ${this.deploy_distance}km)`;
|
||||||
let effects = this.effects.map(effect => {
|
let effects = this.effects.map(effect => {
|
||||||
let suffix = `for ships in ${this.effect_radius}km radius`;
|
let suffix = `for ships in ${this.effect_radius}km radius`;
|
||||||
return "• " + effect.getDescription() + " " + suffix;
|
return "• " + effect.getDescription() + " " + suffix;
|
||||||
|
|
|
@ -5,26 +5,26 @@ module TS.SpaceTac.Equipments {
|
||||||
|
|
||||||
let equipment = template.generate(1);
|
let equipment = template.generate(1);
|
||||||
expect(equipment.requirements).toEqual({ "skill_human": 1 });
|
expect(equipment.requirements).toEqual({ "skill_human": 1 });
|
||||||
expect(equipment.action).toEqual(new DeployDroneAction(equipment, 4, 300, 1, 100, [new ValueEffect("hull", 30)]));
|
expect(equipment.action).toEqual(new DeployDroneAction(equipment, 4, 300, 10, 100, [new ValueEffect("hull", 10)]));
|
||||||
|
|
||||||
equipment = template.generate(2);
|
equipment = template.generate(2);
|
||||||
expect(equipment.requirements).toEqual({ "skill_human": 2 });
|
expect(equipment.requirements).toEqual({ "skill_human": 2 });
|
||||||
expect(equipment.action).toEqual(new DeployDroneAction(equipment, 4, 310, 1, 110, [new ValueEffect("hull", 33)]));
|
expect(equipment.action).toEqual(new DeployDroneAction(equipment, 4, 310, 11, 110, [new ValueEffect("hull", 12)]));
|
||||||
|
|
||||||
equipment = template.generate(3);
|
equipment = template.generate(3);
|
||||||
expect(equipment.requirements).toEqual({ "skill_human": 3 });
|
expect(equipment.requirements).toEqual({ "skill_human": 3 });
|
||||||
expect(equipment.action).toEqual(new DeployDroneAction(equipment, 4, 320, 1, 120, [new ValueEffect("hull", 36)]));
|
expect(equipment.action).toEqual(new DeployDroneAction(equipment, 4, 320, 12, 120, [new ValueEffect("hull", 14)]));
|
||||||
|
|
||||||
equipment = template.generate(10);
|
equipment = template.generate(10);
|
||||||
expect(equipment.requirements).toEqual({ "skill_human": 10 });
|
expect(equipment.requirements).toEqual({ "skill_human": 10 });
|
||||||
expect(equipment.action).toEqual(new DeployDroneAction(equipment, 4, 390, 2, 190, [new ValueEffect("hull", 57)]));
|
expect(equipment.action).toEqual(new DeployDroneAction(equipment, 4, 390, 19, 190, [new ValueEffect("hull", 28)]));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("generates a drone that may repair ships hull", function () {
|
it("generates a drone that may repair ships hull", function () {
|
||||||
let template = new RepairDrone();
|
let template = new RepairDrone();
|
||||||
|
|
||||||
let equipment = template.generate(1);
|
let equipment = template.generate(1);
|
||||||
expect(equipment.action).toEqual(new DeployDroneAction(equipment, 4, 300, 1, 100, [new ValueEffect("hull", 30)]));
|
expect(equipment.action).toEqual(new DeployDroneAction(equipment, 4, 300, 10, 100, [new ValueEffect("hull", 10)]));
|
||||||
|
|
||||||
let battle = new Battle();
|
let battle = new Battle();
|
||||||
let ship = battle.fleets[0].addShip();
|
let ship = battle.fleets[0].addShip();
|
||||||
|
@ -36,11 +36,11 @@ module TS.SpaceTac.Equipments {
|
||||||
|
|
||||||
expect(battle.drones.length).toBe(1);
|
expect(battle.drones.length).toBe(1);
|
||||||
let drone = battle.drones[0];
|
let drone = battle.drones[0];
|
||||||
expect(drone.duration).toBe(1);
|
expect(drone.duration).toBe(10);
|
||||||
ship.setAttribute("hull_capacity", 100);
|
ship.setAttribute("hull_capacity", 100);
|
||||||
ship.setValue("hull", 55);
|
ship.setValue("hull", 85);
|
||||||
drone.apply([ship]);
|
drone.apply([ship]);
|
||||||
expect(ship.getValue("hull")).toBe(85);
|
expect(ship.getValue("hull")).toBe(95);
|
||||||
drone.apply([ship]);
|
drone.apply([ship]);
|
||||||
expect(ship.getValue("hull")).toBe(100);
|
expect(ship.getValue("hull")).toBe(100);
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,8 +10,8 @@ module TS.SpaceTac.Equipments {
|
||||||
|
|
||||||
this.setSkillsRequirements({ "skill_human": 1 });
|
this.setSkillsRequirements({ "skill_human": 1 });
|
||||||
this.setCooldown(irepeat(1), istep(3, irepeat(0.2)));
|
this.setCooldown(irepeat(1), istep(3, irepeat(0.2)));
|
||||||
this.addDroneAction(irepeat(4), istep(300, irepeat(10)), istep(1, irepeat(0.2)), istep(100, irepeat(10)), [
|
this.addDroneAction(irepeat(4), istep(300, irepeat(10)), istep(10, irepeat(1)), istep(100, irepeat(10)), [
|
||||||
new EffectTemplate(new ValueEffect("hull"), { "value": istep(30, irepeat(3)) })
|
new EffectTemplate(new ValueEffect("hull"), { "value": istep(10, irepeat(2)) })
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,22 +4,25 @@ module TS.SpaceTac.UI {
|
||||||
*/
|
*/
|
||||||
export class ArenaDrone extends Phaser.Group {
|
export class ArenaDrone extends Phaser.Group {
|
||||||
// Link to view
|
// Link to view
|
||||||
view: BattleView;
|
view: BattleView
|
||||||
|
|
||||||
// Link to displayed drone
|
// Link to displayed drone
|
||||||
drone: Drone;
|
drone: Drone
|
||||||
|
|
||||||
// Sprite
|
// Sprite
|
||||||
sprite: Phaser.Button;
|
sprite: Phaser.Button
|
||||||
|
|
||||||
// Radius
|
// Radius
|
||||||
radius: Phaser.Graphics;
|
radius: Phaser.Graphics
|
||||||
|
|
||||||
// Activation effect
|
// Activation effect
|
||||||
activation: Phaser.Graphics;
|
activation: Phaser.Graphics
|
||||||
|
|
||||||
|
// Duration info
|
||||||
|
duration: Phaser.Text
|
||||||
|
|
||||||
// Destroyed state
|
// Destroyed state
|
||||||
destroyed = false;
|
destroyed = false
|
||||||
|
|
||||||
constructor(battleview: BattleView, drone: Drone) {
|
constructor(battleview: BattleView, drone: Drone) {
|
||||||
super(battleview.game);
|
super(battleview.game);
|
||||||
|
@ -32,7 +35,7 @@ module TS.SpaceTac.UI {
|
||||||
this.radius.beginFill(0xe9f2f9, 0.0);
|
this.radius.beginFill(0xe9f2f9, 0.0);
|
||||||
this.radius.drawCircle(0, 0, drone.radius * 2);
|
this.radius.drawCircle(0, 0, drone.radius * 2);
|
||||||
this.radius.endFill();
|
this.radius.endFill();
|
||||||
this.addChild(this.radius);
|
this.add(this.radius);
|
||||||
|
|
||||||
this.activation = new Phaser.Graphics(this.game, 0, 0);
|
this.activation = new Phaser.Graphics(this.game, 0, 0);
|
||||||
this.activation.lineStyle(2, 0xe9f2f9, 0.7);
|
this.activation.lineStyle(2, 0xe9f2f9, 0.7);
|
||||||
|
@ -40,12 +43,17 @@ module TS.SpaceTac.UI {
|
||||||
this.activation.drawCircle(0, 0, drone.radius * 2);
|
this.activation.drawCircle(0, 0, drone.radius * 2);
|
||||||
this.activation.endFill();
|
this.activation.endFill();
|
||||||
this.activation.visible = false;
|
this.activation.visible = false;
|
||||||
this.addChild(this.activation);
|
this.add(this.activation);
|
||||||
|
|
||||||
this.sprite = new Phaser.Button(this.game, 0, 0, `battle-actions-deploy-${drone.code}`);
|
this.sprite = new Phaser.Button(this.game, 0, 0, `battle-actions-deploy-${drone.code}`);
|
||||||
this.sprite.anchor.set(0.5, 0.5);
|
this.sprite.anchor.set(0.5, 0.5);
|
||||||
this.sprite.scale.set(0.1, 0.1);
|
this.sprite.scale.set(0.1, 0.1);
|
||||||
this.addChild(this.sprite);
|
this.add(this.sprite);
|
||||||
|
|
||||||
|
this.duration = new Phaser.Text(this.game, 0, 40, "", { font: "bold 16pt Arial", fill: "#ffdd4b" });
|
||||||
|
this.duration.anchor.set(0.5, 0.5);
|
||||||
|
this.duration.visible = false;
|
||||||
|
this.add(this.duration);
|
||||||
|
|
||||||
this.view.tooltip.bindDynamicText(this.sprite, () => {
|
this.view.tooltip.bindDynamicText(this.sprite, () => {
|
||||||
return this.destroyed ? "" : this.drone.getDescription();
|
return this.destroyed ? "" : this.drone.getDescription();
|
||||||
|
@ -90,8 +98,12 @@ module TS.SpaceTac.UI {
|
||||||
* Set the tactical mode display
|
* Set the tactical mode display
|
||||||
*/
|
*/
|
||||||
setTacticalMode(active: boolean) {
|
setTacticalMode(active: boolean) {
|
||||||
|
if (active) {
|
||||||
|
this.duration.text = `${this.drone.duration}`;
|
||||||
|
}
|
||||||
this.sprite.rotation = active ? -this.rotation : 0;
|
this.sprite.rotation = active ? -this.rotation : 0;
|
||||||
this.sprite.scale.set(active ? 0.3 : 0.1);
|
this.sprite.scale.set(active ? 0.2 : 0.1);
|
||||||
|
this.view.animations.setVisible(this.duration, active, 200);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue