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: display tooltips larger and on the side of screen where the finger is not
|
||||||
* Mobile: targetting in two times, using a draggable target indicator
|
* 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
|
Postponed
|
||||||
---------
|
---------
|
||||||
|
|
||||||
|
|
|
@ -227,9 +227,11 @@ module TS.SpaceTac.Specs {
|
||||||
expect(action.activated).toBe(false);
|
expect(action.activated).toBe(false);
|
||||||
|
|
||||||
expect(battle.log.events).toEqual([
|
expect(battle.log.events).toEqual([
|
||||||
|
new ActionAppliedEvent(ship, action, null),
|
||||||
new ToggleEvent(ship, action, true),
|
new ToggleEvent(ship, action, true),
|
||||||
new ActiveEffectsEvent(ship, [], [], [new AttributeEffect("power_capacity", 1)]),
|
new ActiveEffectsEvent(ship, [], [], [new AttributeEffect("power_capacity", 1)]),
|
||||||
new ValueChangeEvent(ship, new ShipAttribute("power capacity", 1), 1),
|
new ValueChangeEvent(ship, new ShipAttribute("power capacity", 1), 1),
|
||||||
|
new ActionAppliedEvent(ship, action, null),
|
||||||
new ToggleEvent(ship, action, false),
|
new ToggleEvent(ship, action, false),
|
||||||
new ActiveEffectsEvent(ship, [], [], []),
|
new ActiveEffectsEvent(ship, [], [], []),
|
||||||
new ValueChangeEvent(ship, new ShipAttribute("power capacity", 0), -1),
|
new ValueChangeEvent(ship, new ShipAttribute("power capacity", 0), -1),
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
module TS.SpaceTac {
|
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 {
|
export class BaseAction {
|
||||||
// Identifier code for the type of action
|
// Identifier code for the type of action
|
||||||
code: string
|
code: string
|
||||||
|
@ -140,6 +144,11 @@ module TS.SpaceTac {
|
||||||
this.equipment.cooldown.use();
|
this.equipment.cooldown.use();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let battle = ship.getBattle();
|
||||||
|
if (battle) {
|
||||||
|
battle.log.add(new ActionAppliedEvent(ship, this, checked_target));
|
||||||
|
}
|
||||||
|
|
||||||
this.customApply(ship, checked_target);
|
this.customApply(ship, checked_target);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -50,6 +50,7 @@ module TS.SpaceTac {
|
||||||
expect(drone.radius).toEqual(4);
|
expect(drone.radius).toEqual(4);
|
||||||
expect(drone.effects).toEqual([new DamageEffect(50)]);
|
expect(drone.effects).toEqual([new DamageEffect(50)]);
|
||||||
expect(battle.log.events).toEqual([
|
expect(battle.log.events).toEqual([
|
||||||
|
new ActionAppliedEvent(ship, action, Target.newFromLocation(5, 0)),
|
||||||
new DroneDeployedEvent(drone)
|
new DroneDeployedEvent(drone)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
|
@ -61,16 +61,19 @@ module TS.SpaceTac {
|
||||||
expect(ship.arena_y).toBeCloseTo(3.535533, 0.00001);
|
expect(ship.arena_y).toBeCloseTo(3.535533, 0.00001);
|
||||||
expect(ship.values.power.get()).toEqual(0);
|
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].code).toEqual("value");
|
||||||
expect(battle.log.events[0].ship).toBe(ship);
|
expect(battle.log.events[0].ship).toBe(ship);
|
||||||
expect((<ValueChangeEvent>battle.log.events[0]).value).toEqual(
|
expect((<ValueChangeEvent>battle.log.events[0]).value).toEqual(
|
||||||
new ShipValue("power", 0, 20));
|
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);
|
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.x).toBeCloseTo(3.535533, 0.00001);
|
||||||
expect(dest.y).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);
|
expect(equipment.action.checkCannotBeApplied(ship)).toBe(null);
|
||||||
equipment.action.apply(ship, target);
|
equipment.action.apply(ship, target);
|
||||||
checkHP(50, 10, 50, 10, 50, 10);
|
checkHP(50, 10, 50, 10, 50, 10);
|
||||||
expect(battle.log.events.length).toBe(4);
|
expect(battle.log.events.length).toBe(5);
|
||||||
expect(battle.log.events[0]).toEqual(new FireEvent(ship, equipment, Target.newFromLocation(1, 0)));
|
expect(battle.log.events[0]).toEqual(new ActionAppliedEvent(ship, equipment.action, Target.newFromLocation(1, 0)));
|
||||||
expect(battle.log.events[1]).toEqual(new DamageEvent(ship, 0, 20));
|
expect(battle.log.events[1]).toEqual(new FireEvent(ship, equipment, Target.newFromLocation(1, 0)));
|
||||||
expect(battle.log.events[2]).toEqual(new DamageEvent(enemy1, 0, 20));
|
expect(battle.log.events[2]).toEqual(new DamageEvent(ship, 0, 20));
|
||||||
expect(battle.log.events[3]).toEqual(new DamageEvent(enemy2, 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();
|
battle.log.clear();
|
||||||
equipment.cooldown.cool();
|
equipment.cooldown.cool();
|
||||||
|
@ -74,10 +75,11 @@ module TS.SpaceTac.Equipments {
|
||||||
expect(equipment.action.checkCannotBeApplied(ship)).toBe(null);
|
expect(equipment.action.checkCannotBeApplied(ship)).toBe(null);
|
||||||
equipment.action.apply(ship, target);
|
equipment.action.apply(ship, target);
|
||||||
checkHP(50, 10, 40, 0, 40, 0);
|
checkHP(50, 10, 40, 0, 40, 0);
|
||||||
expect(battle.log.events.length).toBe(3);
|
expect(battle.log.events.length).toBe(4);
|
||||||
expect(battle.log.events[0]).toEqual(new FireEvent(ship, equipment, target));
|
expect(battle.log.events[0]).toEqual(new ActionAppliedEvent(ship, equipment.action, target));
|
||||||
expect(battle.log.events[1]).toEqual(new DamageEvent(enemy1, 10, 10));
|
expect(battle.log.events[1]).toEqual(new FireEvent(ship, equipment, target));
|
||||||
expect(battle.log.events[2]).toEqual(new DamageEvent(enemy2, 10, 10));
|
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();
|
battle.log.clear();
|
||||||
equipment.cooldown.cool();
|
equipment.cooldown.cool();
|
||||||
|
@ -87,8 +89,9 @@ module TS.SpaceTac.Equipments {
|
||||||
expect(equipment.action.checkCannotBeApplied(ship)).toBe(null);
|
expect(equipment.action.checkCannotBeApplied(ship)).toBe(null);
|
||||||
equipment.action.apply(ship, target);
|
equipment.action.apply(ship, target);
|
||||||
checkHP(50, 10, 40, 0, 40, 0);
|
checkHP(50, 10, 40, 0, 40, 0);
|
||||||
expect(battle.log.events.length).toBe(1);
|
expect(battle.log.events.length).toBe(2);
|
||||||
expect(battle.log.events[0]).toEqual(new FireEvent(ship, equipment, target));
|
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);
|
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
|
// Interacting player
|
||||||
player: Player
|
player: Player
|
||||||
|
|
||||||
// Exchange (for remote session only)
|
// Multiplayer sharing
|
||||||
exchange: Multi.Exchange | null
|
multi: MultiBattle
|
||||||
|
|
||||||
// Layers
|
// Layers
|
||||||
layer_background: Phaser.Group
|
layer_background: Phaser.Group
|
||||||
|
@ -64,7 +64,7 @@ module TS.SpaceTac.UI {
|
||||||
this.battle = battle;
|
this.battle = battle;
|
||||||
this.ship_hovered = null;
|
this.ship_hovered = null;
|
||||||
this.background = null;
|
this.background = null;
|
||||||
this.exchange = null;
|
this.multi = new MultiBattle();
|
||||||
|
|
||||||
this.battle.timer = this.timer;
|
this.battle.timer = this.timer;
|
||||||
|
|
||||||
|
@ -141,7 +141,8 @@ module TS.SpaceTac.UI {
|
||||||
|
|
||||||
// If we are on a remote session, start the exchange
|
// If we are on a remote session, start the exchange
|
||||||
if (!this.session.primary && this.gameui.session_token) {
|
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.player.revertBattle();
|
||||||
this.game.state.start('router');
|
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();
|
let conn = this.view.getConnection();
|
||||||
try {
|
try {
|
||||||
let token = await conn.publish(this.view.session, "Multiplayer invitation");
|
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);
|
if (this.view instanceof BattleView) {
|
||||||
await exchange.start();
|
await this.view.multi.setup(this.view, this.view.battle, token, true);
|
||||||
|
} else {
|
||||||
// TODO Setup the exchange on current view
|
// TODO
|
||||||
|
this.displayConnectionError();
|
||||||
|
}
|
||||||
|
|
||||||
this.close();
|
this.close();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
Loading…
Reference in a new issue