Line | Hits | Source |
---|---|---|
1 | 1 | module.exports = { |
2 | ||
3 | name: {m: true, t: "String"}, | |
4 | start: {m: true, t: "Function"}, | |
5 | minPlayers: {m: true, t: "Integer"}, | |
6 | maxPlayers: {m: true, t: "Integer"}, | |
7 | ||
8 | init: {t: "Function"}, | |
9 | firstStage: {t: "String"}, | |
10 | parameters: {t: "Array"}, | |
11 | stages: {t: "Object"}, | |
12 | processMessage: {t: "Function"}, | |
13 | onDisconnect: {t: "Function"}, | |
14 | version: {t: "String"}, | |
15 | description: {t: "String"}, | |
16 | opVersion: {t: "String"}, | |
17 | css: {t: "Array"} | |
18 | ||
19 | }; | |
20 |
Line | Hits | Source |
---|---|---|
1 | 1 | var path = require('path'); |
2 | 1 | var fs = require('fs'); |
3 | 1 | var color = require('colors'); |
4 | 1 | var sem = require('semver'); |
5 | 1 | var utils = require('./utils'); |
6 | 1 | var attrs = require('./attrs'); |
7 | ||
8 | 1 | module.exports = function(basePath, callback) { |
9 | ||
10 | 8 | if(!basePath) |
11 | 1 | basePath = path.join(__dirname, "..", "data"); |
12 | 8 | if(!callback) |
13 | 1 | callback = function(){}; |
14 | ||
15 | 8 | var gametypes = {}; |
16 | ||
17 | 8 | fs.readdir(basePath, function(err, files) { |
18 | ||
19 | 8 | if(err) { |
20 | 1 | return callback( "!! Unable to load game definitions\n".red + |
21 | " Install gameplay definitions in data/ subdirectory\n".red + | |
22 | (" Was: " + err).red, null); | |
23 | } | |
24 | ||
25 | 7 | var ok = false; |
26 | ||
27 | 7 | files.forEach(function(filePath) { |
28 | 28 | if(fs.statSync(path.join(basePath, filePath)).isDirectory()) { |
29 | ||
30 | 21 | var modulePath = path.join(basePath, filePath, "definition.js"); |
31 | 21 | if(!fs.existsSync(modulePath)) |
32 | 10 | return; |
33 | ||
34 | 11 | if((module = checkFile(modulePath))) { |
35 | 5 | ok = true; |
36 | 5 | gametypes[filePath] = module; |
37 | ||
38 | // Has public files ? | |
39 | 5 | var publicPath = path.join(basePath, filePath, "public"); |
40 | 5 | if(fs.existsSync(publicPath)) { |
41 | 5 | __app.use(__app.express.static(publicPath)); |
42 | } | |
43 | } | |
44 | } | |
45 | }); | |
46 | ||
47 | 7 | if(!ok) { |
48 | 2 | return callback("!! No gameplay definition was correct. Aborting.".red, null); |
49 | } | |
50 | ||
51 | 5 | callback(null, gametypes); |
52 | ||
53 | }); | |
54 | }; | |
55 | ||
56 | 1 | var checkFile = function(path) { |
57 | ||
58 | 11 | var errorLog = []; |
59 | ||
60 | 11 | try { |
61 | 11 | module = require(path); |
62 | 10 | var instance = new module(); |
63 | ||
64 | 9 | checkMissingAttributes(instance, errorLog); |
65 | 9 | checkWrongAttributes(instance, errorLog); |
66 | ||
67 | 9 | if(errorLog.length === 1) { |
68 | 2 | throw new Error("One semantic failure"); |
69 | 7 | } else if(errorLog.length > 1) { |
70 | 2 | throw new Error(errorLog.length + " semantic failures"); |
71 | } | |
72 | ||
73 | // Everything is ok! | |
74 | 5 | return module; |
75 | } catch(e) { | |
76 | 6 | console.error(("~~ Cannot load '" + path + "'\n " + e).yellow) |
77 | 6 | errorLog.forEach(function(a) { |
78 | 10 | console.error((" - " + a).gray.italic); |
79 | }); | |
80 | 6 | return null; |
81 | } | |
82 | }; | |
83 | ||
84 | 1 | var checkMissingAttributes = function(instance, errorLog) { |
85 | ||
86 | 9 | for(var attr in attrs) { |
87 | 126 | if(!attrs[attr].m) { |
88 | 90 | continue; |
89 | } | |
90 | ||
91 | 36 | if(!instance[attr]) { |
92 | 4 | errorLog.push("Missing '" + attr + "' mandatory attribute (" + attrs[attr].t + ")"); |
93 | } | |
94 | } | |
95 | ||
96 | }; | |
97 | ||
98 | 1 | var checkType = function(e, t) { |
99 | ||
100 | 69 | var fn; |
101 | 69 | switch(t) { |
102 | 40 | case "String": fn = utils.isString; break; |
103 | 46 | case "Function": fn = utils.isFunction; break; |
104 | 32 | case "Integer": fn = utils.isInteger; break; |
105 | 12 | case "Array": fn = utils.isArray; break; |
106 | 8 | case "Object": fn = utils.isObject; break; |
107 | } | |
108 | 69 | return fn(e); |
109 | ||
110 | }; | |
111 | ||
112 | 1 | var checkWrongAttributes = function(instance, errorLog) { |
113 | ||
114 | 9 | for(var attr in attrs) { |
115 | 126 | if(instance[attr] && !checkType(instance[attr], attrs[attr].t)) { |
116 | 2 | errorLog.push("'" + attr + "' must be of type (" + attrs[attr].t + ")"); |
117 | } | |
118 | } | |
119 | ||
120 | 9 | if(instance.maxPlayers <= 0) { |
121 | 1 | errorLog.push("'maxPlayers' must be positive"); |
122 | } | |
123 | ||
124 | 9 | if(instance.minPlayers <= 0) { |
125 | 1 | errorLog.push("'minPlayers' must be positive"); |
126 | } | |
127 | ||
128 | 9 | if(instance.maxPlayers < instance.minPlayers) { |
129 | 1 | errorLog.push("'minPlayers' is greater than 'maxPlayers'"); |
130 | } | |
131 | ||
132 | 9 | if(instance.opVersion && !sem.satisfies(__version, instance.opVersion)) { |
133 | 1 | errorLog.push("This module is not ready for this version of OpenParty (" + __version + " does not satisfy " + instance.opVersion + ")"); |
134 | } | |
135 | } | |
136 |
Line | Hits | Source |
---|---|---|
1 | 1 | var utils = require("/home/lesterpig/workspace/openparty/lib/./utils.js"); |
2 | ||
3 | 1 | var Player = function(socket, room) { |
4 | ||
5 | /** | |
6 | * roles = { | |
7 | * <name>: {channels: {}, actions: {}}, | |
8 | * ... | |
9 | * } | |
10 | * | |
11 | */ | |
12 | 8 | this.roles = {}; |
13 | ||
14 | 8 | this.channels = {}; |
15 | 8 | this.actions = {}; |
16 | ||
17 | 8 | this.socket = socket; // circular |
18 | 8 | this.room = room; // circular |
19 | 8 | this.username = socket.username; |
20 | ||
21 | }; | |
22 | ||
23 | 15 | Player.prototype.join = function(channel) { this.socket.join("room_" + this.room.id + "_" + channel); }; |
24 | 6 | Player.prototype.leave = function(channel) { this.socket.leave("room_" + this.room.id + "_" + channel); }; |
25 | ||
26 | 1 | Player.prototype.setRole = function(role, value) { |
27 | 5 | if(!value && this.roles[role]) { |
28 | // Remove all registered channels | |
29 | 1 | for(var channel in this.roles[role].channels) { |
30 | 2 | if(this.roles[role].channels[channel].r && !this.channels[channel]) { |
31 | 2 | this.leave(channel); |
32 | } | |
33 | } | |
34 | 4 | } else if(value) { |
35 | // Join all channels | |
36 | 3 | for(var channel in value.channels) { |
37 | 5 | if(value.channels[channel].r) { |
38 | 4 | this.join(channel); |
39 | } | |
40 | } | |
41 | } | |
42 | 5 | setAttributeObject(this, "roles", role, value); |
43 | 5 | this.sendWriteChannels(); |
44 | }; | |
45 | ||
46 | 1 | Player.prototype.setAction = function(name, value) { |
47 | 3 | setAttributeObject(this, "actions", name, value); |
48 | }; | |
49 | ||
50 | 1 | Player.prototype.setChannel = function(name, rights) { |
51 | ||
52 | 12 | if(!rights) { |
53 | 1 | if(this.channels[name]) { |
54 | 1 | this.leave(name); |
55 | 1 | delete this.channels[name]; |
56 | // Refreshing channels | |
57 | 1 | for(var role in this.roles) { |
58 | 1 | for(var channel in this.roles[role].channels) { |
59 | 2 | if(channel === name && this.roles[role].channels[channel].r) |
60 | 1 | this.join(channel); |
61 | } | |
62 | } | |
63 | } | |
64 | } | |
65 | ||
66 | else { | |
67 | 11 | if(rights.r) { |
68 | 9 | this.join(name); |
69 | } | |
70 | else { | |
71 | 2 | this.leave(name); |
72 | } | |
73 | 11 | this.channels[name] = rights; |
74 | } | |
75 | 12 | this.sendWriteChannels(); |
76 | }; | |
77 | ||
78 | 1 | Player.prototype.getWriteChannels = function() { |
79 | 34 | var channels = {}; |
80 | ||
81 | 34 | for(var role in this.roles) { |
82 | 23 | for(var channel in this.roles[role].channels) { |
83 | 41 | if(this.roles[role].channels[channel].w) { |
84 | 23 | channels[channel] = this.roles[role].channels[channel]; |
85 | } | |
86 | } | |
87 | } | |
88 | ||
89 | 34 | for(var channel in this.channels) { // override "default" behavior |
90 | 46 | if(this.channels[channel].w) { |
91 | 39 | channels[channel] = this.channels[channel]; |
92 | } | |
93 | else { | |
94 | 7 | delete channels[channel]; |
95 | } | |
96 | } | |
97 | ||
98 | 34 | return channels; |
99 | }; | |
100 | ||
101 | 1 | Player.prototype.sendWriteChannels = function() { |
102 | 17 | return this.emit("setAllowedChannels", this.getWriteChannels()); |
103 | }; | |
104 | ||
105 | 1 | Player.prototype.getAvailableActions = function(clone) { |
106 | 35 | var output = {}; |
107 | ||
108 | // role actions | |
109 | 35 | for(var role in this.roles) { |
110 | 25 | for(var action in this.roles[role].actions) { |
111 | 76 | if(this.roles[role].actions[action].isAvailable(this)) { |
112 | 71 | output[action] = this.roles[role].actions[action]; |
113 | } | |
114 | } | |
115 | } | |
116 | ||
117 | // personal actions (override) | |
118 | 35 | for(var action in this.actions) { |
119 | 6 | if(this.actions[action].isAvailable && this.actions[action].isAvailable(this)) { |
120 | 5 | output[action] = this.actions[action]; |
121 | } else { | |
122 | 1 | delete output[action]; |
123 | } | |
124 | } | |
125 | ||
126 | 35 | for(var action in output) { |
127 | 75 | if(clone) { |
128 | 23 | output[action] = { |
129 | type: output[action].type, | |
130 | options: output[action].options | |
131 | }; | |
132 | } | |
133 | ||
134 | 75 | processActionOptions(output[action], this.room); |
135 | } | |
136 | ||
137 | 35 | return output; |
138 | }; | |
139 | ||
140 | 1 | Player.prototype.sendAvailableActions = function() { |
141 | 20 | return this.emit("setAvailableActions", this.getAvailableActions(true)); |
142 | }; | |
143 | ||
144 | 1 | Player.prototype.emit = function(event, data) { |
145 | 38 | this.socket.emit(event,data); |
146 | 38 | return true; |
147 | }; | |
148 | ||
149 | 1 | Player.prototype.message = function(m) { |
150 | 0 | this.emit("chatMessage", {message: m}); |
151 | }; | |
152 | ||
153 | 1 | module.exports = { |
154 | ||
155 | init: function(room) { | |
156 | 2 | room.players.forEach(function(p) { |
157 | 8 | p.player = new Player(p, room); |
158 | 8 | p.player.setChannel("general", {r:true, w:true, n:"General"}); |
159 | }); | |
160 | }, | |
161 | ||
162 | Player: Player | |
163 | ||
164 | }; | |
165 | ||
166 | /** PRIVATE FUNCTIONS **/ | |
167 | ||
168 | 1 | function setAttributeObject(p, attr, name, value) { |
169 | 8 | if(value === null || value === undefined) { |
170 | 3 | delete p[attr][name]; |
171 | } | |
172 | ||
173 | else { | |
174 | 5 | p[attr][name] = value; |
175 | } | |
176 | } | |
177 | ||
178 | 1 | function processActionOptions(action, room) { |
179 | ||
180 | 75 | switch(action.type) { |
181 | ||
182 | case "select": | |
183 | ||
184 | 53 | if(!action.options.choices) { |
185 | 1 | action.options.choices = "players"; |
186 | } | |
187 | ||
188 | 53 | if(utils.isString(action.options.choices)) { |
189 | ||
190 | 19 | var out = []; |
191 | 19 | room.players.forEach(function(e) { |
192 | 76 | out.push(e.username); |
193 | }); | |
194 | 19 | action.options.safeChoices = out; |
195 | ||
196 | 34 | } else if(Array.isArray(action.options.choices)) { |
197 | 19 | action.options.safeChoices = action.options.choices; |
198 | } else { | |
199 | 15 | action.options.safeChoices = action.options.choices(room, this); |
200 | } | |
201 | ||
202 | 53 | break; |
203 | ||
204 | default: | |
205 | 22 | break; |
206 | } | |
207 | ||
208 | } | |
209 |
Line | Hits | Source |
---|---|---|
1 | 1 | var utils = require("/home/lesterpig/workspace/openparty/lib/./utils.js"); |
2 | 1 | var players = require("/home/lesterpig/workspace/openparty/lib/./players.js"); |
3 | 1 | var Player = players.Player; |
4 | ||
5 | ||
6 | 1 | var Room = function(name, password, gameplay) { |
7 | ||
8 | 3 | this.isRoom = true; |
9 | 3 | this.id = utils.randomString(20); // TODO make it better... |
10 | 3 | this.players = []; |
11 | 3 | this.name = name; |
12 | 3 | this.size = gameplay.minPlayers; |
13 | 3 | this.creationDate = new Date(); |
14 | 3 | this.password = password; |
15 | ||
16 | 3 | this.gameplay = gameplay; |
17 | 3 | this.gameplay.room = this; // circular |
18 | ||
19 | // Stages | |
20 | 3 | this.started = false; |
21 | 3 | this.timeout = null; |
22 | 3 | this.currentStage = null; |
23 | ||
24 | 3 | if(this.gameplay.init) { |
25 | 3 | this.gameplay.init(this); |
26 | } | |
27 | ||
28 | }; | |
29 | ||
30 | /** | |
31 | * Broadcast a message to several sockets | |
32 | * @param {String} [channel] | |
33 | * @param {String} event | |
34 | * @param {*} [data] | |
35 | */ | |
36 | 1 | Room.prototype.broadcast = function(channel, event, data) { |
37 | ||
38 | 39 | if(data === undefined && channel) { |
39 | 24 | data = event; |
40 | 24 | event = channel; |
41 | 24 | channel = ""; |
42 | 15 | } else if(channel !== "") { |
43 | 11 | channel = "_" + channel; |
44 | } | |
45 | 39 | __app.io.to("room_" + this.id + channel).emit(event, data); |
46 | }; | |
47 | ||
48 | /** | |
49 | * Send more information about a player. | |
50 | * Client-side, it will be displayed in players list. | |
51 | * | |
52 | * @param {String} [channel] | |
53 | * @param {Object} player (socket or Player object) | |
54 | * @param {String} value | |
55 | */ | |
56 | 1 | Room.prototype.playerInfo = function(channel, player, value) { |
57 | ||
58 | 4 | if(!value) { |
59 | 2 | if(channel instanceof Player) { |
60 | 1 | channel = channel.socket; |
61 | } | |
62 | 2 | this.broadcast("playerInfo", {username: channel.username, value: player}); |
63 | } else { | |
64 | 2 | if(player instanceof Player) { |
65 | 1 | player = player.socket; |
66 | } | |
67 | 2 | this.broadcast(channel, "playerInfo", {username: player.username, value: value}); |
68 | } | |
69 | } | |
70 | ||
71 | /** | |
72 | * Send a chat message to players (system message) | |
73 | * @param {String} [channel] | |
74 | * @param {String} message | |
75 | */ | |
76 | 1 | Room.prototype.message = function(channel, message) { |
77 | 6 | if(!message) { |
78 | 4 | this.broadcast("chatMessage", {message:channel}); |
79 | } | |
80 | else { | |
81 | 2 | this.broadcast(channel, "chatMessage", {message:message}); |
82 | } | |
83 | }; | |
84 | ||
85 | /** | |
86 | * Get public information about the room and its players | |
87 | * @return {Object} | |
88 | */ | |
89 | 1 | Room.prototype.getPublicInfo = function() { |
90 | ||
91 | 35 | var output = {}; |
92 | ||
93 | 35 | output.id = this.id; |
94 | 35 | output.isRoom = true; |
95 | 35 | output.name = this.name; |
96 | 35 | output.players = []; |
97 | 35 | output.size = this.size; |
98 | 35 | output.started = this.started; |
99 | 35 | output.password = !!this.password; |
100 | ||
101 | 35 | for(var i = 0; i < this.players.length; i++) { |
102 | 72 | output.players.push({username: this.players[i].username}); |
103 | } | |
104 | ||
105 | 35 | var parameters = []; |
106 | 35 | this.gameplay.parameters.forEach(function(p) { |
107 | 70 | parameters.push({ |
108 | name: p.name, | |
109 | value: p.value, | |
110 | help: p.help, | |
111 | isBoolean: p.type === Boolean | |
112 | }); | |
113 | }); | |
114 | ||
115 | 35 | output.gameplay = { |
116 | name: this.gameplay.name, | |
117 | description: this.gameplay.description, | |
118 | parameters: parameters, | |
119 | maxPlayers: this.gameplay.maxPlayers, | |
120 | minPlayers: this.gameplay.minPlayers | |
121 | }; | |
122 | ||
123 | 35 | return output; |
124 | ||
125 | }; | |
126 | ||
127 | /** | |
128 | * Set room size | |
129 | * @param {Number} size | |
130 | */ | |
131 | 1 | Room.prototype.setSize = function(size) { |
132 | ||
133 | 3 | if(size >= this.gameplay.minPlayers && size <= this.gameplay.maxPlayers) { |
134 | 2 | this.size = size; |
135 | 2 | sendUpdateRoom(this); |
136 | } | |
137 | }; | |
138 | ||
139 | /** | |
140 | * Set room parameter | |
141 | * @param {} parameter | |
142 | * @param {*} value | |
143 | * @param {Socket} socket | |
144 | */ | |
145 | 1 | Room.prototype.setParameter = function(parameter, value, socket) { |
146 | ||
147 | ||
148 | 3 | this.gameplay.parameters.forEach(function(e) { |
149 | 6 | if(e.name === parameter) { |
150 | 2 | e.value = e.type(value); //deal with it. |
151 | 2 | sendUpdateRoom(this, socket); |
152 | 2 | return; |
153 | } | |
154 | }.bind(this)); | |
155 | }; | |
156 | ||
157 | 1 | Room.prototype.sendMessage = function(channel, message, socket) { |
158 | ||
159 | 24 | var allowed = false; |
160 | ||
161 | 24 | if(channel === "preChat") { |
162 | 5 | if(this.started) { |
163 | 1 | return; |
164 | } | |
165 | 4 | allowed = true; |
166 | 4 | channel = ""; |
167 | } else { | |
168 | 19 | if(!this.started) { |
169 | 2 | return; |
170 | } | |
171 | 17 | var channels = socket.player.getWriteChannels(); |
172 | 17 | allowed = (channels[channel] ? (true === channels[channel].w) : false); |
173 | } | |
174 | ||
175 | 21 | if(allowed) { |
176 | 15 | socket.emit("messageSent"); |
177 | 15 | if(this.gameplay.processMessage && this.started) { |
178 | 11 | message = this.gameplay.processMessage(channel, message, socket.player); |
179 | 11 | if(!message) { |
180 | 4 | return; |
181 | } | |
182 | } | |
183 | 11 | this.broadcast(channel, "chatMessage", {message: message, sender: socket.username}); |
184 | } | |
185 | }; | |
186 | ||
187 | 1 | Room.prototype.start = function() { |
188 | ||
189 | 2 | players.init(this); |
190 | 2 | this.gameplay.start(this, function(err) { |
191 | ||
192 | 2 | if(err) { |
193 | 1 | return this.broadcast("chatMessage", { message: err }); |
194 | } | |
195 | ||
196 | 1 | this.started = true; |
197 | 1 | sendUpdateRoom(this); |
198 | 1 | this.broadcast("gameStarted"); |
199 | ||
200 | 1 | if(this.gameplay.firstStage) { |
201 | 1 | this.nextStage(this.gameplay.firstStage); |
202 | } | |
203 | ||
204 | }.bind(this)); | |
205 | }; | |
206 | ||
207 | /** | |
208 | * End the current stage and start another one | |
209 | * @param {String} stage The new stage name | |
210 | * @param {Function} [callback] | |
211 | */ | |
212 | 1 | Room.prototype.nextStage = function(stage, callback) { |
213 | ||
214 | 5 | this.currentStage = stage; |
215 | ||
216 | 5 | this.gameplay.stages[stage].start(this, function(err, duration) { |
217 | ||
218 | 5 | this.setStageDuration(duration); |
219 | ||
220 | // Send actions to players | |
221 | ||
222 | 5 | this.players.forEach(function(player) { |
223 | 20 | player.player.sendAvailableActions(); |
224 | }); | |
225 | ||
226 | 5 | if(callback) { |
227 | 1 | callback(null); |
228 | } | |
229 | ||
230 | }.bind(this)); | |
231 | ||
232 | }; | |
233 | ||
234 | /** | |
235 | * End the current stage, without starting another one | |
236 | */ | |
237 | 1 | Room.prototype.endStage = function() { |
238 | 1 | clearTimeout(this.timeout); |
239 | 1 | this.gameplay.stages[this.currentStage].end(this, function() {}); |
240 | }; | |
241 | ||
242 | /** | |
243 | * Change current stage duration | |
244 | * @param {Number} duration (sec) | |
245 | */ | |
246 | 1 | Room.prototype.setStageDuration = function(duration) { |
247 | ||
248 | 6 | clearTimeout(this.timeout); |
249 | ||
250 | 6 | if(duration < 0) { |
251 | 3 | this.broadcast("clearTimer"); |
252 | 3 | return; |
253 | } | |
254 | ||
255 | 3 | this.broadcast("setTimer", duration); |
256 | 3 | this.timeout = setTimeout(this.endStage.bind(this), duration * 1000); |
257 | }; | |
258 | ||
259 | /** | |
260 | * Get player object from username | |
261 | * @param {String} username | |
262 | * @return {Socket} | |
263 | */ | |
264 | 1 | Room.prototype.resolveUsername = function(username) { |
265 | 3 | for(var i = 0; i < this.players.length; i++) { |
266 | 7 | if(this.players[i].username === username) { |
267 | 2 | return this.players[i]; |
268 | } | |
269 | } | |
270 | 1 | return null; |
271 | }; | |
272 | ||
273 | 1 | var rooms = module.exports = { |
274 | ||
275 | rooms: [], | |
276 | ||
277 | getRooms: function() { | |
278 | ||
279 | 1 | var output = []; |
280 | 1 | for(var i = 0; i < this.rooms.length; i++) { |
281 | 1 | if(!this.rooms[i].started) { |
282 | 1 | output.push(this.rooms[i].getPublicInfo()); |
283 | } | |
284 | } | |
285 | ||
286 | 1 | return output; |
287 | }, | |
288 | ||
289 | createRoom: function(name, password, type, socket) { | |
290 | ||
291 | 4 | if(!type || type === "default") { |
292 | 2 | for(var i in __gametypes) { |
293 | 2 | type = i; |
294 | 2 | break; |
295 | } | |
296 | } | |
297 | ||
298 | 4 | if(!__gametypes[type]) { |
299 | 1 | return; |
300 | } | |
301 | ||
302 | 3 | var gameplay = new __gametypes[type](); |
303 | ||
304 | 3 | var room = new Room(name, password, gameplay); |
305 | ||
306 | 3 | this.rooms.push(room); |
307 | 3 | __app.io.to("lobby").emit("roomCreated", room.getPublicInfo()); |
308 | 3 | this.joinRoom(room.id, password, socket); |
309 | }, | |
310 | ||
311 | getRoom: function(id) { | |
312 | ||
313 | 12 | for(var i = 0; i < this.rooms.length; i++) { |
314 | 12 | if(this.rooms[i].id === id) { |
315 | 11 | return this.rooms[i]; |
316 | } | |
317 | } | |
318 | ||
319 | 1 | return null; |
320 | ||
321 | }, | |
322 | ||
323 | joinRoom: function(id, password, socket) { | |
324 | ||
325 | 12 | var room = this.getRoom(id); |
326 | 12 | if(!room) { |
327 | 1 | return; |
328 | } | |
329 | ||
330 | 11 | if(room.players.length >= room.size) { |
331 | 1 | return; |
332 | } | |
333 | ||
334 | 10 | if(room.password && room.password !== password) { |
335 | 0 | socket.emit("invalidRoomPassword"); |
336 | 0 | return; |
337 | } | |
338 | ||
339 | 10 | if(room.players.indexOf(socket) < 0) { |
340 | 10 | room.players.push(socket); |
341 | } | |
342 | ||
343 | 10 | sendUpdateRoom(room); |
344 | 10 | socket.emit("roomJoined", room.getPublicInfo()); |
345 | 10 | socket.join("room_" + id); |
346 | 10 | socket.currentRoom = room; |
347 | ||
348 | 10 | room.broadcast("chatMessage", {message: socket.username + " has joined the room"}); |
349 | ||
350 | }, | |
351 | ||
352 | leaveRoom: function(socket) { | |
353 | ||
354 | 13 | var room = socket.currentRoom; |
355 | 13 | if(!room) { |
356 | 4 | return; |
357 | } | |
358 | ||
359 | 9 | var i = room.players.indexOf(socket); |
360 | 9 | if(i < 0) { |
361 | 1 | return; |
362 | } | |
363 | ||
364 | 8 | room.players.splice(i, 1); |
365 | ||
366 | 8 | if(room.gameplay.onDisconnect && socket.player) { |
367 | 2 | room.gameplay.onDisconnect(room, socket.player); |
368 | } | |
369 | ||
370 | 8 | if(room.players.length === 0) { |
371 | 2 | this.removeRoom(room); |
372 | } else { | |
373 | 6 | sendUpdateRoom(room); |
374 | } | |
375 | ||
376 | 8 | socket.player = null; |
377 | 8 | socket.currentRoom = null; |
378 | ||
379 | // Leave concerned socket rooms | |
380 | // Buffered, because socket.rooms is dynamically updated by socket.io | |
381 | 8 | var regex = /^room\_/; |
382 | 8 | var buffer = socket.rooms.filter(function(r) { |
383 | 16 | return regex.test(r); |
384 | }); | |
385 | ||
386 | 8 | try { |
387 | 8 | buffer.forEach(function(r) { |
388 | 6 | socket.leave(r); |
389 | }); | |
390 | 8 | socket.emit("roomLeft"); |
391 | } catch(e) {} | |
392 | ||
393 | }, | |
394 | ||
395 | removeRoom: function(room) { | |
396 | ||
397 | 2 | for(var i = 0; i < this.rooms.length; i++) { |
398 | 2 | if(this.rooms[i].id === room.id) { |
399 | 2 | clearTimeout(this.rooms[i].timeout); |
400 | 2 | this.rooms.splice(i, 1); |
401 | 2 | __app.io.to("lobby").emit("roomRemoved", room.id); |
402 | 2 | return; |
403 | } | |
404 | } | |
405 | ||
406 | } | |
407 | ||
408 | }; | |
409 | ||
410 | /** PRIVATE FUNCTIONS **/ | |
411 | ||
412 | 1 | function sendUpdateRoom(room, socket) { |
413 | 21 | if(!socket) { |
414 | 19 | __app.io.to("lobby").emit("roomUpdated", room.getPublicInfo()); |
415 | } | |
416 | else { | |
417 | 2 | socket.broadcast.to("lobby").emit("roomUpdated", room.getPublicInfo()); |
418 | } | |
419 | } | |
420 |
Line | Hits | Source |
---|---|---|
1 | 1 | var utils = require("/home/lesterpig/workspace/openparty/lib/./utils"); |
2 | 1 | var rooms = require("/home/lesterpig/workspace/openparty/lib/./rooms"); |
3 | ||
4 | 1 | module.exports = function(app) { |
5 | ||
6 | /** | |
7 | * ROOT | |
8 | */ | |
9 | 1 | app.get("/", function(req, res) { |
10 | 1 | req.session.locale = req.getLocale(); |
11 | 1 | res.render("index", {passwordRequired: __conf.password !== null }); |
12 | }); | |
13 | ||
14 | /*** | |
15 | * ABOUT | |
16 | */ | |
17 | 1 | app.get("/about", function(req, res) { |
18 | 1 | res.render("about", {version: __version, locales: __conf.locales, gamemodes: __staticGametypes }); |
19 | }); | |
20 | ||
21 | /** | |
22 | * IO : Ping | |
23 | */ | |
24 | 1 | app.io.route("ping", function(socket) { |
25 | 1 | socket.volatile.emit("pong"); |
26 | }); | |
27 | ||
28 | /** | |
29 | * IO : Disconnect | |
30 | */ | |
31 | 1 | app.io.route("disconnect", function(socket) { |
32 | ||
33 | 4 | var room = utils.isInGame(socket); |
34 | 5 | if(!room) return; |
35 | ||
36 | 3 | rooms.leaveRoom(socket); |
37 | }); | |
38 | ||
39 | /** | |
40 | * IO : Login | |
41 | */ | |
42 | 1 | app.io.route("login", function(socket, data) { |
43 | ||
44 | 18 | var locale = socket.session.locale || __conf.defaultLocale; |
45 | ||
46 | 18 | if(data.password !== __conf.password && __conf.password !== null || !data.username) { |
47 | 4 | return socket.emit("loginResult", {err: __i18n({phrase: "Bad Credentials", locale: locale})}); |
48 | } | |
49 | ||
50 | // Check username | |
51 | 14 | try { |
52 | 14 | var username = utils.checkUsername(data.username); |
53 | } catch(e) { | |
54 | 5 | return socket.emit("loginResult", {err: __i18n({phrase: e.message, locale: locale})}); |
55 | } | |
56 | ||
57 | 9 | socket.join("lobby"); |
58 | 9 | socket.username = username; |
59 | ||
60 | 9 | socket.emit("loginResult", {err: null, username: username, gametypes: __staticGametypes}); |
61 | }); | |
62 | ||
63 | /** | |
64 | * IO : Get Rooms | |
65 | */ | |
66 | 1 | app.io.route("getRooms", function(socket) { |
67 | 2 | if(!utils.isInRoom(socket, "lobby")) |
68 | 1 | return; |
69 | 1 | socket.emit("roomsList", rooms.getRooms()); |
70 | }); | |
71 | ||
72 | /** | |
73 | * IO : Create Room | |
74 | */ | |
75 | 1 | app.io.route("createRoom", function(socket, data) { |
76 | 6 | if(!utils.isInRoom(socket, "lobby")) |
77 | 1 | return; |
78 | 5 | if(utils.isInGame(socket)) |
79 | 1 | return; |
80 | 4 | rooms.createRoom(data.name, data.password, data.type, socket); |
81 | }); | |
82 | ||
83 | /** | |
84 | * IO : Join Room | |
85 | */ | |
86 | 1 | app.io.route("joinRoom", function(socket, data) { |
87 | 11 | if(!utils.isInRoom(socket, "lobby")) |
88 | 1 | return; |
89 | 10 | if(utils.isInGame(socket)) |
90 | 1 | return; |
91 | 9 | rooms.joinRoom(data.id, data.password, socket); |
92 | }); | |
93 | ||
94 | /** | |
95 | * IO : Leave Room | |
96 | */ | |
97 | 1 | app.io.route("leaveRoom", function(socket) { |
98 | 9 | rooms.leaveRoom(socket); |
99 | }); | |
100 | ||
101 | /** | |
102 | * IO : Kick Player | |
103 | */ | |
104 | 1 | app.io.route("kickPlayer", function(socket, username) { |
105 | ||
106 | 0 | var room = utils.isInGame(socket); |
107 | 0 | var player; |
108 | 0 | if( !room |
109 | || room.started | |
110 | || room.players[0] !== socket | |
111 | || !(player = room.resolveUsername(username))) { | |
112 | 0 | return; |
113 | } | |
114 | 0 | room.message(username + " has been kicked out of this room."); |
115 | 0 | rooms.leaveRoom(player); |
116 | }); | |
117 | ||
118 | /** | |
119 | * IO : Room Parameters | |
120 | */ | |
121 | 1 | app.io.route("setRoomSize", function(socket, data) { |
122 | 4 | var room = utils.isInGame(socket); |
123 | 4 | if(room && room.players[0] === socket && room.players.length <= +data) |
124 | 3 | room.setSize(+data); |
125 | }); | |
126 | ||
127 | 1 | app.io.route("setRoomParameter", function(socket, data) { |
128 | 4 | var room = utils.isInGame(socket); |
129 | 4 | if(room && room.players[0] === socket) |
130 | 3 | room.setParameter(data.parameter, data.value, socket); |
131 | }); | |
132 | ||
133 | /** | |
134 | * IO : Chat Management | |
135 | */ | |
136 | 1 | app.io.route("sendMessage", function(socket, data) { |
137 | 27 | var room = utils.isInGame(socket); |
138 | 27 | var message = utils.sanitizeHtml(data.message); |
139 | 27 | if(!message) |
140 | 3 | return; |
141 | 24 | if(room) |
142 | 24 | room.sendMessage(data.channel, message, socket); |
143 | }); | |
144 | ||
145 | /** | |
146 | * IO : Start Game ! | |
147 | */ | |
148 | 1 | app.io.route("startRoom", function(socket) { |
149 | 3 | var room = utils.isInGame(socket); |
150 | 3 | if(room && room.players[0] === socket && !room.started && room.size === room.players.length) |
151 | 2 | room.start(); |
152 | }); | |
153 | ||
154 | /** | |
155 | * IO : Execute Action | |
156 | */ | |
157 | 1 | app.io.route("executeAction", function(socket, data) { |
158 | ||
159 | 18 | if(!socket.player || !data || !data.action) |
160 | 3 | return; // Not authorized |
161 | ||
162 | 15 | var actions = socket.player.getAvailableActions(false); |
163 | ||
164 | 15 | if(!actions[data.action] || actions[data.action].locked) |
165 | 2 | return; // Not ready for this action |
166 | ||
167 | 13 | if(actions[data.action].type === "select" |
168 | && actions[data.action].options.safeChoices.indexOf(data.value) < 0) | |
169 | 7 | return; // Bad value |
170 | ||
171 | 6 | if(actions[data.action].type === "select" |
172 | && actions[data.action].options.choices === "players") { | |
173 | 1 | data.value = socket.player.room.resolveUsername(data.value).player; |
174 | } | |
175 | ||
176 | 6 | actions[data.action].locked = true; |
177 | 6 | actions[data.action].execute(socket.player, data.value); |
178 | 6 | actions[data.action].locked = false; |
179 | }); | |
180 | } | |
181 |
Line | Hits | Source |
---|---|---|
1 | 1 | var crypto = require("crypto"); |
2 | ||
3 | 1 | global.GET_RANDOM = function(from, to) { |
4 | 1024 | return Math.floor(Math.random() * (to - from + 1)) + from; |
5 | }; | |
6 | ||
7 | 1 | module.exports = { |
8 | ||
9 | randomString: function(len) { | |
10 | 3 | return crypto.randomBytes(Math.ceil(len * 3 / 4)) |
11 | .toString('base64') // convert to base64 format | |
12 | .slice(0, len) // return required number of characters | |
13 | .replace(/\+/g, '0') // replace '+' with '0' | |
14 | .replace(/\//g, '0'); // replace '/' with '0' | |
15 | }, | |
16 | ||
17 | isInRoom: function(socket, room) { | |
18 | 19 | return socket.rooms.indexOf(room) >= 0; |
19 | }, | |
20 | ||
21 | isInGame: function(socket) { | |
22 | ||
23 | 57 | if(!socket.currentRoom) |
24 | 14 | return false; |
25 | else | |
26 | 43 | return socket.currentRoom; |
27 | ||
28 | }, | |
29 | ||
30 | sanitizeHtml: function(string) { | |
31 | 40 | if(!string) |
32 | 3 | return ""; |
33 | 37 | string = string.replace(/</ig,"<"); |
34 | 37 | string = string.replace(/>/ig,">"); |
35 | 37 | return string; |
36 | }, | |
37 | ||
38 | checkUsername: function(username) { | |
39 | 14 | if(username.length > 20) |
40 | 1 | throw new Error("Username is too long."); |
41 | ||
42 | 13 | username = this.sanitizeHtml(username); |
43 | 13 | username = username.trim(); |
44 | ||
45 | 13 | if(username.length === 0) |
46 | 1 | throw new Error("Username is invalid."); |
47 | ||
48 | // already connected ? | |
49 | ||
50 | 12 | var exists = __app.io.sockets.sockets.some(function(s) { |
51 | 63 | if(!s.username) |
52 | 39 | return false; |
53 | 24 | if(s.username.toLowerCase() === username.toLowerCase()) |
54 | 3 | return true; |
55 | }); | |
56 | ||
57 | 12 | if(exists) |
58 | 3 | throw new Error("Username is not available."); |
59 | ||
60 | 9 | return username; |
61 | ||
62 | }, | |
63 | ||
64 | /** TYPES */ | |
65 | ||
66 | isString: function(s) { | |
67 | 78 | return typeof s === 'string' || s instanceof String; |
68 | }, | |
69 | ||
70 | isFunction: function(f) { | |
71 | 23 | return typeof f === 'function' |
72 | }, | |
73 | ||
74 | isInteger: function(i) { | |
75 | 16 | return i === parseInt(i, 10); |
76 | }, | |
77 | ||
78 | isArray: function(a) { | |
79 | 6 | return require("util").isArray(a); |
80 | }, | |
81 | ||
82 | isObject: function(o) { | |
83 | 4 | return typeof o === 'object' |
84 | } | |
85 | } | |
86 |