Skip to content

Commit

Permalink
Adds CrossWindow plugin for all Mantra games
Browse files Browse the repository at this point in the history
  • Loading branch information
Marak committed Mar 8, 2024
1 parent 47b8f3e commit 6ab05aa
Show file tree
Hide file tree
Showing 2 changed files with 192 additions and 0 deletions.
3 changes: 3 additions & 0 deletions mantra-game/plugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ import RBush from './plugins/rbush/RBush.js';

// Utility
import Markup from './plugins/markup/Markup.js';
import CrossWindow from './plugins/crosswindow/CrossWindow.js';


// AI Nueral Net
import TensorFlow from './plugins/tensorflow/TensorFlow.js';
Expand Down Expand Up @@ -217,6 +219,7 @@ let plugins = {
Timers,
RBush,
Markup,
CrossWindow,
TensorFlow,
Midi,
MidiGUI,
Expand Down
189 changes: 189 additions & 0 deletions mantra-game/plugins/crosswindow/CrossWindow.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
// CrossWindow.js - Mantra Plugin - Marak Squires 2024
// see: CrossWindow.js: https://github.com/yantra-core/CrossWindow.js

import * as CW from '../../../../CrossWindow.js/browser-shim.js';
import * as CWDEBUG from '../../../../CrossWindow.js/browser-shim-debugger.js';

export default class CrossWindow {
static id = 'crosswindow';
constructor(game) {
this.id = CrossWindow.id;
this.crosswindow = null;
}

init(game) {
this.game = game;
console.log('CrossWindow plugin initialized', CW);
// Initialize CrossWindow instance
this.crosswindow = new CW.CrossWindow(window, {
broadcastMouseEvents: true,
broadcastKeyboardEvents: true,
});

// Optionally initialize CrossWindow debugger
this.crossWindowDebugger = new CWDEBUG.CrossWindowDebugger(this.crosswindow, {
showOtherWindows: true,
showWindowLegend: true,
showWindowCount: true,
});

// Listen for Mantra and CrossWindow events
this.setupListeners();
}

setupListeners() {
// Mantra event listener for entity exiting viewport
this.game.on('entity::exited::viewport', this.handleEntityExit.bind(this));

// CrossWindow message listener
this.crosswindow.on('message', this.handleMessage.bind(this));
}

// Function to assemble data about an entity, omitting functions and specific properties
assembleEntityData(entity, actualItems) {
const entityData = { ...entity };

// Removing function properties and the 'graphics' property
Object.keys(entityData).forEach(key => {
if (typeof entityData[key] === 'function' || key === 'graphics') {
delete entityData[key];
}
});

// Setting default friction values
Object.assign(entityData, {
screenPosition: entity.screenPosition,
friction: 0,
frictionAir: 0,
frictionStatic: 0
});

return entityData;
}

// Main logic for handling an entity exiting the viewport
handleEntityExit(entity) {
let game = this.game;
//console.log('handleEntityExithandleEntityExit entityentityentityentity', entity)
let entityData = this.assembleEntityData(entity);
//console.log("entityDataentityData", entityData)
const bestWindow = this.crosswindow.getBestWindow(entityData, game.data.camera.currentZoom);
//console.log('handleEntityExit', entityData, bestWindow)
if (bestWindow) {
if (bestWindow.windowId === this.crosswindow.windowId) {
// If the best window is the current window, update its position without messaging
const position = { ...bestWindow.entryPosition };
entityData.position = position;
this.ensuresEntityInViewport(entityData, 50); // Ensure the entity is within the viewport
game.setPosition(entity.id, position);
} else {
// If the best window is a different window, remove the entity and send a message
game.removeEntity(entity.id);
entityData.action = 'message'; // TODO: fix this and add 'action' and 'payload' top-level scope to CrossWindow
bestWindow.postMessage(entityData);
}
} else {
console.warn('No best window found for entity');
}
}

// Example usage: handleEntityExit(entity, gameInstance, crosswindowInstance);

handleMessage(event) {
// console.log('handleMessagehandleMessage', event)
const data = event; // TODO: fix this in CrossWindow, add top-level scope of "action" and "payload"
if (!data) return;

// Focus the current window to ensure user attention
window.focus();
switch (event.action) {
case 'intersecting':
// Handle intersecting windows if necessary
break;
case 'message':
this.ensuresEntityInViewport(data); // Ensure the entity is within viewport bounds

if (data.type === 'PLAYER') {
// Specific logic for player entities, e.g., setPlayerId, updateEntity, etc.
// TODO: better global tracking of data cross windows via local storage
// Remark: might be better suited as separate namespace from CrossWindow localstorage
// Could use LocalStorage plugin for Mantra, etc
// this.game.setPlayerId(data.id);
localStorage.setItem('mantra-currentPlayer', JSON.stringify({ x: data.position.x + window.screenX, y: data.position.y + window.screenY }));
}

let entity = data; // remove this
let entityData = {
id: entity.id,
type: entity.type,
health: entity.health,
position: entity.position,
rotation: entity.rotation,
// velocity: entity.velocity,
rotation: entity.rotation,
meta: entity.meta,
// items: actualItems, // TODO: items merge with source map
size: entity.size,
body: entity.body,
mass: entity.mass,
color: entity.color,
source: entity.source,
// etc, all props, iterate? mantra helper?
};


for (let p in entity) {
if (typeof entity[p] !== 'function' && p !== 'graphics') {
entityData[p] = entity[p];
}
}
entityData.screenPosition = entity.screenPosition;
// TODO: friction isn't a component property in mantra, its subset of body
entityData.friction = 0;
entityData.frictionAir = 0;
entityData.frictionStatic = 0;
entityData.source = this.crosswindow.windowId;

// Inflate or update entity in the game
let ent = this.game.inflateEntity(entityData);
if (ent.type === 'PLAYER') {
this.game.setPlayerId(ent.id); // for now
}
break;
default:
console.warn('Unhandled message action:', data.action);
}
}

ensuresEntityInViewport(entityData, buffer = 50) {
// console.log('ensuresEntityInViewport', entityData)

let result = game.systems['graphics-css'].isEntityInViewport(entityData, game.data.camera.currentZoom);

if (!result.inViewport) {

// clamp the values
// console.log('before adjustement', entityData.position.x, entityData.position.y)
if (result.outsideOf.left) {
entityData.position.x = -window.innerWidth / 2 / game.data.camera.currentZoom;
entityData.position.x += buffer;
}
if (result.outsideOf.right) {
entityData.position.x = window.innerWidth / 2 / game.data.camera.currentZoom;
entityData.position.x -= buffer;
}
if (result.outsideOf.top) {
entityData.position.y = -window.innerHeight / 2 / game.data.camera.currentZoom;
entityData.position.y += buffer;
}
if (result.outsideOf.bottom) {
entityData.position.y = window.innerHeight / 2 / game.data.camera.currentZoom;
entityData.position.y -= buffer;
}

// console.log('after adjustement', entityData.position.x, entityData.position.y)

}

}
}

0 comments on commit 6ab05aa

Please sign in to comment.