2017-09-24 22:23:22 +00:00
|
|
|
module TK.SpaceTac.UI {
|
2017-05-29 23:15:32 +00:00
|
|
|
/**
|
2017-11-14 00:07:06 +00:00
|
|
|
* Processor of diffs coming from the battle log
|
2017-05-29 23:15:32 +00:00
|
|
|
*
|
2017-11-14 00:07:06 +00:00
|
|
|
* This will bind to the actual battle log, update the "displayed" battle state accordingly, and refresh the view.
|
2017-05-29 23:15:32 +00:00
|
|
|
*/
|
2014-12-31 00:00:00 +00:00
|
|
|
export class LogProcessor {
|
|
|
|
// Link to the battle view
|
2017-05-30 16:24:55 +00:00
|
|
|
private view: BattleView
|
2014-12-31 00:00:00 +00:00
|
|
|
|
2017-11-14 00:07:06 +00:00
|
|
|
// Log client (to receive actual battle diffs)
|
|
|
|
private log: BattleLogClient
|
2017-10-08 21:26:33 +00:00
|
|
|
|
2017-12-04 18:12:06 +00:00
|
|
|
// Registered subscribers
|
|
|
|
private subscriber: LogProcessorSubscriber[] = []
|
|
|
|
|
|
|
|
// Background delegates promises
|
|
|
|
private background_promises: Promise<void>[] = []
|
2017-02-19 21:33:07 +00:00
|
|
|
|
2017-11-28 18:01:56 +00:00
|
|
|
// Debug indicators
|
2017-09-14 22:46:50 +00:00
|
|
|
private debug = false
|
2017-11-28 18:01:56 +00:00
|
|
|
private ai_disabled = false
|
2017-09-14 22:46:50 +00:00
|
|
|
|
2014-12-31 00:00:00 +00:00
|
|
|
constructor(view: BattleView) {
|
|
|
|
this.view = view;
|
2017-11-14 00:07:06 +00:00
|
|
|
this.log = new BattleLogClient(view.battle, view.actual_battle.log);
|
2017-05-29 23:15:32 +00:00
|
|
|
|
2017-05-30 16:24:55 +00:00
|
|
|
view.inputs.bindCheat("PageUp", "Step backward", () => {
|
2017-11-14 00:07:06 +00:00
|
|
|
this.log.backward();
|
2017-05-29 23:15:32 +00:00
|
|
|
});
|
|
|
|
view.inputs.bindCheat("PageDown", "Step forward", () => {
|
2017-11-14 00:07:06 +00:00
|
|
|
this.log.forward();
|
2017-05-29 23:15:32 +00:00
|
|
|
});
|
|
|
|
view.inputs.bindCheat("Home", "Jump to beginning", () => {
|
2017-11-14 00:07:06 +00:00
|
|
|
this.log.jumpToStart();
|
2017-05-29 23:15:32 +00:00
|
|
|
});
|
|
|
|
view.inputs.bindCheat("End", "Jump to end", () => {
|
2017-11-14 00:07:06 +00:00
|
|
|
this.log.jumpToEnd();
|
2017-05-30 16:24:55 +00:00
|
|
|
});
|
2017-12-04 18:12:06 +00:00
|
|
|
|
|
|
|
// 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));
|
2017-02-21 22:38:31 +00:00
|
|
|
}
|
2014-12-31 00:00:00 +00:00
|
|
|
|
2017-02-21 22:38:31 +00:00
|
|
|
/**
|
|
|
|
* Start log processing
|
|
|
|
*/
|
|
|
|
start() {
|
2017-06-13 20:48:43 +00:00
|
|
|
if (!this.view.gameui.headless) {
|
2017-11-14 00:07:06 +00:00
|
|
|
this.log.play(async diff => {
|
2017-11-14 22:45:41 +00:00
|
|
|
while (this.view.game.paused) {
|
|
|
|
await this.view.timer.sleep(500);
|
|
|
|
}
|
|
|
|
|
2017-11-14 00:07:06 +00:00
|
|
|
await this.processBattleDiff(diff);
|
|
|
|
});
|
2017-11-14 22:45:41 +00:00
|
|
|
|
2017-11-14 00:07:06 +00:00
|
|
|
this.transferControl();
|
2017-10-08 21:26:33 +00:00
|
|
|
}
|
2017-02-19 21:33:07 +00:00
|
|
|
}
|
|
|
|
|
2017-05-30 16:24:55 +00:00
|
|
|
/**
|
2017-11-14 00:07:06 +00:00
|
|
|
* Process all pending diffs, synchronously
|
2017-05-30 16:24:55 +00:00
|
|
|
*/
|
2017-11-14 00:07:06 +00:00
|
|
|
processPending() {
|
|
|
|
if (this.log.isPlaying()) {
|
|
|
|
throw new Error("Cannot process diffs synchronously while playing the log");
|
2017-10-08 21:26:33 +00:00
|
|
|
} else {
|
2017-11-14 00:07:06 +00:00
|
|
|
let diff: Diff<Battle> | null;
|
|
|
|
while (diff = this.log.forward()) {
|
|
|
|
this.processBattleDiff(diff, false);
|
2017-06-13 20:48:43 +00:00
|
|
|
}
|
2017-05-30 18:23:35 +00:00
|
|
|
}
|
2017-05-30 16:24:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-11-14 00:07:06 +00:00
|
|
|
* Destroy the processor
|
2017-05-30 16:24:55 +00:00
|
|
|
*
|
2017-11-14 00:07:06 +00:00
|
|
|
* This should be done to ensure it will stop processing and free resources
|
2017-05-30 16:24:55 +00:00
|
|
|
*/
|
2017-11-14 00:07:06 +00:00
|
|
|
destroy() {
|
|
|
|
if (this.log.isPlaying()) {
|
|
|
|
this.log.stop(true);
|
2017-05-30 18:23:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if we need a player or AI to interact at this point
|
|
|
|
*/
|
|
|
|
getPlayerNeeded(): Player | null {
|
2017-11-14 00:07:06 +00:00
|
|
|
if (this.log.isPlaying() && this.log.atEnd()) {
|
|
|
|
let playing_ship = this.view.actual_battle.playing_ship;
|
|
|
|
return playing_ship ? playing_ship.getPlayer() : null;
|
2017-05-30 18:23:35 +00:00
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
2017-05-30 16:24:55 +00:00
|
|
|
}
|
|
|
|
|
2017-02-19 21:33:07 +00:00
|
|
|
/**
|
2017-12-04 18:12:06 +00:00
|
|
|
* Register a diff subscriber
|
2017-02-19 21:33:07 +00:00
|
|
|
*/
|
2017-12-04 18:12:06 +00:00
|
|
|
register(subscriber: LogProcessorSubscriber) {
|
|
|
|
this.subscriber.push(subscriber);
|
2014-12-31 00:00:00 +00:00
|
|
|
}
|
|
|
|
|
2017-05-17 18:14:45 +00:00
|
|
|
/**
|
2017-12-04 18:12:06 +00:00
|
|
|
* Register a diff for a specific ship
|
2017-05-17 18:14:45 +00:00
|
|
|
*/
|
2017-12-04 18:12:06 +00:00
|
|
|
registerForShip(ship: Ship, subscriber: (diff: BaseBattleShipDiff) => LogProcessorDelegate) {
|
|
|
|
this.register(diff => {
|
|
|
|
if (diff instanceof BaseBattleShipDiff && diff.ship_id === ship.id) {
|
|
|
|
return subscriber(diff);
|
2017-05-22 18:06:33 +00:00
|
|
|
} else {
|
2017-12-04 18:12:06 +00:00
|
|
|
return {};
|
2017-05-17 18:14:45 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-02-14 00:30:50 +00:00
|
|
|
/**
|
2017-11-14 00:07:06 +00:00
|
|
|
* Register to playing ship changes
|
2017-12-04 18:12:06 +00:00
|
|
|
*
|
|
|
|
* 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
|
2017-11-14 00:07:06 +00:00
|
|
|
*/
|
2017-12-04 18:12:06 +00:00
|
|
|
watchForShipChange(callback: (ship: Ship) => LogProcessorDelegate, initial = true, immediate = false) {
|
2017-11-14 00:07:06 +00:00
|
|
|
this.register(diff => {
|
2017-12-04 18:12:06 +00:00
|
|
|
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);
|
2018-02-08 15:16:03 +00:00
|
|
|
if (ship && ship.actions.getById(diff.action) instanceof EndTurnAction) {
|
2017-12-04 18:12:06 +00:00
|
|
|
changed = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (changed) {
|
2017-11-14 00:07:06 +00:00
|
|
|
let ship = this.view.battle.playing_ship;
|
|
|
|
if (ship) {
|
|
|
|
return callback(ship);
|
2017-12-04 18:12:06 +00:00
|
|
|
} else {
|
|
|
|
return {};
|
2017-11-14 00:07:06 +00:00
|
|
|
}
|
2017-12-04 18:12:06 +00:00
|
|
|
} else {
|
|
|
|
return {};
|
2017-11-14 00:07:06 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
if (initial) {
|
|
|
|
let ship = this.view.battle.playing_ship;
|
|
|
|
if (ship) {
|
2017-12-04 18:12:06 +00:00
|
|
|
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);
|
|
|
|
}
|
2017-11-14 00:07:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Process a single battle diff
|
2017-02-14 00:30:50 +00:00
|
|
|
*/
|
2017-11-14 00:07:06 +00:00
|
|
|
async processBattleDiff(diff: BaseBattleDiff, timed = true): Promise<void> {
|
2017-09-14 22:46:50 +00:00
|
|
|
if (this.debug) {
|
2017-11-14 00:07:06 +00:00
|
|
|
console.log("Battle diff", diff);
|
2017-09-14 22:46:50 +00:00
|
|
|
}
|
2017-12-04 18:12:06 +00:00
|
|
|
let timer = timed ? this.view.timer : new Timer(true);
|
|
|
|
|
|
|
|
// 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 (foregrounds.length > 0) {
|
|
|
|
if (this.background_promises.length > 0) {
|
|
|
|
await Promise.all(this.background_promises);
|
|
|
|
this.background_promises = [];
|
2017-08-23 17:59:22 +00:00
|
|
|
}
|
2017-05-30 18:23:35 +00:00
|
|
|
|
2017-12-04 18:12:06 +00:00
|
|
|
let promises = foregrounds.map(foreground => foreground(timed, timer));
|
|
|
|
await Promise.all(promises);
|
2017-10-08 21:26:33 +00:00
|
|
|
}
|
|
|
|
|
2017-12-04 18:12:06 +00:00
|
|
|
let promises = backgrounds.map(background => background(timed, timed ? this.view.timer : new Timer(true)));
|
|
|
|
this.background_promises = this.background_promises.concat(promises);
|
2017-06-13 20:48:43 +00:00
|
|
|
}
|
2017-05-30 18:23:35 +00:00
|
|
|
|
2017-06-13 20:48:43 +00:00
|
|
|
/**
|
|
|
|
* Transfer control to the needed player (or not)
|
|
|
|
*/
|
2017-11-14 00:07:06 +00:00
|
|
|
private transferControl() {
|
2017-05-30 18:23:35 +00:00
|
|
|
let player = this.getPlayerNeeded();
|
|
|
|
if (player) {
|
2017-11-14 00:07:06 +00:00
|
|
|
if (player.is(this.view.player)) {
|
2017-05-30 18:23:35 +00:00
|
|
|
this.view.setInteractionEnabled(true);
|
2017-11-28 18:01:56 +00:00
|
|
|
} else if (!this.ai_disabled) {
|
2017-05-30 18:23:35 +00:00
|
|
|
this.view.playAI();
|
2017-11-28 18:01:56 +00:00
|
|
|
} else {
|
|
|
|
this.view.applyAction(EndTurnAction.SINGLETON);
|
2017-05-30 18:23:35 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
this.view.setInteractionEnabled(false);
|
2017-10-08 21:26:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-12-04 18:12:06 +00:00
|
|
|
* Check if a personality reaction should be triggered for a diff
|
2017-10-08 21:26:33 +00:00
|
|
|
*/
|
2017-12-04 18:12:06 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
2017-05-30 18:23:35 +00:00
|
|
|
}
|
2017-12-04 18:12:06 +00:00
|
|
|
|
|
|
|
return {};
|
2014-12-31 00:00:00 +00:00
|
|
|
}
|
|
|
|
|
2017-12-04 18:12:06 +00:00
|
|
|
/**
|
|
|
|
* 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 {};
|
2017-06-07 17:08:53 +00:00
|
|
|
}
|
2015-02-03 00:00:00 +00:00
|
|
|
}
|
|
|
|
|
2017-12-04 18:12:06 +00:00
|
|
|
/**
|
|
|
|
* 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) {
|
2018-02-08 15:16:03 +00:00
|
|
|
let action = ship.actions.getById(diff.action);
|
|
|
|
if (action && action instanceof TriggerAction) {
|
|
|
|
let effect = new WeaponEffect(this.view.arena, ship, diff.target, action);
|
2017-12-04 18:12:06 +00:00
|
|
|
return {
|
|
|
|
foreground: async (animate, timer) => {
|
|
|
|
if (animate) {
|
|
|
|
await this.view.timer.sleep(effect.start())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-01-15 22:05:00 +00:00
|
|
|
}
|
2017-12-04 18:12:06 +00:00
|
|
|
|
|
|
|
return {};
|
2015-02-03 00:00:00 +00:00
|
|
|
}
|
|
|
|
|
2017-12-04 18:12:06 +00:00
|
|
|
/**
|
|
|
|
* Check if a ship died
|
|
|
|
*/
|
|
|
|
private checkShipDeath(diff: BaseBattleDiff): LogProcessorDelegate {
|
|
|
|
if (diff instanceof ShipDeathDiff) {
|
|
|
|
let ship = this.view.battle.getShip(diff.ship_id);
|
2017-10-25 22:45:53 +00:00
|
|
|
|
2017-12-04 18:12:06 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-10-25 22:45:53 +00:00
|
|
|
}
|
|
|
|
}
|
2015-02-20 00:00:00 +00:00
|
|
|
|
2017-12-04 18:12:06 +00:00
|
|
|
return {};
|
2015-04-21 22:17:00 +00:00
|
|
|
}
|
2015-04-22 20:03:59 +00:00
|
|
|
|
2017-12-04 18:12:06 +00:00
|
|
|
/**
|
|
|
|
* Check if the battle ended
|
|
|
|
*/
|
|
|
|
private checkBattleEnded(diff: BaseBattleDiff): LogProcessorDelegate {
|
|
|
|
if (diff instanceof EndBattleDiff) {
|
|
|
|
return {
|
|
|
|
foreground: async () => this.view.endBattle()
|
|
|
|
}
|
2017-05-17 16:21:14 +00:00
|
|
|
}
|
|
|
|
|
2017-12-04 18:12:06 +00:00
|
|
|
return {};
|
2017-02-08 18:54:02 +00:00
|
|
|
}
|
2014-12-31 00:00:00 +00:00
|
|
|
}
|
2017-12-04 18:12:06 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
2015-01-07 00:00:00 +00:00
|
|
|
}
|