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 | 31 | 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 | 23 | 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, silent) { |
51 | ||
52 | 20 | 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 | 19 | if(rights.r) { |
68 | 17 | this.join(name); |
69 | } | |
70 | else { | |
71 | 2 | this.leave(name); |
72 | } | |
73 | 19 | this.channels[name] = rights; |
74 | } | |
75 | 20 | if(!silent) { |
76 | 4 | this.sendWriteChannels(); |
77 | } | |
78 | }; | |
79 | ||
80 | 1 | Player.prototype.getWriteChannels = function() { |
81 | 34 | var channels = {}; |
82 | ||
83 | 34 | for(var role in this.roles) { |
84 | 23 | for(var channel in this.roles[role].channels) { |
85 | 41 | if(this.roles[role].channels[channel].w) { |
86 | 23 | channels[channel] = this.roles[role].channels[channel]; |
87 | } | |
88 | } | |
89 | } | |
90 | ||
91 | 34 | for(var channel in this.channels) { // override "default" behavior |
92 | 80 | if(this.channels[channel].w) { |
93 | 39 | channels[channel] = this.channels[channel]; |
94 | } | |
95 | else { | |
96 | 41 | delete channels[channel]; |
97 | } | |
98 | } | |
99 | ||
100 | 34 | return channels; |
101 | }; | |
102 | ||
103 | 1 | Player.prototype.sendWriteChannels = function() { |
104 | 17 | return this.emit("setAllowedChannels", this.getWriteChannels()); |
105 | }; | |
106 | ||
107 | 1 | Player.prototype.getAvailableActions = function(clone) { |
108 | 35 | var output = {}; |
109 | ||
110 | // role actions | |
111 | 35 | for(var role in this.roles) { |
112 | 25 | for(var action in this.roles[role].actions) { |
113 | 76 | if(this.roles[role].actions[action].isAvailable(this)) { |
114 | 71 | output[action] = this.roles[role].actions[action]; |
115 | } | |
116 | } | |
117 | } | |
118 | ||
119 | // personal actions (override) | |
120 | 35 | for(var action in this.actions) { |
121 | 6 | if(this.actions[action].isAvailable && this.actions[action].isAvailable(this)) { |
122 | 5 | output[action] = this.actions[action]; |
123 | } else { | |
124 | 1 | delete output[action]; |
125 | } | |
126 | } | |
127 | ||
128 | 35 | for(var action in output) { |
129 | 75 | if(clone) { |
130 | 23 | output[action] = { |
131 | type: output[action].type, | |
132 | options: output[action].options | |
133 | }; | |
134 | } | |
135 | ||
136 | 75 | processActionOptions(output[action], this.room); |
137 | } | |
138 | ||
139 | 35 | return output; |
140 | }; | |
141 | ||
142 | 1 | Player.prototype.sendAvailableActions = function() { |
143 | 20 | return this.emit("setAvailableActions", this.getAvailableActions(true)); |
144 | }; | |
145 | ||
146 | 1 | Player.prototype.emit = function(event, data) { |
147 | 38 | this.socket.emit(event,data); |
148 | 38 | return true; |
149 | }; | |
150 | ||
151 | 1 | Player.prototype.message = function(m) { |
152 | 0 | this.emit("chatMessage", {message: m}); |
153 | }; | |
154 | ||
155 | 1 | module.exports = { |
156 | ||
157 | init: function(room) { | |
158 | 2 | room.players.forEach(function(p) { |
159 | 8 | p.player = new Player(p, room); |
160 | 8 | p.player.setChannel("general", {r:true, w:true, n:"General"}, true); |
161 | 8 | p.player.setChannel("player-" + p.player.username, {r: true, w: false}, true); |
162 | 8 | p.player.sendWriteChannels(); |
163 | }); | |
164 | }, | |
165 | ||
166 | Player: Player | |
167 | ||
168 | }; | |
169 | ||
170 | /** PRIVATE FUNCTIONS **/ | |
171 | ||
172 | 1 | function setAttributeObject(p, attr, name, value) { |
173 | 8 | if(value === null || value === undefined) { |
174 | 3 | delete p[attr][name]; |
175 | } | |
176 | ||
177 | else { | |
178 | 5 | p[attr][name] = value; |
179 | } | |
180 | } | |
181 | ||
182 | 1 | function processActionOptions(action, room) { |
183 | ||
184 | 75 | switch(action.type) { |
185 | ||
186 | case "select": | |
187 | ||
188 | 53 | if(!action.options.choices) { |
189 | 1 | action.options.choices = "players"; |
190 | } | |
191 | ||
192 | 53 | if(utils.isString(action.options.choices)) { |
193 | ||
194 | 19 | var out = []; |
195 | 19 | room.players.forEach(function(e) { |
196 | 76 | out.push(e.username); |
197 | }); | |
198 | 19 | action.options.safeChoices = out; |
199 | ||
200 | 34 | } else if(Array.isArray(action.options.choices)) { |
201 | 19 | action.options.safeChoices = action.options.choices; |
202 | } else { | |
203 | 15 | action.options.safeChoices = action.options.choices(room, this); |
204 | } | |
205 | ||
206 | 53 | break; |
207 | ||
208 | default: | |
209 | 22 | break; |
210 | } | |
211 | ||
212 | } | |
213 |
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 | 46 | if(data === undefined && channel) { |
39 | 30 | data = event; |
40 | 30 | event = channel; |
41 | 30 | channel = ""; |
42 | 16 | } else if(channel !== "") { |
43 | 12 | channel = "_" + channel; |
44 | } | |
45 | 46 | __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 | var endFn = this.gameplay.stages[this.currentStage].end; |
240 | 1 | if(endFn) |
241 | 1 | endFn(this, function() {}); |
242 | }; | |
243 | ||
244 | /** | |
245 | * Change current stage duration | |
246 | * @param {Number} duration (sec) | |
247 | */ | |
248 | 1 | Room.prototype.setStageDuration = function(duration) { |
249 | ||
250 | 6 | clearTimeout(this.timeout); |
251 | ||
252 | 6 | if(duration < 0) { |
253 | 3 | this.broadcast("clearTimer"); |
254 | 3 | this.currentStageEnd = Infinity; |
255 | 3 | return; |
256 | } | |
257 | ||
258 | 3 | this.currentStageEnd = new Date().getTime() + duration * 1000; |
259 | 3 | this.broadcast("setTimer", duration); |
260 | 3 | this.timeout = setTimeout(this.endStage.bind(this), duration * 1000); |
261 | }; | |
262 | ||
263 | /** | |
264 | * @return Number Milliseconds before next stage. Can be "Infinity". | |
265 | */ | |
266 | 1 | Room.prototype.getRemainingTime = function() { |
267 | ||
268 | 2 | return this.currentStageEnd - new Date().getTime(); |
269 | ||
270 | }; | |
271 | ||
272 | /** | |
273 | * Get player object from username | |
274 | * @param {String} username | |
275 | * @return {Socket} | |
276 | */ | |
277 | 1 | Room.prototype.resolveUsername = function(username) { |
278 | 3 | for(var i = 0; i < this.players.length; i++) { |
279 | 7 | if(this.players[i].username === username) { |
280 | 2 | return this.players[i]; |
281 | } | |
282 | } | |
283 | 1 | return null; |
284 | }; | |
285 | ||
286 | 1 | var rooms = module.exports = { |
287 | ||
288 | rooms: [], | |
289 | ||
290 | getRooms: function() { | |
291 | ||
292 | 1 | var output = []; |
293 | 1 | for(var i = 0; i < this.rooms.length; i++) { |
294 | 1 | if(!this.rooms[i].started) { |
295 | 1 | output.push(this.rooms[i].getPublicInfo()); |
296 | } | |
297 | } | |
298 | ||
299 | 1 | return output; |
300 | }, | |
301 | ||
302 | createRoom: function(name, password, type, socket) { | |
303 | ||
304 | 4 | if(!type || type === "default") { |
305 | 2 | for(var i in __gametypes) { |
306 | 2 | type = i; |
307 | 2 | break; |
308 | } | |
309 | } | |
310 | ||
311 | 4 | if(!__gametypes[type]) { |
312 | 1 | return; |
313 | } | |
314 | ||
315 | 3 | var gameplay = new __gametypes[type](); |
316 | ||
317 | 3 | var room = new Room(name, password, gameplay); |
318 | ||
319 | 3 | this.rooms.push(room); |
320 | 3 | __app.io.to("lobby").emit("roomCreated", room.getPublicInfo()); |
321 | 3 | this.joinRoom(room.id, password, socket); |
322 | }, | |
323 | ||
324 | getRoom: function(id) { | |
325 | ||
326 | 12 | for(var i = 0; i < this.rooms.length; i++) { |
327 | 12 | if(this.rooms[i].id === id) { |
328 | 11 | return this.rooms[i]; |
329 | } | |
330 | } | |
331 | ||
332 | 1 | return null; |
333 | ||
334 | }, | |
335 | ||
336 | joinRoom: function(id, password, socket) { | |
337 | ||
338 | 12 | var room = this.getRoom(id); |
339 | 12 | if(!room) { |
340 | 1 | return; |
341 | } | |
342 | ||
343 | 11 | if(room.players.length >= room.size) { |
344 | 1 | return; |
345 | } | |
346 | ||
347 | 10 | if(room.password && room.password !== password) { |
348 | 0 | socket.emit("invalidRoomPassword"); |
349 | 0 | return; |
350 | } | |
351 | ||
352 | 10 | if(room.players.indexOf(socket) < 0) { |
353 | 10 | room.players.push(socket); |
354 | } | |
355 | ||
356 | 10 | sendUpdateRoom(room); |
357 | 10 | socket.emit("roomJoined", room.getPublicInfo()); |
358 | 10 | socket.join("room_" + id); |
359 | 10 | socket.currentRoom = room; |
360 | ||
361 | 10 | room.broadcast("chatMessage", {message: "<span class='glyphicon glyphicon-ok-circle'></span> <strong>" + socket.username + "</strong> has joined the room"}); |
362 | ||
363 | }, | |
364 | ||
365 | leaveRoom: function(socket) { | |
366 | ||
367 | 13 | var room = socket.currentRoom; |
368 | 13 | if(!room) { |
369 | 4 | return; |
370 | } | |
371 | ||
372 | 9 | var i = room.players.indexOf(socket); |
373 | 9 | if(i < 0) { |
374 | 1 | return; |
375 | } | |
376 | ||
377 | 8 | room.players.splice(i, 1); |
378 | ||
379 | 8 | if(room.gameplay.onDisconnect && socket.player) { |
380 | 2 | room.gameplay.onDisconnect(room, socket.player); |
381 | } | |
382 | ||
383 | 8 | if(!room.started && socket.username) { |
384 | 6 | room.broadcast("chatMessage", {message: "<span class='glyphicon glyphicon-remove-circle'></span> <strong>" + socket.username + "</strong> has left the room"}); |
385 | } | |
386 | ||
387 | 8 | if(room) |
388 | ||
389 | 8 | if(room.players.length === 0) { |
390 | 2 | this.removeRoom(room); |
391 | } else { | |
392 | 6 | sendUpdateRoom(room); |
393 | } | |
394 | ||
395 | 8 | socket.player = null; |
396 | 8 | socket.currentRoom = null; |
397 | ||
398 | // Leave concerned socket rooms | |
399 | // Buffered, because socket.rooms is dynamically updated by socket.io | |
400 | 8 | var regex = /^room\_/; |
401 | 8 | var buffer = socket.rooms.filter(function(r) { |
402 | 17 | return regex.test(r); |
403 | }); | |
404 | ||
405 | 8 | try { |
406 | 8 | buffer.forEach(function(r) { |
407 | 7 | socket.leave(r); |
408 | }); | |
409 | 8 | socket.emit("roomLeft"); |
410 | } catch(e) {} | |
411 | ||
412 | }, | |
413 | ||
414 | removeRoom: function(room) { | |
415 | ||
416 | 2 | for(var i = 0; i < this.rooms.length; i++) { |
417 | 2 | if(this.rooms[i].id === room.id) { |
418 | 2 | clearTimeout(this.rooms[i].timeout); |
419 | 2 | this.rooms.splice(i, 1); |
420 | 2 | __app.io.to("lobby").emit("roomRemoved", room.id); |
421 | 2 | return; |
422 | } | |
423 | } | |
424 | ||
425 | } | |
426 | ||
427 | }; | |
428 | ||
429 | /** PRIVATE FUNCTIONS **/ | |
430 | ||
431 | 1 | function sendUpdateRoom(room, socket) { |
432 | 21 | if(!socket) { |
433 | 19 | __app.io.to("lobby").emit("roomUpdated", room.getPublicInfo()); |
434 | } | |
435 | else { | |
436 | 2 | socket.broadcast.to("lobby").emit("roomUpdated", room.getPublicInfo()); |
437 | } | |
438 | } | |
439 |
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 | 1 | var git = require("git-repo-info"); |
4 | ||
5 | // we want to define this only one time | |
6 | 1 | var repo = git(".git"); |
7 | 1 | if(repo.branch) { |
8 | 1 | repo.url = require("/home/lesterpig/workspace/openparty/lib/../package.json").repository.url.replace(/\.git$/, "") + "/commit/" + repo.sha; |
9 | } | |
10 | ||
11 | 1 | module.exports = function(app) { |
12 | ||
13 | /** | |
14 | * ROOT | |
15 | */ | |
16 | 1 | app.get("/", function(req, res) { |
17 | 1 | req.session.locale = req.getLocale(); |
18 | 1 | res.render("index", {passwordRequired: __conf.password !== null }); |
19 | }); | |
20 | ||
21 | /*** | |
22 | * ABOUT | |
23 | */ | |
24 | 1 | app.get("/about", function(req, res) { |
25 | 1 | res.render("about", {repo: repo, version: __version, locales: __conf.locales, gamemodes: __staticGametypes }); |
26 | }); | |
27 | ||
28 | /** | |
29 | * IO : Ping | |
30 | */ | |
31 | 1 | app.io.route("ping", function(socket) { |
32 | 1 | socket.volatile.emit("pong"); |
33 | }); | |
34 | ||
35 | /** | |
36 | * IO : Disconnect | |
37 | */ | |
38 | 1 | app.io.route("disconnect", function(socket) { |
39 | ||
40 | 4 | var room = utils.isInGame(socket); |
41 | 5 | if(!room) return; |
42 | ||
43 | 3 | rooms.leaveRoom(socket); |
44 | }); | |
45 | ||
46 | /** | |
47 | * IO : Login | |
48 | */ | |
49 | 1 | app.io.route("login", function(socket, data) { |
50 | ||
51 | 18 | var locale = socket.session.locale || __conf.defaultLocale; |
52 | ||
53 | 18 | if(data.password !== __conf.password && __conf.password !== null || !data.username) { |
54 | 4 | return socket.emit("loginResult", {err: __i18n({phrase: "Bad Credentials", locale: locale})}); |
55 | } | |
56 | ||
57 | // Check username | |
58 | 14 | try { |
59 | 14 | var username = utils.checkUsername(data.username); |
60 | } catch(e) { | |
61 | 5 | return socket.emit("loginResult", {err: __i18n({phrase: e.message, locale: locale})}); |
62 | } | |
63 | ||
64 | 9 | socket.join("lobby"); |
65 | 9 | socket.username = username; |
66 | ||
67 | 9 | socket.emit("loginResult", {err: null, username: username, gametypes: __staticGametypes}); |
68 | }); | |
69 | ||
70 | /** | |
71 | * IO : Get Rooms | |
72 | */ | |
73 | 1 | app.io.route("getRooms", function(socket) { |
74 | 2 | if(!utils.isInRoom(socket, "lobby")) |
75 | 1 | return; |
76 | 1 | socket.emit("roomsList", rooms.getRooms()); |
77 | }); | |
78 | ||
79 | /** | |
80 | * IO : Create Room | |
81 | */ | |
82 | 1 | app.io.route("createRoom", function(socket, data) { |
83 | 6 | if(!utils.isInRoom(socket, "lobby")) |
84 | 1 | return; |
85 | 5 | if(utils.isInGame(socket)) |
86 | 1 | return; |
87 | 4 | rooms.createRoom(data.name, data.password, data.type, socket); |
88 | }); | |
89 | ||
90 | /** | |
91 | * IO : Join Room | |
92 | */ | |
93 | 1 | app.io.route("joinRoom", function(socket, data) { |
94 | 11 | if(!utils.isInRoom(socket, "lobby")) |
95 | 1 | return; |
96 | 10 | if(utils.isInGame(socket)) |
97 | 1 | return; |
98 | 9 | rooms.joinRoom(data.id, data.password, socket); |
99 | }); | |
100 | ||
101 | /** | |
102 | * IO : Leave Room | |
103 | */ | |
104 | 1 | app.io.route("leaveRoom", function(socket) { |
105 | 9 | rooms.leaveRoom(socket); |
106 | }); | |
107 | ||
108 | /** | |
109 | * IO : Kick Player | |
110 | */ | |
111 | 1 | app.io.route("kickPlayer", function(socket, username) { |
112 | ||
113 | 0 | var room = utils.isInGame(socket); |
114 | 0 | var player; |
115 | 0 | if( !room |
116 | || room.started | |
117 | || room.players[0] !== socket | |
118 | || !(player = room.resolveUsername(username))) { | |
119 | 0 | return; |
120 | } | |
121 | 0 | room.message(username + " has been kicked out of this room."); |
122 | 0 | rooms.leaveRoom(player); |
123 | }); | |
124 | ||
125 | /** | |
126 | * IO : Room Parameters | |
127 | */ | |
128 | 1 | app.io.route("setRoomSize", function(socket, data) { |
129 | 4 | var room = utils.isInGame(socket); |
130 | 4 | if(room && room.players[0] === socket && room.players.length <= +data) |
131 | 3 | room.setSize(+data); |
132 | }); | |
133 | ||
134 | 1 | app.io.route("setRoomParameter", function(socket, data) { |
135 | 4 | var room = utils.isInGame(socket); |
136 | 4 | if(room && room.players[0] === socket) |
137 | 3 | room.setParameter(data.parameter, data.value, socket); |
138 | }); | |
139 | ||
140 | /** | |
141 | * IO : Chat Management | |
142 | */ | |
143 | 1 | app.io.route("sendMessage", function(socket, data) { |
144 | 27 | var room = utils.isInGame(socket); |
145 | 27 | var message = utils.sanitizeHtml(data.message); |
146 | 27 | if(!message) |
147 | 3 | return; |
148 | 24 | if(room) |
149 | 24 | room.sendMessage(data.channel, message, socket); |
150 | }); | |
151 | ||
152 | /** | |
153 | * IO : Start Game ! | |
154 | */ | |
155 | 1 | app.io.route("startRoom", function(socket) { |
156 | 3 | var room = utils.isInGame(socket); |
157 | 3 | if(room && room.players[0] === socket && !room.started && room.size === room.players.length) |
158 | 2 | room.start(); |
159 | }); | |
160 | ||
161 | /** | |
162 | * IO : Execute Action | |
163 | */ | |
164 | 1 | app.io.route("executeAction", function(socket, data) { |
165 | ||
166 | 18 | if(!socket.player || !data || !data.action) |
167 | 3 | return; // Not authorized |
168 | ||
169 | 15 | var actions = socket.player.getAvailableActions(false); |
170 | ||
171 | 15 | if(!actions[data.action] || actions[data.action].locked) |
172 | 2 | return; // Not ready for this action |
173 | ||
174 | 13 | if(actions[data.action].type === "select" |
175 | && actions[data.action].options.safeChoices.indexOf(data.value) < 0) | |
176 | 7 | return; // Bad value |
177 | ||
178 | 6 | if(actions[data.action].type === "select" |
179 | && actions[data.action].options.choices === "players") { | |
180 | 1 | data.value = socket.player.room.resolveUsername(data.value).player; |
181 | } | |
182 | ||
183 | 6 | actions[data.action].locked = true; |
184 | 6 | actions[data.action].execute(socket.player, data.value); |
185 | 6 | actions[data.action].locked = false; |
186 | }); | |
187 | } | |
188 |
Line | Hits | Source |
---|---|---|
1 | 1 | var crypto = require("crypto"); |
2 | ||
3 | 1 | global.GET_RANDOM = function(from, to) { |
4 | 1027 | 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 |