This commit is contained in:
logsol 2014-03-02 21:50:10 +01:00
commit d0e9f0d458
37 changed files with 643 additions and 393 deletions

1
.gitignore vendored
View file

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

View file

@ -1,7 +1,7 @@
define([
'http',
'node-static',
'Lobby/Api'
'Server/Api'
],
function (http, nodeStatic, Api) {

View file

@ -1,7 +1,7 @@
define([
"Game/Server/GameController",
"Game/Channel/GameController",
"Lib/Utilities/NotificationCenter",
"Game/Server/User",
"Game/Channel/User",
"Lib/Utilities/Protocol/Helper",
"Lib/Utilities/Options",
"Game/Config/Settings"
@ -9,7 +9,7 @@
function (GameController, Nc, User, ProtocolHelper, Options, Settings) {
function Channel (pipeToLobby, name, options) {
function Channel (pipeToServer, options) {
var self = this;
@ -17,10 +17,10 @@
levelUids: Settings.DEFAULT_LEVELS
});
this.name = name;
this.name = options.channelName;
this.users = {};
this.pipeToLobby = pipeToLobby;
this.pipeToServer = pipeToServer;
this.gameController = new GameController(this);
@ -34,32 +34,38 @@
Nc.on('broadcastGameCommand', this.broadcastGameCommand, this);
Nc.on('broadcastGameCommandExcept', this.broadcastGameCommandExcept, this);
console.checkpoint('channel ' + name + ' created');
}
console.checkpoint('channel ' + this.name + ' created');
Channel.validateName = function (name) {
return true;
setTimeout(function() {
if(Object.keys(self.users).length < 1) {
self.destroy();
}
}, Settings.CHANNEL_DESTRUCTION_TIME * 1000);
}
// Channel command callbacks
Channel.prototype.onAddUser = function (userId) {
Channel.prototype.onAddUser = function (options) {
var self = this;
if(!this.gameController.level || !this.gameController.level.isLoaded) {
var token = Nc.on("game/level/loaded", function() {
self.sendJoinSuccess(userId);
self.sendJoinSuccess(options);
Nc.off(token);
});
} else {
self.sendJoinSuccess(userId);
self.sendJoinSuccess(options);
}
}
Channel.prototype.sendJoinSuccess = function(userId) {
var user = new User(userId, this);
var joinedUsers = Object.keys(this.users);
Channel.prototype.sendJoinSuccess = function(options) {
var user = new User(options.id, options);
var joinedUsers = [];
for(var userId in this.users) {
joinedUsers.push(this.users[userId].options)
}
var levelUid = null;
if(this.gameController.level) {
@ -69,22 +75,35 @@
this.users[user.id] = user;
var options = {
userId: user.id,
channelName: this.name,
user: user.options,
joinedUsers: joinedUsers,
levelUid: levelUid
};
Nc.trigger('user/' + user.id + "/joinSuccess", options);
//Nc.trigger('user/' + user.id + "/joinSuccess", options);
user.sendControlCommand("joinSuccess", options);
Nc.trigger('user/joined', user);
this.broadcastControlCommandExcept("userJoined", user.options, user);
};
Channel.prototype.onReleaseUser = function (userId) {
var user = this.users[userId];
this.broadcastControlCommandExcept("userLeft", user.id, user);
Nc.trigger('user/left', user);
delete this.users[user.id];
Nc.trigger('user/left', userId);
delete this.users[userId];
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

View file

@ -1,15 +1,15 @@
define([
"Game/Core/GameController",
"Game/Server/Physics/Engine",
"Game/Channel/Physics/Engine",
"Game/Config/Settings",
"Game/Server/Control/PlayerController",
"Game/Channel/Control/PlayerController",
"Lib/Utilities/RequestAnimFrame",
"Lib/Utilities/NotificationCenter",
"Lib/Vendor/Box2D",
"Game/Server/Player",
"Game/Server/GameObjects/GameObject",
"Game/Server/GameObjects/Doll",
"Game/Server/GameObjects/Items/RagDoll"
"Game/Channel/Player",
"Game/Channel/GameObjects/GameObject",
"Game/Channel/GameObjects/Doll",
"Game/Channel/GameObjects/Items/RagDoll"
],
function (Parent, PhysicsEngine, Settings, PlayerController, requestAnimFrame, Nc, Box2D, Player, GameObject, Doll, RagDoll) {
@ -21,7 +21,7 @@ function (Parent, PhysicsEngine, Settings, PlayerController, requestAnimFrame, N
Parent.call(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/clientReady', this.onClientReady, this);
Nc.on('player/killed', this.onPlayerKilled, this);
@ -88,15 +88,6 @@ function (Parent, PhysicsEngine, Settings, PlayerController, requestAnimFrame, N
}, respawnTime * 1000);
};
/*
GameController.prototype.createPlayer = function(user) {
var player = new Player(user.id, this.physicsEngine);
player.setPlayerController(new PlayerController(player))
return player;
};
*/
GameController.prototype.updateWorld = function () {
var update = this.getWorldUpdateObject(false);

View file

@ -1,6 +1,6 @@
define([
"Game/Core/GameObjects/Doll",
"Game/Server/GameObjects/Item",
"Game/Channel/GameObjects/Item",
"Lib/Vendor/Box2D",
"Lib/Utilities/NotificationCenter"
],

View file

@ -1,11 +1,11 @@
define([
"Lib/Utilities/NotificationCenter",
"Game/Server/Channel"
"Game/Channel/Channel"
],
function (Nc, Channel) {
function PipeToLobby (process) {
function PipeToServer (process) {
var self = this;
@ -17,10 +17,9 @@ function (Nc, Channel) {
process.on('message', function (message, handle) {
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')) {
self.channel.destroy();
process.exit(0);
} else {
self.onMessage(message);
}
@ -28,7 +27,7 @@ function (Nc, Channel) {
});
}
PipeToLobby.prototype.send = function (recipient, data) {
PipeToServer.prototype.send = function (recipient, data) {
var message = {
recipient: recipient,
data: data
@ -37,10 +36,15 @@ function (Nc, Channel) {
this.process.send(message);
};
PipeToLobby.prototype.onMessage = function (message) {
PipeToServer.prototype.onMessage = function (message) {
Nc.trigger(message.recipient + '/controlCommand', message);
}
return PipeToLobby;
PipeToServer.prototype.destroy = function() {
this.send('coordinator', {destroy:this.channel.name});
this.process.exit(0);
};
return PipeToServer;
});

View file

@ -5,8 +5,8 @@ define([
function (Parent, Nc) {
function Player(id, physicsEngine) {
Parent.call(this, id, physicsEngine);
function Player(id, physicsEngine, user) {
Parent.call(this, id, physicsEngine, user);
}
Player.prototype = Object.create(Parent.prototype);

View file

@ -7,20 +7,13 @@ define([
function(Parent, Nc, ProtocolHelper, ProtocolParser) {
function User(id, channel) {
Parent.call(this, id);
function User(id, options) {
Parent.call(this, id, options);
this.channel = channel;
this.player = null;
this.isReady = false;
var self = this;
Nc.on('user/joined', function(user) { // FIXME: use sendToAllUsersExcept instead
if(user.id != self.id) {
self.sendControlCommand("userJoined", user.id);
}
});
Nc.on('user/' + this.id + "/joinSuccess", function(options) {
self.sendControlCommand("joinSuccess", options);
});

View file

@ -208,10 +208,12 @@ function (Parent, Box2D, PhysicsEngine, ViewManager, PlayerController, Nc, reque
};
var sortedPlayers = playersArray.sort(function(a,b) {
if(a.score > b.score) return 1;
if(a.score < b.score) return -1;
if(a.deaths < b.deaths) return 1;
if(a.deaths > b.deaths) return -1;
if(a.stats.score > b.stats.score) return -1;
if(a.stats.score < b.stats.score) return 1;
if(a.stats.deaths < b.stats.deaths) return -1;
if(a.stats.deaths > b.stats.deaths) return 1;
if(a.stats.health > b.stats.health) return -1;
if(a.stats.health < b.stats.health) return 1;
return 0;
});
@ -236,7 +238,7 @@ function (Parent, Box2D, PhysicsEngine, ViewManager, PlayerController, Nc, reque
var lines = [];
sortedPlayers.forEach(function(player, i) {
var name = player == this.me ? "You" : "Player " + (Object.keys(this.players).indexOf(player.id) + 1);
var name = player.getNickname();
lines.push(
pad("" + (i + 1) + ".", 2, false) + " " +
pad(name, 12, true) +

View file

@ -13,14 +13,14 @@ function (ProtocolHelper, GameController, User, Nc, Settings, DomController) {
this.socketLink = socketLink;
this.gameController = null;
this.users = {};
this.userId = null;
this.socketLink.on('connect', this.onConnect.bind(this));
this.socketLink.on('disconnect', this.onDisconnect.bind(this));
var self = this;
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);
}
ProtocolHelper.applyCommand(message, self);
@ -35,8 +35,13 @@ function (ProtocolHelper, GameController, User, Nc, Settings, DomController) {
Networker.prototype.onConnect = function () {
console.log('connected.')
var channel = JSON.parse(localStorage["channel"]);
var player = JSON.parse(localStorage["player"]);
if(channel.name) {
this.sendCommand('join', channel.name);
var options = {
channelName: channel.name,
nickname: player.nickname
}
this.sendCommand('join', options);
} else {
window.location.href = "/";
}
@ -51,12 +56,11 @@ function (ProtocolHelper, GameController, User, Nc, Settings, DomController) {
Networker.prototype.onJoinSuccess = function (options) {
console.log("join success")
this.userId = options.userId;
this.gameController = new GameController();
this.gameController.loadLevel(options.levelUid);
this.onUserJoined(options.userId);
this.onUserJoined(options.user);
if (options.joinedUsers) {
for(var i = 0; i < options.joinedUsers.length; i++) {
@ -67,6 +71,11 @@ function (ProtocolHelper, GameController, User, Nc, Settings, DomController) {
this.initPing();
}
Networker.prototype.onJoinError = function(options) {
alert(options.message);
window.location.href = "/";
};
Networker.prototype.onLevelLoaded = function() {
for (var userId in this.users) {
this.gameController.createPlayer(this.users[userId]);
@ -103,9 +112,10 @@ function (ProtocolHelper, GameController, User, Nc, Settings, DomController) {
// Commands from server
Networker.prototype.onUserJoined = function (userId) {
var user = new User(userId);
this.users[userId] = user;
Networker.prototype.onUserJoined = function (options) {
var user = new User(options.id, options);
console.log(options.nickname)
this.users[user.id] = user;
if (this.gameController
&& this.gameController.level
@ -116,8 +126,7 @@ function (ProtocolHelper, GameController, User, Nc, Settings, DomController) {
}
Networker.prototype.onUserLeft = function (userId) {
var user = this.users[userId];
this.gameController.onUserLeft(user);
this.gameController.onUserLeft(userId);
delete this.users[userId];
}

View file

@ -6,8 +6,8 @@ define([
function (Parent, Nc, Settings) {
function Player(id, physicsEngine) {
Parent.call(this, id, physicsEngine);
function Player(id, physicsEngine, user) {
Parent.call(this, id, physicsEngine, user);
this.playerInfoView = null;
this.playerInfoViewVisibleTimeout = null;
@ -81,6 +81,10 @@ function (Parent, Nc, Settings) {
}
};
Player.prototype.getNickname = function() {
return this.user.options.nickname;
};
Player.prototype.render = function() {
// dolls are self responsible

View file

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

View file

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

View file

@ -10,13 +10,14 @@ define([
function (Doll, Settings, Nc, Exception, SpectatorDoll, RagDoll) {
function Player (id, physicsEngine) {
function Player (id, physicsEngine, user) {
this.stats = {
health: 100,
deaths: 0,
score: 0
}
this.user = user;
this.physicsEngine = physicsEngine;
this.playerController = null;
this.doll;

View file

@ -1,7 +1,13 @@
define(function () {
function User (id) {
function User (id, options) {
this.id = id;
this.options = options;
this.options.id = this.id;
if(!this.options.nickname) {
this.options.nickname = this.id;
}
}
return User;

View file

@ -8,7 +8,7 @@ function() {
return f + this.substr(1);
}
if(GLOBALS.context == "Server") {
if(GLOBALS.context == "Channel") {
console.checkpoint = function (s) {
console.log(' \033[32mbeep - \033[0m' + s);

View file

@ -6,6 +6,71 @@ function () {
function NotificationCenter () {
this.topics = {};
this.subUid = -1;
/*
var i = 0;
this.nc = {
client: {
view: {
mesh: {
create: i++,
add: i++,
remove: i++,
update: i++
},
playerInfo: {
createAndAdd: i++,
remove: i++,
update: i++
},
preloadBar: {
update: i++
},
fullScreen: {
change: i++
},
debugMode: {
toggle: i++
},
gameInfo: {
toggle: i++
}
events: {
ready: i++
}
},
input: {
handAction: {
request: i++
},
xy: {
change: i++
}
},
server: {
gameCommand: {
send: i++
}
}
},
core: {
game: {
gameObject: {
add: i++,
remove: i++
}
events: {
level: {
loaded: i++
}
}
}
},
channel: {
pipeToServer: function(v) { return v + "-ns.channel.pipeToServer")}
}
};
*/
}
NotificationCenter.prototype.trigger = function (topic /*, arguments*/) {

View file

@ -1,52 +0,0 @@
define([
"Lib/Utilities/NotificationCenter",
"Lib/Utilities/Protocol/Helper"
],
function (Nc, ProtocolHelper) {
function Api(coordinator) {
this.coordinator = coordinator;
this.isError = false;
this.output = null;
}
Api.prototype.handleCall = function(queryParameters) {
var command;
try {
var message = JSON.parse(queryParameters);
command = message.command;
} catch(e) {
console.error(e)
}
var output = null;
switch(command) {
case "getChannels":
output = this.coordinator.getChannels();
break;
default:
this.isError = true;
output = "Command not found";
break;
}
this.output = output;
}
Api.prototype.getOutput = function() {
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;
});

View file

@ -1,107 +0,0 @@
define([
"Lobby/User",
"Game/Server/Channel",
"Lobby/PipeToChannel",
"Lib/Utilities/NotificationCenter"
],
function (User, Channel, PipeToChannel, Nc) {
function Coordinator () {
this.channelPipes = {};
this.lobbyUsers = {};
console.checkpoint('create Coordinator');
}
Coordinator.prototype.createUser = function (socketLink) {
var user = 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) {
if(user.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) {
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) {
var list = [];
for (var channelName in this.channelPipes) {
list.push({
name: channelName
});
}
return list;
};
return Coordinator;
});

69
app/Server/Api.js Normal file
View file

@ -0,0 +1,69 @@
define([
"Lib/Utilities/NotificationCenter",
"Lib/Utilities/Protocol/Helper"
],
function (Nc, ProtocolHelper) {
function Api(coordinator) {
this.coordinator = coordinator;
this.isError = false;
this.output = null;
}
Api.prototype.handleCall = function(queryParameters) {
var command,
output = null;
try {
var message = JSON.parse(queryParameters);
command = message.command;
} catch(e) {
this.isError = true;
output = "JSON syntax error";
console.error(e)
}
switch(command) {
case "getChannels":
output = this.coordinator.getChannels();
break;
case "createChannel":
// FIXME: sanitize input
output = this.createChannel(message.options);
break;
default:
this.isError = true;
output = "Command not found";
break;
}
this.output = output;
}
Api.prototype.createChannel = function(options) {
var result = this.coordinator.createChannel(options);
if(result !== false) {
return result;
} else {
this.isError = true;
return "Could not create channel, name might already exist.";
}
};
Api.prototype.getOutput = function() {
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;
});

65
app/Server/Coordinator.js Normal file
View file

@ -0,0 +1,65 @@
define([
"Server/User",
"Game/Channel/Channel",
"Server/PipeToChannel",
"Lib/Utilities/NotificationCenter",
"Game/Config/Settings"
],
function (User, Channel, PipeToChannel, Nc, Settings) {
function Coordinator() {
this.channelPipes = {};
Nc.on('coordinator/message', this.onMessage, this);
console.checkpoint('create Coordinator');
}
Coordinator.prototype.createUser = function (socketLink) {
new User(socketLink, this);
}
Coordinator.prototype.assignUserToChannel = function (user, channelName) {
var channelPipe = this.channelPipes[channelName];
user.setChannelPipe(channelPipe);
}
Coordinator.prototype.onDestroyPipe = function(channelName) {
delete this.channelPipes[channelName];
}
Coordinator.prototype.getChannels = function(options) {
var list = [];
for (var channelName in this.channelPipes) {
list.push({
name: channelName
});
}
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
}
};
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;
function PipeToChannel (channelName) {
function PipeToChannel (options) {
this.channelPipe = null;
this.fork = null;
try {
this.channelPipe = fork('channel.js');
this.fork = fork('channel.js');
} catch (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;
}
@ -33,7 +33,7 @@ function (Nc, childProcess) {
data: data
}
this.channelPipe.send(message);
this.fork.send(message);
}
// If user already created
@ -43,7 +43,7 @@ function (Nc, childProcess) {
data: data
}
this.channelPipe.send(message);
this.fork.send(message);
}
PipeToChannel.prototype.onMessage = function (message) {

37
app/Lobby/User.js → app/Server/User.js Executable file → Normal file
View file

@ -7,11 +7,11 @@ define([
function (Parent, ProtocolHelper, Nc) {
function User (socketLink, coordinator) {
Parent.call(this, socketLink.id);
Parent.call(this, socketLink.id, {});
this.coordinator = coordinator;
this.channelProcess = null;
this.socketLink = socketLink;
this.channelPipe = null;
socketLink.on('message', this.onMessage.bind(this));
socketLink.on('disconnect', this.onDisconnect.bind(this));
@ -21,6 +21,15 @@ function (Parent, ProtocolHelper, Nc) {
User.prototype = Object.create(Parent.prototype);
User.prototype.setChannelPipe = function(channelPipe) {
if(channelPipe) {
this.channelPipe = channelPipe;
} else {
var message = ProtocolHelper.encodeCommand("joinError", {message:"Channel not found"});
this.socketLink.send(message);
}
};
// Socket callbacks
@ -29,7 +38,12 @@ function (Parent, ProtocolHelper, Nc) {
}
User.prototype.onDisconnect = function () {
this.coordinator.removeUser(this);
if(!this.channelPipe) {
console.warn("Disconnecting user without a channel.");
return;
}
this.channelPipe.send('channel', { releaseUser: this.id });
}
@ -37,17 +51,24 @@ function (Parent, ProtocolHelper, Nc) {
// Remember: control commands are coordinator relevant commands
User.prototype.onJoin = function(options) {
this.coordinator.assignUserToChannel(this, options);
};
this.coordinator.assignUserToChannel(this, options.channelName);
User.prototype.onLeave = function(options) {
this.coordinator.assignUserToLobby(this);
if(!this.channelPipe) {
console.warn("Can not join user because channel (" + options.channelName + ") does not exist.")
return;
}
var userOptions = {
id: this.id,
nickname: options.nickname
}
this.channelPipe.send('channel', { addUser: userOptions });
};
User.prototype.onGameCommand = function(options) {
// repacking for transport via pipe
var message = ProtocolHelper.encodeCommand("gameCommand", options);
Nc.trigger("user/controlCommand", this.id, message);
this.channelPipe.sendToUser(this.id, message);
};
User.prototype.onPing = function(timestamp) {

View file

@ -1,4 +1,4 @@
GLOBALS = { context: "Server" };
GLOBALS = { context: "Channel" };
var requirejs = require('requirejs');
requirejs.config({
@ -10,11 +10,11 @@ requirejs.config({
var inspector = {};
requirejs([
"Game/Server/PipeToLobby"
"Game/Channel/PipeToServer"
],
function (PipeToLobby) {
var PipeToLobby = new PipeToLobby(process);
function (PipeToServer) {
var PipeToServer = new PipeToServer(process);
inspector.PipeToLobby = PipeToLobby;
inspector.PipeToServer = PipeToServer;
});

View file

@ -1,4 +1,4 @@
GLOBALS = { context: "Server" };
GLOBALS = { context: "Channel" };
var requirejs = require('requirejs');
var inspector;
@ -23,7 +23,7 @@ var options = {
requirejs([
"Bootstrap/HttpServer",
"Bootstrap/Socket",
"Lobby/Coordinator"
"Server/Coordinator"
],
function (HttpServer, Socket, Coordinator) {

View file

@ -2,9 +2,21 @@
<html>
<head>
<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>
<body>
<form action="game.html" method="POST">
<article>
<h1>Chuck</h1>
<p>
<label>
Nickname:<br>
@ -12,32 +24,70 @@
</label>
</p>
<form action="#" id="createform">
<div>
<h2>Create your own!</h2>
<p><label>Name:<br> <input id="customname"></label></p>
<fieldset>
<legend>Maps</legend>
<ul>
<li>
<label><input name="maps" value="debug" type="checkbox" checked> Debug</label>
</li>
<li>
<label><input name="maps" value="stones2" type="checkbox" checked> Stones2</label>
</li>
</ul>
</fieldset>
<p>
<button>Run</button>
<button onclick="show('#listform'); return false;">Cancel</button>
</p>
</div>
</form>
<form action="#" 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="#" method="GET" id="listform">
<h2>Channel list</h2>
<table>
<thead>
<tr><th>Name</th></tr>
</thead>
<tfoot>
<tr>
<td>
<label>
<input type="radio" name="channel" id="radiochannel" checked>
custom channel: <input id="customname">
</label>
</td>
<th>Name</th>
<th>Type</th>
</tr>
</tfoot>
</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>
</form>
</article>
<script>
function $(selector) {
return document.querySelector(selector);
}
function $$(selector) {
return document.querySelectorAll(selector);
}
if(localStorage["player"]) {
var player = JSON.parse(localStorage["player"]);
if(player.nickname) {
@ -45,32 +95,27 @@
}
}
$("#customname").onfocus = function() {
$("#radiochannel").checked = true;
}
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;
if(localStorage["customname"]) {
$("#customname").value = localStorage["customname"];
}
var lastRefreshResponse;
function refresh() {
$("#list").innerHTML = "";
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if(xhr.readyState == 4) {
if(xhr.status == 200) {
populate(JSON.parse(xhr.responseText).success);
var response = xhr.responseText;
if(response != lastRefreshResponse) {
lastRefreshResponse = response;
populate(JSON.parse(response).success);
}
document.body.className = "";
} else {
console.error("Ajax error: " + xhr.status + " " + xhr.statusText)
console.error("Ajax error: " + xhr.status + " " + xhr.responseText)
$("#list").innerHTML = "";
document.body.className = "offline";
}
}
}
@ -81,35 +126,31 @@
function populate(list) {
var html = "";
if(list.length > 0) {
for (var i = 0; i < list.length; i++) {
var channel = list[i];
html += "<tr><td><label>";
html += "<input name='channel' type='radio' value='" + channel.name + "'"
if(!hash && i == 0) html += " checked"
if(i == 0) html += " checked"
html += "> ";
html += channel.name
html += "</label></td></tr>";
html += "</label></td><td></td></tr>";
};
} else {
html += "<tr><td colspan='2'>No channels found.</td></tr>";
}
$("#list").innerHTML = html;
}
$("form").onsubmit = function(e) {
$("form#listform").onsubmit = function(e) {
var nickname = $("#nick").value;
if(!nickname || nickname.length < 3) {
alert("nickname too short")
return false;
}
localStorage["player"] = JSON.stringify({nickname: nickname});
if ($("#radiochannel").checked) {
var name = $("#customname").value;
if(name) {
$("#radiochannel").value = name;
} else {
alert("custom channel empty")
return false;
}
} else {
var radios = document.querySelectorAll("form input[name=channel]");
var radios = document.querySelectorAll("form#listform input[name=channel]");
for (var i = 0; i < radios.length; i++) {
var radio = radios[i];
if(radio.checked) {
@ -117,21 +158,136 @@
break;
}
};
}
if(name) {
localStorage["channel"] = JSON.stringify({
name: name,
customname: $("#customname").value
name: name
});
window.location.href = "/game.html";
return false;
} else {
alert("no channel selected")
alert("No channel selected")
return false;
}
}
$("form#createform").onsubmit = function(e) {
try {
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}));
} catch(e) {
console.error(e)
}
return false;
}
$("form#customjoinform").onsubmit = function(e) {
try {
var nickname = $("#nick").value;
if(!nickname || nickname.length < 3) {
alert("nickname too short")
return false;
}
localStorage["player"] = JSON.stringify({nickname: nickname});
var name = $("#customname").value;
localStorage["channel"] = JSON.stringify({
name: name
});
window.location.href = "/game.html";
} catch(e) {
console.error(e)
}
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();
setInterval(refresh, 5000);
</script>
</body>
</html>