multiplayer: WIP on spectator mode for battles
This commit is contained in:
parent
c0ee40633a
commit
daf59cc16d
8
TODO.md
8
TODO.md
|
@ -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
|
||||
---------
|
||||
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
]);
|
||||
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
17
src/core/events/ActionAppliedEvent.ts
Normal file
17
src/core/events/ActionAppliedEvent.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
96
src/ui/battle/MultiBattle.ts
Normal file
96
src/ui/battle/MultiBattle.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue