├── .gitignore ├── media ├── mover.png ├── 04b03.font.png ├── sprites │ ├── acid.png │ ├── blob.png │ ├── debris.png │ ├── blob-gibs.png │ ├── test-tube.png │ ├── player-blue.png │ ├── player-red.png │ ├── projectile.png │ ├── respawn-pod.png │ └── player-green.png └── tiles │ └── biolab.png ├── README.md ├── package.json ├── index.html ├── lib ├── game │ ├── entities │ │ ├── remote-player.js │ │ └── player.js │ ├── events.js │ ├── main.js │ └── levels │ │ └── main.js ├── messages.js └── net │ ├── room-connection.js │ ├── adapter.js │ ├── peer-connection.js │ └── socket.io.js └── signalling └── server.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | .idea 4 | .project 5 | npm-debug.log 6 | -------------------------------------------------------------------------------- /media/mover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gutnikov/webrtc-shooter/HEAD/media/mover.png -------------------------------------------------------------------------------- /media/04b03.font.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gutnikov/webrtc-shooter/HEAD/media/04b03.font.png -------------------------------------------------------------------------------- /media/sprites/acid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gutnikov/webrtc-shooter/HEAD/media/sprites/acid.png -------------------------------------------------------------------------------- /media/sprites/blob.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gutnikov/webrtc-shooter/HEAD/media/sprites/blob.png -------------------------------------------------------------------------------- /media/tiles/biolab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gutnikov/webrtc-shooter/HEAD/media/tiles/biolab.png -------------------------------------------------------------------------------- /media/sprites/debris.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gutnikov/webrtc-shooter/HEAD/media/sprites/debris.png -------------------------------------------------------------------------------- /media/sprites/blob-gibs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gutnikov/webrtc-shooter/HEAD/media/sprites/blob-gibs.png -------------------------------------------------------------------------------- /media/sprites/test-tube.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gutnikov/webrtc-shooter/HEAD/media/sprites/test-tube.png -------------------------------------------------------------------------------- /media/sprites/player-blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gutnikov/webrtc-shooter/HEAD/media/sprites/player-blue.png -------------------------------------------------------------------------------- /media/sprites/player-red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gutnikov/webrtc-shooter/HEAD/media/sprites/player-red.png -------------------------------------------------------------------------------- /media/sprites/projectile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gutnikov/webrtc-shooter/HEAD/media/sprites/projectile.png -------------------------------------------------------------------------------- /media/sprites/respawn-pod.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gutnikov/webrtc-shooter/HEAD/media/sprites/respawn-pod.png -------------------------------------------------------------------------------- /media/sprites/player-green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gutnikov/webrtc-shooter/HEAD/media/sprites/player-green.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # webrtc-shooter 2 | A short experiment on making peer to peer game with webrtc and impactjs 3 | 4 | # how to test it 5 | 6 | 1. In cloned repo directory: npm i 7 | 2. npm run signalling 8 | 3. npm run http 9 | 4. visit http://localhost:8080 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webrtc-shooter", 3 | "version": "0.0.1", 4 | "description": "A short experiment on making peer to peer game with webrtc and impactjs", 5 | "scripts": { 6 | "sg-start": "pm2 start signalling/server.js -i 1 --name signalling", 7 | "sg-stop": "pm2 delete signalling", 8 | "signalling": "node signalling/server.js", 9 | "http": "http-server -c-1 . -p 8080" 10 | }, 11 | "keywords": [ 12 | "webrtc", 13 | "p2p", 14 | "games", 15 | "javascript" 16 | ], 17 | "author": "alx.gutnikov@gmail.com", 18 | "devDependencies": { 19 | "http-server": "^0.9.0", 20 | "socket.io": "^1.4.8" 21 | }, 22 | "jscsConfig": { 23 | "preset": "google", 24 | "maxErrors": 100, 25 | "maximumLineLength": 100, 26 | "validateIndentation" : 2 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Impact Game 5 | 30 | 31 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 | 0. Use arrows to move
47 | 1. X - jump
48 | 2. C - shoot
49 | 3. Collect tubes to recharge weapon
50 | 4. Be careful of acid burns!
51 | 5. Invite your friends by sending them a link to page url
52 |
53 | 54 | 55 | -------------------------------------------------------------------------------- /lib/game/entities/remote-player.js: -------------------------------------------------------------------------------- 1 | // jscs:disable validateIndentation 2 | ig.module( 3 | 'game.entities.remote-player' 4 | ) 5 | .requires( 6 | 'impact.entity', 7 | 'game.entities.particle' 8 | ) 9 | .defines(function() { 10 | EntityRemotePlayer = ig.Entity.extend({ 11 | 12 | type: ig.Entity.TYPE.B, 13 | 14 | size: { 15 | x: 8, 16 | y: 14 17 | }, 18 | 19 | offset: { 20 | x: 4, 21 | y: 2 22 | }, 23 | 24 | stateUpdated: false, 25 | 26 | animSheet: new ig.AnimationSheet('media/sprites/player-blue.png', 16, 16), 27 | 28 | init: function(x, y, settings) { 29 | this.parent(x, y, settings); 30 | this.addAnim('idle', 1, [0]); 31 | this.addAnim('scratch', 0.3, [2, 1, 2, 1, 2], true); 32 | this.addAnim('shrug', 0.3, [3, 3, 3, 3, 3, 3, 4, 3, 3], true); 33 | this.addAnim('run', 0.07, [6, 7, 8, 9, 10, 11]); 34 | this.addAnim('jump', 1, [15]); 35 | this.addAnim('fall', 0.4, [12, 13]); 36 | this.addAnim('land', 0.15, [14]); 37 | this.addAnim('die', 0.07, [18, 19, 20, 21, 22, 23, 16, 16, 16]); 38 | this.addAnim('spawn', 0.07, [16, 16, 16, 23, 22, 21, 20, 19, 18]); 39 | }, 40 | setState: function(state) { 41 | var x = state.getX() / 10; 42 | var y = state.getY() / 10; 43 | this.dx = state.getVelX() / 10; //x - this.pos.x; 44 | this.dy = state.getVelY() / 10; //y - this.pos.y; 45 | this.pos = { 46 | x: x, 47 | y: y 48 | }; 49 | this.currentAnim = this.getAnimById(state.getAnim()); 50 | this.currentAnim.frame = state.getFrame(); 51 | this.currentAnim.flip.x = !!state.getFlip(); 52 | this.stateUpdated = true; 53 | }, 54 | update: function() { 55 | if (this.stateUpdated) { 56 | this.stateUpdated = false; 57 | } else { 58 | this.pos.x += this.dx; 59 | this.pos.y += this.dy; 60 | if (this.currentAnim) { 61 | this.currentAnim.update(); 62 | } 63 | } 64 | }, 65 | kill: function() { 66 | this.parent(); 67 | } 68 | }); 69 | 70 | }); 71 | -------------------------------------------------------------------------------- /lib/game/events.js: -------------------------------------------------------------------------------- 1 | // jscs:disable validateIndentation 2 | ig.module( 3 | 'game.events' 4 | ) 5 | .defines(function() { 6 | 7 | // Event emitter + utils to subscribe/unsubscribe events in different ways: 8 | // addEventListener/removeEventListener for RTCPeerConnection etc 9 | // on/off for socket.io sockets 10 | EventEmitter = ig.Class.extend({ 11 | lastToken: 0, 12 | subscribers: null, 13 | 14 | init: function() { 15 | this.subscribers = {}; 16 | }, 17 | 18 | destroy: function() { 19 | this.subscribers = null; 20 | }, 21 | 22 | emit: function(event) { 23 | var emitArgs = [].slice.call(arguments, 1); 24 | for (var k in this.subscribers) { 25 | scb = this.subscribers[k]; 26 | if (scb.event === event) { 27 | scb.subscriber.apply(scb.ctx, emitArgs); 28 | } 29 | } 30 | }, 31 | 32 | on: function(event, subscriber, ctx) { 33 | var token = ++this.lastToken; 34 | this.subscribers[token] = { 35 | event: event, 36 | subscriber: subscriber, 37 | ctx: ctx 38 | }; 39 | return token; 40 | }, 41 | 42 | off: function(token) { 43 | delete this.subscribers[token]; 44 | } 45 | }); 46 | 47 | Events = { 48 | 49 | Emitter: EventEmitter, 50 | 51 | listen: function() { 52 | this._listen('addEventListener', arguments); 53 | }, 54 | 55 | unlisten: function() { 56 | this._unlisten('removeEventListener', arguments); 57 | }, 58 | 59 | on: function() { 60 | this._listen('on', arguments); 61 | }, 62 | 63 | off: function() { 64 | this._unlisten('off', arguments); 65 | }, 66 | 67 | _listen: function(method, argsObject) { 68 | var args = [].slice.apply(argsObject); 69 | var object = args[0]; 70 | var handlers = args[1]; 71 | var context = args[2]; 72 | var bindArgs = args.slice(2); 73 | for (var k in handlers) { 74 | var bound = context[k + '_bound'] = handlers[k].bind.apply(handlers[k], bindArgs); 75 | object[method](k, bound); 76 | } 77 | }, 78 | 79 | _unlisten: function(method, args) { 80 | var object = args[0]; 81 | var handlers = args[1]; 82 | var context = args[2]; 83 | for (var k in handlers) { 84 | object[method](k, context[k + '_bound']); 85 | } 86 | } 87 | }; 88 | 89 | }); 90 | -------------------------------------------------------------------------------- /lib/messages.js: -------------------------------------------------------------------------------- 1 | (function(global) { 2 | 3 | var UNDERLINED_RX = /(^\w|_\w)/g; 4 | 5 | var MessageBuilder = { 6 | init: function() { 7 | this.typeProps = {}; 8 | this.ctors = {}; 9 | }, 10 | registerMessageType: function(messageType, messagePropNames) { 11 | // Register message fields structure 12 | this.typeProps[messageType] = messagePropNames; 13 | 14 | // Create message prototype 15 | var messageProto = {}; 16 | for (var pi = 0; pi < messagePropNames.length; pi++) { 17 | var propName = messagePropNames[pi]; 18 | var propCamelCase = camelCase(propName); 19 | messageProto['get' + propCamelCase] = getter(propName); 20 | messageProto['set' + propCamelCase] = setter(propName); 21 | } 22 | var ctr = function() { 23 | this.type = messageType; 24 | }; 25 | ctr.prototype = messageProto; 26 | this.ctors[messageType] = ctr; 27 | }, 28 | serialize: function(message) { 29 | var type = message.getType(); 30 | if (!this.typeProps[type]) { 31 | throw new Error('Unknown message. Type =' + type); 32 | } 33 | 34 | // Create an array buffer sufficient for storing all props of message 35 | // And a buffer view, to access it 36 | var messageProps = this.typeProps[type]; 37 | var arrayBuffer = new ArrayBuffer(messageProps.length * 2); 38 | var bufferView = new Int16Array(arrayBuffer); 39 | 40 | // For every prop: write it at proper place to array view 41 | for (var i = 0; i < messageProps.length; i++) { 42 | var prop = messageProps[i]; 43 | bufferView[i] = message[prop]; 44 | } 45 | return arrayBuffer; 46 | }, 47 | deserialize: function(arrayBuffer) { 48 | var bufferView = new Int16Array(arrayBuffer); 49 | var type = bufferView[0]; 50 | var message = new this.ctors[type](); 51 | var messageProps = this.typeProps[type]; 52 | for (var i = 0; i < messageProps.length; i++) { 53 | var prop = messageProps[i]; 54 | message[prop] = bufferView[i]; 55 | } 56 | return message; 57 | }, 58 | createMessage: function(type) { 59 | if (!this.typeProps[type]) { 60 | throw new Error('Unknown message. Type =' + type); 61 | } 62 | return new this.ctors[type](); 63 | } 64 | }; 65 | MessageBuilder.init(); 66 | 67 | function getter(propName) { 68 | return function() { 69 | return this[propName]; 70 | }; 71 | } 72 | 73 | function setter(propName) { 74 | return function(value) { 75 | this[propName] = value; 76 | return this; 77 | }; 78 | } 79 | 80 | function camelCase(name) { 81 | return name.replace(UNDERLINED_RX, function($1) { 82 | return $1.replace('_', '').toUpperCase(); 83 | }); 84 | } 85 | 86 | global.MessageBuilder = MessageBuilder; 87 | 88 | })(window); 89 | -------------------------------------------------------------------------------- /signalling/server.js: -------------------------------------------------------------------------------- 1 | var PORT = 8033; 2 | var MAX_ROOM_USERS = 5; 3 | 4 | var fs = require('fs'); 5 | var log = console.log.bind(console); 6 | var io = require('socket.io')(PORT); 7 | 8 | var rooms = {}; 9 | var lastUserId = 0; 10 | var lastRoomId = 0; 11 | 12 | var MessageType = { 13 | // A messages you send to server, when want to join or leave etc. 14 | JOIN: 'join', 15 | DISCONNECT: 'disconnect', 16 | 17 | // You receive room info as a response for join command. It contains information about 18 | // the room you joined, and it's users 19 | ROOM: 'room', 20 | 21 | // A messages you receive from server when another user want to join or leave etc. 22 | USER_JOIN: 'user_join', 23 | USER_READY: 'user_ready', 24 | USER_LEAVE: 'user_leave', 25 | 26 | // WebRtc signalling info, session and ice-framework related 27 | SDP: 'sdp', 28 | ICE_CANDIDATE: 'ice_candidate', 29 | 30 | // Errors... shit happens 31 | ERROR_ROOM_IS_FULL: 'error_room_is_full', 32 | ERROR_USER_INITIALIZED: 'error_user_initialized' 33 | }; 34 | 35 | function User() { 36 | this.userId = ++lastUserId; 37 | } 38 | User.prototype = { 39 | getId: function() { 40 | return this.userId; 41 | } 42 | }; 43 | 44 | function Room(name) { 45 | this.roomName = name; 46 | this.users = []; 47 | this.sockets = {}; 48 | } 49 | Room.prototype = { 50 | getName: function() { 51 | return this.roomName; 52 | }, 53 | getUsers: function() { 54 | return this.users; 55 | }, 56 | getUserById: function(id) { 57 | return this.users.find(function(user) { 58 | return user.getId() === id; 59 | }); 60 | }, 61 | numUsers: function() { 62 | return this.users.length; 63 | }, 64 | isEmpty: function() { 65 | return this.users.length === 0; 66 | }, 67 | addUser: function(user, socket) { 68 | this.users.push(user); 69 | this.sockets[user.getId()] = socket; 70 | }, 71 | removeUser: function(id) { 72 | this.users = this.users.filter(function(user) { 73 | return user.getId() !== id; 74 | }); 75 | delete this.sockets[id]; 76 | }, 77 | sendTo: function(user, message, data) { 78 | var socket = this.sockets[user.getId()]; 79 | socket.emit(message, data); 80 | }, 81 | sendToId: function(userId, message, data) { 82 | return this.sendTo(this.getUserById(userId), message, data); 83 | }, 84 | broadcastFrom: function(fromUser, message, data) { 85 | this.users.forEach(function(user) { 86 | if (user.getId() !== fromUser.getId()) { 87 | this.sendTo(user, message, data); 88 | } 89 | }, this); 90 | } 91 | }; 92 | 93 | // socket 94 | function handleSocket(socket) { 95 | 96 | var user = null; 97 | var room = null; 98 | 99 | socket.on(MessageType.JOIN, onJoin); 100 | socket.on(MessageType.SDP, onSdp); 101 | socket.on(MessageType.ICE_CANDIDATE, onIceCandidate); 102 | socket.on(MessageType.DISCONNECT, onLeave); 103 | 104 | function onJoin(joinData) { 105 | // Somehow sent join request twice? 106 | if (user !== null || room !== null) { 107 | room.sendTo(user, MessageType.ERROR_USER_INITIALIZED); 108 | return; 109 | } 110 | 111 | // Let's get a room, or create if none still exists 112 | room = getOrCreateRoom(joinData.roomName); 113 | if (room.numUsers() >= MAX_ROOM_USERS) { 114 | room.sendTo(user, MessageType.ERROR_ROOM_IS_FULL); 115 | return; 116 | } 117 | 118 | // Add a new user 119 | room.addUser(user = new User(), socket); 120 | 121 | // Send room info to new user 122 | room.sendTo(user, MessageType.ROOM, { 123 | userId: user.getId(), 124 | roomName: room.getName(), 125 | users: room.getUsers() 126 | }); 127 | // Notify others of a new user joined 128 | room.broadcastFrom(user, MessageType.USER_JOIN, { 129 | userId: user.getId(), 130 | users: room.getUsers() 131 | }); 132 | log('User %s joined room %s. Users in room: %d', 133 | user.getId(), room.getName(), room.numUsers()); 134 | } 135 | 136 | function getOrCreateRoom(name) { 137 | var room; 138 | if (!name) { 139 | name = ++lastRoomId + '_room'; 140 | } 141 | if (!rooms[name]) { 142 | room = new Room(name); 143 | rooms[name] = room; 144 | } 145 | return rooms[name]; 146 | } 147 | 148 | function onLeave() { 149 | if (room === null) { 150 | return; 151 | } 152 | room.removeUser(user.getId()); 153 | log('User %d left room %s. Users in room: %d', 154 | user.getId(), room.getName(), room.numUsers()); 155 | if (room.isEmpty()) { 156 | log('Room is empty - dropping room %s', room.getName()); 157 | delete rooms[room.getName()]; 158 | } 159 | room.broadcastFrom(user, MessageType.USER_LEAVE, { 160 | userId: user.getId() 161 | }); 162 | } 163 | 164 | function onSdp(message) { 165 | room.sendToId(message.userId, MessageType.SDP, { 166 | userId: user.getId(), 167 | sdp: message.sdp 168 | }); 169 | } 170 | 171 | function onIceCandidate(message) { 172 | room.sendToId(message.userId, MessageType.ICE_CANDIDATE, { 173 | userId: user.getId(), 174 | candidate: message.candidate 175 | }); 176 | } 177 | } 178 | 179 | io.on('connection', handleSocket); 180 | log('Running room server on port %d', PORT); 181 | -------------------------------------------------------------------------------- /lib/net/room-connection.js: -------------------------------------------------------------------------------- 1 | // jscs:disable validateIndentation 2 | ig.module( 3 | 'net.room-connection' 4 | ) 5 | .requires( 6 | 'game.events', 7 | 'net.peer-connection' 8 | ) 9 | .defines(function() { 10 | 11 | RoomConnection = Events.Emitter.extend({ 12 | peers: null, 13 | socket: null, 14 | roomName: null, 15 | roomInfo: null, 16 | pendingSdp: null, 17 | pendidateCandidates: null, 18 | 19 | init: function(roomName, socket) { 20 | this.parent(); 21 | this.socket = socket; 22 | this.roomName = roomName; 23 | this.pendingSdp = {}; 24 | this.pendingCandidates = {}; 25 | 26 | this.socketHandlers = { 27 | 'sdp': this.onSdp, 28 | 'ice_candidate': this.onIceCandidate, 29 | 'room': this.onJoinedRoom, 30 | 'user_join': this.onUserJoin, 31 | 'user_ready': this.onUserReady, 32 | 'user_leave': this.onUserLeave, 33 | 'error': this.onError 34 | }; 35 | 36 | this.peerConnectionHandlers = { 37 | 'open': this.onPeerChannelOpen, 38 | 'close': this.onPeerChannelClose, 39 | 'message': this.onPeerMessage 40 | }; 41 | 42 | Events.on(this.socket, this.socketHandlers, this); 43 | }, 44 | 45 | destroy: function() { 46 | this.parent(); 47 | Events.off(this.socket, this.socketHandlers, this); 48 | }, 49 | 50 | connect: function() { 51 | this.sendJoin(this.roomName); 52 | }, 53 | 54 | initPeerConnection: function(user, isInitiator) { 55 | // Create connection 56 | var cnt = new PeerConnection(this.socket, user, isInitiator); 57 | Events.on(cnt, this.peerConnectionHandlers, this, cnt, user); 58 | 59 | // Sometimes sdp|candidates may arrive before we initialized 60 | // peer connection, so not to loose the, we save them as pending 61 | var userId = user.userId; 62 | var pendingSdp = this.pendingSdp[userId]; 63 | if (pendingSdp) { 64 | cnt.setSdp(pendingSdp); 65 | delete this.pendingSdp[userId]; 66 | } 67 | var pendingCandidates = this.pendingCandidates[userId]; 68 | if (pendingCandidates) { 69 | pendingCandidates.forEach(cnt.addIceCandidate, cnt); 70 | delete this.pendingCandidates[userId]; 71 | } 72 | return cnt; 73 | }, 74 | 75 | onSdp: function(message) { 76 | var userId = message.userId; 77 | if (!this.peers[userId]) { 78 | this.log('Adding pending sdp from another player. id = ' + userId, 'gray'); 79 | this.pendingSdp[userId] = message.sdp; 80 | return; 81 | } 82 | this.peers[userId].setSdp(message.sdp); 83 | }, 84 | 85 | onIceCandidate: function(message) { 86 | var userId = message.userId; 87 | if (!this.peers[userId]) { 88 | this.log('Adding pending candidate from another player. id =' + userId, 'gray'); 89 | if (!this.pendingCandidates[userId]) { 90 | this.pendingCandidates[userId] = []; 91 | } 92 | this.pendingCandidates[userId].push(message.candidate); 93 | return; 94 | } 95 | this.peers[userId].addIceCandidate(message.candidate); 96 | }, 97 | 98 | onJoinedRoom: function(roomInfo) { 99 | this.emit('joined', roomInfo); 100 | this.roomInfo = roomInfo; 101 | this.peers = {}; 102 | for (var k in this.roomInfo.users) { 103 | var user = this.roomInfo.users[k]; 104 | if (user.userId !== this.roomInfo.userId) { 105 | this.peers[user.userId] = this.initPeerConnection(this.roomInfo.users[k], true); 106 | } 107 | } 108 | }, 109 | 110 | onError: function(error) { 111 | this.log('Error connecting to room' + error.message, 'red'); 112 | }, 113 | 114 | onUserJoin: function(user) { 115 | this.log('Another player joined. id = ' + user.userId, 'orange'); 116 | var peerConnection = this.initPeerConnection(user, false); 117 | this.roomInfo.users.push(user); 118 | this.peers[user.userId] = peerConnection; 119 | }, 120 | 121 | onUserReady: function(user) { 122 | this.log('Another player ready. id = ' + user.userId, 'orange'); 123 | this.emit('user_ready', user); 124 | }, 125 | 126 | onPeerChannelOpen: function(peer, user) { 127 | this.emit('peer_open', user, peer); 128 | }, 129 | 130 | onPeerChannelClose: function(peer, user) { 131 | this.emit('peer_close', user, peer); 132 | }, 133 | 134 | onPeerMessage: function(peer, user, message) { 135 | this.emit('peer_message', message, user, peer); 136 | }, 137 | 138 | onUserLeave: function(goneUser) { 139 | if (!this.peers[goneUser.userId]) { 140 | return; 141 | } 142 | var cnt = this.peers[goneUser.userId]; 143 | Events.off(cnt, this.peerConnectionHandlers, this); 144 | cnt.destroy(); 145 | delete this.peers[goneUser.userId]; 146 | delete this.roomInfo.users[goneUser.userId]; 147 | this.emit('user_leave', goneUser); 148 | }, 149 | 150 | sendJoin: function(roomName) { 151 | this.socket.emit('join', { 152 | roomName: roomName 153 | }); 154 | }, 155 | 156 | sendLeave: function() { 157 | this.socket.emit(MessageType.LEAVE); 158 | }, 159 | 160 | broadcastMessage: function(message) { 161 | this.broadcast(MessageBuilder.serialize(message)); 162 | }, 163 | 164 | sendMessageTo: function(userId, message) { 165 | var peer = this.peers[userId]; 166 | this.peerSend(peer, MessageBuilder.serialize(message)); 167 | }, 168 | 169 | broadcast: function(arrayBuffer) { 170 | for (var p in this.peers) { 171 | this.peerSend(this.peers[p], arrayBuffer); 172 | } 173 | }, 174 | 175 | peerSend: function(peer, data) { 176 | peer.sendMessage(data); 177 | }, 178 | 179 | log: function(message, color) { 180 | console.log('%c%s', 'color:' + color, message); 181 | } 182 | }); 183 | 184 | }); 185 | -------------------------------------------------------------------------------- /lib/net/adapter.js: -------------------------------------------------------------------------------- 1 | ;(function(global) { 2 | 3 | var RTCPeerConnection = global.RTCPeerConnection; 4 | var RTCSessionDescription = global.RTCSessionDescription; 5 | var RTCIceCandidate = global.RTCIceCandidate; 6 | var getUserMedia = null; 7 | var createIceServer = null; 8 | var attachMediaStream = null; 9 | var reattachMediaStream = null; 10 | var webrtcDetectedBrowser = null; 11 | var webrtcDetectedVersion = null; 12 | 13 | function trace(text) { 14 | // This function is used for logging. 15 | if (text[text.length - 1] == '\n') { 16 | text = text.substring(0, text.length - 1); 17 | } 18 | console.log((performance.now() / 1000).toFixed(3) + ': ' + text); 19 | } 20 | 21 | if (navigator.mozGetUserMedia) { 22 | console.log('This appears to be Firefox'); 23 | 24 | webrtcDetectedBrowser = 'firefox'; 25 | 26 | webrtcDetectedVersion = 27 | parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1]); 28 | 29 | // The RTCPeerConnection object. 30 | RTCPeerConnection = mozRTCPeerConnection; 31 | 32 | // The RTCSessionDescription object. 33 | RTCSessionDescription = mozRTCSessionDescription; 34 | 35 | // The RTCIceCandidate object. 36 | RTCIceCandidate = mozRTCIceCandidate; 37 | 38 | // Get UserMedia (only difference is the prefix). 39 | // Code from Adam Barth. 40 | getUserMedia = navigator.mozGetUserMedia.bind(navigator); 41 | 42 | // Creates iceServer from the url for FF. 43 | createIceServer = function(url, username, password) { 44 | var iceServer = null; 45 | var url_parts = url.split(':'); 46 | if (url_parts[0].indexOf('stun') === 0) { 47 | // Create iceServer with stun url. 48 | iceServer = { 'url': url }; 49 | } else if (url_parts[0].indexOf('turn') === 0 && 50 | (url.indexOf('transport=udp') !== -1 || url.indexOf('?transport') === -1)) { 51 | // Create iceServer with turn url. 52 | // Ignore the transport parameter from TURN url. 53 | var turn_url_parts = url.split("?"); 54 | iceServer = { 'url': turn_url_parts[0], 55 | 'credential': password, 56 | 'username': username }; 57 | } 58 | return iceServer; 59 | }; 60 | 61 | // Attach a media stream to an element. 62 | attachMediaStream = function(element, stream) { 63 | console.log("Attaching media stream"); 64 | element.mozSrcObject = stream; 65 | element.play(); 66 | }; 67 | 68 | reattachMediaStream = function(to, from) { 69 | console.log('Reattaching media stream'); 70 | to.mozSrcObject = from.mozSrcObject; 71 | to.play(); 72 | }; 73 | 74 | // Fake get{Video,Audio}Tracks 75 | MediaStream.prototype.getVideoTracks = function() { 76 | return []; 77 | }; 78 | 79 | MediaStream.prototype.getAudioTracks = function() { 80 | return []; 81 | }; 82 | } else if (navigator.webkitGetUserMedia) { 83 | console.log('This appears to be Chrome'); 84 | 85 | webrtcDetectedBrowser = 'chrome'; 86 | webrtcDetectedVersion = parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2]); 87 | 88 | // Creates iceServer from the url for Chrome. 89 | createIceServer = function(url, username, password) { 90 | var iceServer = null; 91 | var url_parts = url.split(':'); 92 | if (url_parts[0].indexOf('stun') === 0) { 93 | // Create iceServer with stun url. 94 | iceServer = { 'url': url }; 95 | } else if (url_parts[0].indexOf('turn') === 0) { 96 | if (webrtcDetectedVersion < 28) { 97 | // For pre-M28 chrome versions use old TURN format. 98 | var url_turn_parts = url.split("turn:"); 99 | iceServer = { 'url': 'turn:' + username + '@' + url_turn_parts[1], 100 | 'credential': password }; 101 | } else { 102 | // For Chrome M28 & above use new TURN format. 103 | iceServer = { 'url': url, 104 | 'credential': password, 105 | 'username': username }; 106 | } 107 | } 108 | return iceServer; 109 | }; 110 | 111 | // The RTCPeerConnection object. 112 | RTCPeerConnection = webkitRTCPeerConnection; 113 | 114 | // Get UserMedia (only difference is the prefix). 115 | // Code from Adam Barth. 116 | getUserMedia = navigator.webkitGetUserMedia.bind(navigator); 117 | 118 | // Attach a media stream to an element. 119 | attachMediaStream = function(element, stream) { 120 | if (typeof element.srcObject !== 'undefined') { 121 | element.srcObject = stream; 122 | } else if (typeof element.mozSrcObject !== 'undefined') { 123 | element.mozSrcObject = stream; 124 | } else if (typeof element.src !== 'undefined') { 125 | element.src = URL.createObjectURL(stream); 126 | } else { 127 | console.log('Error attaching stream to element.'); 128 | } 129 | }; 130 | 131 | reattachMediaStream = function(to, from) { 132 | to.src = from.src; 133 | }; 134 | 135 | // The representation of tracks in a stream is changed in M26. 136 | // Unify them for earlier Chrome versions in the coexisting period. 137 | if (!webkitMediaStream.prototype.getVideoTracks) { 138 | webkitMediaStream.prototype.getVideoTracks = function() { 139 | return this.videoTracks; 140 | }; 141 | webkitMediaStream.prototype.getAudioTracks = function() { 142 | return this.audioTracks; 143 | }; 144 | } 145 | 146 | // New syntax of getXXXStreams method in M26. 147 | if (!webkitRTCPeerConnection.prototype.getLocalStreams) { 148 | webkitRTCPeerConnection.prototype.getLocalStreams = function() { 149 | return this.localStreams; 150 | }; 151 | webkitRTCPeerConnection.prototype.getRemoteStreams = function() { 152 | return this.remoteStreams; 153 | }; 154 | } 155 | } else { 156 | console.log('Browser does not appear to be WebRTC-capable'); 157 | } 158 | 159 | function $config(to, from) { 160 | for (var k in from) { 161 | to[k] = from[k]; 162 | } 163 | } 164 | 165 | $config(global, { 166 | RTCPeerConnection : RTCPeerConnection, 167 | RTCSessionDescription : RTCSessionDescription, 168 | RTCIceCandidate : RTCIceCandidate, 169 | getUserMedia : getUserMedia, 170 | createIceServer : createIceServer, 171 | attachMediaStream : attachMediaStream, 172 | reattachMediaStream : reattachMediaStream, 173 | webrtcDetectedBrowser : webrtcDetectedBrowser, 174 | webrtcDetectedVersion : webrtcDetectedVersion 175 | }); 176 | 177 | })(window); 178 | 179 | -------------------------------------------------------------------------------- /lib/net/peer-connection.js: -------------------------------------------------------------------------------- 1 | // jscs:disable validateIndentation 2 | ig.module( 3 | 'net.peer-connection' 4 | ) 5 | .requires( 6 | 'game.events' 7 | ) 8 | .defines(function() { 9 | 10 | PeerConnection = Events.Emitter.extend({ 11 | 12 | CHANNEL_NAME: 'data', 13 | 14 | iceServers: [{ 15 | url: 'stun:stun.l.google.com:19302' 16 | },{ 17 | url: 'stun:stun.anyfirewall.com:3478' 18 | },{ 19 | url: 'turn:turn.bistri.com:80', 20 | credential: 'homeo', 21 | username: 'homeo' 22 | },{ 23 | url: 'turn:turn.anyfirewall.com:443?transport=tcp', 24 | credential: 'webrtc', 25 | username: 'webrtc' 26 | }], 27 | 28 | socket: null, 29 | isInitiator: false, 30 | dataChannelReady: false, 31 | peerConnection: null, 32 | dataChannel: null, 33 | remoteDescriptionReady: false, 34 | pendingCandidates: null, 35 | lastMessageOrd: null, 36 | 37 | init: function(socket, peerUser, isInitiator) { 38 | this.parent(); 39 | this.socket = socket; 40 | this.peerUser = peerUser; 41 | this.isInitiator = isInitiator; 42 | this.pendingCandidates = []; 43 | this.peerHandlers = { 44 | 'icecandidate': this.onLocalIceCandidate, 45 | 'iceconnectionstatechange': this.onIceConnectionStateChanged, 46 | 'datachannel': this.onDataChannel 47 | }; 48 | this.dataChannelHandlers = { 49 | 'open': this.onDataChannelOpen, 50 | 'close': this.onDataChannelClose, 51 | 'message': this.onDataChannelMessage 52 | }; 53 | this.connect(); 54 | }, 55 | 56 | destroy: function() { 57 | this.parent(); 58 | this.closePeerConnection(); 59 | }, 60 | 61 | connect: function() { 62 | this.peerConnection = new RTCPeerConnection({ 63 | iceServers: this.iceServers 64 | }); 65 | Events.listen(this.peerConnection, this.peerHandlers, this); 66 | if (this.isInitiator) { 67 | this.openDataChannel( 68 | this.peerConnection.createDataChannel(this.CHANNEL_NAME, { 69 | ordered: false 70 | })); 71 | } 72 | if (this.isInitiator) { 73 | this.setLocalDescriptionAndSend(); 74 | } 75 | }, 76 | 77 | closePeerConnection: function() { 78 | this.closeDataChannel(); 79 | Events.unlisten(this.peerConnection, this.peerHandlers, this); 80 | if (this.peerConnection.signalingState !== 'closed') { 81 | this.peerConnection.close(); 82 | } 83 | }, 84 | 85 | setSdp: function(sdp) { 86 | var self = this; 87 | // Create session description from sdp data 88 | var rsd = new RTCSessionDescription(sdp); 89 | // And set it as remote description for peer connection 90 | self.peerConnection.setRemoteDescription(rsd) 91 | .then(function() { 92 | self.remoteDescriptionReady = true; 93 | self.log('Got SDP from remote peer', 'green'); 94 | // Add all received remote candidates 95 | while (self.pendingCandidates.length) { 96 | self.addRemoteCandidate(self.pendingCandidates.pop()); 97 | } 98 | // Got offer? send answer 99 | if (!self.isInitiator) { 100 | self.setLocalDescriptionAndSend(); 101 | } 102 | }); 103 | }, 104 | 105 | setLocalDescriptionAndSend: function() { 106 | var self = this; 107 | self.getDescription() 108 | .then(function(localDescription) { 109 | self.peerConnection.setLocalDescription(localDescription) 110 | .then(function() { 111 | self.log('Sending SDP', 'green'); 112 | self.sendSdp(self.peerUser.userId, localDescription); 113 | }); 114 | }) 115 | .catch(function(error) { 116 | self.log('onSdpError: ' + error.message, 'red'); 117 | }); 118 | }, 119 | 120 | getDescription: function() { 121 | return this.isInitiator ? 122 | this.peerConnection.createOffer() : 123 | this.peerConnection.createAnswer(); 124 | }, 125 | 126 | addIceCandidate: function(candidate) { 127 | if (this.remoteDescriptionReady) { 128 | this.addRemoteCandidate(candidate); 129 | } else { 130 | this.pendingCandidates.push(candidate); 131 | } 132 | }, 133 | 134 | addRemoteCandidate: function(candidate) { 135 | try { 136 | this.peerConnection.addIceCandidate(new RTCIceCandidate(candidate)); 137 | this.log('Added his ICE-candidate:' + candidate.candidate, 'gray'); 138 | } catch (err) { 139 | this.log('Error adding remote ice candidate' + err.message, 'red'); 140 | } 141 | }, 142 | 143 | // When ice framework discoveres new ice candidate, we should send it 144 | // to opponent, so he knows how to reach us 145 | onLocalIceCandidate: function(event) { 146 | if (event.candidate) { 147 | this.log('Send my ICE-candidate: ' + event.candidate.candidate, 'gray'); 148 | this.sendIceCandidate(this.peerUser.userId, event.candidate); 149 | } else { 150 | this.log('No more candidates', 'gray'); 151 | } 152 | }, 153 | 154 | // Connectivity has changed? For example someone turned off wifi 155 | onIceConnectionStateChanged: function(event) { 156 | this.log('Connection state: ' + event.target.iceConnectionState, 'green'); 157 | }, 158 | 159 | onDataChannel: function(event) { 160 | if (!this.isInitiator) { 161 | this.openDataChannel(event.channel); 162 | } 163 | }, 164 | 165 | openDataChannel: function(channel) { 166 | this.dataChannel = channel; 167 | Events.listen(this.dataChannel, this.dataChannelHandlers, this); 168 | }, 169 | 170 | closeDataChannel: function() { 171 | Events.unlisten(this.dataChannel, this.dataChannelHandlers, this); 172 | this.dataChannel.close(); 173 | }, 174 | 175 | // Data channel 176 | sendMessage: function(message) { 177 | if (!this.dataChannelReady) { 178 | return; 179 | } 180 | this.dataChannel.send(message); 181 | }, 182 | 183 | onDataChannelOpen: function() { 184 | this.dataChannelReady = true; 185 | this.emit('open'); 186 | }, 187 | 188 | onDataChannelMessage: function(event) { 189 | this.emit('message', MessageBuilder.deserialize(event.data)); 190 | }, 191 | 192 | onDataChannelClose: function() { 193 | this.dataChannelReady = false; 194 | this.emit('closed'); 195 | }, 196 | 197 | sendSdp: function(userId, sdp) { 198 | this.socket.emit('sdp', { 199 | userId: userId, 200 | sdp: sdp 201 | }); 202 | }, 203 | 204 | sendIceCandidate: function(userId, candidate) { 205 | this.socket.emit('ice_candidate', { 206 | userId: userId, 207 | candidate: candidate 208 | }); 209 | }, 210 | 211 | log: function(message, color) { 212 | console.log('%c[Peer-%d, %s] %s', 'color:' + color, this.peerUser.userId, 213 | this.peerConnection.signalingState, message); 214 | } 215 | }); 216 | 217 | }); 218 | -------------------------------------------------------------------------------- /lib/game/entities/player.js: -------------------------------------------------------------------------------- 1 | // jscs:disable validateIndentation 2 | ig.module( 3 | 'game.entities.player' 4 | ).requires( 5 | 'impact.entity', 6 | 'game.entities.particle' 7 | ).defines(function() { 8 | EntityPlayer = ig.Entity.extend({ 9 | MAX_WEAPONS: 8, 10 | size: { 11 | x: 8, 12 | y: 14 13 | }, 14 | offset: { 15 | x: 4, 16 | y: 2 17 | }, 18 | maxVel: { 19 | x: 60, 20 | y: 240 21 | }, 22 | accelDef: { 23 | ground: 400, 24 | air: 200 25 | }, 26 | frictionDef: { 27 | ground: 400, 28 | air: 100 29 | }, 30 | jump: 120, 31 | bounciness: 0, 32 | health: 30, 33 | type: ig.Entity.TYPE.A, 34 | checkAgainst: ig.Entity.TYPE.NONE, 35 | collides: ig.Entity.COLLIDES.PASSIVE, 36 | flip: false, 37 | flippedAnimOffset: 24, 38 | idle: false, 39 | moved: false, 40 | wasStanding: false, 41 | canHighJump: false, 42 | highJumpTimer: null, 43 | idleTimer: null, 44 | weaponsLeft: 3, 45 | // sfxPlasma: new ig.Sound('media/sounds/plasma.ogg'), 46 | // sfxDie: new ig.Sound('media/sounds/die-respawn.ogg', false), 47 | animSheet: null, 48 | init: function(x, y, settings) { 49 | this.animSheet = new ig.AnimationSheet('media/sprites/player-red.png', 16, 16), 50 | this.friction.y = 0; 51 | this.parent(x, y, settings); 52 | this.idleTimer = new ig.Timer(); 53 | this.highJumpTimer = new ig.Timer(); 54 | this.addAnim('idle', 1, [0]); 55 | this.addAnim('scratch', 0.3, [2, 1, 2, 1, 2], true); 56 | this.addAnim('shrug', 0.3, [3, 3, 3, 3, 3, 3, 4, 3, 3], true); 57 | this.addAnim('run', 0.07, [6, 7, 8, 9, 10, 11]); 58 | this.addAnim('jump', 1, [15]); 59 | this.addAnim('fall', 0.4, [12, 13]); 60 | this.addAnim('land', 0.15, [14]); 61 | this.addAnim('die', 0.07, [18, 19, 20, 21, 22, 23, 16, 16, 16]); 62 | this.addAnim('spawn', 0.07, [16, 16, 16, 23, 22, 21, 20, 19, 18]); 63 | }, 64 | update: function() { 65 | // If spawns - wait for spawn animation finished 66 | // then goto Idle 67 | if (this.currentAnim == this.anims.spawn) { 68 | this.currentAnim.update(); 69 | if (this.currentAnim.loopCount) { 70 | this.currentAnim = this.anims.idle.rewind(); 71 | } else { 72 | return; 73 | } 74 | } 75 | // Same for Die, but at the end of animation 76 | // do die )) 77 | if (this.currentAnim == this.anims.die) { 78 | this.currentAnim.update(); 79 | if (this.currentAnim.loopCount) { 80 | this.kill(); 81 | } 82 | return; 83 | } 84 | this.moved = false; 85 | this.friction.x = this.standing ? this.frictionDef.ground : this.frictionDef.air; 86 | 87 | // left or right button is pressed ( or holding ) 88 | // set x-axis acceleration 89 | if (ig.input.state('left')) { 90 | this.accel.x = -(this.standing ? this.accelDef.ground : this.accelDef.air); 91 | this.flip = true; 92 | this.moved = true; 93 | } else if (ig.input.state('right')) { 94 | this.accel.x = (this.standing ? this.accelDef.ground : this.accelDef.air); 95 | this.flip = false; 96 | this.moved = true; 97 | } else { 98 | this.accel.x = 0; 99 | } 100 | 101 | // fire button pressed 102 | if (ig.input.pressed('shoot')) { 103 | if (this.weaponsLeft > 0) { 104 | this.weaponsLeft--; 105 | ig.game.playerShoot(); 106 | } 107 | // this.sfxPlasma.play(); 108 | } 109 | this.wantsJump = this.wantsJump || ig.input.pressed('jump'); 110 | if (this.standing && (ig.input.pressed('jump') || 111 | (!this.wasStanding && this.wantsJump && ig.input.state('jump')))) { 112 | ig.mark('jump'); 113 | this.wantsJump = false; 114 | this.canHighJump = true; 115 | this.highJumpTimer.set(0.14); 116 | this.vel.y = -this.jump / 4; 117 | } else if (this.canHighJump) { 118 | var d = this.highJumpTimer.delta(); 119 | if (ig.input.state('jump')) { 120 | var f = Math.max(0, d > 0 ? ig.system.tick - d : ig.system.tick); 121 | this.vel.y -= this.jump * f * 6.5; 122 | } else { 123 | this.canHighJump = false; 124 | } 125 | if (d > 0) { 126 | this.canHighJump = false; 127 | } 128 | } 129 | this.wasStanding = this.standing; 130 | this.parent(); 131 | this.setAnimation(); 132 | }, 133 | setAnimation: function() { 134 | if ((!this.wasStanding && this.standing)) { 135 | this.currentAnim = this.anims.land.rewind(); 136 | } else if (this.standing && (this.currentAnim != this.anims.land || 137 | this.currentAnim.loopCount > 0)) { 138 | if (this.moved) { 139 | if (this.standing) { 140 | this.currentAnim = this.anims.run; 141 | } 142 | this.idle = false; 143 | } else { 144 | if (!this.idle || this.currentAnim.stop && this.currentAnim.loopCount > 0) { 145 | this.idle = true; 146 | this.idleTimer.set(Math.random() * 4 + 3); 147 | this.currentAnim = this.anims.idle; 148 | } 149 | if (this.idleTimer.delta() > 0) { 150 | this.idleTimer.reset(); 151 | this.currentAnim = [this.anims.scratch, this.anims.shrug].random().rewind(); 152 | } 153 | } 154 | } else if (!this.standing) { 155 | if (this.vel.y < 0) { 156 | this.currentAnim = this.anims.jump; 157 | } else { 158 | if (this.currentAnim != this.anims.fall) { 159 | this.anims.fall.rewind(); 160 | } 161 | this.currentAnim = this.anims.fall; 162 | } 163 | this.idle = false; 164 | } 165 | this.currentAnim.flip.x = this.flip; 166 | if (this.flip) { 167 | this.currentAnim.tile += this.flippedAnimOffset; 168 | } 169 | }, 170 | collideWith: function(other, axis) { 171 | if (axis == 'y' && this.standing && this.currentAnim != this.anims.die) { 172 | this.currentAnim.update(); 173 | this.setAnimation(); 174 | } 175 | }, 176 | receiveDamage: function(amount, from) { 177 | this.health -= amount; 178 | if (this.health > 0) { 179 | return; 180 | } 181 | if (from.userId) { 182 | this.killerId = from.userId; 183 | } 184 | if (this.currentAnim != this.anims.die) { 185 | this.currentAnim = this.anims.die.rewind(); 186 | for (var i = 0; i < 3; i++) { 187 | ig.game.spawnEntity(EntityPlayerGib, this.pos.x, this.pos.y); 188 | } 189 | ig.game.spawnEntity(EntityPlayerGibGun, this.pos.x, this.pos.y); 190 | // this.sfxDie.play(); 191 | } 192 | }, 193 | kill: function() { 194 | ig.game.playerDied(this.killerId); 195 | this.parent(); 196 | }, 197 | addWeapons: function(weaponsCount) { 198 | this.weaponsLeft = Math.min(this.MAX_WEAPONS, this.weaponsLeft + weaponsCount); 199 | } 200 | }); 201 | EntityPlayerGib = EntityParticle.extend({ 202 | lifetime: 0.8, 203 | fadetime: 0.4, 204 | friction: { 205 | x: 0, 206 | y: 0 207 | }, 208 | vel: { 209 | x: 30, 210 | y: 80 211 | }, 212 | gravityFactor: 0, 213 | animSheet: new ig.AnimationSheet('media/sprites/player-red.png', 8, 8), 214 | init: function(x, y, settings) { 215 | this.addAnim('idle', 7, [82, 94]); 216 | this.parent(x, y, settings); 217 | }, 218 | update: function() { 219 | this.parent(); 220 | } 221 | }); 222 | EntityPlayerGibGun = EntityParticle.extend({ 223 | lifetime: 2, 224 | fadetime: 0.4, 225 | size: { 226 | x: 8, 227 | y: 8 228 | }, 229 | friction: { 230 | x: 30, 231 | y: 0 232 | }, 233 | vel: { 234 | x: 60, 235 | y: 50 236 | }, 237 | animSheet: new ig.AnimationSheet('media/sprites/player-red.png', 8, 8), 238 | init: function(x, y, settings) { 239 | this.addAnim('idle', 0.5, [11]); 240 | this.parent(x, y, settings); 241 | this.currentAnim.flip.y = false; 242 | } 243 | }); 244 | EntityProjectile = ig.Entity.extend({ 245 | size: { 246 | x: 6, 247 | y: 3 248 | }, 249 | offset: { 250 | x: 1, 251 | y: 2 252 | }, 253 | maxVel: { 254 | x: 200, 255 | y: 0 256 | }, 257 | gravityFactor: 0, 258 | type: ig.Entity.TYPE.NONE, 259 | checkAgainst: ig.Entity.TYPE.B, 260 | collides: ig.Entity.COLLIDES.NEVER, 261 | flip: false, 262 | hasHit: false, 263 | animSheet: new ig.AnimationSheet('media/sprites/projectile.png', 8, 8), 264 | init: function(x, y, settings) { 265 | this.parent(x, y, settings); 266 | this.vel.x = (settings.flip ? -this.maxVel.x : this.maxVel.x); 267 | this.addAnim('idle', 1, [0]); 268 | this.addAnim('hit', 0.1, [0, 1, 2, 3, 4, 5], true); 269 | }, 270 | update: function() { 271 | if (this.hasHit && this.currentAnim.loopCount > 0) { 272 | this.kill(); 273 | } 274 | this.parent(); 275 | this.currentAnim.flip.x = this.flip; 276 | }, 277 | handleMovementTrace: function(res) { 278 | this.parent(res); 279 | if (res.collision.x || res.collision.y) { 280 | this.currentAnim = this.anims.hit; 281 | this.hasHit = true; 282 | } 283 | }, 284 | check: function(other) { 285 | if (!this.hasHit) { 286 | this.hasHit = true; 287 | this.currentAnim = this.anims.hit; 288 | this.vel.x = 0; 289 | } 290 | } 291 | }); 292 | 293 | EntityProjectileRemote = EntityProjectile.extend({ 294 | checkAgainst: ig.Entity.TYPE.A, 295 | check: function(other) { 296 | if (!this.hasHit) { 297 | other.receiveDamage(10, this); 298 | this.hasHit = true; 299 | this.currentAnim = this.anims.hit; 300 | this.vel.x = 0; 301 | } 302 | } 303 | }); 304 | 305 | }); 306 | -------------------------------------------------------------------------------- /lib/game/main.js: -------------------------------------------------------------------------------- 1 | // jscs:disable validateIndentation 2 | ig.module( 3 | 'game.main' 4 | ) 5 | .requires( 6 | 'impact.game', 7 | 'impact.font', 8 | 9 | 'game.camera', 10 | 'game.entities.player', 11 | 'game.entities.remote-player', 12 | 'game.levels.main', 13 | 'game.levels.main1', 14 | 'game.events', 15 | 16 | 'net.room-connection' 17 | ) 18 | .defines(function() { 19 | 20 | var log = console.log.bind(console); 21 | 22 | // Messages 23 | var MESSAGE_STATE = 0; 24 | var MESSAGE_DIED = 2; 25 | var MESSAGE_SHOOT = 3; 26 | var MESSAGE_COLLECT_WEAPON = 4; 27 | var MESSAGE_FRAG_COUNT = 5; 28 | 29 | // Fields 30 | var FIELD_TYPE = 'type'; 31 | var FIELD_X = 'x'; 32 | var FIELD_Y = 'y'; 33 | var FIELD_VEL_X = 'vel_x'; 34 | var FIELD_VEL_Y = 'vel_y'; 35 | var FIELD_ANIM = 'anim'; 36 | var FIELD_FRAME = 'frame'; 37 | var FIELD_FLIP = 'flip'; 38 | var FIELD_WEAPON_ID = 'weapon_id'; 39 | var FIELD_KILLER_ID = 'killer_id'; 40 | var FIELD_FRAG_COUNT = 'frag_count'; 41 | 42 | MyGame = ig.Game.extend({ 43 | 44 | WEAPON_RESPAWN_TIME: 10, //sec 45 | WEAPON_PER_TUBE: 2, 46 | 47 | SHOW_MESSAGE_PERIOD: 1.3, 48 | 49 | // Load a font 50 | font: new ig.Font('media/04b03.font.png'), 51 | 52 | gravity: 240, 53 | 54 | roomName: window.location.search, 55 | 56 | fragCount: 0, 57 | 58 | connection: null, 59 | 60 | player: null, 61 | remotePlayers: {}, 62 | 63 | spawnWeaponQueue: [], 64 | 65 | textMessage: 'FIGHT!!!', 66 | textMessageTimer: 0, 67 | 68 | init: function() { 69 | this.connection = gameRoom.roomConnection; 70 | this.connectionHandlers = { 71 | 'peer_message': this.onPeerMessage, 72 | 'user_leave': this.onUserLeave 73 | }; 74 | Events.on(this.connection, this.connectionHandlers, this); 75 | 76 | // input 77 | ig.input.bind(ig.KEY.LEFT_ARROW, 'left'); 78 | ig.input.bind(ig.KEY.RIGHT_ARROW, 'right'); 79 | ig.input.bind(ig.KEY.X, 'jump'); 80 | ig.input.bind(ig.KEY.C, 'shoot'); 81 | this.loadLevel(LevelMai); 82 | 83 | // player 84 | this.spawnPlayer(); 85 | 86 | // camera 87 | this.camera = new Camera(ig.system.width / 4,ig.system.height / 3,5); 88 | this.camera.trap.size.x = ig.system.width / 10; 89 | this.camera.trap.size.y = ig.system.height / 3; 90 | this.camera.lookAhead.x = ig.ua.mobile ? ig.system.width / 6 : 0; 91 | this.camera.max.x = this.collisionMap.width * this.collisionMap.tilesize - ig.system.width; 92 | this.camera.max.y = this.collisionMap.height * this.collisionMap.tilesize - ig.system.height; 93 | this.camera.set(this.player); 94 | }, 95 | 96 | playerShoot: function() { 97 | var isFlip = this.player.flip; 98 | var x = this.player.pos.x + (isFlip ? -3 : 5); 99 | var y = this.player.pos.y + 6; 100 | 101 | // Spawn an entity, and broadcast about it 102 | ig.game.spawnEntity(EntityProjectile, x, y, { 103 | flip: isFlip 104 | }); 105 | this.connection.broadcastMessage(MessageBuilder.createMessage(MESSAGE_SHOOT) 106 | .setX(x) 107 | .setY(y) 108 | .setFlip(isFlip) 109 | ); 110 | }, 111 | 112 | playerDied: function(killerId) { 113 | var msg = MessageBuilder.createMessage(MESSAGE_DIED); 114 | if (killerId) { 115 | msg.setKillerId(killerId); 116 | } 117 | this.connection.broadcastMessage(msg); 118 | this.spawnPlayer(); 119 | }, 120 | 121 | spawnPlayer: function() { 122 | var spawnPos = this.getRandomSpawnPos(); 123 | this.player = this.spawnEntity(EntityPlayer, spawnPos.x, spawnPos.y); 124 | }, 125 | 126 | getRandomSpawnPos: function() { 127 | var pods = ig.game.getEntitiesByType(EntityRespawnPod); 128 | var pod = pods[0/*Number.random(0, pods.length - 1)*/]; 129 | return pod.getSpawnPos(); 130 | }, 131 | 132 | onUserLeave: function(user) { 133 | var remotePlayer = this.remotePlayers[user.userId]; 134 | if (remotePlayer) { 135 | remotePlayer.kill(); 136 | delete this.remotePlayers[user.userId]; 137 | } 138 | }, 139 | 140 | onPeerMessage: function(message, user, peer) { 141 | var remotePlayer = this.remotePlayers[user.userId]; 142 | if (!remotePlayer && message.getType() === MESSAGE_STATE) { 143 | log('%cCreated remote player for %d', 'color: blue;', user.userId); 144 | remotePlayer = this.spawnRemotePlayer(user, message.getX(), message.getY()); 145 | } 146 | switch (message.getType()) { 147 | case MESSAGE_STATE: 148 | this.onPlayerState(remotePlayer, message); 149 | break; 150 | 151 | case MESSAGE_DIED: 152 | this.onPlayerDied(remotePlayer, message, user); 153 | break; 154 | 155 | case MESSAGE_SHOOT: 156 | this.onPlayerShoot(remotePlayer, message, user); 157 | break; 158 | 159 | case MESSAGE_COLLECT_WEAPON: 160 | this.onRemotePlayerCollectedWeapon(remotePlayer, message); 161 | break; 162 | 163 | case MESSAGE_FRAG_COUNT: 164 | this.onRemotePlayerFragCount(remotePlayer, message, user); 165 | break; 166 | } 167 | }, 168 | 169 | spawnRemotePlayer: function(user, x, y) { 170 | this.remotePlayers[user.userId] = 171 | this.spawnEntity(EntityRemotePlayer, x, y); 172 | return this.remotePlayers[user.userId]; 173 | }, 174 | 175 | onPlayerState: function(remotePlayer, message) { 176 | remotePlayer.setState(message); 177 | }, 178 | 179 | onPlayerDied: function(remotePlayer, message, user) { 180 | if (message.getKillerId() === this.connection.roomInfo.userId) { 181 | this.fragCount++; 182 | this.connection.broadcastMessage(MessageBuilder.createMessage(MESSAGE_FRAG_COUNT) 183 | .setFragCount(this.fragCount) 184 | ); 185 | this.setTextMessage('Yeahh! ' + this.fragCount + ' frags!'); 186 | } 187 | if (remotePlayer) { 188 | remotePlayer.kill(); 189 | } 190 | delete this.remotePlayers[user.userId]; 191 | }, 192 | 193 | onPlayerShoot: function(remotePlayer, message, user) { 194 | ig.game.spawnEntity(EntityProjectileRemote, message.getX(), message.getY(), { 195 | flip: message.getFlip(), 196 | userId: user.userId 197 | }); 198 | }, 199 | 200 | onRemotePlayerCollectedWeapon: function(remotePlayer, message) { 201 | this.onWeaponCollected(message.getWeaponId()); 202 | }, 203 | 204 | onRemotePlayerFragCount: function(remotePlayer, message, user) { 205 | this.setTextMessage('Player ' + user.userId + ' has ' + message.getFragCount() + ' frags!!'); 206 | }, 207 | 208 | onPlayerCollectedWeapon: function(weapon) { 209 | this.player.addWeapons(this.WEAPON_PER_TUBE); 210 | this.connection.broadcastMessage(MessageBuilder.createMessage(MESSAGE_COLLECT_WEAPON) 211 | .setWeaponId(weapon.weaponId) 212 | ); 213 | this.onWeaponCollected(weapon.weaponId); 214 | }, 215 | 216 | onWeaponCollected: function(weaponId) { 217 | var weapon = ig.game.getEntitiesByType(EntityTestTube).find(function(weapon) { 218 | return weapon.weaponId === weaponId; 219 | }); 220 | if (weapon) { 221 | weapon.kill(); 222 | this.spawnWeaponQueue.push([weapon, ig.Timer.time]); 223 | } 224 | }, 225 | 226 | spawnWeapons: function() { 227 | while (this.spawnWeaponQueue.length) { 228 | var delta = ig.Timer.time - this.spawnWeaponQueue[0][1]; 229 | if (delta < this.WEAPON_RESPAWN_TIME) { 230 | break; 231 | } 232 | var weapon = this.spawnWeaponQueue.pop()[0]; 233 | ig.game.spawnEntity(EntityTestTube, weapon.pos.x, weapon.pos.y, { 234 | weaponId: weapon.weaponId 235 | }); 236 | } 237 | }, 238 | 239 | update: function() { 240 | this.camera.follow(this.player); 241 | // Update all entities and backgroundMaps 242 | this.parent(); 243 | 244 | // Broadcast state 245 | this.connection.broadcastMessage(MessageBuilder.createMessage(MESSAGE_STATE) 246 | .setX(this.player.pos.x * 10) 247 | .setY(this.player.pos.y * 10) 248 | .setVelX((this.player.pos.x - this.player.last.x) * 10) 249 | .setVelY((this.player.pos.y - this.player.last.y) * 10) 250 | .setFrame(this.player.getAnimFrame()) 251 | .setAnim(this.player.getAnimId()) 252 | .setFlip(this.player.currentAnim.flip.x ? 1 : 0)); 253 | 254 | this.updateText(); 255 | this.spawnWeapons(); 256 | }, 257 | 258 | draw: function() { 259 | // Draw all entities and backgroundMaps 260 | this.parent(); 261 | this.camera.draw(); 262 | this.drawText(); 263 | }, 264 | 265 | setTextMessage: function(message) { 266 | this.textMessage = message; 267 | this.textMessageTimer = ig.Timer.time; 268 | }, 269 | 270 | drawText: function() { 271 | if (this.textMessage) { 272 | this.font.draw(this.textMessage, ig.system.width / 2, 8, ig.Font.ALIGN.CENTER); 273 | } 274 | }, 275 | 276 | updateText: function() { 277 | if (!this.textMessage) { 278 | return; 279 | } 280 | if (ig.Timer.time - this.textMessageTimer >= this.SHOW_MESSAGE_PERIOD) { 281 | this.textMessage = ''; 282 | } 283 | } 284 | }); 285 | 286 | GameRoom = ig.Class.extend({ 287 | roomId: null, 288 | roomConnection: null, 289 | socket: null, 290 | 291 | init: function(socketUrl) { 292 | this.roomId = window.location.search.slice(1); 293 | this.registerMessages(); 294 | this.socket = io(socketUrl); 295 | this.roomConnection = new RoomConnection(this.roomId, this.socket); 296 | this.roomConnection.on('joined', this.onJoinedRoom, this); 297 | this.roomConnection.connect(); 298 | }, 299 | 300 | registerMessages: function() { 301 | MessageBuilder.registerMessageType(MESSAGE_STATE, [ 302 | FIELD_TYPE, 303 | FIELD_X, 304 | FIELD_Y, 305 | FIELD_VEL_X, 306 | FIELD_VEL_Y, 307 | FIELD_FRAME, 308 | FIELD_ANIM, 309 | FIELD_FLIP 310 | ]); 311 | MessageBuilder.registerMessageType(MESSAGE_DIED, [ 312 | FIELD_TYPE, 313 | FIELD_KILLER_ID 314 | ]); 315 | MessageBuilder.registerMessageType(MESSAGE_SHOOT, [ 316 | FIELD_TYPE, 317 | FIELD_X, 318 | FIELD_Y, 319 | FIELD_FLIP 320 | ]); 321 | MessageBuilder.registerMessageType(MESSAGE_COLLECT_WEAPON, [ 322 | FIELD_TYPE, 323 | FIELD_WEAPON_ID 324 | ]); 325 | MessageBuilder.registerMessageType(MESSAGE_FRAG_COUNT, [ 326 | FIELD_TYPE, 327 | FIELD_FRAG_COUNT 328 | ]); 329 | }, 330 | 331 | onJoinedRoom: function(roomInfo) { 332 | console.log('%cJoined room', 'color: green', roomInfo); 333 | ig.main('#canvas', MyGame, 60, 240, 160, 3); 334 | } 335 | }); 336 | var gameRoom = new GameRoom('http://' + window.location.hostname + ':8033'); 337 | 338 | }); 339 | -------------------------------------------------------------------------------- /lib/game/levels/main.js: -------------------------------------------------------------------------------- 1 | ig.module( 'game.levels.main' ) 2 | .requires( 'impact.image','game.entities.respawn-pod','game.entities.test-tube','game.entities.acid' ) 3 | .defines(function(){ 4 | LevelMain=/*JSON[*/{ 5 | "entities": [ 6 | { 7 | "type": "EntityRespawnPod", 8 | "x": 24, 9 | "y": 336 10 | }, 11 | { 12 | "type": "EntityRespawnPod", 13 | "x": 460, 14 | "y": 48 15 | }, 16 | { 17 | "type": "EntityRespawnPod", 18 | "x": 420, 19 | "y": 296 20 | }, 21 | { 22 | "type": "EntityRespawnPod", 23 | "x": 12, 24 | "y": 48 25 | }, 26 | { 27 | "type": "EntityTestTube", 28 | "x": 248, 29 | "y": 348, 30 | "settings": { 31 | "weaponId": 10 32 | } 33 | }, 34 | { 35 | "type": "EntityTestTube", 36 | "x": 68, 37 | "y": 208, 38 | "settings": { 39 | "weaponId": 6 40 | } 41 | }, 42 | { 43 | "type": "EntityTestTube", 44 | "x": 496, 45 | "y": 116, 46 | "settings": { 47 | "weaponId": 5 48 | } 49 | }, 50 | { 51 | "type": "EntityTestTube", 52 | "x": 68, 53 | "y": 48, 54 | "settings": { 55 | "weaponId": 1 56 | } 57 | }, 58 | { 59 | "type": "EntityAcid", 60 | "x": 104, 61 | "y": 160 62 | }, 63 | { 64 | "type": "EntityAcid", 65 | "x": 120, 66 | "y": 160 67 | }, 68 | { 69 | "type": "EntityAcid", 70 | "x": 128, 71 | "y": 160 72 | }, 73 | { 74 | "type": "EntityAcid", 75 | "x": 152, 76 | "y": 160 77 | }, 78 | { 79 | "type": "EntityAcid", 80 | "x": 168, 81 | "y": 160 82 | }, 83 | { 84 | "type": "EntityAcid", 85 | "x": 240, 86 | "y": 144 87 | }, 88 | { 89 | "type": "EntityAcid", 90 | "x": 320, 91 | "y": 144 92 | }, 93 | { 94 | "type": "EntityAcid", 95 | "x": 336, 96 | "y": 144 97 | }, 98 | { 99 | "type": "EntityAcid", 100 | "x": 248, 101 | "y": 144 102 | }, 103 | { 104 | "type": "EntityAcid", 105 | "x": 264, 106 | "y": 144 107 | }, 108 | { 109 | "type": "EntityAcid", 110 | "x": 280, 111 | "y": 144 112 | }, 113 | { 114 | "type": "EntityAcid", 115 | "x": 296, 116 | "y": 144 117 | }, 118 | { 119 | "type": "EntityAcid", 120 | "x": 392, 121 | "y": 368 122 | }, 123 | { 124 | "type": "EntityAcid", 125 | "x": 408, 126 | "y": 368 127 | }, 128 | { 129 | "type": "EntityAcid", 130 | "x": 424, 131 | "y": 368 132 | }, 133 | { 134 | "type": "EntityAcid", 135 | "x": 352, 136 | "y": 368 137 | }, 138 | { 139 | "type": "EntityAcid", 140 | "x": 368, 141 | "y": 368 142 | }, 143 | { 144 | "type": "EntityAcid", 145 | "x": 304, 146 | "y": 368 147 | }, 148 | { 149 | "type": "EntityAcid", 150 | "x": 288, 151 | "y": 368 152 | }, 153 | { 154 | "type": "EntityAcid", 155 | "x": 232, 156 | "y": 368 157 | }, 158 | { 159 | "type": "EntityAcid", 160 | "x": 208, 161 | "y": 368 162 | }, 163 | { 164 | "type": "EntityAcid", 165 | "x": 192, 166 | "y": 368 167 | }, 168 | { 169 | "type": "EntityAcid", 170 | "x": 152, 171 | "y": 368 172 | }, 173 | { 174 | "type": "EntityAcid", 175 | "x": 136, 176 | "y": 368 177 | }, 178 | { 179 | "type": "EntityAcid", 180 | "x": 104, 181 | "y": 368 182 | }, 183 | { 184 | "type": "EntityAcid", 185 | "x": 128, 186 | "y": 272 187 | }, 188 | { 189 | "type": "EntityAcid", 190 | "x": 144, 191 | "y": 272 192 | }, 193 | { 194 | "type": "EntityAcid", 195 | "x": 272, 196 | "y": 144 197 | }, 198 | { 199 | "type": "EntityTestTube", 200 | "x": 264, 201 | "y": 348, 202 | "settings": { 203 | "weaponId": 12 204 | } 205 | }, 206 | { 207 | "type": "EntityAcid", 208 | "x": 288, 209 | "y": 144 210 | }, 211 | { 212 | "type": "EntityAcid", 213 | "x": 136, 214 | "y": 160 215 | }, 216 | { 217 | "type": "EntityAcid", 218 | "x": 304, 219 | "y": 144 220 | }, 221 | { 222 | "type": "EntityTestTube", 223 | "x": 176, 224 | "y": 300, 225 | "settings": { 226 | "weaponId": 8 227 | } 228 | }, 229 | { 230 | "type": "EntityAcid", 231 | "x": 400, 232 | "y": 368 233 | }, 234 | { 235 | "type": "EntityAcid", 236 | "x": 144, 237 | "y": 160 238 | }, 239 | { 240 | "type": "EntityAcid", 241 | "x": 416, 242 | "y": 368 243 | }, 244 | { 245 | "type": "EntityTestTube", 246 | "x": 180, 247 | "y": 144, 248 | "settings": { 249 | "weaponId": 3 250 | } 251 | }, 252 | { 253 | "type": "EntityAcid", 254 | "x": 344, 255 | "y": 368 256 | }, 257 | { 258 | "type": "EntityAcid", 259 | "x": 160, 260 | "y": 160 261 | }, 262 | { 263 | "type": "EntityAcid", 264 | "x": 360, 265 | "y": 368 266 | }, 267 | { 268 | "type": "EntityTestTube", 269 | "x": 256, 270 | "y": 40, 271 | "settings": { 272 | "weaponId": 2 273 | } 274 | }, 275 | { 276 | "type": "EntityAcid", 277 | "x": 312, 278 | "y": 368 279 | }, 280 | { 281 | "type": "EntityAcid", 282 | "x": 232, 283 | "y": 144 284 | }, 285 | { 286 | "type": "EntityAcid", 287 | "x": 296, 288 | "y": 368 289 | }, 290 | { 291 | "type": "EntityTestTube", 292 | "x": 344, 293 | "y": 284, 294 | "settings": { 295 | "weaponId": 9 296 | } 297 | }, 298 | { 299 | "type": "EntityAcid", 300 | "x": 280, 301 | "y": 368 302 | }, 303 | { 304 | "type": "EntityAcid", 305 | "x": 312, 306 | "y": 144 307 | }, 308 | { 309 | "type": "EntityAcid", 310 | "x": 224, 311 | "y": 372 312 | }, 313 | { 314 | "type": "EntityTestTube", 315 | "x": 256, 316 | "y": 348, 317 | "settings": { 318 | "weaponId": 11 319 | } 320 | }, 321 | { 322 | "type": "EntityAcid", 323 | "x": 200, 324 | "y": 368 325 | }, 326 | { 327 | "type": "EntityAcid", 328 | "x": 328, 329 | "y": 144 330 | }, 331 | { 332 | "type": "EntityAcid", 333 | "x": 160, 334 | "y": 368 335 | }, 336 | { 337 | "type": "EntityTestTube", 338 | "x": 280, 339 | "y": 188, 340 | "settings": { 341 | "weaponId": 7 342 | } 343 | }, 344 | { 345 | "type": "EntityAcid", 346 | "x": 144, 347 | "y": 368 348 | }, 349 | { 350 | "type": "EntityAcid", 351 | "x": 344, 352 | "y": 144 353 | }, 354 | { 355 | "type": "EntityAcid", 356 | "x": 112, 357 | "y": 368 358 | }, 359 | { 360 | "type": "EntityTestTube", 361 | "x": 288, 362 | "y": 100, 363 | "settings": { 364 | "weaponId": 4 365 | } 366 | }, 367 | { 368 | "type": "EntityAcid", 369 | "x": 96, 370 | "y": 368 371 | }, 372 | { 373 | "type": "EntityAcid", 374 | "x": 256, 375 | "y": 144 376 | }, 377 | { 378 | "type": "EntityAcid", 379 | "x": 112, 380 | "y": 160 381 | }, 382 | { 383 | "type": "EntityAcid", 384 | "x": 136, 385 | "y": 272 386 | } 387 | ], 388 | "layer": [ 389 | { 390 | "name": "collision", 391 | "width": 64, 392 | "height": 48, 393 | "linkWithCollision": false, 394 | "visible": 0, 395 | "tilesetName": "", 396 | "repeat": false, 397 | "preRender": false, 398 | "distance": 1, 399 | "tilesize": 8, 400 | "foreground": false, 401 | "data": [ 402 | [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 403 | [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 404 | [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 405 | [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 406 | [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 407 | [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1], 408 | [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1], 409 | [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1], 410 | [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1], 411 | [1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,1], 412 | [1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 413 | [1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 414 | [1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1], 415 | [1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 416 | [1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 417 | [1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 418 | [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1], 419 | [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 420 | [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,1], 421 | [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,1], 422 | [1,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1], 423 | [1,0,0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,1], 424 | [1,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 425 | [1,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 426 | [1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1], 427 | [1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,1], 428 | [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,1], 429 | [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1], 430 | [1,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,1,0,1,0,0,0,1,1,1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,1], 431 | [1,1,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,1,1], 432 | [1,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,1], 433 | [1,0,0,0,0,0,0,1,0,0,0,1,1,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1], 434 | [1,0,0,0,1,1,1,1,0,0,0,1,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1], 435 | [1,0,0,0,0,1,1,1,0,0,0,1,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 436 | [1,0,0,0,0,1,1,1,0,0,0,1,1,1,1,1,0,0,0,1,1,1,1,1,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,1], 437 | [1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,1,1], 438 | [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1], 439 | [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1], 440 | [1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,1,1,0,1,1,1,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,1], 441 | [1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,1,1,1,1,1,1,1,0,0,0,0,0,0,1], 442 | [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 443 | [1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1], 444 | [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 445 | [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 446 | [1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1], 447 | [1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,1,1,1,0,0,0,0,0,0,0,1,0,0,0,0,0,1,1,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1], 448 | [1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,0,0,0,0,1,1,1,0,0,0,1,0,0,1,1,1,1,1,0,0,0,0,0,1,1,1,0,0,0,0,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1], 449 | [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1] 450 | ] 451 | }, 452 | { 453 | "name": "bg", 454 | "width": 30, 455 | "height": 20, 456 | "linkWithCollision": false, 457 | "visible": 0, 458 | "tilesetName": "media/tiles/biolab.png", 459 | "repeat": true, 460 | "preRender": true, 461 | "distance": "3", 462 | "tilesize": 8, 463 | "foreground": false, 464 | "data": [ 465 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 466 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 467 | [0,109,0,0,0,0,108,107,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 468 | [0,0,0,0,0,0,0,0,0,0,0,0,0,108,107,0,0,0,0,0,0,0,0,0,0,108,107,0,0,0], 469 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,107,0,0,0,0,0,0,0,0,0,0,108,108,0,0,0], 470 | [0,0,0,0,108,107,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 471 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,108,0,0], 472 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,107,0,0,0,0,0,0,0,0,0,0,108,107,0], 473 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 474 | [0,0,0,0,0,108,0,0,0,0,0,107,107,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,108,0], 475 | [0,0,109,0,0,0,107,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,107,0], 476 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 477 | [0,0,0,0,0,0,0,0,108,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 478 | [0,0,0,0,0,0,0,0,107,108,0,0,0,0,0,0,0,0,108,0,0,0,0,0,0,0,0,0,0,0], 479 | [0,0,0,109,0,0,0,0,0,107,0,0,0,0,0,0,0,0,107,0,0,0,0,0,0,0,0,0,0,0], 480 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,108,0,0,0,0,0,0,0,0,0,109,109,0,0], 481 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,107,0,0,0,0,0,0,0,0,0,0,109,0,0], 482 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 483 | [0,0,0,109,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 484 | [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] 485 | ] 486 | }, 487 | { 488 | "name": "main", 489 | "width": 64, 490 | "height": 48, 491 | "linkWithCollision": true, 492 | "visible": 1, 493 | "tilesetName": "media/tiles/biolab.png", 494 | "repeat": false, 495 | "preRender": false, 496 | "distance": "1", 497 | "tilesize": 8, 498 | "foreground": false, 499 | "data": [ 500 | [17,0,0,0,0,0,0,0,0,0,0,0,0,0,71,0,0,0,0,0,0,0,0,71,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,71,0,0,0,0,0,0,0,0,0,0,0,71,0,0,0,0,0,0,1], 501 | [1,0,0,0,0,0,0,0,0,0,0,0,0,0,71,0,0,0,0,0,0,0,0,71,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,71,0,0,0,0,0,0,0,0,0,0,0,71,0,0,0,0,0,0,6], 502 | [1,0,0,0,0,0,0,0,0,0,0,0,0,0,71,0,0,0,0,0,0,0,0,71,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,71,0,0,0,0,0,0,0,0,0,0,0,71,0,0,0,0,0,0,1], 503 | [1,0,0,0,0,0,0,0,0,0,0,0,0,0,71,0,0,0,0,0,0,0,0,71,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,71,0,0,0,0,0,0,0,0,0,0,0,71,0,0,0,0,0,0,1], 504 | [1,0,0,0,0,0,0,0,0,0,0,0,0,0,71,0,0,0,0,0,0,0,0,71,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,71,0,0,0,0,0,0,0,0,0,0,0,71,0,0,0,0,0,0,1], 505 | [1,0,0,0,0,0,0,0,0,0,0,0,0,0,71,0,0,0,0,0,0,0,0,71,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,71,0,0,0,0,0,0,0,0,0,0,0,71,0,0,0,0,0,19,1], 506 | [1,0,0,0,0,0,0,0,0,0,0,0,0,0,71,0,0,0,0,0,0,0,0,71,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,71,0,0,0,0,0,0,0,0,0,0,0,71,0,0,0,0,19,19,1], 507 | [1,0,0,0,0,0,0,0,0,0,0,0,0,0,71,0,0,0,0,0,0,0,0,71,0,0,0,0,0,0,0,13,14,15,0,0,0,0,0,0,0,0,0,0,71,0,0,0,0,0,0,0,0,0,0,0,71,0,0,0,19,19,19,6], 508 | [30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,0,0,0,0,0,0,0,71,0,0,0,0,0,0,30,30,30,30,30,0,0,0,0,0,0,0,0,0,71,0,0,0,0,0,0,0,0,29,30,30,30,30,30,30,30,30,30,1], 509 | [1,48,0,0,0,0,0,0,0,0,0,0,0,0,46,47,0,0,0,0,0,29,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,31,0,0,0,0,0,0,0,47,46,0,0,0,0,0,46,47,47,1], 510 | [22,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 511 | [24,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 512 | [24,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,0,0,0,0,0,0,0,0,0,0,0,6], 513 | [24,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 514 | [24,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 515 | [24,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,29,30,30,30,30,31,0,0,0,0,43,0,0,0,0,0,44,0,0,0,0,29,30,30,30,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 516 | [40,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,46,52,34,56,34,47,0,0,0,0,46,0,0,0,0,0,47,0,0,0,0,0,35,55,35,46,0,0,0,0,0,0,0,0,0,0,0,0,0,23,1], 517 | [22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,44,43,0,0,0,0,0,52,34,56,34,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,35,55,35,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 518 | [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,46,45,0,0,0,0,0,52,34,56,34,70,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,35,55,35,0,0,0,35,35,0,0,0,0,0,0,0,0,0,0,1], 519 | [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,52,34,56,34,70,92,92,92,92,92,92,92,92,92,92,92,92,92,92,92,70,35,55,35,0,0,0,35,35,0,0,0,0,0,0,0,0,0,0,1], 520 | [1,0,0,0,0,0,0,65,65,32,32,65,70,0,0,0,0,0,0,0,0,0,70,33,33,33,69,33,33,53,34,70,70,70,34,70,17,17,70,53,70,70,53,70,70,33,33,33,33,70,70,70,70,33,33,70,70,23,0,0,0,0,0,1], 521 | [1,0,0,0,0,0,23,70,60,60,63,63,59,62,89,89,89,89,89,89,89,62,59,60,60,60,60,60,78,98,98,77,77,77,77,77,77,98,77,77,77,98,77,77,77,98,77,77,77,77,77,98,77,77,77,77,77,23,23,0,0,0,0,1], 522 | [1,0,0,0,0,23,23,70,70,70,70,2,63,62,61,62,62,61,71,48,77,62,98,59,78,78,77,98,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6], 523 | [6,0,0,0,0,0,0,0,0,0,0,0,21,78,61,63,59,59,71,56,79,61,59,61,59,78,98,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 524 | [8,0,0,0,0,0,0,0,0,0,0,0,0,0,77,77,79,59,71,56,63,78,78,78,98,98,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,1], 525 | [24,6,0,0,0,0,0,0,0,0,0,0,0,0,0,21,79,77,71,56,63,98,77,77,98,0,0,0,0,0,0,0,0,29,30,30,30,31,0,0,0,0,0,0,0,0,5,21,21,5,21,21,5,21,21,5,5,0,0,0,0,0,0,1], 526 | [24,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,21,0,71,59,77,0,0,0,0,0,0,0,0,0,0,0,0,0,36,35,36,47,0,0,0,0,0,0,0,0,21,68,65,68,70,55,71,52,53,70,21,0,0,0,0,0,0,1], 527 | [24,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,71,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,35,2,0,0,0,0,0,0,0,0,2,21,2,2,2,2,55,71,52,53,70,21,2,0,0,0,0,0,1], 528 | [24,0,0,0,0,0,3,3,1,3,1,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,43,43,43,0,0,0,21,0,21,0,0,0,43,43,43,0,0,0,23,70,68,68,2,55,71,2,53,70,23,0,0,0,0,0,0,1], 529 | [24,23,0,0,0,0,0,3,0,46,0,24,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,46,0,46,0,0,0,2,35,2,0,0,0,46,0,46,0,0,0,0,23,70,70,2,55,71,2,53,2,0,0,0,0,0,0,2,1], 530 | [24,46,0,0,0,0,0,3,0,0,0,24,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,35,2,0,0,0,0,0,0,0,0,0,0,27,27,27,27,27,27,27,27,27,0,0,0,0,0,0,0,1], 531 | [24,0,0,0,0,0,0,1,0,0,0,24,31,0,0,0,0,0,0,0,0,2,21,2,0,0,0,0,0,0,0,0,0,29,21,35,2,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,71,0,0,0,0,0,0,0,0,0,0,1], 532 | [24,0,0,0,5,21,22,3,0,0,0,24,0,0,0,0,0,0,0,0,0,2,21,2,0,0,0,0,0,0,0,0,0,0,2,35,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,6], 533 | [6,0,0,0,0,3,22,3,0,0,0,24,0,0,0,0,0,0,0,0,0,2,35,2,0,0,0,0,0,0,0,0,0,0,18,35,21,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 534 | [1,0,0,0,0,21,22,1,0,0,0,22,44,42,42,44,0,0,0,3,2,2,2,2,0,0,0,0,21,2,21,4,4,21,5,35,21,21,4,21,4,0,0,0,0,0,2,2,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,1], 535 | [1,23,0,0,0,0,56,0,0,0,0,0,0,0,0,2,89,89,89,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,35,21,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,0,0,0,0,0,2,1], 536 | [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,43,44,43,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,35,18,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,0,0,0,0,0,0,1], 537 | [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,0,0,0,0,0,0,0,21,35,5,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,1], 538 | [23,0,0,0,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,0,0,0,0,33,33,0,18,35,5,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,2,0,0,2,0,0,0,1], 539 | [1,0,0,0,0,0,0,0,21,21,2,2,21,33,2,1,2,1,1,33,1,1,1,2,2,2,2,2,2,2,2,33,33,33,33,33,33,33,33,2,2,2,2,2,2,2,0,0,0,0,2,2,2,2,2,2,2,0,0,0,0,0,0,1], 540 | [1,0,0,0,0,0,0,0,46,0,0,46,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,47,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 541 | [1,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,1], 542 | [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6], 543 | [23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], 544 | [1,1,1,1,1,1,1,1,1,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,1,1,61,61,1,1,1,1], 545 | [5,63,63,55,63,20,2,2,6,5,0,0,0,0,0,6,0,0,0,0,0,0,21,0,0,0,0,56,0,0,0,20,20,20,0,0,0,0,0,0,0,21,0,0,0,0,0,20,1,0,0,0,0,0,0,4,58,58,58,58,58,58,5,1], 546 | [63,60,77,55,63,63,63,2,3,3,3,3,0,0,0,1,1,0,0,0,0,1,1,1,0,0,0,56,0,0,1,20,20,3,1,0,0,0,0,0,1,1,1,0,0,0,0,2,20,0,0,0,0,0,4,4,58,61,61,61,61,61,5,1], 547 | [63,77,77,55,78,63,63,63,79,77,79,77,78,79,78,79,78,78,79,78,78,78,79,79,79,78,78,56,78,2,2,2,2,2,2,2,78,79,79,78,79,78,79,78,78,79,78,79,78,78,78,79,78,78,63,63,63,63,61,77,77,63,63,63] 548 | ] 549 | } 550 | ] 551 | }/*]JSON*/; 552 | LevelMainResources=[new ig.Image('media/tiles/biolab.png'), new ig.Image('media/tiles/biolab.png')]; 553 | }); -------------------------------------------------------------------------------- /lib/net/socket.io.js: -------------------------------------------------------------------------------- 1 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.io=f()}})(function(){var define,module,exports;return function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o0){this.extraHeaders=opts.extraHeaders}}this.open()}Socket.priorWebsocketSuccess=false;Emitter(Socket.prototype);Socket.protocol=parser.protocol;Socket.Socket=Socket;Socket.Transport=_dereq_("./transport");Socket.transports=_dereq_("./transports");Socket.parser=_dereq_("engine.io-parser");Socket.prototype.createTransport=function(name){debug('creating transport "%s"',name);var query=clone(this.query);query.EIO=parser.protocol;query.transport=name;if(this.id)query.sid=this.id;var transport=new transports[name]({agent:this.agent,hostname:this.hostname,port:this.port,secure:this.secure,path:this.path,query:query,forceJSONP:this.forceJSONP,jsonp:this.jsonp,forceBase64:this.forceBase64,enablesXDR:this.enablesXDR,timestampRequests:this.timestampRequests,timestampParam:this.timestampParam,policyPort:this.policyPort,socket:this,pfx:this.pfx,key:this.key,passphrase:this.passphrase,cert:this.cert,ca:this.ca,ciphers:this.ciphers,rejectUnauthorized:this.rejectUnauthorized,perMessageDeflate:this.perMessageDeflate,extraHeaders:this.extraHeaders});return transport};function clone(obj){var o={};for(var i in obj){if(obj.hasOwnProperty(i)){o[i]=obj[i]}}return o}Socket.prototype.open=function(){var transport;if(this.rememberUpgrade&&Socket.priorWebsocketSuccess&&this.transports.indexOf("websocket")!=-1){transport="websocket"}else if(0===this.transports.length){var self=this;setTimeout(function(){self.emit("error","No transports available")},0);return}else{transport=this.transports[0]}this.readyState="opening";try{transport=this.createTransport(transport)}catch(e){this.transports.shift();this.open();return}transport.open();this.setTransport(transport)};Socket.prototype.setTransport=function(transport){debug("setting transport %s",transport.name);var self=this;if(this.transport){debug("clearing existing transport %s",this.transport.name);this.transport.removeAllListeners()}this.transport=transport;transport.on("drain",function(){self.onDrain()}).on("packet",function(packet){self.onPacket(packet)}).on("error",function(e){self.onError(e)}).on("close",function(){self.onClose("transport close")})};Socket.prototype.probe=function(name){debug('probing transport "%s"',name);var transport=this.createTransport(name,{probe:1}),failed=false,self=this;Socket.priorWebsocketSuccess=false;function onTransportOpen(){if(self.onlyBinaryUpgrades){var upgradeLosesBinary=!this.supportsBinary&&self.transport.supportsBinary;failed=failed||upgradeLosesBinary}if(failed)return;debug('probe transport "%s" opened',name);transport.send([{type:"ping",data:"probe"}]);transport.once("packet",function(msg){if(failed)return;if("pong"==msg.type&&"probe"==msg.data){debug('probe transport "%s" pong',name);self.upgrading=true;self.emit("upgrading",transport);if(!transport)return;Socket.priorWebsocketSuccess="websocket"==transport.name;debug('pausing current transport "%s"',self.transport.name);self.transport.pause(function(){if(failed)return;if("closed"==self.readyState)return;debug("changing transport and sending upgrade packet");cleanup();self.setTransport(transport);transport.send([{type:"upgrade"}]);self.emit("upgrade",transport);transport=null;self.upgrading=false;self.flush()})}else{debug('probe transport "%s" failed',name);var err=new Error("probe error");err.transport=transport.name;self.emit("upgradeError",err)}})}function freezeTransport(){if(failed)return;failed=true;cleanup();transport.close();transport=null}function onerror(err){var error=new Error("probe error: "+err);error.transport=transport.name;freezeTransport();debug('probe transport "%s" failed because of error: %s',name,err);self.emit("upgradeError",error)}function onTransportClose(){onerror("transport closed")}function onclose(){onerror("socket closed")}function onupgrade(to){if(transport&&to.name!=transport.name){debug('"%s" works - aborting "%s"',to.name,transport.name);freezeTransport()}}function cleanup(){transport.removeListener("open",onTransportOpen);transport.removeListener("error",onerror);transport.removeListener("close",onTransportClose);self.removeListener("close",onclose);self.removeListener("upgrading",onupgrade)}transport.once("open",onTransportOpen);transport.once("error",onerror);transport.once("close",onTransportClose);this.once("close",onclose);this.once("upgrading",onupgrade);transport.open()};Socket.prototype.onOpen=function(){debug("socket open");this.readyState="open";Socket.priorWebsocketSuccess="websocket"==this.transport.name;this.emit("open");this.flush();if("open"==this.readyState&&this.upgrade&&this.transport.pause){debug("starting upgrade probes");for(var i=0,l=this.upgrades.length;i';iframe=document.createElement(html)}catch(e){iframe=document.createElement("iframe");iframe.name=self.iframeId;iframe.src="javascript:0"}iframe.id=self.iframeId;self.form.appendChild(iframe);self.iframe=iframe}initIframe();data=data.replace(rEscapedNewline,"\\\n");this.area.value=data.replace(rNewline,"\\n");try{this.form.submit()}catch(e){}if(this.iframe.attachEvent){this.iframe.onreadystatechange=function(){if(self.iframe.readyState=="complete"){complete()}}}else{this.iframe.onload=complete}}}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:typeof global!=="undefined"?global:{})},{"./polling":8,"component-inherit":16}],7:[function(_dereq_,module,exports){(function(global){var XMLHttpRequest=_dereq_("xmlhttprequest-ssl");var Polling=_dereq_("./polling");var Emitter=_dereq_("component-emitter");var inherit=_dereq_("component-inherit");var debug=_dereq_("debug")("engine.io-client:polling-xhr");module.exports=XHR;module.exports.Request=Request;function empty(){}function XHR(opts){Polling.call(this,opts);if(global.location){var isSSL="https:"==location.protocol;var port=location.port;if(!port){port=isSSL?443:80}this.xd=opts.hostname!=global.location.hostname||port!=opts.port;this.xs=opts.secure!=isSSL}else{this.extraHeaders=opts.extraHeaders}}inherit(XHR,Polling);XHR.prototype.supportsBinary=true;XHR.prototype.request=function(opts){opts=opts||{};opts.uri=this.uri();opts.xd=this.xd;opts.xs=this.xs;opts.agent=this.agent||false;opts.supportsBinary=this.supportsBinary;opts.enablesXDR=this.enablesXDR;opts.pfx=this.pfx;opts.key=this.key;opts.passphrase=this.passphrase;opts.cert=this.cert;opts.ca=this.ca;opts.ciphers=this.ciphers;opts.rejectUnauthorized=this.rejectUnauthorized;opts.extraHeaders=this.extraHeaders;return new Request(opts)};XHR.prototype.doWrite=function(data,fn){var isBinary=typeof data!=="string"&&data!==undefined;var req=this.request({method:"POST",data:data,isBinary:isBinary});var self=this;req.on("success",fn);req.on("error",function(err){self.onError("xhr post error",err)});this.sendXhr=req};XHR.prototype.doPoll=function(){debug("xhr poll");var req=this.request();var self=this;req.on("data",function(data){self.onData(data)});req.on("error",function(err){self.onError("xhr poll error",err)});this.pollXhr=req};function Request(opts){this.method=opts.method||"GET";this.uri=opts.uri;this.xd=!!opts.xd;this.xs=!!opts.xs;this.async=false!==opts.async;this.data=undefined!=opts.data?opts.data:null;this.agent=opts.agent;this.isBinary=opts.isBinary;this.supportsBinary=opts.supportsBinary;this.enablesXDR=opts.enablesXDR;this.pfx=opts.pfx;this.key=opts.key;this.passphrase=opts.passphrase;this.cert=opts.cert;this.ca=opts.ca;this.ciphers=opts.ciphers;this.rejectUnauthorized=opts.rejectUnauthorized;this.extraHeaders=opts.extraHeaders;this.create()}Emitter(Request.prototype);Request.prototype.create=function(){var opts={agent:this.agent,xdomain:this.xd,xscheme:this.xs,enablesXDR:this.enablesXDR};opts.pfx=this.pfx;opts.key=this.key;opts.passphrase=this.passphrase;opts.cert=this.cert;opts.ca=this.ca;opts.ciphers=this.ciphers;opts.rejectUnauthorized=this.rejectUnauthorized;var xhr=this.xhr=new XMLHttpRequest(opts);var self=this;try{debug("xhr open %s: %s",this.method,this.uri);xhr.open(this.method,this.uri,this.async);try{if(this.extraHeaders){xhr.setDisableHeaderCheck(true);for(var i in this.extraHeaders){if(this.extraHeaders.hasOwnProperty(i)){xhr.setRequestHeader(i,this.extraHeaders[i])}}}}catch(e){}if(this.supportsBinary){xhr.responseType="arraybuffer"}if("POST"==this.method){try{if(this.isBinary){xhr.setRequestHeader("Content-type","application/octet-stream")}else{xhr.setRequestHeader("Content-type","text/plain;charset=UTF-8")}}catch(e){}}if("withCredentials"in xhr){xhr.withCredentials=true}if(this.hasXDR()){xhr.onload=function(){self.onLoad()};xhr.onerror=function(){self.onError(xhr.responseText)}}else{xhr.onreadystatechange=function(){if(4!=xhr.readyState)return;if(200==xhr.status||1223==xhr.status){self.onLoad()}else{setTimeout(function(){self.onError(xhr.status)},0)}}}debug("xhr data %s",this.data);xhr.send(this.data)}catch(e){setTimeout(function(){self.onError(e)},0);return}if(global.document){this.index=Request.requestsCount++;Request.requests[this.index]=this}};Request.prototype.onSuccess=function(){this.emit("success");this.cleanup()};Request.prototype.onData=function(data){this.emit("data",data);this.onSuccess()};Request.prototype.onError=function(err){this.emit("error",err);this.cleanup(true)};Request.prototype.cleanup=function(fromError){if("undefined"==typeof this.xhr||null===this.xhr){return}if(this.hasXDR()){this.xhr.onload=this.xhr.onerror=empty}else{this.xhr.onreadystatechange=empty}if(fromError){try{this.xhr.abort()}catch(e){}}if(global.document){delete Request.requests[this.index]}this.xhr=null};Request.prototype.onLoad=function(){var data;try{var contentType;try{contentType=this.xhr.getResponseHeader("Content-Type").split(";")[0]}catch(e){}if(contentType==="application/octet-stream"){data=this.xhr.response}else{if(!this.supportsBinary){data=this.xhr.responseText}else{try{data=String.fromCharCode.apply(null,new Uint8Array(this.xhr.response))}catch(e){var ui8Arr=new Uint8Array(this.xhr.response);var dataArray=[];for(var idx=0,length=ui8Arr.length;idxbytes){end=bytes}if(start>=bytes||start>=end||bytes===0){return new ArrayBuffer(0)}var abv=new Uint8Array(arraybuffer);var result=new Uint8Array(end-start);for(var i=start,ii=0;i>2]; 2 | base64+=chars[(bytes[i]&3)<<4|bytes[i+1]>>4];base64+=chars[(bytes[i+1]&15)<<2|bytes[i+2]>>6];base64+=chars[bytes[i+2]&63]}if(len%3===2){base64=base64.substring(0,base64.length-1)+"="}else if(len%3===1){base64=base64.substring(0,base64.length-2)+"=="}return base64};exports.decode=function(base64){var bufferLength=base64.length*.75,len=base64.length,i,p=0,encoded1,encoded2,encoded3,encoded4;if(base64[base64.length-1]==="="){bufferLength--;if(base64[base64.length-2]==="="){bufferLength--}}var arraybuffer=new ArrayBuffer(bufferLength),bytes=new Uint8Array(arraybuffer);for(i=0;i>4;bytes[p++]=(encoded2&15)<<4|encoded3>>2;bytes[p++]=(encoded3&3)<<6|encoded4&63}return arraybuffer}})("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")},{}],14:[function(_dereq_,module,exports){(function(global){var BlobBuilder=global.BlobBuilder||global.WebKitBlobBuilder||global.MSBlobBuilder||global.MozBlobBuilder;var blobSupported=function(){try{var a=new Blob(["hi"]);return a.size===2}catch(e){return false}}();var blobSupportsArrayBufferView=blobSupported&&function(){try{var b=new Blob([new Uint8Array([1,2])]);return b.size===2}catch(e){return false}}();var blobBuilderSupported=BlobBuilder&&BlobBuilder.prototype.append&&BlobBuilder.prototype.getBlob;function mapArrayBufferViews(ary){for(var i=0;i=31}exports.formatters.j=function(v){return JSON.stringify(v)};function formatArgs(){var args=arguments;var useColors=this.useColors;args[0]=(useColors?"%c":"")+this.namespace+(useColors?" %c":" ")+args[0]+(useColors?"%c ":" ")+"+"+exports.humanize(this.diff);if(!useColors)return args;var c="color: "+this.color;args=[args[0],c,"color: inherit"].concat(Array.prototype.slice.call(args,1));var index=0;var lastC=0;args[0].replace(/%[a-z%]/g,function(match){if("%%"===match)return;index++;if("%c"===match){lastC=index}});args.splice(lastC,0,c);return args}function log(){return"object"===typeof console&&console.log&&Function.prototype.apply.call(console.log,console,arguments)}function save(namespaces){try{if(null==namespaces){exports.storage.removeItem("debug")}else{exports.storage.debug=namespaces}}catch(e){}}function load(){var r;try{r=exports.storage.debug}catch(e){}return r}exports.enable(load());function localstorage(){try{return window.localStorage}catch(e){}}},{"./debug":18}],18:[function(_dereq_,module,exports){exports=module.exports=debug;exports.coerce=coerce;exports.disable=disable;exports.enable=enable;exports.enabled=enabled;exports.humanize=_dereq_("ms");exports.names=[];exports.skips=[];exports.formatters={};var prevColor=0;var prevTime;function selectColor(){return exports.colors[prevColor++%exports.colors.length]}function debug(namespace){function disabled(){}disabled.enabled=false;function enabled(){var self=enabled;var curr=+new Date;var ms=curr-(prevTime||curr);self.diff=ms;self.prev=prevTime;self.curr=curr;prevTime=curr;if(null==self.useColors)self.useColors=exports.useColors();if(null==self.color&&self.useColors)self.color=selectColor();var args=Array.prototype.slice.call(arguments);args[0]=exports.coerce(args[0]);if("string"!==typeof args[0]){args=["%o"].concat(args)}var index=0;args[0]=args[0].replace(/%([a-z%])/g,function(match,format){if(match==="%%")return match;index++;var formatter=exports.formatters[format];if("function"===typeof formatter){var val=args[index];match=formatter.call(self,val);args.splice(index,1);index--}return match});if("function"===typeof exports.formatArgs){args=exports.formatArgs.apply(self,args)}var logFn=enabled.log||exports.log||console.log.bind(console);logFn.apply(self,args)}enabled.enabled=true;var fn=exports.enabled(namespace)?enabled:disabled;fn.namespace=namespace;return fn}function enable(namespaces){exports.save(namespaces);var split=(namespaces||"").split(/[\s,]+/);var len=split.length;for(var i=0;i1){return{type:packetslist[type],data:data.substring(1)}}else{return{type:packetslist[type]}}}var asArray=new Uint8Array(data);var type=asArray[0];var rest=sliceBuffer(data,1);if(Blob&&binaryType==="blob"){rest=new Blob([rest])}return{type:packetslist[type],data:rest}};exports.decodeBase64Packet=function(msg,binaryType){var type=packetslist[msg.charAt(0)];if(!global.ArrayBuffer){return{type:type,data:{base64:true,data:msg.substr(1)}}}var data=base64encoder.decode(msg.substr(1));if(binaryType==="blob"&&Blob){data=new Blob([data])}return{type:type,data:data}};exports.encodePayload=function(packets,supportsBinary,callback){if(typeof supportsBinary=="function"){callback=supportsBinary;supportsBinary=null}var isBinary=hasBinary(packets);if(supportsBinary&&isBinary){if(Blob&&!dontSendBlobs){return exports.encodePayloadAsBlob(packets,callback)}return exports.encodePayloadAsArrayBuffer(packets,callback)}if(!packets.length){return callback("0:")}function setLengthHeader(message){return message.length+":"+message}function encodeOne(packet,doneCallback){exports.encodePacket(packet,!isBinary?false:supportsBinary,true,function(message){doneCallback(null,setLengthHeader(message))})}map(packets,encodeOne,function(err,results){return callback(results.join(""))})};function map(ary,each,done){var result=new Array(ary.length);var next=after(ary.length,done);var eachWithIndex=function(i,el,cb){each(el,function(error,msg){result[i]=msg;cb(error,result)})};for(var i=0;i0){var tailArray=new Uint8Array(bufferTail);var isString=tailArray[0]===0;var msgLength="";for(var i=1;;i++){if(tailArray[i]==255)break;if(msgLength.length>310){numberTooLong=true;break}msgLength+=tailArray[i]}if(numberTooLong)return callback(err,0,1);bufferTail=sliceBuffer(bufferTail,2+msgLength.length);msgLength=parseInt(msgLength);var msg=sliceBuffer(bufferTail,0,msgLength);if(isString){try{msg=String.fromCharCode.apply(null,new Uint8Array(msg))}catch(e){var typed=new Uint8Array(msg);msg="";for(var i=0;i1e4)return;var match=/^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec(str);if(!match)return;var n=parseFloat(match[1]);var type=(match[2]||"ms").toLowerCase();switch(type){case"years":case"year":case"yrs":case"yr":case"y":return n*y;case"days":case"day":case"d":return n*d;case"hours":case"hour":case"hrs":case"hr":case"h":return n*h;case"minutes":case"minute":case"mins":case"min":case"m":return n*m;case"seconds":case"second":case"secs":case"sec":case"s":return n*s;case"milliseconds":case"millisecond":case"msecs":case"msec":case"ms":return n}}function short(ms){if(ms>=d)return Math.round(ms/d)+"d";if(ms>=h)return Math.round(ms/h)+"h";if(ms>=m)return Math.round(ms/m)+"m";if(ms>=s)return Math.round(ms/s)+"s";return ms+"ms"}function long(ms){return plural(ms,d,"day")||plural(ms,h,"hour")||plural(ms,m,"minute")||plural(ms,s,"second")||ms+" ms"}function plural(ms,n,name){if(ms=55296&&value<=56319&&counter65535){value-=65536;output+=stringFromCharCode(value>>>10&1023|55296);value=56320|value&1023}output+=stringFromCharCode(value)}return output}function checkScalarValue(codePoint){if(codePoint>=55296&&codePoint<=57343){throw Error("Lone surrogate U+"+codePoint.toString(16).toUpperCase()+" is not a scalar value")}}function createByte(codePoint,shift){return stringFromCharCode(codePoint>>shift&63|128)}function encodeCodePoint(codePoint){if((codePoint&4294967168)==0){return stringFromCharCode(codePoint)}var symbol="";if((codePoint&4294965248)==0){symbol=stringFromCharCode(codePoint>>6&31|192)}else if((codePoint&4294901760)==0){checkScalarValue(codePoint);symbol=stringFromCharCode(codePoint>>12&15|224);symbol+=createByte(codePoint,6)}else if((codePoint&4292870144)==0){symbol=stringFromCharCode(codePoint>>18&7|240);symbol+=createByte(codePoint,12);symbol+=createByte(codePoint,6)}symbol+=stringFromCharCode(codePoint&63|128);return symbol}function utf8encode(string){var codePoints=ucs2decode(string);var length=codePoints.length;var index=-1;var codePoint;var byteString="";while(++index=byteCount){throw Error("Invalid byte index")}var continuationByte=byteArray[byteIndex]&255;byteIndex++;if((continuationByte&192)==128){return continuationByte&63}throw Error("Invalid continuation byte")}function decodeSymbol(){var byte1;var byte2;var byte3;var byte4;var codePoint;if(byteIndex>byteCount){throw Error("Invalid byte index")}if(byteIndex==byteCount){return false}byte1=byteArray[byteIndex]&255;byteIndex++;if((byte1&128)==0){return byte1}if((byte1&224)==192){var byte2=readContinuationByte();codePoint=(byte1&31)<<6|byte2;if(codePoint>=128){return codePoint}else{throw Error("Invalid continuation byte")}}if((byte1&240)==224){byte2=readContinuationByte();byte3=readContinuationByte();codePoint=(byte1&15)<<12|byte2<<6|byte3;if(codePoint>=2048){checkScalarValue(codePoint);return codePoint}else{throw Error("Invalid continuation byte")}}if((byte1&248)==240){byte2=readContinuationByte();byte3=readContinuationByte();byte4=readContinuationByte();codePoint=(byte1&15)<<18|byte2<<12|byte3<<6|byte4;if(codePoint>=65536&&codePoint<=1114111){return codePoint}}throw Error("Invalid UTF-8 detected")}var byteArray;var byteCount;var byteIndex;function utf8decode(byteString){byteArray=ucs2decode(byteString);byteCount=byteArray.length;byteIndex=0;var codePoints=[];var tmp;while((tmp=decodeSymbol())!==false){codePoints.push(tmp)}return ucs2encode(codePoints)}var utf8={version:"2.0.0",encode:utf8encode,decode:utf8decode};if(typeof define=="function"&&typeof define.amd=="object"&&define.amd){define(function(){return utf8})}else if(freeExports&&!freeExports.nodeType){if(freeModule){freeModule.exports=utf8}else{var object={};var hasOwnProperty=object.hasOwnProperty;for(var key in utf8){hasOwnProperty.call(utf8,key)&&(freeExports[key]=utf8[key])}}}else{root.utf8=utf8}})(this)}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:typeof global!=="undefined"?global:{})},{}],30:[function(_dereq_,module,exports){"use strict";var alphabet="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_".split(""),length=64,map={},seed=0,i=0,prev;function encode(num){var encoded="";do{encoded=alphabet[num%length]+encoded;num=Math.floor(num/length)}while(num>0);return encoded}function decode(str){var decoded=0;for(i=0;i0&&!this.encoding){var pack=this.packetBuffer.shift();this.packet(pack)}};Manager.prototype.cleanup=function(){debug("cleanup");var sub;while(sub=this.subs.shift())sub.destroy();this.packetBuffer=[];this.encoding=false;this.lastPing=null;this.decoder.destroy()};Manager.prototype.close=Manager.prototype.disconnect=function(){debug("disconnect");this.skipReconnect=true;this.reconnecting=false;if("opening"==this.readyState){this.cleanup()}this.backoff.reset();this.readyState="closed";if(this.engine)this.engine.close()};Manager.prototype.onclose=function(reason){debug("onclose");this.cleanup();this.backoff.reset();this.readyState="closed";this.emit("close",reason);if(this._reconnection&&!this.skipReconnect){this.reconnect()}};Manager.prototype.reconnect=function(){if(this.reconnecting||this.skipReconnect)return this;var self=this;if(this.backoff.attempts>=this._reconnectionAttempts){debug("reconnect failed");this.backoff.reset();this.emitAll("reconnect_failed");this.reconnecting=false}else{var delay=this.backoff.duration();debug("will wait %dms before reconnect attempt",delay);this.reconnecting=true;var timer=setTimeout(function(){if(self.skipReconnect)return;debug("attempting reconnect");self.emitAll("reconnect_attempt",self.backoff.attempts);self.emitAll("reconnecting",self.backoff.attempts);if(self.skipReconnect)return;self.open(function(err){if(err){debug("reconnect attempt error");self.reconnecting=false;self.reconnect();self.emitAll("reconnect_error",err.data)}else{debug("reconnect success");self.onreconnect()}})},delay);this.subs.push({destroy:function(){clearTimeout(timer)}})}};Manager.prototype.onreconnect=function(){var attempt=this.backoff.attempts;this.reconnecting=false;this.backoff.reset();this.updateSocketIds();this.emitAll("reconnect",attempt)}},{"./on":33,"./socket":34,backo2:36,"component-bind":37,"component-emitter":38,debug:39,"engine.io-client":1,indexof:42,"socket.io-parser":47}],33:[function(_dereq_,module,exports){module.exports=on;function on(obj,ev,fn){obj.on(ev,fn);return{destroy:function(){obj.removeListener(ev,fn)}}}},{}],34:[function(_dereq_,module,exports){var parser=_dereq_("socket.io-parser");var Emitter=_dereq_("component-emitter");var toArray=_dereq_("to-array");var on=_dereq_("./on");var bind=_dereq_("component-bind");var debug=_dereq_("debug")("socket.io-client:socket");var hasBin=_dereq_("has-binary");module.exports=exports=Socket;var events={connect:1,connect_error:1,connect_timeout:1,connecting:1,disconnect:1,error:1,reconnect:1,reconnect_attempt:1,reconnect_failed:1,reconnect_error:1,reconnecting:1,ping:1,pong:1};var emit=Emitter.prototype.emit;function Socket(io,nsp){this.io=io;this.nsp=nsp;this.json=this;this.ids=0;this.acks={};this.receiveBuffer=[];this.sendBuffer=[];this.connected=false;this.disconnected=true;if(this.io.autoConnect)this.open()}Emitter(Socket.prototype);Socket.prototype.subEvents=function(){if(this.subs)return;var io=this.io;this.subs=[on(io,"open",bind(this,"onopen")),on(io,"packet",bind(this,"onpacket")),on(io,"close",bind(this,"onclose"))]};Socket.prototype.open=Socket.prototype.connect=function(){if(this.connected)return this;this.subEvents();this.io.open();if("open"==this.io.readyState)this.onopen();this.emit("connecting");return this};Socket.prototype.send=function(){var args=toArray(arguments);args.unshift("message");this.emit.apply(this,args);return this};Socket.prototype.emit=function(ev){if(events.hasOwnProperty(ev)){emit.apply(this,arguments);return this}var args=toArray(arguments);var parserType=parser.EVENT;if(hasBin(args)){parserType=parser.BINARY_EVENT}var packet={type:parserType,data:args};packet.options={};packet.options.compress=!this.flags||false!==this.flags.compress;if("function"==typeof args[args.length-1]){debug("emitting packet with ack id %d",this.ids);this.acks[this.ids]=args.pop();packet.id=this.ids++}if(this.connected){this.packet(packet)}else{this.sendBuffer.push(packet)}delete this.flags;return this};Socket.prototype.packet=function(packet){packet.nsp=this.nsp;this.io.packet(packet)};Socket.prototype.onopen=function(){debug("transport is open - connecting");if("/"!=this.nsp){this.packet({type:parser.CONNECT})}};Socket.prototype.onclose=function(reason){debug("close (%s)",reason);this.connected=false;this.disconnected=true;delete this.id;this.emit("disconnect",reason)};Socket.prototype.onpacket=function(packet){if(packet.nsp!=this.nsp)return;switch(packet.type){case parser.CONNECT:this.onconnect();break;case parser.EVENT:this.onevent(packet);break;case parser.BINARY_EVENT:this.onevent(packet);break;case parser.ACK:this.onack(packet);break;case parser.BINARY_ACK:this.onack(packet);break;case parser.DISCONNECT:this.ondisconnect();break;case parser.ERROR:this.emit("error",packet.data);break}};Socket.prototype.onevent=function(packet){var args=packet.data||[];debug("emitting event %j",args);if(null!=packet.id){debug("attaching ack callback to event");args.push(this.ack(packet.id))}if(this.connected){emit.apply(this,args)}else{this.receiveBuffer.push(args)}};Socket.prototype.ack=function(id){var self=this;var sent=false;return function(){if(sent)return;sent=true;var args=toArray(arguments);debug("sending ack %j",args);var type=hasBin(args)?parser.BINARY_ACK:parser.ACK;self.packet({type:type,id:id,data:args})}};Socket.prototype.onack=function(packet){var ack=this.acks[packet.id];if("function"==typeof ack){debug("calling ack %s with %j",packet.id,packet.data);ack.apply(this,packet.data);delete this.acks[packet.id]}else{debug("bad ack %s",packet.id)}};Socket.prototype.onconnect=function(){this.connected=true;this.disconnected=false;this.emit("connect");this.emitBuffered()};Socket.prototype.emitBuffered=function(){var i;for(i=0;i0&&opts.jitter<=1?opts.jitter:0;this.attempts=0}Backoff.prototype.duration=function(){var ms=this.ms*Math.pow(this.factor,this.attempts++);if(this.jitter){var rand=Math.random();var deviation=Math.floor(rand*this.jitter*ms);ms=(Math.floor(rand*10)&1)==0?ms-deviation:ms+deviation}return Math.min(ms,this.max)|0};Backoff.prototype.reset=function(){this.attempts=0};Backoff.prototype.setMin=function(min){this.ms=min};Backoff.prototype.setMax=function(max){this.max=max};Backoff.prototype.setJitter=function(jitter){this.jitter=jitter}},{}],37:[function(_dereq_,module,exports){var slice=[].slice;module.exports=function(obj,fn){if("string"==typeof fn)fn=obj[fn];if("function"!=typeof fn)throw new Error("bind() requires a function");var args=slice.call(arguments,2);return function(){return fn.apply(obj,args.concat(slice.call(arguments)))}}},{}],38:[function(_dereq_,module,exports){module.exports=Emitter;function Emitter(obj){if(obj)return mixin(obj)}function mixin(obj){for(var key in Emitter.prototype){obj[key]=Emitter.prototype[key]}return obj}Emitter.prototype.on=Emitter.prototype.addEventListener=function(event,fn){this._callbacks=this._callbacks||{};(this._callbacks["$"+event]=this._callbacks["$"+event]||[]).push(fn);return this};Emitter.prototype.once=function(event,fn){function on(){this.off(event,on);fn.apply(this,arguments)}on.fn=fn;this.on(event,on);return this};Emitter.prototype.off=Emitter.prototype.removeListener=Emitter.prototype.removeAllListeners=Emitter.prototype.removeEventListener=function(event,fn){this._callbacks=this._callbacks||{};if(0==arguments.length){this._callbacks={};return this}var callbacks=this._callbacks["$"+event];if(!callbacks)return this;if(1==arguments.length){delete this._callbacks["$"+event];return this}var cb;for(var i=0;i1)))/4)-floor((year-1901+month)/100)+floor((year-1601+month)/400)}}if(!(isProperty=objectProto.hasOwnProperty)){isProperty=function(property){var members={},constructor;if((members.__proto__=null,members.__proto__={toString:1},members).toString!=getClass){isProperty=function(property){var original=this.__proto__,result=property in(this.__proto__=null,this);this.__proto__=original;return result}}else{constructor=members.constructor;isProperty=function(property){var parent=(this.constructor||constructor).prototype;return property in this&&!(property in parent&&this[property]===parent[property])}}members=null;return isProperty.call(this,property)}}forEach=function(object,callback){var size=0,Properties,members,property;(Properties=function(){this.valueOf=0}).prototype.valueOf=0;members=new Properties;for(property in members){if(isProperty.call(members,property)){size++}}Properties=members=null;if(!size){members=["valueOf","toString","toLocaleString","propertyIsEnumerable","isPrototypeOf","hasOwnProperty","constructor"];forEach=function(object,callback){var isFunction=getClass.call(object)==functionClass,property,length;var hasProperty=!isFunction&&typeof object.constructor!="function"&&objectTypes[typeof object.hasOwnProperty]&&object.hasOwnProperty||isProperty;for(property in object){if(!(isFunction&&property=="prototype")&&hasProperty.call(object,property)){callback(property)}}for(length=members.length;property=members[--length];hasProperty.call(object,property)&&callback(property));}}else if(size==2){forEach=function(object,callback){var members={},isFunction=getClass.call(object)==functionClass,property;for(property in object){if(!(isFunction&&property=="prototype")&&!isProperty.call(members,property)&&(members[property]=1)&&isProperty.call(object,property)){callback(property)}}}}else{forEach=function(object,callback){var isFunction=getClass.call(object)==functionClass,property,isConstructor;for(property in object){if(!(isFunction&&property=="prototype")&&isProperty.call(object,property)&&!(isConstructor=property==="constructor")){callback(property)}}if(isConstructor||isProperty.call(object,property="constructor")){callback(property)}}}return forEach(object,callback)};if(!has("json-stringify")){var Escapes={92:"\\\\",34:'\\"',8:"\\b",12:"\\f",10:"\\n",13:"\\r",9:"\\t"};var leadingZeroes="000000";var toPaddedString=function(width,value){return(leadingZeroes+(value||0)).slice(-width)};var unicodePrefix="\\u00";var quote=function(value){var result='"',index=0,length=value.length,useCharIndex=!charIndexBuggy||length>10;var symbols=useCharIndex&&(charIndexBuggy?value.split(""):value);for(;index-1/0&&value<1/0){if(getDay){date=floor(value/864e5);for(year=floor(date/365.2425)+1970-1;getDay(year+1,0)<=date;year++);for(month=floor((date-getDay(year,0))/30.42);getDay(year,month+1)<=date;month++);date=1+date-getDay(year,month);time=(value%864e5+864e5)%864e5;hours=floor(time/36e5)%24;minutes=floor(time/6e4)%60;seconds=floor(time/1e3)%60;milliseconds=time%1e3}else{year=value.getUTCFullYear();month=value.getUTCMonth();date=value.getUTCDate();hours=value.getUTCHours();minutes=value.getUTCMinutes();seconds=value.getUTCSeconds();milliseconds=value.getUTCMilliseconds()}value=(year<=0||year>=1e4?(year<0?"-":"+")+toPaddedString(6,year<0?-year:year):toPaddedString(4,year))+"-"+toPaddedString(2,month+1)+"-"+toPaddedString(2,date)+"T"+toPaddedString(2,hours)+":"+toPaddedString(2,minutes)+":"+toPaddedString(2,seconds)+"."+toPaddedString(3,milliseconds)+"Z"}else{value=null}}else if(typeof value.toJSON=="function"&&(className!=numberClass&&className!=stringClass&&className!=arrayClass||isProperty.call(value,"toJSON"))){value=value.toJSON(property)}}if(callback){value=callback.call(object,property,value)}if(value===null){return"null"}className=getClass.call(value);if(className==booleanClass){return""+value}else if(className==numberClass){return value>-1/0&&value<1/0?""+value:"null"}else if(className==stringClass){return quote(""+value)}if(typeof value=="object"){for(length=stack.length;length--;){if(stack[length]===value){throw TypeError()}}stack.push(value);results=[];prefix=indentation;indentation+=whitespace;if(className==arrayClass){for(index=0,length=value.length;index0){for(whitespace="",width>10&&(width=10);whitespace.length=48&&charCode<=57||charCode>=97&&charCode<=102||charCode>=65&&charCode<=70)){abort()}}value+=fromCharCode("0x"+source.slice(begin,Index));break;default:abort()}}else{if(charCode==34){break}charCode=source.charCodeAt(Index);begin=Index;while(charCode>=32&&charCode!=92&&charCode!=34){charCode=source.charCodeAt(++Index)}value+=source.slice(begin,Index)}}if(source.charCodeAt(Index)==34){Index++;return value}abort();default:begin=Index;if(charCode==45){isSigned=true;charCode=source.charCodeAt(++Index)}if(charCode>=48&&charCode<=57){if(charCode==48&&(charCode=source.charCodeAt(Index+1),charCode>=48&&charCode<=57)){abort()}isSigned=false;for(;Index=48&&charCode<=57);Index++);if(source.charCodeAt(Index)==46){position=++Index;for(;position=48&&charCode<=57);position++);if(position==Index){abort()}Index=position}charCode=source.charCodeAt(Index);if(charCode==101||charCode==69){charCode=source.charCodeAt(++Index);if(charCode==43||charCode==45){Index++}for(position=Index;position=48&&charCode<=57);position++);if(position==Index){abort()}Index=position}return+source.slice(begin,Index)}if(isSigned){abort()}if(source.slice(Index,Index+4)=="true"){Index+=4;return true}else if(source.slice(Index,Index+5)=="false"){Index+=5;return false}else if(source.slice(Index,Index+4)=="null"){Index+=4;return null}abort()}}return"$"};var get=function(value){var results,hasMembers;if(value=="$"){abort()}if(typeof value=="string"){if((charIndexBuggy?value.charAt(0):value[0])=="@"){return value.slice(1)}if(value=="["){results=[];for(;;hasMembers||(hasMembers=true)){value=lex();if(value=="]"){break}if(hasMembers){if(value==","){value=lex();if(value=="]"){abort()}}else{abort()}}if(value==","){abort()}results.push(get(value))}return results}else if(value=="{"){results={};for(;;hasMembers||(hasMembers=true)){value=lex();if(value=="}"){break}if(hasMembers){if(value==","){value=lex();if(value=="}"){abort()}}else{abort()}}if(value==","||typeof value!="string"||(charIndexBuggy?value.charAt(0):value[0])!="@"||lex()!=":"){abort()}results[value.slice(1)]=get(lex()) 4 | }return results}abort()}return value};var update=function(source,property,callback){var element=walk(source,property,callback);if(element===undef){delete source[property]}else{source[property]=element}};var walk=function(source,property,callback){var value=source[property],length;if(typeof value=="object"&&value){if(getClass.call(value)==arrayClass){for(length=value.length;length--;){update(value,length,callback)}}else{forEach(value,function(property){update(value,property,callback)})}}return callback.call(source,property,value)};exports.parse=function(source,callback){var result,value;Index=0;Source=""+source;result=get(lex());if(lex()!="$"){abort()}Index=Source=null;return callback&&getClass.call(callback)==functionClass?walk((value={},value[""]=result,value),"",callback):result}}}exports["runInContext"]=runInContext;return exports}if(freeExports&&!isLoader){runInContext(root,freeExports)}else{var nativeJSON=root.JSON,previousJSON=root["JSON3"],isRestored=false;var JSON3=runInContext(root,root["JSON3"]={noConflict:function(){if(!isRestored){isRestored=true;root.JSON=nativeJSON;root["JSON3"]=previousJSON;nativeJSON=previousJSON=null}return JSON3}});root.JSON={parse:JSON3.parse,stringify:JSON3.stringify}}if(isLoader){define(function(){return JSON3})}}).call(this)}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:typeof global!=="undefined"?global:{})},{}],51:[function(_dereq_,module,exports){module.exports=toArray;function toArray(list,index){var array=[];index=index||0;for(var i=index||0;i