├── .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