homegames-core
Version:
Play games at home
417 lines (335 loc) • 16.3 kB
JavaScript
let { Squisher } = require('squish-0767');
const { generateName } = require('./common/util');
const squishMap = require('./common/squish-map');
const HomegamesRoot = require('./homegames_root/HomegamesRoot');
const HomegamesDashboard = require('./dashboard/HomegamesDashboard');
const squisherUpdateTimes = [];
const path = require('path');
let baseDir = path.dirname(require.main.filename);
if (baseDir.endsWith('src')) {
baseDir = baseDir.substring(0, baseDir.length - 3);
}
const { getConfigValue, log } = require('homegames-common');
const HomenamesHelper = require('./util/homenames-helper');
//const BEZEL_SIZE_X = getConfigValue('BEZEL_SIZE_X', 15);
//const _BEZEL_SIZE_Y = getConfigValue('BEZEL_SIZE_Y', 15);
//const PERFORMANCE_PROFILING = getConfigValue('PERFORMANCE_PROFILING', false);
//const BEZEL_SIZE_Y = PERFORMANCE_PROFILING ? _BEZEL_SIZE_Y + 20 : _BEZEL_SIZE_Y;
const BEZEL_SIZE_X = getConfigValue('BEZEL_SIZE_X', 10);
const BEZEL_SIZE_Y = getConfigValue('BEZEL_SIZE_Y', 10);
class GameSession {
constructor(game, port, username) {
this.game = game;
this.port = port;
this.username = username;
const gameSquishVersion = game.constructor.metadata().squishVersion;
if (squishMap[gameSquishVersion]) {
Squisher = require(squishMap[gameSquishVersion]).Squisher;
}
this.homenamesHelper = new HomenamesHelper(this.port, this.username);
this.playerInfoMap = {};
this.clientInfoMap = {};
this.playerSettingsMap = {};
this.stateHistory = [];
this.homegamesRoot = new HomegamesRoot(this, game instanceof HomegamesDashboard, false);
this.customBottomLayer = {
root: this.homegamesRoot.getRoot(),
scale: {x: 1, y: 1},
assets: this.homegamesRoot.constructor.metadata().assets
};
this.customTopLayer = {
root: this.homegamesRoot.getTopLayerRoot(),
scale: {x: 1, y: 1}
};
this.scale = {x: (100 - BEZEL_SIZE_X) / 100, y: (100 - BEZEL_SIZE_Y) / 100};
this.squisher = new Squisher({ game, scale: this.scale, customBottomLayer: this.customBottomLayer, customTopLayer: this.customTopLayer, onAssetUpdate: (newAssetBundle) => {
for (const playerId in this.players) {
this.players[playerId].receiveUpdate(newAssetBundle);
}
} });
this.hgRoot = this.squisher.hgRoot;
this.squisher.addListener((squished) => {this.handleSquisherUpdate(squished);});
this.gameMetadata = this.game.constructor.metadata && this.game.constructor.metadata();
this.aspectRatio = this.gameMetadata && this.gameMetadata.aspectRatio || {x: 16, y: 9};
this.players = {};
this.spectators = {};
}
handleNewAsset(key, asset) {
return new Promise((resolve, reject) => {
this.squisher.handleNewAsset(key, asset).then(newBundle => {
for (const playerId in this.players) {
const player = this.players[playerId];
player.receiveUpdate(newBundle);
}
resolve();
});
});
}
doSendUpdate() {
for (const playerId in this.players) {
const playerSettings = this.playerSettingsMap[playerId] || {};
let playerFrame = this.squisher.getPlayerFrame(playerId);
if (playerFrame) {
this.players[playerId].receiveUpdate(playerFrame.flat());
} else {
log.error('No player frame available for player ' + playerId);
}
}
for (const spectatorId in this.spectators) {
const playerSettings = {};
let playerFrame = this.squisher.getPlayerFrame(spectatorId);
if (playerSettings) {
if ((!playerSettings.SOUND || !playerSettings.SOUND.enabled) && playerFrame) {
playerFrame = playerFrame.filter(f => {
const unsquished = this.squisher.unsquish(f);
if (unsquished.node.asset) {
if (this.game.getAssets && this.game.getAssets() && this.game.getAssets()[Object.keys(unsquished.node.asset)[0]]) {
if (this.game.getAssets()[Object.keys(unsquished.node.asset)[0]].info.type === 'audio') {
return false;
}
}
}
return true;
});
}
}
if (playerFrame) {
this.spectators[spectatorId].receiveUpdate(playerFrame.flat());
}
}
}
handleSquisherUpdate(squished) {
this.doSendUpdate();
}
addSpectator(spectator) {
this.squisher.assetBundle && spectator.receiveUpdate(this.squisher.assetBundle);
// spectator.receiveUpdate(this.squisher.getPlayerFrame(spectator.id));
spectator.addInputListener(this, true);
this.spectators[Number(spectator.id)] = spectator;
this.homegamesRoot.handleNewSpectator(spectator);
}
addPlayer(player) {
this.playerSettingsMap[player.id] = {
'SOUND': true
}
this.squisher.assetBundle && player.receiveUpdate(this.squisher.assetBundle);
this.players[player.id] = player;
const doThing = () => {
this.homenamesHelper.getPlayerInfo(player.id).then(playerInfo => {
this.homenamesHelper.getPlayerSettings(player.id).then(playerSettings => {
this.homenamesHelper.getClientInfo(player.id).then(clientInfo => {
this.playerInfoMap[player.id] = playerInfo;
this.clientInfoMap[player.id] = clientInfo;
this.playerSettingsMap[player.id] = playerSettings;
const playerPayload = {
playerId: player.id,
settings: this.playerSettingsMap[player.id],
info: this.playerInfoMap[player.id],
clientInfo,
requestedGame: player.requestedGame
};
const rootPayload = Object.assign({
requestedGame: player.requestedGame
}, playerPayload);
this.homenamesHelper.addListener(player.id);
this.homegamesRoot.handleNewPlayer(rootPayload);
this.game.handleNewPlayer && this.game.handleNewPlayer(playerPayload);
player.requestedGame = null;
player.addInputListener(this);
});
});
});
};
if (player.info && player.info.name) {
doThing();
} else {
const playerName = generateName();
this.homenamesHelper.updatePlayerInfo(player.id, { playerName }).then(() => {
this.homenamesHelper.updateClientInfo(player.id, player.clientInfo).then(() => {
doThing();
});
});
}
}
handlePlayerUpdate(playerId, {info, settings}) {
this.playerInfoMap[playerId] = info;
this.playerSettingsMap[playerId] = settings;
this.homegamesRoot.handlePlayerUpdate(playerId, {info, settings});
this.game.handlePlayerUpdate && this.game.handlePlayerUpdate(playerId, { info, settings });
}
handleSpectatorDisconnect(spectatorId) {
this.homegamesRoot.handleSpectatorDisconnect(spectatorId);
delete this.spectators[spectatorId];
}
handlePlayerDisconnect(playerId) {
delete this.players[playerId];
this.game.handlePlayerDisconnect && this.game.handlePlayerDisconnect(playerId);
this.homegamesRoot.handlePlayerDisconnect(playerId);
}
initialize(cb) {
if (this.initialized) {
cb && cb();
} else {
this.squisher.initialize().then(() => {
this.initialized = true;
cb && cb();
}).catch(err => {
console.error('Error initializing squisher');
console.error(err);
});
}
}
handlePlayerInput(playerId, input) {
if (input.type === 'click') {
this.handleClick(playerId, input.data);
} else if (input.type === 'keydown') {
this.game.handleKeyDown && this.game.handleKeyDown(playerId, input.key);
} else if (input.type === 'keyup') {
this.game.handleKeyUp && this.game.handleKeyUp(playerId, input.key);
} else if (input.type === 'input') {
if (input.gamepad) {
this.game.handleGamepadInput && this.game.handleGamepadInput(playerId, input);
} else {
const node = this.game.findNode(input.nodeId) || this.customTopLayer.root.findChild(input.nodeId);
if (node && node.node.input) {
// hilarious
if (node.node.input.type === 'file') {
node.node.input.oninput(playerId, Object.values(input.input));
} else {
node.node.input.oninput(playerId, input.input);
}
}
}
} else if (input.type === 'clientInfo') {
// if (this.game && this.game.deviceRules) {
// const deviceRules = this.game.deviceRules();
// if (deviceRules.aspectRatio) {
// deviceRules.aspectRatio(player.id, player.clientInfo.aspectRatio);
// }
// }
} else if (input.type === 'mouseup') {
this.game.handleMouseUp && this.game.handleMouseUp(playerId, input.data);
} else if (input.type === 'onhover') {
const node = this.game.findNode(input.nodeId) || this.customTopLayer.root.findChild(input.nodeId);
if (node && node.node?.onHover) {
node.node.onHover(playerId);
}
} else if (input.type === 'offhover') {
const node = this.game.findNode(input.nodeId) || this.customTopLayer.root.findChild(input.nodeId);
if (node && node.node?.offHover) {
node.node.offHover(playerId);
}
} else {
log.info('Unknown input type: ', input.type);
}
}
movePlayer({ playerId, port }) {
const player = this.players[playerId];
if (player) {
player.receiveUpdate([5, Math.floor(port / 100), Math.floor(port % 100)]);
}
}
handleClick(playerId, click) {
if (click.x >= 100 || click.y >= 100) {
return;
}
const spectating = this.spectators[playerId] ? true : false;
const clickedNode = this.findClick(click.x, click.y, spectating, playerId);
if (clickedNode) {
const clickedNodeId = clickedNode.id;
// todo: implement get node (maybe maintain map in game?)
const realNode = this.game.findNode(clickedNodeId) || this.customBottomLayer.root.findChild(clickedNodeId) || this.customTopLayer.root.findChild(clickedNodeId);
if (click.x <= (BEZEL_SIZE_X / 2) || click.x >= (100 - BEZEL_SIZE_X / 2) || click.y <= BEZEL_SIZE_Y / 2 || click.y >= (100 - BEZEL_SIZE_Y / 2)) {
realNode.node.handleClick && realNode.node.handleClick(playerId, click.x, click.y);//click.x, click.y);//(click.x - (BEZEL_SIZE_X / 2)) * scaleX, (click.y - (BEZEL_SIZE_Y / 2) * scaleY));
} else {
const shiftedX = click.x - (BEZEL_SIZE_X / 2);
const shiftedY = click.y - (BEZEL_SIZE_Y / 2);
const scaledX = shiftedX * ( 1 / ((100 - BEZEL_SIZE_X) / 100));
const scaledY = shiftedY * ( 1 / ((100 - BEZEL_SIZE_Y) / 100));
realNode.node.handleClick && realNode.node.handleClick(playerId, scaledX, scaledY);//click.x, click.y);//(click.x - (BEZEL_SIZE_X / 2)) * scaleX, (click.y - (BEZEL_SIZE_Y / 2) * scaleY));
}
}
}
findClick(x, y, spectating, playerId = 0) {
let clicked = null;
if (this.customBottomLayer) {
const scale = {x: 1, y: 1};
clicked = this.findClickHelper(x, y, spectating, playerId, this.customBottomLayer.root.node, null, scale, false) || clicked;
}
for (const layerIndex in this.game.getLayers()) {
const layer = this.game.getLayers()[layerIndex];
const scale = layer.scale || this.scale;
clicked = this.findClickHelper(x, y, spectating, playerId, this.game.getLayers()[layerIndex].root.node, null, scale, true) || clicked;
}
if (this.customTopLayer) {
const scale = {x: 1, y: 1};
clicked = this.findClickHelper(x, y, spectating, playerId, this.customTopLayer.root.node, null, scale, false) || clicked;
}
return clicked;
}
findClickHelper(x, y, spectating, playerId, node, clicked = null, scale, inGame) {
if (node.playerIds.length > 0 && !node.playerIds.find(x => x === playerId)) {
return clicked;
}
if ((node.playerIds.length === 0 || node.playerIds.find(x => x === playerId)) && node.coordinates2d !== undefined && node.coordinates2d !== null) {
const vertices = [];
for (const i in node.coordinates2d) {
const xOffset = 100 - (scale.x * 100);
const yOffset = 100 - (scale.y * 100);
const scaledX = node.coordinates2d[i][0] * ((100 - xOffset) / 100) + (xOffset / 2);
const scaledY = node.coordinates2d[i][1] * ((100 - yOffset) / 100) + (yOffset / 2);
vertices.push([scaledX, scaledY]);
}
let isInside = false;
let minX = vertices[0][0];
let maxX = vertices[0][0];
let minY = vertices[0][1];
let maxY = vertices[0][1];
for (let i = 1; i < vertices.length; i++) {
const vert = vertices[i];
minX = Math.min(vert[0], minX);
maxX = Math.max(vert[0], maxX);
minY = Math.min(vert[1], minY);
maxY = Math.max(vert[1], maxY);
}
if (!(x < minX || x > maxX || y < minY || y > maxY)) {
let i = 0;
let j = vertices.length - 1;
for (i, j; i < vertices.length; j=i++) {
if ((vertices[i][1] > y) != (vertices[j][1] > y) &&
x < (vertices[j][0] - vertices[i][0]) * (y - vertices[i][1]) / (vertices[j][1] - vertices[i][1]) + vertices[i][0]) {
isInside = !isInside;
}
}
}
if (isInside) {
if (spectating) {
if (!inGame) {
clicked = node;
}
} else {
clicked = node;
}
}
}
for (const i in node.children) {
clicked = this.findClickHelper(x, y, spectating, playerId, node.children[i].node, clicked, scale, inGame);
}
return clicked;
}
setServerCode(serverCode) {
log.info("Public server code: " + serverCode)
if (!this.homegamesRoot.isDashboard) {
this.homegamesRoot.handleServerCode(serverCode);
}
}
spectateSession(playerId) {
const player = this.players[playerId];
player.receiveUpdate([6, Math.floor(this.port / 100), Math.floor(this.port % 100)]);
}
joinSession(spectatorId) {
const spectator = this.spectators[spectatorId];
spectator.receiveUpdate([5, Math.floor(this.port / 100), Math.floor(this.port % 100)]);
}
}
module.exports = GameSession;