1
0
Fork 0

multiplayer: WIP on spectator mode for battles

This commit is contained in:
Michaël Lemaire 2017-07-25 00:02:43 +02:00
parent c0ee40633a
commit daf59cc16d
11 changed files with 167 additions and 35 deletions

View file

@ -86,6 +86,14 @@ Common UI
* Mobile: display tooltips larger and on the side of screen where the finger is not
* Mobile: targetting in two times, using a draggable target indicator
Network
-------
* Handle cancel button in invitation dialog
* Close connection on view exit
* Add timeouts to read operations
* Display connection status
Postponed
---------

View file

@ -227,9 +227,11 @@ module TS.SpaceTac.Specs {
expect(action.activated).toBe(false);
expect(battle.log.events).toEqual([
new ActionAppliedEvent(ship, action, null),
new ToggleEvent(ship, action, true),
new ActiveEffectsEvent(ship, [], [], [new AttributeEffect("power_capacity", 1)]),
new ValueChangeEvent(ship, new ShipAttribute("power capacity", 1), 1),
new ActionAppliedEvent(ship, action, null),
new ToggleEvent(ship, action, false),
new ActiveEffectsEvent(ship, [], [], []),
new ValueChangeEvent(ship, new ShipAttribute("power capacity", 0), -1),

View file

@ -1,5 +1,9 @@
module TS.SpaceTac {
// Base class for action definitions
/**
* Base class for a battle action.
*
* An action should be the only way to modify a battle state.
*/
export class BaseAction {
// Identifier code for the type of action
code: string
@ -140,6 +144,11 @@ module TS.SpaceTac {
this.equipment.cooldown.use();
}
let battle = ship.getBattle();
if (battle) {
battle.log.add(new ActionAppliedEvent(ship, this, checked_target));
}
this.customApply(ship, checked_target);
return true;
} else {

View file

@ -50,6 +50,7 @@ module TS.SpaceTac {
expect(drone.radius).toEqual(4);
expect(drone.effects).toEqual([new DamageEffect(50)]);
expect(battle.log.events).toEqual([
new ActionAppliedEvent(ship, action, Target.newFromLocation(5, 0)),
new DroneDeployedEvent(drone)
]);

View file

@ -61,16 +61,19 @@ module TS.SpaceTac {
expect(ship.arena_y).toBeCloseTo(3.535533, 0.00001);
expect(ship.values.power.get()).toEqual(0);
expect(battle.log.events.length).toBe(2);
expect(battle.log.events.length).toBe(3);
expect(battle.log.events[0].code).toEqual("value");
expect(battle.log.events[0].ship).toBe(ship);
expect((<ValueChangeEvent>battle.log.events[0]).value).toEqual(
new ShipValue("power", 0, 20));
expect(battle.log.events[1].code).toEqual("move");
expect(battle.log.events[1].code).toEqual("action");
expect(battle.log.events[1].ship).toBe(ship);
let dest = (<MoveEvent>battle.log.events[1]).end;
expect(battle.log.events[2].code).toEqual("move");
expect(battle.log.events[2].ship).toBe(ship);
let dest = (<MoveEvent>battle.log.events[2]).end;
expect(dest.x).toBeCloseTo(3.535533, 0.00001);
expect(dest.y).toBeCloseTo(3.535533, 0.00001);
});

View file

@ -60,11 +60,12 @@ module TS.SpaceTac.Equipments {
expect(equipment.action.checkCannotBeApplied(ship)).toBe(null);
equipment.action.apply(ship, target);
checkHP(50, 10, 50, 10, 50, 10);
expect(battle.log.events.length).toBe(4);
expect(battle.log.events[0]).toEqual(new FireEvent(ship, equipment, Target.newFromLocation(1, 0)));
expect(battle.log.events[1]).toEqual(new DamageEvent(ship, 0, 20));
expect(battle.log.events[2]).toEqual(new DamageEvent(enemy1, 0, 20));
expect(battle.log.events[3]).toEqual(new DamageEvent(enemy2, 0, 20));
expect(battle.log.events.length).toBe(5);
expect(battle.log.events[0]).toEqual(new ActionAppliedEvent(ship, equipment.action, Target.newFromLocation(1, 0)));
expect(battle.log.events[1]).toEqual(new FireEvent(ship, equipment, Target.newFromLocation(1, 0)));
expect(battle.log.events[2]).toEqual(new DamageEvent(ship, 0, 20));
expect(battle.log.events[3]).toEqual(new DamageEvent(enemy1, 0, 20));
expect(battle.log.events[4]).toEqual(new DamageEvent(enemy2, 0, 20));
battle.log.clear();
equipment.cooldown.cool();
@ -74,10 +75,11 @@ module TS.SpaceTac.Equipments {
expect(equipment.action.checkCannotBeApplied(ship)).toBe(null);
equipment.action.apply(ship, target);
checkHP(50, 10, 40, 0, 40, 0);
expect(battle.log.events.length).toBe(3);
expect(battle.log.events[0]).toEqual(new FireEvent(ship, equipment, target));
expect(battle.log.events[1]).toEqual(new DamageEvent(enemy1, 10, 10));
expect(battle.log.events[2]).toEqual(new DamageEvent(enemy2, 10, 10));
expect(battle.log.events.length).toBe(4);
expect(battle.log.events[0]).toEqual(new ActionAppliedEvent(ship, equipment.action, target));
expect(battle.log.events[1]).toEqual(new FireEvent(ship, equipment, target));
expect(battle.log.events[2]).toEqual(new DamageEvent(enemy1, 10, 10));
expect(battle.log.events[3]).toEqual(new DamageEvent(enemy2, 10, 10));
battle.log.clear();
equipment.cooldown.cool();
@ -87,8 +89,9 @@ module TS.SpaceTac.Equipments {
expect(equipment.action.checkCannotBeApplied(ship)).toBe(null);
equipment.action.apply(ship, target);
checkHP(50, 10, 40, 0, 40, 0);
expect(battle.log.events.length).toBe(1);
expect(battle.log.events[0]).toEqual(new FireEvent(ship, equipment, target));
expect(battle.log.events.length).toBe(2);
expect(battle.log.events[0]).toEqual(new ActionAppliedEvent(ship, equipment.action, target));
expect(battle.log.events[1]).toEqual(new FireEvent(ship, equipment, target));
});
});
}

View file

@ -0,0 +1,17 @@
/// <reference path="BaseBattleEvent.ts"/>
module TS.SpaceTac {
/**
* Event logged when an action is used.
*/
export class ActionAppliedEvent extends BaseLogShipEvent {
// Action applied
action: BaseAction
constructor(ship: Ship, action: BaseAction, target: Target | null) {
super("action", ship, target);
this.action = action;
}
}
}

View file

@ -39,7 +39,7 @@ module TS.SpaceTac.Multi {
await this.writeMessage(null, true);
}
console.log("Echange established", this.token, this.localpeer, this.remotepeer);
console.log("Exchange established", this.token, this.localpeer, this.remotepeer);
}
/**

View file

@ -9,8 +9,8 @@ module TS.SpaceTac.UI {
// Interacting player
player: Player
// Exchange (for remote session only)
exchange: Multi.Exchange | null
// Multiplayer sharing
multi: MultiBattle
// Layers
layer_background: Phaser.Group
@ -64,7 +64,7 @@ module TS.SpaceTac.UI {
this.battle = battle;
this.ship_hovered = null;
this.background = null;
this.exchange = null;
this.multi = new MultiBattle();
this.battle.timer = this.timer;
@ -141,7 +141,8 @@ module TS.SpaceTac.UI {
// If we are on a remote session, start the exchange
if (!this.session.primary && this.gameui.session_token) {
this.setupRemoteSession(this.gameui.session_token);
// TODO handle errors or timeout
this.multi.setup(this, this.battle, this.gameui.session_token, false);
}
}
@ -285,15 +286,5 @@ module TS.SpaceTac.UI {
this.player.revertBattle();
this.game.state.start('router');
}
/**
* Setup exchange for remote session
*/
async setupRemoteSession(token: string) {
this.exchange = new Multi.Exchange(this.getConnection(), token);
await this.exchange.start();
// TODO read actions and apply them
}
}
}

View file

@ -0,0 +1,96 @@
module TS.SpaceTac.UI {
/**
* Tool to synchronize two players sharing a battle over network
*/
export class MultiBattle {
// Network exchange of messages
exchange: Multi.Exchange
// True if this peer is the primary one (the one that invited the other)
primary: boolean
// Battle being played
battle: Battle
// Count of battle log events that were processed
processed: number
// Serializer to use for actions
serializer: Serializer
// Timer for scheduling
timer: Timer
/**
* Setup the session other a token
*/
async setup(view: BaseView, battle: Battle, token: string, primary: boolean) {
if (this.exchange) {
// TODO close it
}
this.battle = battle;
this.primary = primary;
this.exchange = new Multi.Exchange(view.getConnection(), token, primary);
await this.exchange.start();
this.serializer = new Serializer(TS.SpaceTac);
this.processed = this.battle.log.events.length;
this.timer = view.timer;
// This is voluntarily not waited on, as it is a background task
this.backgroundSync();
}
/**
* Background work to maintain the battle state in sync between the two peers
*/
async backgroundSync() {
while (true) {
if (this.exchange.writing) {
await this.sendActions();
} else {
await this.receiveAction();
}
}
}
/**
* Send all new actions from the battle log
*/
async sendActions() {
let events = this.battle.log.events;
if (this.processed >= events.length) {
await this.timer.sleep(500);
} else {
while (this.processed < events.length) {
let event = events[this.processed];
this.processed++;
if (event instanceof ActionAppliedEvent) {
let data = this.serializer.serialize(event);
// TODO "over" should be true if the current ship should be played by remote player
await this.exchange.writeMessage(data, false);
}
}
}
}
/**
* Read and apply one action from the peer
*/
async receiveAction() {
let message = await this.exchange.readMessage();
let received = this.serializer.unserialize(message);
if (received instanceof ActionAppliedEvent) {
console.log("Received action from exchange", received);
// TODO Find the matching action, ship and target, and apply
} else {
console.error("Exchange received something that is not an action event", received);
}
this.processed = this.battle.log.events.length;
}
}
}

View file

@ -60,12 +60,14 @@ module TS.SpaceTac.UI {
let conn = this.view.getConnection();
try {
let token = await conn.publish(this.view.session, "Multiplayer invitation");
this.displayMultiplayerToken(token); // TODO On cancel
this.displayMultiplayerToken(token);
let exchange = new Multi.Exchange(this.view.getConnection(), token, true);
await exchange.start();
// TODO Setup the exchange on current view
if (this.view instanceof BattleView) {
await this.view.multi.setup(this.view, this.view.battle, token, true);
} else {
// TODO
this.displayConnectionError();
}
this.close();
} catch (err) {