repo: janusweb
action: commit
revision: 
path_from: 
revision_from: 84333fc8ccefbbeaedcc47cc1f6591d79e17d764:
path_to: 
revision_to: 
git.thebackupbox.net
janusweb
git clone git://git.thebackupbox.net/janusweb
commit 84333fc8ccefbbeaedcc47cc1f6591d79e17d764
Author: James Baicoianu 
Date:   Sun Jun 18 20:43:02 2017 -0700

    Added custom config window with avatar details, added multiplayermanager

diff --git a/scripts/client.js b/scripts/client.js
index 1043e852038bd5ca7cc0f3037f5ef1e818960dc6..
index ..a319fd63bb0c9e3e5b331478d7bbe6cbf0dea86b 100644
--- a/scripts/client.js
+++ b/scripts/client.js
@@ -1,4 +1,4 @@
-elation.require(['engine.engine', 'engine.assets', 'engine.things.light_ambient', 'engine.things.light_directional', 'engine.things.light_point', 'janusweb.janusweb', 'janusweb.chat', 'janusweb.janusplayer', 'janusweb.ui'], function() {
+elation.require(['engine.engine', 'engine.assets', 'engine.things.light_ambient', 'engine.things.light_directional', 'engine.things.light_point', 'janusweb.janusweb', 'janusweb.chat', 'janusweb.janusplayer', 'janusweb.ui', 'janusweb.configuration'], function() {

   // If getCurrentScript returns non-null here, then it means we're in release mode
   var clientScript = elation.utils.getCurrentScript();
@@ -193,5 +193,22 @@ elation.require(['engine.engine', 'engine.assets', 'engine.things.light_ambient'
         this.fullscreenbutton.setLabel('Expand');
       }
     }
+    this.configureOptions = function() {
+      if (!this.configmenu) {
+        var configpanel = elation.janusweb.configuration({client: this});
+        this.configmenu = elation.ui.window({
+          append: document.body,
+          classname: this.name + '_config',
+          center: true,
+          resizable: false,
+          title: 'Configuration',
+          controls: true,
+          maximize: false,
+          minimize: false,
+          content: configpanel
+        });
+      }
+      this.configmenu.show();
+    }
   }, elation.engine.client);
 });
diff --git a/scripts/configuration.js b/scripts/configuration.js
new file mode 100644
index 0000000000000000000000000000000000000000..2d523bb81054fb4f4fbbd43ed126ff6e41d406c0
--- /dev/null
+++ b/scripts/configuration.js
@@ -0,0 +1,54 @@
+elation.require(['engine'], function() {
+  elation.component.add('janusweb.configuration', function() {
+    this.initPanels = function(panels) {
+      if (!panels) panels = {};
+
+      panels['general'] = {
+        label: 'General',
+        content: elation.janusweb.configuration.general({client: this.client, engine: this.engine, view: this.view})
+      };
+
+      elation.janusweb.configuration.extendclass.initPanels.call(this, panels);
+
+      return panels;
+    }
+  }, elation.engine.configuration);
+
+  elation.component.add('janusweb.configuration.general', function() {
+    this.init = function() {
+      elation.janusweb.configuration.general.extendclass.init.call(this);
+
+      this.client = this.args.client;
+      this.engine = this.client.engine;
+      this.view = this.client.view;
+
+      // Capture Settings
+      var avatarsection = elation.ui.labeldivider({
+        append: this,
+        label: 'Avatar'
+      });
+      var username = elation.ui.input({
+        append: this,
+        label: 'Username',
+        value: this.client.janusweb.getUsername(),
+        events: {
+          change: elation.bind(this, function(ev) {
+            this.client.janusweb.setUsername(ev.target.value)
+          })
+        }
+      });
+      var avatar = elation.ui.textarea({
+        append: this,
+        label: 'Avatar',
+        value: this.client.player.getAvatarData(),
+        classname: 'janusweb_config_avatar',
+        events: {
+          change: elation.bind(this, function(ev) {
+            this.client.player.setAvatar(ev.target.value)
+          })
+        }
+      });
+      
+    }
+  }, elation.ui.panel_vertical);
+});
diff --git a/scripts/multiplayermanager.js b/scripts/multiplayermanager.js
new file mode 100644
index 0000000000000000000000000000000000000000..87fa7ded94b51a72aba76073f31adc39b770f47c
--- /dev/null
+++ b/scripts/multiplayermanager.js
@@ -0,0 +1,443 @@
+elation.require(['janusweb.external.JanusClientConnection', 'janusweb.external.JanusFireboxParser'], function() {
+  /**
+   * multiplayermanager keeps track of all known rooms, and maintains a list of servers mapped to those rooms
+   * When new rooms are created, we register them with the multiplayermanager which adds some event listeners
+   * to the room.  Each room fires events when the player joins or parts it, and the networkmanager uses these
+   * to manage subscriptions on each server.  Networkmanager will forward all incoming packets to their 
+   * respective rooms, and at the same time will regularly send updates about the player's state in the current 
+   * active room to the appropriate server.
+   *
+   * Client should be good about unsubscribing from rooms as you traverse portals, the networkmanager will remain
+   * connected and subscribed to a room indefinitely unless told to leave
+   */
+
+  elation.component.add('janusweb.multiplayermanager', function() {
+    this.init = function() {
+      this.janusweb = this.args.janusweb;
+      this.servers = {};
+      this.rooms = {};
+      this.roomservers = {};
+      this.remoteplayers = {};
+      this.enabled = false;
+      this.player = this.args.player;
+      this.parser = new JanusFireboxParser();
+
+      this.updateRate = 50;
+      this.sentUpdates = 0;
+      this.lastUpdate = 0;
+      this.avatarNeedsUpdate = true;
+
+      var hashargs = elation.url(),
+          server = elation.utils.any(this.args.server, hashargs['janus.server'], elation.config.get('janusweb.network.server')),
+          host = elation.utils.any(this.args.host, hashargs['janus.host'], elation.config.get('janusweb.network.host')),
+          //port = elation.utils.any(this.args.port, hashargs['janus.port'], elation.config.get('janusweb.network.port', 5567));
+          port = 5567;
+
+      if (server) {
+        this.defaultserver = server;
+      } else if (host.match(/^wss?:/)) {
+        // should be of the format ws://host:port or wss://host:port
+        this.defaultserver = host;
+      } else if (host && port) {
+        this.defaultserver = this.getServerURL(host, port);
+      } 
+      this.defaultport = port;
+
+      elation.events.add(this.player, 'username_change', elation.bind(this, this.handleUsernameChange));
+    }
+    this.enable = function(player) {
+      this.enabled = true;
+      this.player = player;
+
+      this.resetUpdateInterval();
+
+      elation.events.fire({type: 'enabled', element: this});
+    }
+    this.disable = function() {
+      this.enabled = false;
+      elation.events.fire({type: 'disabled', element: this});
+    }
+    this.getUpdateRate = function() {
+      var roomrate;
+      if (this.activeroom) {
+        roomrate = this.activeroom.rate;
+      }
+      var rate = elation.utils.any(roomrate, this.args.rate, elation.config.get('janusweb.network.rate'), 100);
+      return rate;
+    }
+    this.resetUpdateInterval = function() {
+      if (this.updateinterval) {
+        this.stopUpdateInterval();
+      }
+      var rate = this.getUpdateRate();
+      this.updateinterval = setInterval(elation.bind(this, this.sendUpdate), rate);
+console.log('Set update interval', rate);
+    }
+    this.stopUpdateInterval = function() {
+      if (this.updateinterval) {
+        clearTimeout(this.updateinterval);
+      }
+    }
+    this.getServerURL = function(host, port, secure) {
+      if (!port) port = this.defaultport;
+      port = 5567; // FIXME
+      if (typeof secure == 'undefined') secure = (port != 5566);
+    
+      var protocol = (secure ? 'wss' : 'ws');
+
+      return protocol + '://' + host + ':' + port;
+    }
+    this.getServerURLForRoom = function(room, force) {
+      if (force || !this.roomservers[room.roomid]) {
+        var roomserver = this.defaultserver;
+        if (room.server) {
+          roomserver = this.getServerURL(room.server, room.port);
+        } 
+        this.roomservers[room.roomid] = roomserver;
+      }
+      return this.roomservers[room.roomid];
+    }
+    this.getServerForRoom = function(room) {
+      var serverurl = this.getServerURLForRoom(room, true);
+      if (!this.servers[serverurl]) {
+        var server = new JanusClientConnection({
+          host: serverurl,
+          userId: this.janusweb.userId,
+          version: this.janusweb.version,
+          roomUrl: room.url
+        });
+        server.addEventListener('connect', elation.bind(this, this.handleServerConnect));
+        server.addEventListener('disconnect', elation.bind(this, this.handleServerDisconnect));
+        server.addEventListener('message', elation.bind(this, this.handleServerMessage));
+        server.addEventListener('login', elation.bind(this, this.updateAvatar));
+        //elation.events.add(this, 'room_change', elation.bind(this, function(ev) { console.log('DUR', ev); this.enter_room(ev.data); }));
+        //elation.events.add(this, 'room_disable', elation.bind(this, function(ev) { this.unsubscribe(ev.data.url); }));
+
+        this.servers[serverurl] = server;
+      }
+      // TODO - subscribe if not subscribed
+      return this.servers[serverurl];
+    }
+    this.registerRoom = function(room, subscribe) {
+      if (!this.rooms[room.roomid]) {
+        this.rooms[room.roomid] = room;
+        elation.events.add(room, 'join', elation.bind(this, this.handleRoomJoin));
+        elation.events.add(room, 'part', elation.bind(this, this.handleRoomPart));
+      }
+
+      if (!room.loaded) {
+console.log('[MultiplayerManager] registered room, but not loaded yet:', room.url, room);
+        elation.events.add(room, 'room_load_processed', elation.bind(this, this.registerRoom, room, subscribe));
+        return;        
+      }
+
+console.log('[MultiplayerManager] registered room:', room.roomid, room.url, room);
+
+      if (subscribe) {
+        this.subscribe(room);
+      }
+      if (room == this.activeroom) {
+        this.resetUpdateInterval();
+        this.updateAvatar();
+      }
+    }
+    this.setActiveRoom = function(room) {
+console.log('[MultiplayerManager] set active room:', room, this.activeroom);
+      // If we're already in a room, let's leave that one first
+      if (this.activeroom) {
+        var oldserver = this.getServerForRoom(this.activeroom);
+        oldserver.leave_room(room.url);
+      }
+
+      // Tell the server we're now in the new room
+      this.activeroom = room;
+      var server = this.getServerForRoom(room);
+      server.enter_room(room.url);
+    }
+    this.getJanusOrientation = (function() { 
+      var tmpMat = new THREE.Matrix4(),
+          tmpVecX = new THREE.Vector3(),
+          tmpVecY = new THREE.Vector3(),
+          tmpVecZ = new THREE.Vector3();
+      return function(player, head) {
+        // takes a quaternion, returns an object OTF {dir: "0 0 0", up_dir: "0 0 0", view_dir: "0 0 0"}
+        tmpMat.makeRotationFromQuaternion(player.properties.orientation);
+        tmpMat.extractBasis(tmpVecX, tmpVecY, tmpVecZ);
+        var ret = {
+          dir: tmpVecZ.clone().negate().toArray().join(' '),
+          //up_dir: this.tmpVecY.toArray().join(' '),
+          up_dir: '0 1 0',
+          //view_dir: this.tmpVecZ.toArray().join(' ')
+        }
+        if (head) {
+          //this.tmpMat.makeRotationFromQuaternion(head.properties.orientation);
+          tmpMat.copy(head.objects['3d'].matrixWorld);
+          tmpMat.extractBasis(tmpVecX, tmpVecY, tmpVecZ);
+          ret.view_dir = tmpVecZ.clone().negate().toArray().join(' ');
+          ret.up_dir = tmpVecY.toArray().join(' ');
+          var headpos = head.properties.position.clone();//.sub(new THREE.Vector3(0,1.3,0));
+          headpos.x *= -1;
+          headpos.z *= -1;
+          ret.head_pos = headpos.toArray().join(' '); //this.tmpMat.getPosition().toArray().join(' ');
+        } else {
+          ret.view_dir = '0 0 1';
+          ret.head_pos = '0 0 0';
+        }
+        return ret;
+      }
+    })();
+    this.sendUpdate = function(opts) {
+      if (!opts) opts = {};
+      if (!this.activeroom) return;
+
+      // opts.first is a bool, if true then we are sending our avatar along with the move update
+      // else, we send the avatar on every 15th update
+      //if (Date.now() - this.lastUpdate < 20) return;
+      var player = this.player,
+          room = this.activeroom,
+          server = this.getServerForRoom(room);
+
+      if (!server.loggedin) return;
+
+      var dirs = this.getJanusOrientation(player, player.head)
+      var moveData = {
+        "pos": player.properties.position.toArray().map(function(n) { return parseFloat(n.toFixed(4)); }).join(" "),
+        "vel": player.properties.velocity.toArray().map(function(n) { return parseFloat(n.toFixed(4)); }).join(" "),
+        "rotvel": player.properties.angular.toArray().map(function(n) { return parseFloat(n.toFixed(4)); }).join(" "),
+        "dir": dirs.dir,
+        "up_dir": dirs.up_dir,
+        "view_dir": dirs.view_dir,
+        "head_pos": dirs.head_pos,
+        "anim_id": player.getAnimationID()
+      }
+
+      //console.log('[MultiplayerManager] player update', moveData);
+      if (this.avatarNeedsUpdate || this.sentUpdates == this.updateRate) {
+console.log('UPDATE AVATAR');
+        moveData["avatar"] = player.getAvatarData().replace(/"/g, "^");
+        this.sentUpdates = 0;
+        this.avatarNeedsUpdate = false
+      }
+
+      if (player.hasVoipData()) {
+        var voipdata = player.getVoipData();
+        moveData.speaking = true;
+        moveData.audio = window.btoa(voipdata);
+      }
+
+      if (room.hasChanges()) {
+        moveData.room_edit = room.getChanges().replace(/"/g, "^");
+      }
+
+      if (player.hasHands()) {
+        var hands = player.tracker.getHandData();
+
+        if (hands.left && hands.left.active) {
+          moveData.hand0 = {
+            state: hands.left.state
+          };
+        }
+        if (hands.right && hands.right.active) {
+          moveData.hand1 = {
+            state: hands.right.state
+          };
+        }
+      } 
+
+//console.log(moveData, server, room);
+      server.send({'method': 'move', 'data': moveData});
+      this.lastUpdate = Date.now();
+      this.sentUpdates++;
+    }
+    this.subscribe = function(room) {
+      var server = this.getServerForRoom(room);
+console.log('[MultiplayerManager] subscribe', room.url);
+      server.subscribe(room.url);
+    }
+    this.unsubscribe = function(room) {
+console.log('[MultiplayerManager] unsubscribe', room.url);
+      var server = this.getServerForRoom(room);
+      server.unsubscribe(room.url);
+    }
+    this.join = function(room) {
+console.log('[MultiplayerManager] join', room.url);
+      var server = this.getServerForRoom(room);
+      server.enter_room(room.url);
+    }
+    this.part = function(room) {
+console.log('[MultiplayerManager] part', room.url);
+      var server = this.getServerForRoom(room);
+      server.leave_room(room.url);
+    }
+    this.spawnRemotePlayer = function(data) {
+      var userId = data.userId;
+      var roomId = data.roomId;
+
+      var room = this.rooms[roomId] || this.activeroom;
+console.log('[MultiplayerManager] spawn remote guy', userId, roomId, room);
+
+      var spawnpos = (data.position && data.position.pos ? data.position.pos.split(" ").map(parseFloat) : [0,0,0]);
+      this.remoteplayers[userId] = room.spawn('remoteplayer', userId, { position: spawnpos, player_id: userId, player_name: userId, pickable: false, collidable: false, janus: this});
+      var remote = this.remoteplayers[userId];
+
+      this.remotePlayerCount = Object.keys(this.remoteplayers).length;
+      this.playerCount = this.remotePlayerCount + 1;
+      return remote;
+    }
+    this.updateAvatar = function() {
+      this.avatarNeedsUpdate = true;
+    }
+
+    this.handleServerConnect = function(msg) {
+console.log('[MultiplayerManager] connected', msg);
+      //this.sendPlayerUpdate({first: true});
+      var server = msg.target;
+
+      elation.events.fire({element: this, type: 'janusweb_client_connected', data: this.janusweb.userId});
+      if (this.janusweb.chat) {
+        this.janusweb.chat.addmessage({userId: ' ! ', message: 'Connected to ' + server._host + ' as ' + this.janusweb.userId });
+      }
+    }
+    this.handleServerDisconnect = function(ev) {
+console.log('[MultiplayerManager] disconnected', ev);
+      if (this.janusweb.chat) {
+        this.janusweb.chat.addmessage({userId: ' ! ', message: 'Disconnected'});
+      }
+    }
+    this.handleServerMessage = function(msg) {
+//console.log('[MultiplayerManager] server msg', msg);
+      var method = msg.data.method
+      if (method == 'user_moved') {
+        this.handleUserMoved(msg);
+      } else if (method == 'user_disconnected') {
+        this.handleUserDisconnect(msg);
+      } else if (method == 'user_enter') {
+        this.handleUserEnter(msg);
+      } else if (method == 'user_leave') {
+        this.handleUserLeave(msg);
+      } else if (method == 'user_portal') {
+        this.handlePortal(msg);
+      } else if (method == 'user_chat') {
+        this.handleChat(msg);
+      }
+    }
+    this.handleUserMoved = function(msg) {
+      var userId = msg.data.data.position._userId;
+
+      if (!this.remoteplayers[userId]) {
+        var remoteplayer = this.spawnRemotePlayer(msg.data.data);
+        elation.events.fire({element: this, type: 'user_joined', data: remoteplayer});
+      } else {
+        var remote = this.remoteplayers[userId];
+        var room = this.rooms[msg.data.data.roomId] || this.activeroom;
+        var movedata = msg.data.data.position;
+
+        if (remote.room !== room) {
+          remote.setRoom(room);
+        }
+
+        remote.updateData(movedata);
+      }
+    }
+    this.handleUserDisconnect = function(msg) {
+      var remoteplayer = this.remoteplayers[msg.data.data.userId];
+console.log('[MultiplayerManager] player disconnected', msg, remoteplayer);
+      if (remoteplayer) {
+        elation.events.fire({element: this, type: 'janusweb_user_disconnected', data: remoteplayer});
+        if (this.janusweb.chat) {
+          this.janusweb.chat.addmessage({userId: ' ! ', message: msg.data.data.userId + ' disconnected' });
+        }
+        if (remoteplayer && remoteplayer.parent) {
+          remoteplayer.die();
+        }
+        delete this.remoteplayers[msg.data.data.userId];
+        this.remotePlayerCount = Object.keys(this.remoteplayers).length;
+        this.playerCount = this.remotePlayerCount + 1;
+      }
+    }
+    this.handleUserEnter = function(msg) {
+      var remoteplayer = this.remoteplayers[msg.data.data.userId];
+console.log('[MultiplayerManager] player entered', msg, remoteplayer);
+      if (!remoteplayer) {
+        remoteplayer = this.spawnRemotePlayer(msg.data.data);
+      } else {
+        var room = this.rooms[msg.data.data.roomId];
+      }
+      if (remoteplayer.room !== room) {
+console.log('CHANGE ROOM', remoteplayer.room, room);
+        remoteplayer.setRoom(room);
+      }
+      elation.events.fire({element: this, type: 'janusweb_user_joined', data: remoteplayer});
+      if (this.janusweb.chat) {
+        this.janusweb.chat.addmessage({userId: ' ! ', message: msg.data.data.userId + ' joined room' });
+      }
+      this.remotePlayerCount = Object.keys(this.remoteplayers).length;
+      this.playerCount = this.remotePlayerCount + 1;
+    }
+    this.handleUserLeave = function(msg) {
+      var remoteplayer = this.remoteplayers[msg.data.data.userId];
+console.log('[MultiplayerManager] player left', msg, remoteplayer);
+      if (remoteplayer) {
+        elation.events.fire({element: this, type: 'janusweb_user_left', data: remoteplayer});
+        if (this.janusweb.chat) {
+          this.janusweb.chat.addmessage({userId: ' ! ', message: msg.data.data.userId + ' left room' });
+        }
+        if (remoteplayer) {
+          remoteplayer.setRoom(null);
+        }
+        this.remotePlayerCount = Object.keys(this.remoteplayers).length;
+        this.playerCount = this.remotePlayerCount + 1;
+      }
+    }
+    this.handlePortal = function(msg) {
+      var portaldata = msg.data.data;
+      var portalname = 'portal_' + portaldata.userId + '_' + md5(portaldata.url);
+
+      var node = this.parser.parseNode(portaldata);
+      var room = this.rooms[portaldata.roomId];
+
+      if (room) {
+        room.spawn('janusportal', portalname, {
+          url: portaldata.url,
+          janus: this,
+          room: room,
+          title: portaldata.url,
+          position: node.pos,
+          orientation: node.orientation,
+        });
+      }
+    }
+    this.handleUserChat = function(msg) {
+      if (this.janusweb.chat) {
+        this.janusweb.chat.addmessage(msg.data.data);
+      }
+    }
+    this.handleRoomJoin = function(ev) {
+      if (this.activeroom != ev.target) {
+        if (this.activeroom) {
+          this.part(this.activeroom);
+        }
+
+        var room = this.activeroom = ev.target;
+        if (room.loaded) {
+          this.join(room);
+        } else {
+          elation.events.add(room, 'room_load_processed', elation.bind(this, this.join, room));
+        }
+      }
+    }
+    this.handleRoomPart = function(ev) {
+      if (this.activeroom == ev.target) {
+        this.part(this.activeroom);
+        this.activeroom = false;
+      }
+    }
+    this.handleUsernameChange = function(ev) {
+      var name = ev.data;
+      console.log('[MultiplayerManager] player changed username', name);
+      for (var k in this.servers) {
+        this.servers[k].setUserId(name);
+      }
+    }
+  });
+});

-----END OF PAGE-----