This commit is contained in:
Jeena 2014-03-01 23:11:36 +01:00
parent d83376d5c7
commit 810a74a28b
13 changed files with 445 additions and 281 deletions

1
.gitignore vendored
View file

@ -1,5 +1,4 @@
node_modules/ node_modules/
*.log *.log
.DS_Store .DS_Store
pixi.js/
lab/audio/ lab/audio/

View file

@ -9,7 +9,7 @@
function (GameController, Nc, User, ProtocolHelper, Options, Settings) { function (GameController, Nc, User, ProtocolHelper, Options, Settings) {
function Channel (pipeToServer, name, options) { function Channel (pipeToServer, options) {
var self = this; var self = this;
@ -17,7 +17,7 @@
levelUids: Settings.DEFAULT_LEVELS levelUids: Settings.DEFAULT_LEVELS
}); });
this.name = name; this.name = options.channelName;
this.users = {}; this.users = {};
this.pipeToServer = pipeToServer; this.pipeToServer = pipeToServer;
@ -34,11 +34,13 @@
Nc.on('broadcastGameCommand', this.broadcastGameCommand, this); Nc.on('broadcastGameCommand', this.broadcastGameCommand, this);
Nc.on('broadcastGameCommandExcept', this.broadcastGameCommandExcept, this); Nc.on('broadcastGameCommandExcept', this.broadcastGameCommandExcept, this);
console.checkpoint('channel ' + name + ' created'); console.checkpoint('channel ' + this.name + ' created');
}
Channel.validateName = function (name) { setTimeout(function() {
return true; if(Object.keys(self.users).length < 1) {
self.destroy();
}
}, Settings.CHANNEL_DESTRUCTION_TIME * 1000);
} }
@ -81,11 +83,22 @@
Channel.prototype.onReleaseUser = function (userId) { Channel.prototype.onReleaseUser = function (userId) {
var user = this.users[userId]; var user = this.users[userId];
this.broadcastControlCommandExcept("userLeft", user.id, user); Nc.trigger('user/left', userId);
Nc.trigger('user/left', user); delete this.users[userId];
delete this.users[user.id];
this.broadcastControlCommand("userLeft", userId);
// FIXME: if this was the last user terminate forked process
if(Object.keys(this.users).length < 1) {
this.destroy();
}
} }
Channel.prototype.destroy = function() {
console.checkpoint("channel (" + this.name + ") destroyed");
this.pipeToServer.destroy();
};
// Sending commands // Sending commands

View file

@ -21,7 +21,7 @@ function (Parent, PhysicsEngine, Settings, PlayerController, requestAnimFrame, N
Parent.call(this); Parent.call(this);
Nc.on('user/joined', this.onUserJoined, this); Nc.on('user/joined', this.onUserJoined, this);
Nc.on('user/left', this.onUserLeft, this); // FIXME: refactor this.userLeft -> this.onUserLeft, even in core and client Nc.on('user/left', this.onUserLeft, this);
Nc.on('user/resetLevel', this.onResetLevel, this); Nc.on('user/resetLevel', this.onResetLevel, this);
Nc.on('user/clientReady', this.onClientReady, this); Nc.on('user/clientReady', this.onClientReady, this);
Nc.on('player/killed', this.onPlayerKilled, this); Nc.on('player/killed', this.onPlayerKilled, this);

View file

@ -17,10 +17,9 @@ function (Nc, Channel) {
process.on('message', function (message, handle) { process.on('message', function (message, handle) {
if(message.data.hasOwnProperty('CREATE')) { if(message.data.hasOwnProperty('CREATE')) {
self.channel = new Channel(this, message.data['CREATE']); self.channel = new Channel(self, message.data.options);
} else if (message.data.hasOwnProperty('KILL')) { } else if (message.data.hasOwnProperty('KILL')) {
self.channel.destroy(); self.channel.destroy();
process.exit(0);
} else { } else {
self.onMessage(message); self.onMessage(message);
} }
@ -41,6 +40,11 @@ function (Nc, Channel) {
Nc.trigger(message.recipient + '/controlCommand', message); Nc.trigger(message.recipient + '/controlCommand', message);
} }
PipeToServer.prototype.destroy = function() {
this.send('coordinator', {destroy:this.channel.name});
this.process.exit(0);
};
return PipeToServer; return PipeToServer;
}); });

View file

@ -20,7 +20,8 @@ function (ProtocolHelper, GameController, User, Nc, Settings, DomController) {
var self = this; var self = this;
this.socketLink.on('message', function (message) { this.socketLink.on('message', function (message) {
if(Settings.NETWORK_LOG_INCOMING) { var m = JSON.parse(message)
if(Settings.NETWORK_LOG_INCOMING && !m.gameCommand) {
console.log('INCOMING', message); console.log('INCOMING', message);
} }
ProtocolHelper.applyCommand(message, self); ProtocolHelper.applyCommand(message, self);
@ -35,8 +36,13 @@ function (ProtocolHelper, GameController, User, Nc, Settings, DomController) {
Networker.prototype.onConnect = function () { Networker.prototype.onConnect = function () {
console.log('connected.') console.log('connected.')
var channel = JSON.parse(localStorage["channel"]); var channel = JSON.parse(localStorage["channel"]);
var player = JSON.parse(localStorage["player"]);
if(channel.name) { if(channel.name) {
this.sendCommand('join', channel.name); var options = {
channelName: channel.name,
nickname: player.nickname
}
this.sendCommand('join', options);
} else { } else {
window.location.href = "/"; window.location.href = "/";
} }
@ -67,6 +73,11 @@ function (ProtocolHelper, GameController, User, Nc, Settings, DomController) {
this.initPing(); this.initPing();
} }
Networker.prototype.onJoinError = function(options) {
alert(options.message);
window.location.href = "/";
};
Networker.prototype.onLevelLoaded = function() { Networker.prototype.onLevelLoaded = function() {
for (var userId in this.users) { for (var userId in this.users) {
this.gameController.createPlayer(this.users[userId]); this.gameController.createPlayer(this.users[userId]);
@ -116,8 +127,7 @@ function (ProtocolHelper, GameController, User, Nc, Settings, DomController) {
} }
Networker.prototype.onUserLeft = function (userId) { Networker.prototype.onUserLeft = function (userId) {
var user = this.users[userId]; this.gameController.onUserLeft(userId);
this.gameController.onUserLeft(user);
delete this.users[userId]; delete this.users[userId];
} }

View file

@ -62,6 +62,7 @@ define(function() {
// NETWORKING // NETWORKING
WORLD_UPDATE_BROADCAST_INTERVAL: 70, WORLD_UPDATE_BROADCAST_INTERVAL: 70,
CHANNEL_DESTRUCTION_TIME: 30,
NETWORK_LOG_INCOMING: false, NETWORK_LOG_INCOMING: false,
NETWORK_LOG_OUTGOING: false NETWORK_LOG_OUTGOING: false
} }

View file

@ -79,14 +79,18 @@ function (PhysicsEngine, TiledLevel, Player, Nc) {
} }
*/ */
GameController.prototype.onUserLeft = function (user) { GameController.prototype.onUserLeft = function (userId) {
var player = this.players[user.id]; var player = this.players[userId];
if(!player) {
console.warn("User (", userId ,") left who has not joined");
return;
}
var i = this.gameObjects.animated.indexOf(player); var i = this.gameObjects.animated.indexOf(player);
if(i>=0) this.gameObjects.animated.splice(i, 1); if(i>=0) this.gameObjects.animated.splice(i, 1);
player.destroy(); player.destroy();
delete this.players[user.id]; delete this.players[userId];
} }
GameController.prototype.createPlayer = function(user) { GameController.prototype.createPlayer = function(user) {

View file

@ -65,11 +65,8 @@ function () {
} }
} }
}, },
server: { channel: {
pipeToServer: function(v) { return v + "-ns.server.pipeToServer")} pipeToServer: function(v) { return v + "-ns.channel.pipeToServer")}
},
lobby: {
} }
}; };

View file

@ -5,48 +5,65 @@ define([
function (Nc, ProtocolHelper) { function (Nc, ProtocolHelper) {
function Api(coordinator) { function Api(coordinator) {
this.coordinator = coordinator; this.coordinator = coordinator;
this.isError = false; this.isError = false;
this.output = null; this.output = null;
} }
Api.prototype.handleCall = function(queryParameters) { Api.prototype.handleCall = function(queryParameters) {
var command; var command,
try { output = null;
var message = JSON.parse(queryParameters);
command = message.command; try {
} catch(e) { var message = JSON.parse(queryParameters);
console.error(e) command = message.command;
} } catch(e) {
this.isError = true;
output = "JSON syntax error";
console.error(e)
}
var output = null; switch(command) {
switch(command) { case "getChannels":
case "getChannels": output = this.coordinator.getChannels();
output = this.coordinator.getChannels(); break;
break; case "createChannel":
default: // FIXME: sanitize input
this.isError = true; output = this.createChannel(message.options);
output = "Command not found"; break;
break; default:
} this.isError = true;
output = "Command not found";
break;
}
this.output = output; this.output = output;
} }
Api.prototype.getOutput = function() { Api.prototype.createChannel = function(options) {
var output = {}; var result = this.coordinator.createChannel(options);
var key = this.isError ? "error" : "success"; if(result !== false) {
output[key] = this.output; return result;
return JSON.stringify(output); } else {
this.isError = true;
}; return "Could not create channel, name might already exist.";
}
};
Api.prototype.getContentType = function() { Api.prototype.getOutput = function() {
return "application/json"; var output = {};
}; var key = this.isError ? "error" : "success";
output[key] = this.output;
return JSON.stringify(output);
};
Api.prototype.getContentType = function() {
return "application/json";
};
return Api; return Api;
}); });

112
app/Server/Coordinator.js Executable file → Normal file
View file

@ -2,106 +2,64 @@ define([
"Server/User", "Server/User",
"Game/Channel/Channel", "Game/Channel/Channel",
"Server/PipeToChannel", "Server/PipeToChannel",
"Lib/Utilities/NotificationCenter" "Lib/Utilities/NotificationCenter",
"Game/Config/Settings"
], ],
function (User, Channel, PipeToChannel, Nc) { function (User, Channel, PipeToChannel, Nc, Settings) {
function Coordinator () { function Coordinator() {
this.channelPipes = {}; this.channelPipes = {};
this.lobbyUsers = {};
Nc.on('coordinator/message', this.onMessage, this);
console.checkpoint('create Coordinator'); console.checkpoint('create Coordinator');
} }
Coordinator.prototype.createUser = function (socketLink) { Coordinator.prototype.createUser = function (socketLink) {
var user = new User(socketLink, this); new User(socketLink, this);
console.checkpoint('creating user');
this.assignUserToLobby(user);
}
Coordinator.prototype.assignUserToLobby = function (user) {
if(user.channelPipe) {
//user.channel.releaseUser(user); -> generate message
}
this.lobbyUsers[user.id] = user;
console.checkpoint('assign user to lobby');
} }
Coordinator.prototype.assignUserToChannel = function (user, channelName) { Coordinator.prototype.assignUserToChannel = function (user, channelName) {
var channelPipe = this.channelPipes[channelName];
if(user.channelPipe) { user.setChannelPipe(channelPipe);
//user.channel.releaseUser(user); -> generate message
}
if(!Channel.validateName(channelName)) {
//TODO send validation error
return false;
}
var channelPipe = this.channelPipes[channelName];
if(!channelPipe) {
this.createPipe(channelName);
}
//channel.addUser(user);
//user.setChannel(channel);
Nc.trigger('user/joined', user);
delete this.lobbyUsers[user.id];
} }
Coordinator.prototype.removeUser = function (user) { Coordinator.prototype.onDestroyPipe = function(channelName) {
delete this.channelPipes[channelName];
Nc.trigger('user/left', user);
//NotificationCenter.off('channel/' + user.channel.channelName + '/user/' + user.id);
delete this.lobbyUsers[user.id];
} }
Coordinator.prototype.createPipe = function(channelName) {
var channelPipe = new PipeToChannel(channelName);
this.channelPipes[channelName] = channelPipe;
Nc.on('channel/' + channelName + '/message', function (data) {
channelPipe.send('channel', data);
}, this);
// sending info to user
Nc.on('user/joined', function (user) {
/*
Nc.on('channel/' + channelName + '/user/' + user.id, function (recipient, data) {
channelPipe.send(recipient, data);
}, this);
*/
channelPipe.send('channel', { addUser: user.id });
}, this);
Nc.on('user/left', function (user) {
channelPipe.send('channel', { releaseUser: user.id });
}, this);
Nc.on('user/controlCommand', function (userId, data) {
channelPipe.sendToUser(userId, data);
}, this);
return channelPipe;
};
Coordinator.prototype.getChannels = function(options) { Coordinator.prototype.getChannels = function(options) {
var list = []; var list = [];
for (var channelName in this.channelPipes) { for (var channelName in this.channelPipes) {
list.push({ list.push({
name: channelName name: channelName
}); });
} }
return list; return list;
}
Coordinator.prototype.createChannel = function(options) {
if(this.channelPipes[options.channelName]) {
return false;
}
var channelPipe = new PipeToChannel(options);
this.channelPipes[options.channelName] = channelPipe;
return {
channelName: options.channelName,
link: "#" + options.channelName,
timeout: Settings.CHANNEL_DESTRUCTION_TIME
}
}; };
return Coordinator; Coordinator.prototype.onMessage = function(message) {
if(message.destroy) {
delete this.channelPipes[message.destroy];
}
};
return Coordinator;
}); });

View file

@ -7,21 +7,21 @@ function (Nc, childProcess) {
var fork = childProcess.fork; var fork = childProcess.fork;
function PipeToChannel (channelName) { function PipeToChannel (options) {
this.channelPipe = null; this.fork = null;
try { try {
this.channelPipe = fork('channel.js'); this.fork = fork('channel.js');
} catch (err) { } catch (err) {
throw 'Failed to fork channel! (' + err + ')'; throw 'Failed to fork channel! (' + err + ')';
} }
console.checkpoint('creating channel process for ' + channelName); console.checkpoint('creating channel process for ' + options.channelName);
this.send('channel/' + channelName, { CREATE: channelName }); this.send('channel/' + options.channelName, { CREATE: true, options: options });
this.channelPipe.on('message', this.onMessage.bind(this)); this.fork.on('message', this.onMessage.bind(this));
var self = this; var self = this;
} }
@ -33,7 +33,7 @@ function (Nc, childProcess) {
data: data data: data
} }
this.channelPipe.send(message); this.fork.send(message);
} }
// If user already created // If user already created
@ -43,7 +43,7 @@ function (Nc, childProcess) {
data: data data: data
} }
this.channelPipe.send(message); this.fork.send(message);
} }
PipeToChannel.prototype.onMessage = function (message) { PipeToChannel.prototype.onMessage = function (message) {

135
app/Server/User.js Executable file → Normal file
View file

@ -1,60 +1,77 @@
define([ define([
"Game/Core/User", "Game/Core/User",
"Lib/Utilities/Protocol/Helper", "Lib/Utilities/Protocol/Helper",
"Lib/Utilities/NotificationCenter" "Lib/Utilities/NotificationCenter"
], ],
function (Parent, ProtocolHelper, Nc) { function (Parent, ProtocolHelper, Nc) {
function User (socketLink, coordinator) { function User (socketLink, coordinator) {
Parent.call(this, socketLink.id); Parent.call(this, socketLink.id);
this.coordinator = coordinator; this.coordinator = coordinator;
this.channelProcess = null; this.socketLink = socketLink;
this.socketLink = socketLink; this.channelPipe = null;
socketLink.on('message', this.onMessage.bind(this)); socketLink.on('message', this.onMessage.bind(this));
socketLink.on('disconnect', this.onDisconnect.bind(this)); socketLink.on('disconnect', this.onDisconnect.bind(this));
Nc.on("user/" + this.socketLink.id + "/message", this.socketLink.send, this.socketLink); Nc.on("user/" + this.socketLink.id + "/message", this.socketLink.send, this.socketLink);
} }
User.prototype = Object.create(Parent.prototype); User.prototype = Object.create(Parent.prototype);
User.prototype.setChannelPipe = function(channelPipe) {
// Socket callbacks if(channelPipe) {
this.channelPipe = channelPipe;
User.prototype.onMessage = function (message) { } else {
ProtocolHelper.applyCommand(message, this); var message = ProtocolHelper.encodeCommand("joinError", {message:"Channel not found"});
} this.socketLink.send(message);
}
User.prototype.onDisconnect = function () { };
this.coordinator.removeUser(this);
}
// Socket callbacks
// User command callbacks User.prototype.onMessage = function (message) {
// Remember: control commands are coordinator relevant commands ProtocolHelper.applyCommand(message, this);
}
User.prototype.onJoin = function(options) {
this.coordinator.assignUserToChannel(this, options); User.prototype.onDisconnect = function () {
}; if(!this.channelPipe) {
console.warn("Disconnecting user without a channel.");
User.prototype.onLeave = function(options) { return;
this.coordinator.assignUserToLobby(this); }
};
this.channelPipe.send('channel', { releaseUser: this.id });
User.prototype.onGameCommand = function(options) { }
// repacking for transport via pipe
var message = ProtocolHelper.encodeCommand("gameCommand", options);
Nc.trigger("user/controlCommand", this.id, message); // User command callbacks
}; // Remember: control commands are coordinator relevant commands
User.prototype.onPing = function(timestamp) { User.prototype.onJoin = function(options) {
var message = ProtocolHelper.encodeCommand("pong", timestamp); this.coordinator.assignUserToChannel(this, options.channelName);
Nc.trigger("user/" + this.socketLink.id + "/message", message);
}; if(!this.channelPipe) {
console.warn("Can not join user because channel (" + options.channelName + ") does not exist.")
return User; return;
}
this.channelPipe.send('channel', { addUser: this.id });
};
User.prototype.onGameCommand = function(options) {
// repacking for transport via pipe
var message = ProtocolHelper.encodeCommand("gameCommand", options);
this.channelPipe.sendToUser(this.id, message);
};
User.prototype.onPing = function(timestamp) {
var message = ProtocolHelper.encodeCommand("pong", timestamp);
Nc.trigger("user/" + this.socketLink.id + "/message", message);
};
return User;
}); });

View file

@ -2,42 +2,92 @@
<html> <html>
<head> <head>
<title>Chuck Lobby</title> <title>Chuck Lobby</title>
<style type="text/css">
body { background: #222; color: #ccc; font-family: "Lucida Grande", sans-serif; }
input, button { background: black; color: #ccc; border: 0; padding: 0.3em 0.6em; font-size: 1em; }
#createform, #customjoinform { display: none; }
article { margin: 4em auto; background: #1a1a1a; padding: 2em; max-width: 30em; }
table, th, td { border: 1px solid #777; border-collapse: collapse; }
th, td { padding: 0.5em 1em; }
ul { list-style-type: none; padding-left: 0; }
#link { width: 100%; }
.offline { background: #ccc; }
</style>
</head> </head>
<body> <body>
<form action="game.html" method="POST"> <article>
<h1>Chuck</h1>
<p> <p>
<label> <label>
Nickname:<br> Nickname:<br>
<input id="nick" name="nick" type="text"> <input id="nick" name="nick" type="text">
</label> </label>
</p> </p>
<table> <form action="#" id="createform">
<thead> <div>
<tr><th>Name</th></tr> <h2>Create your own!</h2>
</thead> <p><label>Name:<br> <input id="customname"></label></p>
<tfoot> <fieldset>
<tr> <legend>Maps</legend>
<td> <ul>
<label> <li>
<input type="radio" name="channel" id="radiochannel" checked> <label><input name="maps" value="debug" type="checkbox" checked> Debug</label>
custom channel: <input id="customname"> </li>
</label> <li>
</td> <label><input name="maps" value="stones2" type="checkbox" checked> Stones2</label>
</tr> </li>
</tfoot> </ul>
<tbody id="list"></tbody> </fieldset>
</table>
<p> <p>
<button>Join</button> <button>Run</button>
<button id="refresh">Refresh list</button> <button onclick="show('#listform'); return false;">Cancel</button>
</p> </p>
</form> </div>
</form>
<form action="game.html" method="GET" id="customjoinform">
<p>
<label>
Link to share with your friends<br>
<input id="link">
</label>
</p>
<p>
<button>Join</button>
<span id="timeout"></span>
</p>
</form>
<form action="game.html" method="GET" id="listform">
<h2>Channel list</h2>
<table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
</tr>
</thead>
<tbody id="list"></tbody>
</table>
<p>
<button>Join</button>
<button id="refresh">Refresh list</button>
<button onclick="show('#createform'); return false;">Create</button>
</p>
</form>
</article>
<script> <script>
function $(selector) { function $(selector) {
return document.querySelector(selector); return document.querySelector(selector);
} }
function $$(selector) {
return document.querySelectorAll(selector);
}
if(localStorage["player"]) { if(localStorage["player"]) {
var player = JSON.parse(localStorage["player"]); var player = JSON.parse(localStorage["player"]);
if(player.nickname) { if(player.nickname) {
@ -45,34 +95,29 @@
} }
} }
$("#customname").onfocus = function() { if(localStorage["customname"]) {
$("#radiochannel").checked = true; $("#customname").value = localStorage["customname"];
}
if(localStorage["channel"]) {
var channel = JSON.parse(localStorage["channel"])
if(channel.customname) {
$("#customname").value = channel.customname;
}
}
var hash = window.location.hash.split("#").join("");
if(hash) {
$("#customname").value = hash;
$("#radiochannel").checked = true;
} }
var lastRefreshResponse;
function refresh() { function refresh() {
$("#list").innerHTML = "";
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() { xhr.onreadystatechange = function() {
if(xhr.readyState == 4) { if(xhr.readyState == 4) {
if(xhr.status == 200) { if(xhr.status == 200) {
populate(JSON.parse(xhr.responseText).success);
} else { var response = xhr.responseText;
console.error("Ajax error: " + xhr.status + " " + xhr.statusText) if(response != lastRefreshResponse) {
} lastRefreshResponse = response;
populate(JSON.parse(response).success);
}
document.body.className = "";
} else {
console.error("Ajax error: " + xhr.status + " " + xhr.statusText)
$("#list").innerHTML = "";
document.body.className = "offline";
} }
}
} }
xhr.open("POST", "/api", true); xhr.open("POST", "/api", true);
xhr.send(JSON.stringify({command:"getChannels"})); xhr.send(JSON.stringify({command:"getChannels"}));
@ -81,57 +126,156 @@
function populate(list) { function populate(list) {
var html = ""; var html = "";
for (var i = 0; i < list.length; i++) { if(list.length > 0) {
var channel = list[i]; for (var i = 0; i < list.length; i++) {
html += "<tr><td><label>"; var channel = list[i];
html += "<input name='channel' type='radio' value='" + channel.name + "'" html += "<tr><td><label>";
if(!hash && i == 0) html += " checked" html += "<input name='channel' type='radio' value='" + channel.name + "'"
html += "> "; if(i == 0) html += " checked"
html += channel.name html += "> ";
html += "</label></td></tr>"; html += channel.name
}; html += "</label></td><td></td></tr>";
};
} else {
html += "<tr><td colspan='2'>No channels found.</td></tr>";
}
$("#list").innerHTML = html; $("#list").innerHTML = html;
} }
$("form").onsubmit = function(e) { $("form#listform").onsubmit = function(e) {
var nickname = $("#nick").value; var nickname = $("#nick").value;
if(!nickname || nickname.length < 3) { if(!nickname || nickname.length < 3) {
alert("nickname too short") alert("nickname too short")
return false; return false;
} }
localStorage["player"] = JSON.stringify({nickname: nickname}); localStorage["player"] = JSON.stringify({nickname: nickname});
if ($("#radiochannel").checked) { var radios = document.querySelectorAll("form#listform input[name=channel]");
var name = $("#customname").value; for (var i = 0; i < radios.length; i++) {
if(name) { var radio = radios[i];
$("#radiochannel").value = name; if(radio.checked) {
} else { name = radio.value;
alert("custom channel empty") break;
return false; }
} };
} else {
var radios = document.querySelectorAll("form input[name=channel]");
for (var i = 0; i < radios.length; i++) {
var radio = radios[i];
if(radio.checked) {
name = radio.value;
break;
}
};
}
if(name) { if(name) {
localStorage["channel"] = JSON.stringify({ localStorage["channel"] = JSON.stringify({
name: name, name: name
customname: $("#customname").value
}); });
window.location.href = "/game.html";
return false;
} else { } else {
alert("no channel selected") alert("No channel selected")
return false; return false;
} }
} }
$("form#createform").onsubmit = function(e) {
var maps = [];
var checkboxes = document.querySelectorAll("form#createform input[name=maps]");
for (var i = 0; i < checkboxes.length; i++) {
var checkbox = checkboxes[i];
if(checkbox.checked) {
maps.push(checkbox.value);
}
};
if(maps.length == 0) {
alert("Please choose at least one map.")
return false;
}
var name = $("#customname").value;
if(!name) {
alert("Please provide a channel name.")
return false;
}
localStorage["customname"] = name;
var options = {
channelName: name,
maps: maps,
maxUsers: 10,
minUsers: 2
}
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if(xhr.readyState == 4) {
if(xhr.status == 200) {
onCreateSuccess(JSON.parse(xhr.responseText).success);
} else {
console.log(xhr.responseText)
alert(JSON.parse(xhr.responseText).error)
}
}
}
xhr.open("POST", "/api", true);
xhr.send(JSON.stringify({command:"createChannel", options: options}));
return false;
}
$("form#customjoinform").onsubmit = function(e) {
var nickname = $("#nick").value;
if(!nickname || nickname.length < 3) {
alert("nickname too short")
return false;
}
localStorage["player"] = JSON.stringify({nickname: nickname});
var name = $("form#createform input[name=channel]").value;
localStorage["channel"] = JSON.stringify({
name: name
});
window.location.href = "/game.html";
return false;
}
function onCreateSuccess(options) {
$("#customname").value = options.channelName;
$("#link").value = window.location.href + options.link;
show("#customjoinform");
startTimer(options.timeout);
}
function show(id) {
$("#createform").style.display = "none";
$("#listform").style.display = "none";
$("#customjoinform").style.display = "none";
$(id).style.display = "block";
}
function startTimer(seconds) {
var now = new Date();
var end = new Date(now.getTime() + seconds * 1000);
setInterval(function() {
now = new Date();
var diff = new Date(end.getTime() - now.getTime());
if(diff.getTime() < 0) {
alert("Your channel has timed out.");
window.location.href = "/";
} else {
$("#timeout").innerHTML = " within " + formatDate(diff) + " minutes";
}
}, 1000);
}
function formatDate(date) {
var minutes = date.getMinutes();
var seconds = date.getSeconds();
if(minutes < 10) minutes = "0" + minutes;
if(seconds < 10) seconds = "0" + seconds;
return minutes + ":" + seconds;
}
$("#refresh").onclick = refresh; $("#refresh").onclick = refresh;
refresh(); refresh();
setInterval(refresh, 5000);
</script> </script>
</body> </body>
</html> </html>