import { SocketIOWrapper } from './SocketIOWrapper';
import { InputManager } from './InputManager';
import { AssetManager } from './AssetManager';
import {
ScriptBase as ExternalScriptBase,
Context as ExternalContext,
} from './Context';
import { objectOverWrite, objectParseNumeric } from '@ud-viz/utils_shared';
import {
StateInterpolator,
constant,
Object3D,
State,
StateDiff,
} from '@ud-viz/game_shared';
import { Planar, PlanarOption } from '@ud-viz/frame3d';
import { RequestAnimationFrameProcess } from '@ud-viz/utils_browser';
import * as itowns from 'itowns';
/**
* @classdesc Create a multi player game in a {@link Planar}
*/
export class MultiPlanarProcess {
/**
*
* @param {SocketIOWrapper} socketIOWrapper - socket to communicate with gamesocketservice
* @param {itowns.Extent} extent - extent of the itowns view
* @param {AssetManager} assetManager - assetManager of the game {@link AssetManager}
* @param {InputManager} inputManager - input manager of the game {@link InputManager}
* @param {object} options - multi player game planar options
* @param {PlanarOption} options.frame3DPlanarOptions - options frame3Dplanar {@link PlanarOption}
* @param {Object<string,ExternalScriptBase>=} options.externalGameScriptClass - custom external scripts class of your object3D
* @param {number=} options.interpolatorDelay - delay between state computed in game process and the ones in external context
* @param {boolean} options.computeBandWidth - compute bandwidth of the interpolator or not
*/
constructor(
socketIOWrapper,
extent,
assetManager = new AssetManager(),
inputManager = new InputManager(),
options = {}
) {
/**
* websocket communication
*
@type {SocketIOWrapper} */
this.socketIOWrapper = socketIOWrapper;
/**
* buffer to rebuild a frame3Dplanar on demand
*
@type {itowns.Extent}*/
this.extent = extent;
/**
* buffer to rebuild a frame3Dplanar on demand
*
@type {PlanarOption} */
this.frame3DPlanarOptions = options.frame3DPlanarOptions || {};
/**
* game view
*
@type {Planar} */
this.frame3DPlanar = new Planar(extent, this.frame3DPlanarOptions);
/** @type {InputManager} */
this.inputManager = inputManager;
/**
* interpolator to smooth comminucation between the two process
*
@type {StateInterpolator} */
this.interpolator = new StateInterpolator(
options.interpolatorDelay,
options.computeBandWidth
);
/**
* render audio external script context
*
@type {ExternalContext} */
this.externalGameContext = new ExternalContext(
this.frame3DPlanar,
assetManager,
inputManager,
options.externalGameScriptClass || {},
{
socketIOWrapper: this.socketIOWrapper,
interpolator: this.interpolator,
}
);
}
/**
* Start game communication with server
*
* @param {object} readyForGameSocketServiceParams - object serialize then send when game is ready to SocketService
* @param {string} readyForGameSocketServiceParams.entryGameObject3DUUID - uuid of the game object3D to connect with SocketService
* @param {object} readyForGameSocketServiceParams.userData - other information to emit to SocketService
*/
start(readyForGameSocketServiceParams = {}) {
this.externalGameContext.sendCommandsToGameContext = (cmds) => {
if (!cmds.length) return;
this.socketIOWrapper.emit(
constant.WEBSOCKET.MSG_TYPE.COMMANDS,
cmds.map((el) => el.toJSON())
);
};
// start listening on socket events
this.socketIOWrapper.on(
constant.WEBSOCKET.MSG_TYPE.NEW_GAME,
(gameData) => {
console.log(gameData);
const stateJSON = gameData.state;
const state = new State(
new Object3D(stateJSON.object3D),
stateJSON.timestamp
);
objectOverWrite(this.externalGameContext.userData, gameData.userData);
objectParseNumeric(this.externalGameContext.userData);
// check if a game was already running
if (this.interpolator._getLastStateReceived()) {
// TODO: reuse the same view
// replace frame3D
this.frame3DPlanar.dispose();
this.frame3DPlanar = new Planar(
this.extent,
this.frame3DPlanarOptions
);
// reset
this.externalGameContext.reset(this.frame3DPlanar);
} else {
// first state received start process
const process = new RequestAnimationFrameProcess(30);
process.start((dt) => {
// send commands
const commands = this.inputManager
.computeCommands()
.map((el) => el.toJSON());
if (commands.length) {
this.socketIOWrapper.emit(
constant.WEBSOCKET.MSG_TYPE.COMMANDS,
commands
);
}
// simulation
this.externalGameContext.step(
dt,
this.interpolator.computeCurrentStates()
);
this.frame3DPlanar.render();
});
}
// init start
this.frame3DPlanar.enableItownsViewRendering(false);
this.interpolator.onFirstState(state);
this.inputManager.startListening(this.frame3DPlanar.domElement);
}
);
this.socketIOWrapper.on(constant.WEBSOCKET.MSG_TYPE.GAME_DIFF, (diff) => {
this.interpolator.onNewDiff(new StateDiff(diff));
});
this.socketIOWrapper.emit(
constant.WEBSOCKET.MSG_TYPE.READY_FOR_GAME,
readyForGameSocketServiceParams
);
}
}