1
0
Fork 0

Some refactoring needed for future work on battle log

This commit is contained in:
Michaël Lemaire 2017-05-30 01:15:32 +02:00
parent be6f00d0e9
commit 83807beb20
27 changed files with 113 additions and 64 deletions

2
TODO
View file

@ -1,3 +1,4 @@
* New battle internal flow: any game state change should be done through revertable events
* UI: use a common component class, and a layer abstraction
* Character sheet: add initial character creation
* Character sheet: disable interaction during battle (except for loot screen)
@ -23,7 +24,6 @@
* Arena: fix effects originating from real ship location instead of current sprite (when AI fires then moves)
* Arena: add engine trail
* Arena: draw move exclusion circles and border (in move targetting only)
* Remove "initial events" injection
* Fix capacity limit effect not refreshing associated value (for example, on "limit power capacity to 3", potential "power" value change is not broadcast)
* Actions: show power usage/recovery in power bar, on action hover
* Actions: fix targetting not resetting when using keyboard shortcuts

View file

@ -278,11 +278,9 @@ module TS.SpaceTac {
// check initial log fill
battle.drones = [drone];
battle.log.events = [];
battle.injectInitialEvents();
let expected = new DroneDeployedEvent(drone);
expected.initial = true;
expect(battle.log.events).toEqual([expected]);
expect(battle.getBootstrapEvents()).toEqual([expected]);
});
it("checks if a player is able to play", function () {

View file

@ -271,20 +271,22 @@ module TS.SpaceTac {
this.throwInitiative();
iforeach(this.iships(), ship => ship.startBattle());
this.advanceToNextShip();
// For now, we inject bootstrap events in the log
this.getBootstrapEvents().forEach(event => this.log.add(event));
}
// Force an injection of events in the battle log to simulate the initial state
// For instance, this may be called after 'start', to use the log subscription system
// to initialize a battle UI
// Attributes 'play_order' and 'playing_ship' should be defined before calling this
injectInitialEvents(): void {
var log = this.log;
/**
* Get a set of minimal events required to reach current state from an empty state.
*/
getBootstrapEvents(): BaseBattleEvent[] {
let result: BaseBattleEvent[] = [];
// Simulate initial ship placement
this.play_order.forEach(ship => {
let event = new MoveEvent(ship, ship.arena_x, ship.arena_y, 0);
event.initial = true;
log.add(event);
result.push(event);
});
// Indicate emergency stasis
@ -292,7 +294,7 @@ module TS.SpaceTac {
if (!ship.alive) {
let event = new DeathEvent(ship);
event.initial = true;
log.add(event);
result.push(event);
}
});
@ -300,15 +302,17 @@ module TS.SpaceTac {
this.drones.forEach(drone => {
let event = new DroneDeployedEvent(drone);
event.initial = true;
log.add(event);
result.push(event);
});
// Simulate game turn
if (this.playing_ship) {
let event = new ShipChangeEvent(this.playing_ship, this.playing_ship);
event.initial = true;
log.add(event);
result.push(event);
}
return result;
}
/**

View file

@ -1,8 +1,8 @@
/// <reference path="events/BaseLogEvent.ts"/>
/// <reference path="events/BaseBattleEvent.ts"/>
module TS.SpaceTac {
// Check a single game log event
function checkEvent(got: BaseLogEvent, ship: Ship, code: string,
function checkEvent(got: BaseBattleEvent, ship: Ship, code: string,
target_ship: Ship | null = null, target_x: number | null = null, target_y: number | null = null): void {
if (target_ship) {
if (target_x === null) {
@ -33,7 +33,7 @@ module TS.SpaceTac {
}
// Fake event
class FakeEvent extends BaseLogEvent {
class FakeEvent extends BaseBattleEvent {
constructor() {
super("fake", new Ship());
}
@ -42,10 +42,10 @@ module TS.SpaceTac {
describe("BattleLog", function () {
it("forwards events to subscribers, until unsubscribe", function () {
var log = new BattleLog();
var received: BaseLogEvent[] = [];
var received: BaseBattleEvent[] = [];
var fake = new FakeEvent();
var sub = log.subscribe(function (event: BaseLogEvent) {
var sub = log.subscribe(function (event: BaseBattleEvent) {
received.push(event);
});
@ -74,18 +74,14 @@ module TS.SpaceTac {
it("can receive simulated initial state events", function () {
let battle = Battle.newQuickRandom(true, 1, 4);
let playing = nn(battle.playing_ship);
battle.log.clear();
battle.log.addFilter("value");
expect(battle.log.events.length).toBe(0);
battle.injectInitialEvents();
expect(battle.log.events.length).toBe(9);
let result = battle.getBootstrapEvents();
expect(result.length).toBe(9);
for (var i = 0; i < 8; i++) {
checkEvent(battle.log.events[i], battle.play_order[i], "move", null,
checkEvent(result[i], battle.play_order[i], "move", null,
battle.play_order[i].arena_x, battle.play_order[i].arena_y);
}
checkEvent(battle.log.events[8], playing, "ship_change", playing);
checkEvent(result[8], playing, "ship_change", playing);
});
});
}

View file

@ -2,14 +2,14 @@ module TS.SpaceTac {
/**
* Function called to inform subscribers of new events.
*/
export type LogSubscriber = (event: BaseLogEvent) => any;
export type LogSubscriber = (event: BaseBattleEvent) => any;
// Log of a battle
// This keeps track of all events in a battle
// It also allows to register a callback to receive these events
export class BattleLog {
// Full list of battle events
events: BaseLogEvent[];
events: BaseBattleEvent[];
// List of subscribers
private subscribers: LogSubscriber[];
@ -34,7 +34,7 @@ module TS.SpaceTac {
}
// Add a battle event to the log
add(event: BaseLogEvent): void {
add(event: BaseBattleEvent): void {
// Apply filters
var filtered = false;
this.filters.forEach((code: string) => {

View file

@ -22,16 +22,19 @@ module TS.SpaceTac.Specs {
let battle = new Battle();
let attacker = battle.fleets[0].addShip();
let defender = battle.fleets[1].addShip();
stats.watchLog(battle.log, battle.fleets[0]);
stats.processLog(battle.log, battle.fleets[0]);
expect(stats.stats).toEqual({});
battle.log.add(new DamageEvent(attacker, 10, 12));
stats.processLog(battle.log, battle.fleets[0]);
expect(stats.stats).toEqual({ "Damage dealt": [0, 22] });
battle.log.add(new DamageEvent(defender, 40, 0));
stats.processLog(battle.log, battle.fleets[0]);
expect(stats.stats).toEqual({ "Damage dealt": [40, 22] });
battle.log.add(new DamageEvent(attacker, 5, 4));
stats.processLog(battle.log, battle.fleets[0]);
expect(stats.stats).toEqual({ "Damage dealt": [40, 31] });
})
@ -40,13 +43,15 @@ module TS.SpaceTac.Specs {
let battle = new Battle();
let attacker = battle.fleets[0].addShip();
let defender = battle.fleets[1].addShip();
stats.watchLog(battle.log, battle.fleets[0]);
stats.processLog(battle.log, battle.fleets[0]);
expect(stats.stats).toEqual({});
battle.log.add(new MoveEvent(attacker, 0, 0, 10));
stats.processLog(battle.log, battle.fleets[0]);
expect(stats.stats).toEqual({ "Move distance (km)": [10, 0] });
battle.log.add(new MoveEvent(defender, 0, 0, 58));
stats.processLog(battle.log, battle.fleets[0]);
expect(stats.stats).toEqual({ "Move distance (km)": [10, 58] });
})
@ -55,13 +60,15 @@ module TS.SpaceTac.Specs {
let battle = new Battle();
let attacker = battle.fleets[0].addShip();
let defender = battle.fleets[1].addShip();
stats.watchLog(battle.log, battle.fleets[0]);
stats.processLog(battle.log, battle.fleets[0]);
expect(stats.stats).toEqual({});
battle.log.add(new DroneDeployedEvent(new Drone(attacker)));
stats.processLog(battle.log, battle.fleets[0]);
expect(stats.stats).toEqual({ "Drones deployed": [1, 0] });
battle.log.add(new DroneDeployedEvent(new Drone(defender)));
stats.processLog(battle.log, battle.fleets[0]);
expect(stats.stats).toEqual({ "Drones deployed": [1, 1] });
})
})

View file

@ -35,10 +35,12 @@ module TS.SpaceTac {
}
/**
* Watch a battle log to automatically feed the collector
* Process a battle log
*/
watchLog(log: BattleLog, attacker: Fleet) {
log.subscribe(event => {
processLog(log: BattleLog, attacker: Fleet) {
this.stats = {};
log.events.forEach(event => {
if (event instanceof DamageEvent) {
this.addStat("Damage dealt", event.hull + event.shield, event.ship.fleet !== attacker);
} else if (event instanceof MoveEvent) {

View file

@ -196,7 +196,7 @@ module TS.SpaceTac {
}
// Add an event to the battle log, if any
addBattleEvent(event: BaseLogEvent): void {
addBattleEvent(event: BaseBattleEvent): void {
var battle = this.getBattle();
if (battle && battle.log) {
battle.log.add(event);

View file

@ -85,7 +85,7 @@ module TS.SpaceTac {
var encounter = this.tryGenerateEncounter();
if (encounter) {
var battle = new Battle(fleet, encounter);
battle.log.subscribe((event: BaseLogEvent) => {
battle.log.subscribe((event: BaseBattleEvent) => {
if (event.code === "endbattle") {
var endbattle = <EndBattleEvent>event;
if (!endbattle.outcome.draw && endbattle.outcome.winner !== encounter) {

View file

@ -1,6 +1,12 @@
module TS.SpaceTac {
// Base class for a BattleLog event
export class BaseLogEvent {
/**
* Base class for battle events
*
* Events are the proper way to modify the battle state
*
* All events may be applied either forward or backward on a battle state
*/
export class BaseBattleEvent {
// Code of the event (its type)
code: string;
@ -18,10 +24,22 @@ module TS.SpaceTac {
this.ship = ship;
this.target = target;
}
/**
* Apply the event forward on a battle state
*/
apply(battle: Battle) {
}
/**
* Apply the event backward (revert it) on a battle state
*/
revert(battle: Battle) {
}
}
// Base class for a BattleLog event linked to a ship
export class BaseLogShipEvent extends BaseLogEvent {
export class BaseLogShipEvent extends BaseBattleEvent {
ship: Ship;
constructor(code: string, ship: Ship, target: Target | null = null) {

View file

@ -1,4 +1,4 @@
/// <reference path="BaseLogEvent.ts"/>
/// <reference path="BaseBattleEvent.ts"/>
module TS.SpaceTac {
// Event logged when a ship takes damage

View file

@ -1,4 +1,4 @@
/// <reference path="BaseLogEvent.ts"/>
/// <reference path="BaseBattleEvent.ts"/>
module TS.SpaceTac {
// Event logged when a ship is dead

View file

@ -1,4 +1,4 @@
/// <reference path="BaseLogEvent.ts"/>
/// <reference path="BaseBattleEvent.ts"/>
module TS.SpaceTac {
/**

View file

@ -1,4 +1,4 @@
/// <reference path="BaseLogEvent.ts"/>
/// <reference path="BaseBattleEvent.ts"/>
module TS.SpaceTac {
// Event logged when a drone is deployed by a ship

View file

@ -1,4 +1,4 @@
/// <reference path="BaseLogEvent.ts"/>
/// <reference path="BaseBattleEvent.ts"/>
module TS.SpaceTac {
// Event logged when a drone is destroyed

View file

@ -1,4 +1,4 @@
/// <reference path="BaseLogEvent.ts"/>
/// <reference path="BaseBattleEvent.ts"/>
module TS.SpaceTac {
// Event logged when a sticky effect is added to a ship

View file

@ -1,4 +1,4 @@
/// <reference path="BaseLogEvent.ts"/>
/// <reference path="BaseBattleEvent.ts"/>
module TS.SpaceTac {
// Event logged when a sticky effect is added to a ship

View file

@ -1,4 +1,4 @@
/// <reference path="BaseLogEvent.ts"/>
/// <reference path="BaseBattleEvent.ts"/>
module TS.SpaceTac {
// Event logged when a sticky effect is removed from a ship

View file

@ -1,9 +1,9 @@
/// <reference path="BaseLogEvent.ts"/>
/// <reference path="BaseBattleEvent.ts"/>
module TS.SpaceTac {
// Event logged when the battle ended
// This is always the last event of a battle log
export class EndBattleEvent extends BaseLogEvent {
export class EndBattleEvent extends BaseBattleEvent {
// Outcome of the battle
outcome: BattleOutcome;

View file

@ -1,4 +1,4 @@
/// <reference path="BaseLogEvent.ts"/>
/// <reference path="BaseBattleEvent.ts"/>
module TS.SpaceTac {
// Event logged when a weapon is used on a target

View file

@ -1,4 +1,4 @@
/// <reference path="BaseLogEvent.ts"/>
/// <reference path="BaseBattleEvent.ts"/>
module TS.SpaceTac {
// Event logged when a ship moves

View file

@ -1,4 +1,4 @@
/// <reference path="BaseLogEvent.ts"/>
/// <reference path="BaseBattleEvent.ts"/>
module TS.SpaceTac {
// Battle event, when a ship turn ended, and advanced to a new one

View file

@ -1,4 +1,4 @@
/// <reference path="BaseLogEvent.ts"/>
/// <reference path="BaseBattleEvent.ts"/>
module TS.SpaceTac {
// Event logged when a ship value or attribute changed

View file

@ -124,7 +124,7 @@ module TS.SpaceTac.UI {
/**
* Process a battle log event
*/
private processLogEvent(event: BaseLogEvent): number {
private processLogEvent(event: BaseBattleEvent): number {
if (event instanceof ShipChangeEvent) {
if (event.new_ship === this.ship) {
this.play_order.text = "";

View file

@ -257,6 +257,8 @@ module TS.SpaceTac.UI {
this.gameui.session.setBattleEnded();
this.battle.stats.processLog(this.battle.log, this.player.fleet);
let dialog = new OutcomeDialog(this, this.player, this.battle.outcome, this.battle.stats);
dialog.moveToLayer(this.outcome_layer);
} else {

View file

@ -1,6 +1,12 @@
module TS.SpaceTac.UI {
// Processor of battle log events
// This will process incoming battle events, and update the battleview accordingly
/**
* Processor of events coming from the battle log
*
* This will bind to the battle log to receive new events, and update
* the battle view accordingly.
*
* It is also possible to go back/forward in time.
*/
export class LogProcessor {
// Link to the battle view
private view: BattleView;
@ -18,7 +24,7 @@ module TS.SpaceTac.UI {
private delayed = false;
// Processing queue, when delay is active
private queue: BaseLogEvent[] = [];
private queue: BaseBattleEvent[] = [];
// Forward events to other subscribers
private forwarding: LogSubscriber[] = [];
@ -27,6 +33,19 @@ module TS.SpaceTac.UI {
this.view = view;
this.battle = view.battle;
this.log = view.battle.log;
/*view.inputs.bindCheat("PageUp", "Step backward", () => {
this.stepBackward();
});
view.inputs.bindCheat("PageDown", "Step forward", () => {
this.stepForward();
});
view.inputs.bindCheat("Home", "Jump to beginning", () => {
this.jumpToStart();
});
view.inputs.bindCheat("End", "Jump to end", () => {
this.jumpToEnd();
});*/
}
/**
@ -34,8 +53,7 @@ module TS.SpaceTac.UI {
*/
start() {
this.subscription = this.log.subscribe(event => this.processBattleEvent(event));
this.battle.injectInitialEvents();
this.battle.stats.watchLog(this.battle.log, this.view.player.fleet);
this.battle.getBootstrapEvents().forEach(event => this.processBattleEvent(event));
}
/**
@ -46,7 +64,7 @@ module TS.SpaceTac.UI {
*
* The callback may return the duration it needs to display the change.
*/
register(callback: (event: BaseLogEvent) => number) {
register(callback: (event: BaseBattleEvent) => number) {
this.forwarding.push(event => {
let duration = callback(event);
if (duration) {
@ -92,7 +110,7 @@ module TS.SpaceTac.UI {
/**
* Process a single event
*/
processBattleEvent(event: BaseLogEvent) {
processBattleEvent(event: BaseBattleEvent) {
if (!this.subscription) {
return;
}

View file

@ -5,6 +5,7 @@ module TS.SpaceTac.UI {
* Manager for keyboard/mouse/touch events.
*/
export class InputManager {
private debug = false
private view: BaseView
private game: MainUI
private input: Phaser.Input
@ -48,7 +49,10 @@ module TS.SpaceTac.UI {
});
this.input.keyboard.addCallbacks(this, undefined, (event: KeyboardEvent) => {
// console.debug(event);
if (this.debug) {
console.log(event);
}
if (!contains(["Control", "Shift", "Alt", "Meta"], event.key)) {
this.keyPress(event.key);
if (event.code != event.key) {