1
0
Fork 0

Improved battle "flow" (for better information reading)

This commit is contained in:
Michaël Lemaire 2017-12-04 19:12:06 +01:00
parent 90ec4491b2
commit bb780415a3
13 changed files with 504 additions and 243 deletions

View file

@ -357,8 +357,14 @@ module TK.SpaceTac {
applyOneAction(action: BaseAction, target?: Target): boolean {
let ship = this.playing_ship;
if (ship) {
if (!target) {
target = action.getDefaultTarget(ship);
}
if (action.apply(this, ship, target)) {
this.performChecks();
if (!this.ended) {
this.applyDiffs([new ShipActionEndedDiff(ship, action, target)]);
}
return true;
} else {
return false;

View file

@ -40,7 +40,11 @@ module TK.SpaceTac {
* This may not contain ALL the diffs needed, and should be called again while it returns diffs.
*/
checkAll(): BaseBattleDiff[] {
let diffs: BaseBattleDiff[];
let diffs: BaseBattleDiff[] = [];
if (this.battle.ended) {
return diffs;
}
diffs = this.checkAreaEffects();
if (diffs.length) {

View file

@ -3,7 +3,7 @@ module TK.SpaceTac {
export class TestTools {
// Create a battle between two fleets, with a fixed play order (owned ships, then enemy ships)
static createBattle(own_ships = 1, enemy_ships = 0): Battle {
static createBattle(own_ships = 1, enemy_ships = 1): Battle {
var fleet1 = new Fleet();
var fleet2 = new Fleet();

View file

@ -34,49 +34,73 @@ module TK.SpaceTac.Specs {
});
test.case("generates power for previous ship", check => {
let battle = TestTools.createBattle(1, 0);
let ship = battle.play_order[0];
TestTools.setShipAP(ship, 10, 3);
let weapon = TestTools.addWeapon(ship);
let battle = TestTools.createBattle(1, 1);
let [ship1, ship2] = battle.play_order;
TestTools.setShipAP(ship1, 10, 3);
let weapon = TestTools.addWeapon(ship1);
weapon.action = new ToggleAction(weapon, 2);
ship.setValue("power", 6);
ship1.setValue("power", 6);
TestTools.actionChain(check, battle, [
[ship, weapon.action, Target.newFromShip(ship)],
[ship, weapon.action, Target.newFromShip(ship)],
[ship, EndTurnAction.SINGLETON, Target.newFromShip(ship)],
[ship, weapon.action, Target.newFromShip(ship)],
[ship, EndTurnAction.SINGLETON, Target.newFromShip(ship)],
[ship, EndTurnAction.SINGLETON, Target.newFromShip(ship)],
[ship, weapon.action, Target.newFromShip(ship)],
[ship, EndTurnAction.SINGLETON, Target.newFromShip(ship)],
[ship1, weapon.action, Target.newFromShip(ship1)],
[ship1, weapon.action, Target.newFromShip(ship1)],
[ship1, EndTurnAction.SINGLETON, Target.newFromShip(ship1)],
[ship2, EndTurnAction.SINGLETON, Target.newFromShip(ship2)],
[ship1, weapon.action, Target.newFromShip(ship1)],
[ship1, EndTurnAction.SINGLETON, Target.newFromShip(ship1)],
[ship2, EndTurnAction.SINGLETON, Target.newFromShip(ship2)],
[ship1, EndTurnAction.SINGLETON, Target.newFromShip(ship1)],
[ship2, EndTurnAction.SINGLETON, Target.newFromShip(ship2)],
[ship1, weapon.action, Target.newFromShip(ship1)],
[ship1, EndTurnAction.SINGLETON, Target.newFromShip(ship1)],
], [
check => {
check.equals(ship.getValue("power"), 6, "power value");
check.equals(ship1.getValue("power"), 6, "power value");
check.same(battle.playing_ship, ship1);
},
check => {
check.equals(ship.getValue("power"), 4, "power value");
check.equals(ship1.getValue("power"), 4, "power value");
check.same(battle.playing_ship, ship1);
},
check => {
check.equals(ship.getValue("power"), 6, "power value");
check.equals(ship1.getValue("power"), 6, "power value");
check.same(battle.playing_ship, ship1);
},
check => {
check.equals(ship.getValue("power"), 9, "power value");
check.equals(ship1.getValue("power"), 9, "power value");
check.same(battle.playing_ship, ship2);
},
check => {
check.equals(ship.getValue("power"), 7, "power value");
check.equals(ship1.getValue("power"), 9, "power value");
check.same(battle.playing_ship, ship1);
},
check => {
check.equals(ship.getValue("power"), 8, "power value");
check.equals(ship1.getValue("power"), 7, "power value");
check.same(battle.playing_ship, ship1);
},
check => {
check.equals(ship.getValue("power"), 9, "power value");
check.equals(ship1.getValue("power"), 8, "power value");
check.same(battle.playing_ship, ship2);
},
check => {
check.equals(ship.getValue("power"), 10, "power value");
check.equals(ship1.getValue("power"), 8, "power value");
check.same(battle.playing_ship, ship1);
},
check => {
check.equals(ship.getValue("power"), 10, "power value");
check.equals(ship1.getValue("power"), 9, "power value");
check.same(battle.playing_ship, ship2);
},
check => {
check.equals(ship1.getValue("power"), 9, "power value");
check.same(battle.playing_ship, ship1);
},
check => {
check.equals(ship1.getValue("power"), 10, "power value");
check.same(battle.playing_ship, ship1);
},
check => {
check.equals(ship1.getValue("power"), 10, "power value");
check.same(battle.playing_ship, ship2);
},
]);
});

View file

@ -38,6 +38,7 @@ module TK.SpaceTac.Specs {
test.case("applies and reverts", check => {
let battle = TestTools.createBattle();
let ship = battle.play_order[0];
ship.setArenaPosition(500, 600)
TestTools.setShipAP(ship, 20);
ship.setValue("power", 5);
@ -47,16 +48,16 @@ module TK.SpaceTac.Specs {
ship.addSlot(SlotType.Engine).attach(engine);
TestTools.actionChain(check, battle, [
[ship, action, Target.newFromLocation(10, 5)],
[ship, action, Target.newFromLocation(510, 605)],
], [
check => {
check.equals(ship.arena_x, 0, "ship X");
check.equals(ship.arena_y, 0, "ship Y");
check.equals(ship.arena_x, 500, "ship X");
check.equals(ship.arena_y, 600, "ship Y");
check.equals(ship.getValue("power"), 5, "power");
},
check => {
check.nears(ship.arena_x, 4.382693, 5, "ship X");
check.nears(ship.arena_y, 2.191346, 5, "ship Y");
check.nears(ship.arena_x, 504.382693, 5, "ship X");
check.nears(ship.arena_y, 602.191346, 5, "ship Y");
check.equals(ship.getValue("power"), 0, "power");
}
]);

View file

@ -0,0 +1,23 @@
/// <reference path="BaseBattleDiff.ts"/>
module TK.SpaceTac {
/**
* A ship action is fully ended
*
* This does not do anything, it is just used to mark the effective end of the action diffs (battle checks included)
*/
export class ShipActionEndedDiff extends BaseBattleShipDiff {
// Action applied
action: RObjectId
// Target for the action
target: Target
constructor(ship: Ship, action: BaseAction, target: Target) {
super(ship);
this.action = action.id;
this.target = target;
}
}
}

View file

@ -60,32 +60,44 @@ module TK.SpaceTac.UI {
battleview.tooltip.bindStaticText(button, "Game options");
// Log processing
battleview.log_processor.register(event => {
if (!(event instanceof BaseBattleShipDiff) || !this.ship || !this.ship.is(event.ship_id)) {
return 0;
battleview.log_processor.register(diff => {
if (!(diff instanceof BaseBattleShipDiff) || !this.ship || !this.ship.is(diff.ship_id)) {
return {};
}
if (event instanceof ShipValueDiff) {
if (event.code == "power") {
this.updatePower();
this.action_icons.forEach(action => action.refresh());
if (diff instanceof ShipValueDiff && diff.code == "power") {
return {
background: async () => {
this.updatePower();
this.action_icons.forEach(action => action.refresh());
}
}
} else if (event instanceof ShipAttributeDiff) {
if (event.code == "power_capacity") {
this.updatePower();
} else if (diff instanceof ShipAttributeDiff && diff.code == "power_capacity") {
return {
background: async () => this.updatePower()
}
} else if (event instanceof ShipActionUsedDiff || event instanceof ShipActionToggleDiff) {
this.action_icons.forEach(action => action.refresh());
} else if (event instanceof ShipCooldownDiff) {
let icons = this.action_icons.filter(icon => icon.action.equipment && icon.action.equipment.is(event.equipment));
icons.forEach(icon => icon.refresh());
} else if (diff instanceof ShipActionUsedDiff || diff instanceof ShipActionToggleDiff) {
return {
background: async () => this.action_icons.forEach(action => action.refresh())
}
} else if (diff instanceof ShipCooldownDiff) {
return {
background: async () => {
let icons = this.action_icons.filter(icon => icon.action.equipment && icon.action.equipment.is(diff.equipment));
icons.forEach(icon => icon.refresh());
}
}
} else {
return {}
}
return 0;
});
battleview.log_processor.watchForShipChange(ship => {
this.setShip(ship);
return 0;
return {
background: async () => {
this.setShip(ship);
}
}
});
this.setInteractive(false);
}

View file

@ -69,8 +69,11 @@ module TK.SpaceTac.UI {
this.container.onDestroy.add(() => this.destroy());
view.log_processor.watchForShipChange(ship => {
this.setShipPlaying(ship);
return 0;
return {
foreground: async () => {
await this.setShipPlaying(ship)
}
}
});
}
@ -192,19 +195,22 @@ module TK.SpaceTac.UI {
}
}
// Set the playing state on a ship sprite
setShipPlaying(ship: Ship | null): void {
/**
* Set the playing state on a ship sprite
*/
async setShipPlaying(ship: Ship | null, animate = true): Promise<void> {
if (this.playing) {
this.playing.setPlaying(false);
this.playing = null;
}
if (ship) {
var arena_ship = this.findShipSprite(ship);
let arena_ship = this.findShipSprite(ship);
if (arena_ship) {
this.layer_ships.bringToTop(arena_ship);
arena_ship.setPlaying(true);
await arena_ship.setPlaying(true, animate);
}
this.playing = arena_ship;
}
}

View file

@ -128,8 +128,8 @@ module TK.SpaceTac.UI {
}
// Log processing
this.battleview.log_processor.register(event => this.processLogEvent(event));
this.battleview.log_processor.registerForShip(ship, event => this.processShipLogEvent(event));
this.battleview.log_processor.register(diff => this.processBattleDiff(diff));
this.battleview.log_processor.registerForShip(ship, diff => this.processShipDiff(diff));
}
jasmineToString(): string {
@ -137,72 +137,136 @@ module TK.SpaceTac.UI {
}
/**
* Process a battle log event
* Process a battle diff
*/
private processLogEvent(event: BaseBattleDiff): number {
if (event instanceof ShipChangeDiff) {
private processBattleDiff(diff: BaseBattleDiff) {
if (diff instanceof ShipChangeDiff) {
this.updatePlayOrder();
}
return 0;
return {};
}
/**
* Process a log event for this ship
* Process a ship diff
*/
private processShipLogEvent(event: BaseBattleShipDiff): number {
if (event instanceof ShipEffectAddedDiff || event instanceof ShipEffectRemovedDiff) {
this.updateActiveEffects();
return 0;
} else if (event instanceof ShipValueDiff) {
if (event.code == "hull") {
this.toggle_hsp.manipulate("value")(1500);
this.hull_bar.setValue(this.ship.getValue("hull"), this.ship.getAttribute("hull_capacity") || 0);
this.hull_text.text = `${this.ship.getValue("hull")}`;
this.battleview.animations.blink(this.hull_text);
} else if (event.code == "shield") {
this.toggle_hsp.manipulate("value")(1500);
this.shield_bar.setValue(this.ship.getValue("shield"), this.ship.getAttribute("shield_capacity") || 0);
this.shield_text.text = `${this.ship.getValue("shield")}`;
this.battleview.animations.blink(this.shield_text);
/*if (this.shield_bar.getValue() == 0) {
this.displayEffect("Shield failure", false);
}*/
} else if (event.code == "power") {
this.toggle_hsp.manipulate("value")(1500);
this.power_text.text = `${this.ship.getValue("power")}`;
this.battleview.animations.blink(this.power_text);
private processShipDiff(diff: BaseBattleShipDiff): LogProcessorDelegate {
if (diff instanceof ShipEffectAddedDiff || diff instanceof ShipEffectRemovedDiff) {
return {
background: async () => this.updateActiveEffects()
}
return 0;
} else if (event instanceof ShipAttributeDiff) {
this.displayAttributeChanged(event);
return 0;
} else if (event instanceof ShipDamageDiff) {
this.displayEffect(`${event.hull + event.shield} damage`, false);
return 0;
} else if (event instanceof ShipActionToggleDiff) {
let action = this.ship.getAction(event.action);
if (action && action.equipment) {
let equname = action.equipment.name;
if (event.activated) {
this.displayEffect(`${equname} ON`, true);
} else {
this.displayEffect(`${equname} OFF`, false);
} else if (diff instanceof ShipValueDiff) {
return {
background: async (animate, timer) => {
if (animate) {
this.toggle_hsp.manipulate("value")(true);
}
if (diff.code == "hull") {
this.hull_bar.setValue(this.ship.getValue("hull"), this.ship.getAttribute("hull_capacity") || 0);
this.hull_text.text = `${this.ship.getValue("hull")}`;
if (animate) {
await this.battleview.animations.blink(this.hull_text);
}
} else if (diff.code == "shield") {
this.shield_bar.setValue(this.ship.getValue("shield"), this.ship.getAttribute("shield_capacity") || 0);
this.shield_text.text = `${this.ship.getValue("shield")}`;
if (animate) {
await this.battleview.animations.blink(this.shield_text);
}
/*if (this.shield_bar.getValue() == 0) {
this.displayEffect("Shield failure", false);
}*/
} else if (diff.code == "power") {
this.power_text.text = `${this.ship.getValue("power")}`;
if (animate) {
await this.battleview.animations.blink(this.power_text);
}
}
if (animate) {
await timer.sleep(500);
this.toggle_hsp.manipulate("value")(false);
}
}
this.updateEffectsRadius();
}
return 300;
} else if (event instanceof ShipActionUsedDiff) {
let action = this.ship.getAction(event.action);
if (action && !(action instanceof ToggleAction) && action.equipment) {
this.displayEffect(action.equipment.name, true);
} else if (diff instanceof ShipAttributeDiff) {
return {
background: async (animate, timer) => {
if (animate) {
this.displayAttributeChanged(diff)
await timer.sleep(1000);
}
}
}
return 300;
} else if (event instanceof ShipMoveDiff) {
this.moveTo(event.start.x, event.start.y, event.start.angle, false);
let duration = this.moveTo(event.end.x, event.end.y, event.end.angle, true, !!event.engine);
return duration;
} else if (diff instanceof ShipDamageDiff) {
return {
background: async (animate, timer) => {
if (animate) {
await this.displayEffect(`${diff.hull + diff.shield} damage`, false);
await timer.sleep(1000);
}
}
}
} else if (diff instanceof ShipActionToggleDiff) {
return {
foreground: async (animate, timer) => {
let action = this.ship.getAction(diff.action);
if (action && action.equipment) {
let equname = action.equipment.name;
if (animate) {
if (diff.activated) {
await this.displayEffect(`${equname} ON`, true);
} else {
await this.displayEffect(`${equname} OFF`, false);
}
}
this.updateEffectsRadius();
await timer.sleep(500);
}
}
}
} else if (diff instanceof ShipActionUsedDiff) {
let action = this.ship.getAction(diff.action);
if (action) {
if (!(action instanceof ToggleAction) && action.equipment) {
let equipment = action.equipment;
return {
foreground: async (animate, timer) => {
if (animate) {
await this.displayEffect(equipment.name, true);
await timer.sleep(300);
}
}
}
} else if (action instanceof EndTurnAction) {
return {
foreground: async (animate, timer) => {
if (animate) {
await this.displayEffect("End turn", true);
await timer.sleep(500);
}
}
}
} else {
return {};
}
} else {
return {};
}
} else if (diff instanceof ShipMoveDiff) {
return {
background: async (animate: boolean, timer: Timer) => {
this.moveTo(diff.start.x, diff.start.y, diff.start.angle, false);
let duration = this.moveTo(diff.end.x, diff.end.y, diff.end.angle, animate, !!diff.engine);
if (duration && animate) {
await timer.sleep(duration);
}
}
};
} else {
return 0;
return {};
}
}
@ -230,12 +294,14 @@ module TK.SpaceTac.UI {
*
* This will alter the HUD frame to show this state
*/
setPlaying(playing: boolean) {
async setPlaying(playing: boolean, animate = true): Promise<void> {
this.frame.alpha = playing ? 1 : 0.35;
this.frame.visible = this.ship.alive;
/*if (playing) {
this.battleview.animations.blink(this.frame);
}*/
if (playing && animate) {
this.battleview.audio.playOnce("battle-ship-change");
await this.battleview.animations.blink(this.frame);
}
}
/**
@ -274,7 +340,7 @@ module TK.SpaceTac.UI {
/**
* Briefly show an effect on this ship
*/
displayEffect(message: string, beneficial: boolean) {
async displayEffect(message: string, beneficial: boolean) {
if (!this.effects_messages.visible) {
this.effects_messages.removeAll(true);
}
@ -289,6 +355,7 @@ module TK.SpaceTac.UI {
);
this.effects_messages_toggle.manipulate("added")(1000);
await this.battleview.timer.sleep(1000);
}
/**

View file

@ -117,6 +117,7 @@ module TK.SpaceTac.UI {
this.action_bar.position.set(0, this.getHeight() - 132);
this.ship_list = new ShipList(this, this.battle, this.player, this.toggle_tactical_mode, this,
this.layer_borders, this.getWidth() - 112, 0);
this.ship_list.bindToLog(this.log_processor);
this.ship_tooltip = new ShipTooltip(this);
this.character_sheet = new CharacterSheet(this, -this.getWidth());
this.layer_sheets.add(this.character_sheet);
@ -193,7 +194,11 @@ module TK.SpaceTac.UI {
if (ship) {
let ship_action = first(ship.getAvailableActions(), ac => ac.is(action));
if (ship_action) {
return this.actual_battle.applyOneAction(action, target);
let result = this.actual_battle.applyOneAction(action, target);
if (result) {
this.setInteractionEnabled(false);
}
return result;
} else {
console.error("Action not found in available list", action, ship.getAvailableActions());
return false;

View file

@ -11,16 +11,16 @@ module TK.SpaceTac.UI {
// Log client (to receive actual battle diffs)
private log: BattleLogClient
// Forward diffs to other subscribers
private forwarding: ((diff: BaseBattleDiff) => number)[] = []
// Registered subscribers
private subscriber: LogProcessorSubscriber[] = []
// Background delegates promises
private background_promises: Promise<void>[] = []
// Debug indicators
private debug = false
private ai_disabled = false
// Time at which the last action was applied
private last_action: number
constructor(view: BattleView) {
this.view = view;
this.log = new BattleLogClient(view.battle, view.actual_battle.log);
@ -37,6 +37,15 @@ module TK.SpaceTac.UI {
view.inputs.bindCheat("End", "Jump to end", () => {
this.log.jumpToEnd();
});
// Internal subscribers
this.register((diff) => this.checkReaction(diff));
this.register((diff) => this.checkControl(diff));
this.register((diff) => this.checkProjectileFired(diff));
this.register((diff) => this.checkShipDeath(diff));
this.register((diff) => this.checkBattleEnded(diff));
this.register((diff) => this.checkDroneDeployed(diff));
this.register((diff) => this.checkDroneRecalled(diff));
}
/**
@ -50,7 +59,6 @@ module TK.SpaceTac.UI {
}
await this.processBattleDiff(diff);
this.transferControl();
});
this.transferControl();
@ -68,7 +76,6 @@ module TK.SpaceTac.UI {
while (diff = this.log.forward()) {
this.processBattleDiff(diff, false);
}
this.transferControl();
}
}
@ -96,48 +103,70 @@ module TK.SpaceTac.UI {
}
/**
* Register a sub-subscriber.
*
* The difference with registering directly to the BattleLog is that diffs may add delay
* for animations.
*
* The callback may return the duration it needs to display the change.
* Register a diff subscriber
*/
register(callback: (diff: BaseBattleDiff) => number) {
this.forwarding.push(callback);
register(subscriber: LogProcessorSubscriber) {
this.subscriber.push(subscriber);
}
/**
* Register a sub-subscriber, to receive diffs for a specific ship
* Register a diff for a specific ship
*/
registerForShip(ship: Ship, callback: (diff: BaseBattleShipDiff) => number) {
this.register(event => {
if (event instanceof BaseBattleShipDiff && event.ship_id === ship.id) {
return callback(event);
registerForShip(ship: Ship, subscriber: (diff: BaseBattleShipDiff) => LogProcessorDelegate) {
this.register(diff => {
if (diff instanceof BaseBattleShipDiff && diff.ship_id === ship.id) {
return subscriber(diff);
} else {
return 0;
return {};
}
});
}
/**
* Register to playing ship changes
*
* If *initial* is true, the callback will be fired once at register time
*
* If *immediate* is true, the ShipChangeDiff is watched, otherwise the end of the EndTurn action
*/
watchForShipChange(callback: (ship: Ship) => number, initial = true) {
watchForShipChange(callback: (ship: Ship) => LogProcessorDelegate, initial = true, immediate = false) {
this.register(diff => {
if (diff instanceof ShipChangeDiff) {
let changed = false;
if (immediate && diff instanceof ShipChangeDiff) {
changed = true;
} else if (!immediate && diff instanceof ShipActionEndedDiff) {
let ship = this.view.battle.getShip(diff.ship_id);
if (ship && ship.getAction(diff.action) instanceof EndTurnAction) {
changed = true;
}
}
if (changed) {
let ship = this.view.battle.playing_ship;
if (ship) {
return callback(ship);
} else {
return {};
}
} else {
return {};
}
return 0;
});
if (initial) {
let ship = this.view.battle.playing_ship;
if (ship) {
callback(ship);
let result = callback(ship);
let timer = new Timer(true);
if (result.foreground) {
let promise = result.foreground(false, timer);
if (result.background) {
let next = result.background;
promise.then(() => next(false, timer));
}
} else if (result.background) {
result.background(false, timer);
}
}
}
}
@ -149,56 +178,28 @@ module TK.SpaceTac.UI {
if (this.debug) {
console.log("Battle diff", diff);
}
let timer = timed ? this.view.timer : new Timer(true);
let durations = this.forwarding.map(subscriber => subscriber(diff));
let t = (new Date()).getTime()
// TODO add priority to sort the delegates
let delegates = this.subscriber.map(subscriber => subscriber(diff));
let foregrounds = nna(delegates.map(delegate => delegate.foreground || null));
let backgrounds = nna(delegates.map(delegate => delegate.background || null));
if (diff instanceof ShipActionUsedDiff) {
let ship = this.view.actual_battle.getShip(diff.ship_id);
if (ship) {
if (!ship.getPlayer().is(this.view.player)) {
// AI is playing, do not make it play too fast
let since_last = t - this.last_action;
if (since_last < 2000) {
durations.push(2000 - since_last);
}
}
if (foregrounds.length > 0) {
if (this.background_promises.length > 0) {
console.log("wait", this.background_promises);
await Promise.all(this.background_promises);
this.background_promises = [];
}
this.last_action = t;
} else if (diff instanceof ProjectileFiredDiff) {
let ship = this.view.battle.getShip(diff.ship_id);
if (ship) {
let equipment = ship.getEquipment(diff.equipment);
if (equipment && equipment.slot_type == SlotType.Weapon) {
let effect = new WeaponEffect(this.view.arena, ship, diff.target, equipment);
durations.push(effect.start());
}
}
} else if (diff instanceof ShipChangeDiff) {
durations.push(this.processShipChangeEvent(diff));
} else if (diff instanceof ShipDeathDiff) {
durations.push(this.processDeathEvent(diff));
} else if (diff instanceof ShipDamageDiff) {
durations.push(this.processDamageEvent(diff));
} else if (diff instanceof EndBattleDiff) {
durations.push(this.processEndBattleEvent(diff));
} else if (diff instanceof DroneDeployedDiff) {
durations.push(this.processDroneDeployedEvent(diff));
} else if (diff instanceof DroneRecalledDiff) {
durations.push(this.processDroneRecalledEvent(diff));
let promises = foregrounds.map(foreground => foreground(timed, timer));
await Promise.all(promises);
}
let delay = max([0].concat(durations));
if (delay && timed) {
await this.view.timer.sleep(delay);
}
if (this.log.isPlaying()) {
let reaction = this.view.session.reactions.check(this.view.player, this.view.battle, this.view.battle.playing_ship, diff);
if (reaction) {
await this.processReaction(reaction);
}
}
console.log("start backgrounds");
let promises = backgrounds.map(background => background(timed, timed ? this.view.timer : new Timer(true)));
this.background_promises = this.background_promises.concat(promises);
console.log("added", diff, this.background_promises);
}
/**
@ -220,78 +221,143 @@ module TK.SpaceTac.UI {
}
/**
* Process a personality reaction
* Check if a personality reaction should be triggered for a diff
*/
private async processReaction(reaction: PersonalityReaction): Promise<void> {
if (reaction instanceof PersonalityReactionConversation) {
let conversation = UIConversation.newFromPieces(this.view, reaction.messages);
await conversation.waitEnd();
} else {
console.warn("[LogProcessor] Unknown personality reaction type", reaction);
}
}
// Playing ship changed
private processShipChangeEvent(event: ShipChangeDiff): number {
this.view.ship_list.refresh();
if (event.ship_id != event.new_ship) {
this.view.audio.playOnce("battle-ship-change");
}
return 0;
}
// Damage to ship
private processDamageEvent(event: ShipDamageDiff): number {
var item = this.view.ship_list.findItem(event.ship_id);
if (item) {
item.setDamageHit();
}
return 0;
}
// A ship died
private processDeathEvent(event: ShipDeathDiff): number {
let ship = this.view.battle.getShip(event.ship_id);
if (ship) {
if (this.view.ship_hovered === ship) {
this.view.setShipHovered(null);
private checkReaction(diff: BaseBattleDiff): LogProcessorDelegate {
if (this.log.isPlaying()) {
let reaction = this.view.session.reactions.check(this.view.player, this.view.battle, this.view.battle.playing_ship, diff);
if (reaction) {
return {
foreground: async () => {
if (reaction instanceof PersonalityReactionConversation) {
let conversation = UIConversation.newFromPieces(this.view, reaction.messages);
await conversation.waitEnd();
} else {
console.warn("[LogProcessor] Unknown personality reaction type", reaction);
}
}
};
}
this.view.arena.markAsDead(ship);
this.view.ship_list.refresh();
}
return 3000;
return {};
}
/**
* Check if control should be transferred to the player, or an AI, after a diff
*/
private checkControl(diff: BaseBattleDiff): LogProcessorDelegate {
if (diff instanceof ShipActionEndedDiff) {
return {
foreground: async () => this.transferControl()
}
} else {
return 0;
return {};
}
}
// Battle ended (victory or defeat)
private processEndBattleEvent(event: EndBattleDiff): number {
this.view.endBattle();
return 0;
}
// New drone deployed
private processDroneDeployedEvent(event: DroneDeployedDiff): number {
let duration = this.view.arena.addDrone(event.drone);
if (duration) {
this.view.gameui.audio.playOnce("battle-drone-deploy");
/**
* Check if a projectile is fired
*/
private checkProjectileFired(diff: BaseBattleDiff): LogProcessorDelegate {
if (diff instanceof ProjectileFiredDiff) {
let ship = this.view.battle.getShip(diff.ship_id);
if (ship) {
let equipment = ship.getEquipment(diff.equipment);
if (equipment && equipment.slot_type == SlotType.Weapon) {
let effect = new WeaponEffect(this.view.arena, ship, diff.target, equipment);
return {
foreground: async (animate, timer) => {
if (animate) {
await this.view.timer.sleep(effect.start())
}
}
}
}
}
}
return duration;
return {};
}
// Drone destroyed
private processDroneRecalledEvent(event: DroneRecalledDiff): number {
let duration = this.view.arena.removeDrone(event.drone);
/**
* Check if a ship died
*/
private checkShipDeath(diff: BaseBattleDiff): LogProcessorDelegate {
if (diff instanceof ShipDeathDiff) {
let ship = this.view.battle.getShip(diff.ship_id);
if (duration) {
this.view.gameui.audio.playOnce("battle-drone-destroy");
if (ship) {
let dead_ship = ship;
return {
foreground: async (animate) => {
if (dead_ship.is(this.view.ship_hovered)) {
this.view.setShipHovered(null);
}
this.view.arena.markAsDead(dead_ship);
this.view.ship_list.refresh();
if (animate) {
await this.view.timer.sleep(2000);
}
}
}
}
}
return duration;
return {};
}
/**
* Check if the battle ended
*/
private checkBattleEnded(diff: BaseBattleDiff): LogProcessorDelegate {
if (diff instanceof EndBattleDiff) {
return {
foreground: async () => this.view.endBattle()
}
}
return {};
}
/**
* Check if a new drone as been deployed
*/
private checkDroneDeployed(diff: BaseBattleDiff): LogProcessorDelegate {
if (diff instanceof DroneDeployedDiff) {
return {
foreground: async (animate) => {
let duration = this.view.arena.addDrone(diff.drone, animate);
if (duration) {
this.view.gameui.audio.playOnce("battle-drone-deploy");
if (animate) {
await this.view.timer.sleep(duration);
}
}
}
}
} else {
return {};
}
}
/**
* Check if a drone as been recalled
*/
private checkDroneRecalled(diff: BaseBattleDiff): LogProcessorDelegate {
if (diff instanceof DroneRecalledDiff) {
return {
foreground: async () => {
let duration = this.view.arena.removeDrone(diff.drone);
if (duration) {
this.view.gameui.audio.playOnce("battle-drone-destroy");
await this.view.timer.sleep(duration);
}
}
}
} else {
return {};
}
}
// Drone applied
@ -310,4 +376,20 @@ module TK.SpaceTac.UI {
}
}*/
}
/**
* Effective work done by a subscriber
*
* *foreground* is started when no other delegate (background or foreground) is working
* *background* is started when no other foreground delegate is working or pending
*/
export type LogProcessorDelegate = {
foreground?: (animate: boolean, timer: Timer) => Promise<void>,
background?: (animate: boolean, timer: Timer) => Promise<void>,
}
/**
* Subscriber to receive diffs from the battle log
*/
type LogProcessorSubscriber = (diff: BaseBattleDiff) => LogProcessorDelegate
}

View file

@ -67,6 +67,34 @@ module TK.SpaceTac.UI {
this.refresh(animate);
}
/**
* Bind to a log processor, to watch for events
*/
bindToLog(log: LogProcessor): void {
log.watchForShipChange(ship => {
return {
foreground: async (animate) => {
this.refresh(animate)
}
}
});
log.register(diff => {
if (diff instanceof ShipDamageDiff) {
return {
background: async () => {
let item = this.findItem(diff.ship_id);
if (item) {
item.setDamageHit();
}
}
}
} else {
return {};
}
})
}
/**
* Add a ship card
*/

View file

@ -151,6 +151,9 @@ module TK.SpaceTac.UI {
resolve();
});
tween.start();
// By security, if the tween is destroyed before completion, we resolve the promise using the timer
Timer.global.schedule(delay + duration, resolve);
});
}