]>
Commit | Line | Data |
---|---|---|
1ea98fd5 | 1 | -- labspace 1.0 |
2 | -- Copyright (C) 2011 Gunnar Beutner | |
3 | -- | |
4 | -- This program is free software; you can redistribute it and/or | |
5 | -- modify it under the terms of the GNU General Public License | |
6 | -- as published by the Free Software Foundation; either version 2 | |
7 | -- of the License, or (at your option) any later version. | |
8 | -- | |
9 | -- This program is distributed in the hope that it will be useful, | |
10 | -- but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | -- GNU General Public License for more details. | |
13 | -- | |
14 | -- You should have received a copy of the GNU General Public License | |
15 | -- along with this program; if not, write to the Free Software | |
16 | -- Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |
17 | ||
1ea98fd5 | 18 | local BOTNICK = "labspace" |
19 | local BOTACCOUNT = "labspace" | |
20 | local BOTACCOUNTID = 5022574 | |
3b38a98a | 21 | local HOMECHANNEL = "#labspace" |
1ea98fd5 | 22 | local MINPLAYERS = 6 |
23 | local MAXPLAYERS = 30 | |
24 | local DEBUG = false | |
25 | local DB = "labspace.db" | |
26 | ||
27 | local KILLMESSAGES = { | |
28 | "was brutally murdered.", | |
29 | "was vaporized by the scientist's death ray.", | |
30 | "slipped into a coma after drinking their poison-laced morning coffee.", | |
31 | "was crushed to death by a 5-ton boulder.", | |
73e8ef30 | 32 | "couldn't escape from the scientist's killbot army.", |
33 | "was torn apart by a group of ferocious labrats.", | |
34 | "was sucked into an artificial black hole.", | |
ba2a0251 | 35 | "took a bath in concentrated sulfuric acid.", |
c338ac05 | 36 | "'s house was leveled by an orbital ion cannon.", |
37 | "got baked and there was no cake.", | |
38 | "was pushed into a portal leading to the sun." | |
1ea98fd5 | 39 | } |
40 | ||
41 | local ls_bot | |
3b38a98a | 42 | local ls_hlbot |
1ea98fd5 | 43 | local ls_gamestate = {} |
44 | local ls_db = {} | |
45 | local ls_lastsave = 0 | |
6fbcf5fd | 46 | local ls_lastalivecheck = 0 |
1ea98fd5 | 47 | local ls_sched = Scheduler() |
48 | ||
49 | function onload() | |
50 | ls_dbload() | |
51 | onconnect() | |
52 | end | |
53 | ||
54 | function onunload() | |
55 | ls_dbsave() | |
56 | end | |
57 | ||
58 | function onconnect() | |
3b38a98a | 59 | ls_bot = irc_localregisteruserid(BOTNICK, "labspace", "brought.to.you.by.science", "For science!", BOTACCOUNT, BOTACCOUNTID, "+iXr", gamehandler) |
1ea98fd5 | 60 | ls_join_channels() |
3b38a98a | 61 | |
a35de60a | 62 | ls_hlbot = irc_localregisteruser("hl-" .. BOTNICK, "will.spam", "for.food", "Got some change?", "labspace-hl", "+iX", highlighthandler) |
3b38a98a | 63 | irc_localjoin(ls_hlbot, HOMECHANNEL) |
1ea98fd5 | 64 | end |
65 | ||
66 | function ls_join_channels() | |
659137f7 | 67 | local channel = irctolower(HOMECHANNEL) |
68 | ls_add_channel(channel) | |
1ea98fd5 | 69 | |
70 | for _, channel in pairs(ls_db.channels) do | |
71 | if not ls_is_game_channel(channel) then | |
72 | ls_add_channel(channel) | |
73 | end | |
74 | end | |
75 | end | |
76 | ||
77 | function ls_split_message(message) | |
78 | message, _ = message:gsub("^ +", "") | |
79 | message, _ = message:gsub(" +", " ") | |
80 | message, _ = message:gsub(" +$", "") | |
81 | ||
82 | local tokens = {} | |
83 | ||
84 | for token in string.gmatch(message, "%S+") do | |
85 | table.insert(tokens, token) | |
86 | end | |
87 | ||
88 | return tokens | |
89 | end | |
90 | ||
3b38a98a | 91 | function gamehandler(target, revent, ...) |
1ea98fd5 | 92 | if revent == "irc_onchanmsg" then |
93 | local numeric, channel, message = ... | |
94 | ||
659137f7 | 95 | channel = irctolower(channel) |
96 | ||
1ea98fd5 | 97 | if not ls_is_game_channel(channel) then |
98 | return | |
99 | end | |
100 | ||
101 | ls_keepalive(channel, numeric) | |
102 | ||
103 | local tokens = ls_split_message(message) | |
099a790b | 104 | local command = tokens[1]:lower() |
1ea98fd5 | 105 | |
106 | if command then | |
107 | if command == "!add" then | |
108 | ls_cmd_add(channel, numeric) | |
109 | elseif command == "!remove" then | |
110 | ls_cmd_remove(channel, numeric) | |
111 | elseif command == "!wait" then | |
112 | ls_cmd_wait(channel, numeric) | |
113 | elseif command == "!start" then | |
114 | ls_cmd_start(channel, numeric) | |
115 | elseif command == "!status" then | |
116 | ls_cmd_status(channel, numeric) | |
223b51b5 | 117 | elseif command == "!help" then |
cb26e5a2 | 118 | ls_cmd_help(channel, numeric) |
1ea98fd5 | 119 | elseif command == "!hl" then |
120 | ls_cmd_hl(channel, numeric) | |
93a44b51 | 121 | elseif command == "!enable" then |
122 | ls_cmd_enable(channel, numeric) | |
123 | elseif command == "!disable" then | |
124 | ls_cmd_disable(channel, numeric) | |
1ea98fd5 | 125 | end |
126 | ||
127 | ls_flush_modes(channel) | |
128 | end | |
45dc5011 | 129 | elseif revent == "irc_onmsg" or revent == "irc_onnotice" then |
1ea98fd5 | 130 | local numeric, message = ... |
131 | ||
132 | local tokens = ls_split_message(message) | |
133 | ||
099a790b | 134 | local command = tokens[1]:lower() |
1ea98fd5 | 135 | local argument = tokens[2] |
136 | ||
137 | if command then | |
138 | if command == "kill" then | |
139 | ls_cmd_kill(numeric, argument) | |
140 | elseif command == "investigate" then | |
141 | ls_cmd_investigate(numeric, argument) | |
142 | elseif command == "vote" then | |
143 | ls_cmd_vote(numeric, argument) | |
04f34aee | 144 | elseif command == "guard" then |
145 | ls_cmd_guard(numeric, argument) | |
fa947f3a | 146 | elseif command == "stats" then |
147 | ls_cmd_stats(numeric, argument) | |
d6560b7b | 148 | elseif command == "help" then |
149 | ls_cmd_msghelp(numeric, argument) | |
150 | elseif command == "showcommands" then | |
151 | ls_cmd_msgshowcommands(numeric, argument) | |
1ea98fd5 | 152 | elseif command == "smite" and onstaff(numeric) then |
153 | ls_cmd_smite(numeric, argument) | |
5601e62e | 154 | elseif command == "killgame" and onstaff(numeric) then |
155 | ls_cmd_killgame(numeric, argument) | |
85446399 | 156 | elseif command == "killmessage" and onstaff(numeric) then |
157 | local message | |
158 | ||
159 | if table.getn(tokens) > 2 then | |
160 | message = table.concat(tokens, " ", 3) | |
161 | end | |
162 | ||
163 | ls_cmd_killmessage(numeric, argument, message) | |
1ea98fd5 | 164 | elseif command == "addchan" and ontlz(numeric) then |
165 | ls_cmd_addchan(numeric, argument) | |
166 | elseif command == "delchan" and ontlz(numeric) then | |
167 | ls_cmd_delchan(numeric, argument) | |
d6560b7b | 168 | else |
169 | ls_notice(numeric, "Not sure which command you're looking for, try /msg " .. BOTNICK .. " showcommands.") | |
1ea98fd5 | 170 | end |
171 | end | |
172 | elseif revent == "irc_onkilled" then | |
173 | ls_bot = nil | |
174 | ls_gamestate = {} | |
175 | elseif revent == "irc_onkillreconnect" then | |
9a541dd9 | 176 | ls_bot = target |
177 | ls_join_channels() | |
1ea98fd5 | 178 | end |
179 | end | |
180 | ||
3b38a98a | 181 | function highlighthandler(target, revent, ...) |
182 | if revent == "irc_onkilled" then | |
183 | ls_hlbot = nil | |
184 | elseif revent == "irc_onkillreconnect" then | |
185 | ls_hlbot = target | |
186 | ls_localjoin(ls_hlbot, HOMECHANNEL) | |
187 | end | |
188 | end | |
189 | ||
1ea98fd5 | 190 | function irc_onpart(channel, numeric, message) |
659137f7 | 191 | channel = irctolower(channel) |
192 | ||
1ea98fd5 | 193 | if not ls_is_game_channel(channel) then |
194 | return | |
195 | end | |
196 | ||
197 | if ls_get_role(channel, numeric) then | |
fa947f3a | 198 | if ls_get_role(channel, numeric) ~= "lobby" then |
199 | ls_incr_stats_user(numeric, "killed_suicide") | |
200 | end | |
201 | ||
1ea98fd5 | 202 | ls_remove_player(channel, numeric) |
203 | ls_advance_state(channel) | |
204 | end | |
205 | end | |
206 | ||
207 | function irc_onkick(channel, kicked_numeric, kicker_numeric, message) | |
659137f7 | 208 | channel = irctolower(channel) |
209 | ||
1ea98fd5 | 210 | if not ls_is_game_channel(channel) then |
211 | return | |
212 | end | |
213 | ||
214 | if ls_bot == kicked_numeric then | |
215 | ls_remove_channel(channel) | |
216 | return | |
217 | end | |
218 | ||
219 | if ls_get_role(channel, kicked_numeric) then | |
fa947f3a | 220 | if ls_get_role(channel, numeric) ~= "lobby" then |
221 | ls_incr_stats_user(numeric, "killed_suicide") | |
222 | end | |
223 | ||
1ea98fd5 | 224 | ls_remove_player(channel, kicked_numeric) |
225 | ls_advance_state(channel) | |
226 | end | |
227 | end | |
228 | irc_onkickall = irc_onkick | |
229 | ||
230 | function irc_onquit(numeric) | |
231 | for channel, _ in pairs(ls_gamestate) do | |
232 | if ls_get_role(channel, numeric) then | |
fa947f3a | 233 | if ls_get_role(channel, numeric) ~= "lobby" then |
234 | ls_incr_stats_user(numeric, "killed_suicide") | |
235 | end | |
236 | ||
1ea98fd5 | 237 | ls_remove_player(channel, numeric) |
238 | ls_advance_state(channel) | |
239 | end | |
240 | end | |
241 | end | |
242 | ||
243 | function ontick() | |
244 | for channel, _ in pairs(ls_gamestate) do | |
245 | ls_advance_state(channel, true) | |
246 | ls_flush_modes(channel) | |
247 | end | |
248 | ||
6fbcf5fd | 249 | if ls_lastalivecheck < os.time() - 30 then |
250 | ls_lastalivecheck = os.time() | |
251 | ||
252 | for channel, _ in pairs(ls_gamestate) do | |
253 | ls_check_alive(channel) | |
254 | end | |
255 | end | |
256 | ||
1ea98fd5 | 257 | if ls_lastsave < os.time() - 60 then |
258 | ls_lastsave = os.time() | |
259 | ls_dbsave() | |
260 | end | |
261 | end | |
262 | ||
263 | -- sends a debug message | |
264 | function ls_debug(channel, message) | |
265 | if DEBUG then | |
266 | irc_localchanmsg(ls_bot, channel, "DEBUG: " .. message) | |
267 | end | |
268 | end | |
269 | ||
270 | -- sends a notice to the specified target | |
271 | function ls_notice(numeric, text) | |
272 | irc_localnotice(ls_bot, numeric, text) | |
273 | end | |
274 | ||
275 | -- sends a message to the specified target | |
276 | function ls_chanmsg(channel, text) | |
277 | irc_localchanmsg(ls_bot, channel, text) | |
278 | end | |
279 | ||
280 | -- formats the specified role identifier for output in a message | |
281 | function ls_format_role(role) | |
282 | if role == "scientist" then | |
283 | return "Mad Scientist" | |
284 | elseif role == "investigator" then | |
285 | return "Investigator" | |
286 | elseif role == "citizen" then | |
287 | return "Citizen" | |
288 | elseif role == "lobby" then | |
289 | return "Lobby" | |
290 | else | |
291 | return "Unknown Role" | |
292 | end | |
293 | end | |
294 | ||
87fa5be3 | 295 | -- formats the specified trait identifier for output in a message |
296 | function ls_format_trait(trait) | |
297 | if trait == "teleporter" then | |
298 | return "Personal Teleporter" | |
40996a22 | 299 | elseif trait == "infested" then |
87fa5be3 | 300 | return "Alien Parasite" |
301 | elseif trait == "force" then | |
302 | return "Force Field Generator" | |
303 | else | |
304 | return "Unknown Trait" | |
305 | end | |
306 | end | |
307 | ||
1ea98fd5 | 308 | -- formats the specified player name for output in a message (optionally |
87fa5be3 | 309 | -- revealing that player's role and their traits in the game) |
310 | function ls_format_player(channel, numeric, reveal_role, reveal_traits) | |
1ea98fd5 | 311 | local nick = irc_getnickbynumeric(numeric) |
312 | local result = "\002" .. nick.nick .. "\002" | |
313 | ||
87fa5be3 | 314 | if reveal_role then |
315 | result = result .. " (" .. ls_format_role(ls_get_role(channel, numeric)) | |
316 | ||
317 | if reveal_traits then | |
318 | for _, trait in pairs(ls_get_traits(channel, numeric)) do | |
301d987f | 319 | if ls_get_trait(channel, numeric, trait) then |
320 | result = result .. ", " .. ls_format_trait(trait) | |
321 | end | |
87fa5be3 | 322 | end |
323 | end | |
324 | ||
325 | result = result .. ")" | |
1ea98fd5 | 326 | end |
327 | ||
328 | return result | |
329 | end | |
330 | ||
331 | -- formats a list of player names for output in a message (optionally | |
87fa5be3 | 332 | -- revealing their roles and traits in the game) |
333 | function ls_format_players(channel, numerics, reveal_role, reveal_traits, no_and) | |
1ea98fd5 | 334 | local i = 0 |
335 | local result = "" | |
336 | ||
337 | for _, numeric in pairs(numerics) do | |
338 | if i ~= 0 then | |
339 | if not no_and and i == table.getn(numerics) - 1 then | |
340 | result = result .. " and " | |
341 | else | |
342 | result = result .. ", " | |
343 | end | |
344 | end | |
345 | ||
40996a22 | 346 | result = result .. ls_format_player(channel, numeric, reveal_role, reveal_traits) |
1ea98fd5 | 347 | i = i + 1 |
348 | end | |
349 | ||
350 | return result | |
351 | end | |
352 | ||
353 | -- returns the current state of the game | |
354 | function ls_get_state(channel) | |
355 | return ls_gamestate[channel]["state"] | |
356 | end | |
357 | ||
fa947f3a | 358 | function ls_get_startts(channel) |
359 | return ls_gamestate[channel]["startts"] | |
360 | end | |
361 | ||
1ea98fd5 | 362 | -- gets the timeout for the current state |
363 | function ls_get_timeout(channel) | |
364 | return ls_gamestate[channel]["timeout"] | |
365 | end | |
366 | ||
367 | -- gets the delay for the current state | |
368 | function ls_get_delay(channel) | |
369 | return ls_gamestate[channel]["delay"] | |
370 | end | |
371 | ||
f6812645 | 372 | -- gets the ts when !hl was last used |
373 | function ls_get_lasthl(channel) | |
374 | return ls_gamestate[channel]["lasthl"] | |
375 | end | |
376 | ||
93a44b51 | 377 | -- gets whether the bot is enabled |
378 | function ls_get_enabled(channel) | |
379 | return ls_gamestate[channel]["enabled"] | |
380 | end | |
381 | ||
1ea98fd5 | 382 | -- returns true if the game state delay was exceeded, false otherwise |
383 | function ls_delay_exceeded(channel) | |
384 | return ls_get_delay(channel) < os.time() | |
385 | end | |
386 | ||
387 | function ls_get_waitcount(channel) | |
388 | return ls_gamestate[channel]["waitcount"] | |
389 | end | |
390 | ||
7a0c3489 | 391 | function ls_get_round(channel) |
392 | return ls_gamestate[channel]["round"] | |
393 | end | |
394 | ||
1ea98fd5 | 395 | -- sets the game state |
396 | function ls_set_state(channel, state) | |
397 | ls_gamestate[channel]["state"] = state | |
398 | ||
399 | ls_set_timeout(channel, -1) | |
400 | ls_set_delay(channel, 30) | |
401 | end | |
402 | ||
fa947f3a | 403 | function ls_set_startts(channel, startts) |
404 | ls_gamestate[channel]["startts"] = startts | |
405 | end | |
406 | ||
1ea98fd5 | 407 | -- sets the game state timeout (in seconds) |
408 | function ls_set_timeout(channel, timeout) | |
409 | if timeout == -1 then | |
410 | ls_gamestate[channel]["timeout"] = -1 | |
411 | else | |
412 | ls_gamestate[channel]["timeout"] = os.time() + timeout | |
413 | end | |
414 | end | |
415 | ||
416 | -- sets the game state delay (in seconds) | |
417 | function ls_set_delay(channel, delay) | |
418 | ls_gamestate[channel]["delay"] = os.time() + delay | |
419 | ls_debug(channel, "changed gamestate delay to " .. delay) | |
420 | end | |
421 | ||
f6812645 | 422 | -- sets the !hl timestamp |
423 | function ls_set_lasthl(channel, ts) | |
424 | ls_gamestate[channel]["lasthl"] = ts | |
425 | end | |
426 | ||
93a44b51 | 427 | -- sets whether the bot is enabled |
428 | function ls_set_enabled(channel, enabled) | |
429 | ls_gamestate[channel]["enabled"] = enabled | |
430 | end | |
431 | ||
1ea98fd5 | 432 | function ls_set_waitcount(channel, count) |
433 | ls_gamestate[channel]["waitcount"] = count | |
434 | end | |
435 | ||
7a0c3489 | 436 | function ls_set_round(channel, number) |
437 | ls_gamestate[channel]["round"] = number | |
438 | end | |
439 | ||
1ea98fd5 | 440 | -- returns true if the game state timeout was exceeded, false otherwise |
441 | function ls_timeout_exceeded(channel) | |
442 | local timeout = ls_get_timeout(channel) | |
443 | ||
444 | return timeout ~= -1 and timeout < os.time() | |
445 | end | |
446 | ||
447 | -- returns true if there's a game in progress, false otherwise | |
448 | function ls_game_in_progress(channel) | |
449 | return ls_get_state(channel) ~= "lobby" | |
450 | end | |
451 | ||
452 | -- returns the name of the channel the specified nick is playing on | |
453 | -- if the nick isn't playing any games nil is returned instead | |
454 | function ls_chan_for_numeric(numeric) | |
455 | for channel, _ in pairs(ls_gamestate) do | |
456 | if ls_get_role(channel, numeric) then | |
457 | return channel | |
458 | end | |
459 | end | |
460 | ||
461 | return nil | |
462 | end | |
463 | ||
fa947f3a | 464 | function ls_get_stats_channel(channel, key) |
465 | if not ls_db.stats_channel then | |
466 | return 0 | |
467 | end | |
468 | ||
469 | if not ls_db.stats_channel[channel] then | |
470 | return 0 | |
471 | end | |
472 | ||
473 | return ls_db.stats_channel[channel][key] | |
474 | end | |
475 | ||
476 | function ls_incr_stats_channel(channel, key, num) | |
477 | if not ls_db.stats_channel then | |
478 | ls_db.stats_channel = {} | |
479 | end | |
480 | ||
481 | if not ls_db.stats_channel[channel] then | |
482 | ls_db.stats_channel[channel] = {} | |
483 | end | |
484 | ||
485 | local value = ls_db.stats_channel[channel][key] | |
486 | ||
487 | if not value then | |
488 | value = 0 | |
489 | end | |
490 | ||
491 | if num then | |
492 | value = value + num | |
493 | else | |
494 | value = value + 1 | |
495 | end | |
496 | ||
497 | ls_db.stats_channel[channel][key] = value | |
498 | end | |
499 | ||
500 | function ls_get_stats_user(numeric, key) | |
501 | if not ls_db.stats_user then | |
502 | return 0 | |
503 | end | |
504 | ||
505 | local nick = irc_getnickbynumeric(numeric) | |
506 | local accountid = nick.accountid | |
507 | ||
508 | if not accountid then | |
509 | accountid = -1 | |
510 | end | |
511 | ||
512 | if not ls_db.stats_user[accountid] then | |
513 | return 0 | |
514 | end | |
515 | ||
516 | if not ls_db.stats_user[accountid][key] then | |
517 | return 0 | |
518 | end | |
519 | ||
520 | return ls_db.stats_user[accountid][key] | |
521 | end | |
522 | ||
523 | function ls_get_stats_user_aggregate(key) | |
524 | if not ls_db.stats_user then | |
525 | return 0 | |
526 | end | |
527 | ||
528 | local value = 0 | |
529 | ||
530 | for accountid, _ in pairs(ls_db.stats_user) do | |
531 | if ls_db.stats_user[accountid][key] then | |
532 | value = value + ls_db.stats_user[accountid][key] | |
533 | end | |
534 | end | |
535 | ||
536 | return value | |
537 | end | |
538 | ||
539 | function ls_incr_stats_user(numeric, key, num) | |
540 | local nick = irc_getnickbynumeric(numeric) | |
541 | local accountid = nick.accountid | |
542 | ||
543 | if not accountid then | |
544 | accountid = -1 | |
545 | end | |
546 | ||
547 | if not ls_db.stats_user then | |
548 | ls_db.stats_user = {} | |
549 | end | |
550 | ||
551 | if not ls_db.stats_user[accountid] then | |
552 | ls_db.stats_user[accountid] = {} | |
553 | end | |
554 | ||
555 | local value = ls_db.stats_user[accountid][key] | |
556 | ||
557 | if not value then | |
558 | value = 0 | |
559 | end | |
560 | ||
561 | if num then | |
562 | value = value + num | |
563 | else | |
564 | value = value + 1 | |
565 | end | |
566 | ||
567 | ls_db.stats_user[accountid][key] = value | |
568 | end | |
569 | ||
85446399 | 570 | function ls_get_killmessage(numeric) |
571 | if not ls_db.killmessages then | |
572 | return nil | |
573 | end | |
574 | ||
575 | local nick = irc_getnickbynumeric(numeric) | |
576 | local accountid = nick.accountid | |
577 | ||
578 | if not accountid then | |
579 | return nil | |
580 | end | |
581 | ||
582 | return ls_db.killmessages[accountid] | |
583 | end | |
584 | ||
585 | function ls_set_killmessage(numeric, message) | |
586 | local nick = irc_getnickbynumeric(numeric) | |
587 | local accountid = nick.accountid | |
588 | ||
589 | if not accountid then | |
590 | return | |
591 | end | |
592 | ||
593 | if not ls_db.killmessages then | |
594 | ls_db.killmessages = {} | |
595 | end | |
596 | ||
597 | ls_db.killmessages[accountid] = message | |
598 | end | |
599 | ||
1ea98fd5 | 600 | function ls_cmd_add(channel, numeric) |
601 | ls_add_player(channel, numeric) | |
602 | end | |
603 | ||
604 | function ls_cmd_remove(channel, numeric) | |
605 | ls_remove_player(channel, numeric) | |
606 | end | |
607 | ||
608 | function ls_cmd_wait(channel, numeric) | |
609 | if ls_game_in_progress(channel) then | |
610 | ls_notice(numeric, "Sorry, there's no lobby at the moment.") | |
611 | return | |
612 | end | |
613 | ||
614 | if table.getn(ls_get_players(channel)) >= MINPLAYERS then | |
615 | local count = ls_get_waitcount(channel) | |
616 | ||
617 | if count >= 2 then | |
618 | ls_notice(numeric, "Sorry, the timeout can only be extended twice per game.") | |
619 | return | |
620 | end | |
621 | ||
622 | ls_set_waitcount(channel, count + 1) | |
623 | end | |
624 | ||
625 | if not ls_get_role(channel, numeric) then | |
626 | ls_notice(numeric, "Sorry, you need to be in the lobby to use this command.") | |
627 | return | |
628 | end | |
629 | ||
630 | ls_set_timeout(channel, 120) | |
631 | ls_set_delay(channel, 45) | |
632 | ||
633 | ls_chanmsg(channel, "Lobby timeout was reset.") | |
634 | end | |
635 | ||
636 | function ls_cmd_start(channel, numeric) | |
637 | if ls_game_in_progress(channel) then | |
638 | ls_notice(numeric, "Sorry, there's no lobby at the moment.") | |
639 | return | |
640 | end | |
641 | ||
642 | if not ls_get_role(channel, numeric) then | |
643 | ls_notice(numeric, "Sorry, you need to be in the lobby to use this command.") | |
644 | return | |
645 | end | |
646 | ||
647 | ls_advance_state(channel) | |
648 | ||
649 | ls_flush_modes(channel) | |
650 | end | |
651 | ||
652 | function ls_cmd_status(channel, numeric) | |
653 | if not ls_get_role(channel, numeric) then | |
654 | ls_notice(numeric, "Sorry, you need to be in the lobby to use this command.") | |
655 | return | |
656 | end | |
657 | ||
c37305b5 | 658 | ls_show_status(channel) |
6e5b9638 | 659 | end |
660 | ||
661 | ||
662 | function ls_show_status(channel) | |
1ea98fd5 | 663 | ls_chanmsg(channel, "Players: " .. ls_format_players(channel, ls_get_players(channel))) |
664 | ||
665 | if ls_game_in_progress(channel) then | |
666 | ls_chanmsg(channel, "Roles: " .. | |
667 | table.getn(ls_get_players(channel, "scientist")) .. "x " .. ls_format_role("scientist") .. ", " .. | |
668 | table.getn(ls_get_players(channel, "investigator")) .. "x " .. ls_format_role("investigator") .. ", " .. | |
669 | table.getn(ls_get_players(channel, "citizen")) .. "x " .. ls_format_role("citizen")) | |
23c269fd | 670 | |
671 | if ls_get_state(channel) == "vote" then | |
672 | local voteresult = ls_get_vote_result(channel, false) | |
673 | if table.getn(voteresult.votees) > 0 then | |
674 | ls_show_votes(channel, voteresult, false) | |
675 | end | |
676 | end | |
1ea98fd5 | 677 | end |
678 | end | |
679 | ||
23c269fd | 680 | function ls_show_votes(channel, voteresult, final) |
681 | local prefix = "" | |
682 | ||
683 | if final then | |
684 | prefix = "Final" | |
685 | else | |
686 | prefix = "Current" | |
687 | end | |
688 | ||
689 | if table.getn(voteresult.votees) > 0 then | |
690 | ls_chanmsg(channel, prefix .. " votes: " .. ls_format_votes(voteresult.votes, voteresult.votees)) | |
691 | if not final and table.getn(voteresult.missing_votes) > 0 then | |
692 | ls_chanmsg(channel, "Participants that still need to vote: " .. ls_format_players(channel, voteresult.missing_votes)) | |
693 | end | |
694 | end | |
695 | end | |
696 | ||
223b51b5 | 697 | function ls_cmd_help(channel, numeric) |
698 | ls_notice(numeric, "Read the guide at http://goo.gl/XUyPf") | |
420cbafb | 699 | ls_notice(numeric, "If you have further questions, feel free to ask in " .. HOMECHANNEL) |
223b51b5 | 700 | end |
701 | ||
1ea98fd5 | 702 | function ls_cmd_hl(channel, numeric) |
703 | if ls_game_in_progress(channel) then | |
704 | ls_notice(numeric, "Sorry, there's no lobby at the moment.") | |
705 | return | |
706 | end | |
707 | ||
708 | if not ls_get_role(channel, numeric) then | |
709 | ls_notice(numeric, "Sorry, you need to be in the lobby to use this command.") | |
710 | return | |
711 | end | |
712 | ||
f6812645 | 713 | if ls_get_lasthl(channel) > os.time() - 300 then |
714 | ls_notice(numeric, "Sorry, you can only use that command once every 5 minute.") | |
715 | return | |
716 | end | |
717 | ||
3b38a98a | 718 | if string.lower(channel) ~= string.lower(HOMECHANNEL) then |
602e2249 | 719 | ls_notice(numeric, "Sorry, you can't use this command here.") |
bf938493 | 720 | return |
602e2249 | 721 | end |
722 | ||
f6812645 | 723 | ls_set_lasthl(channel, os.time()) |
724 | ||
1ea98fd5 | 725 | local numerics = {} |
726 | ||
727 | for nick in channelusers_iter(channel, { nickpusher.numeric }) do | |
728 | local numeric = nick[1] | |
729 | ||
730 | if not ls_get_role(channel, numeric) then | |
731 | table.insert(numerics, numeric) | |
732 | end | |
733 | ||
734 | if table.getn(numerics) > 10 then | |
87fa5be3 | 735 | irc_localchanmsg(ls_hlbot, channel, "HL: " .. ls_format_players(channel, numerics, false, false, true)) |
1ea98fd5 | 736 | numerics = {} |
737 | end | |
738 | end | |
739 | ||
740 | if table.getn(numerics) > 0 then | |
94454167 | 741 | irc_localchanmsg(ls_hlbot, channel, "HL: " .. ls_format_players(channel, numerics, false, false, true)) |
1ea98fd5 | 742 | end |
743 | end | |
744 | ||
93a44b51 | 745 | function ls_cmd_enable(channel, numeric) |
746 | local chanuser = irc_getuserchanmodes(numeric, channel) | |
747 | ||
9827ed00 | 748 | if (not chanuser or not chanuser.opped) and not onstaff(numeric) then |
749 | ls_notice(numeric, "You need to be opped to use this command.") | |
93a44b51 | 750 | return |
751 | end | |
752 | ||
bc12c2e1 | 753 | ls_set_enabled(channel, true) |
93a44b51 | 754 | ls_notice(numeric, "Game has been enabled.") |
755 | end | |
756 | ||
757 | function ls_cmd_disable(channel, numeric) | |
758 | local chanuser = irc_getuserchanmodes(numeric, channel) | |
759 | ||
9827ed00 | 760 | if (not chanuser or not chanuser.opped) and not onstaff(numeric) then |
761 | ls_notice(numeric, "You need to be opped to use this command.") | |
93a44b51 | 762 | return |
763 | end | |
764 | ||
765 | if ls_game_in_progress(channel) then | |
766 | ls_chanmsg(channel, ls_format_player(channel, numeric) .. " disabled the game.") | |
767 | end | |
768 | ||
769 | ls_stop_game(channel) | |
770 | ls_flush_modes(channel) | |
771 | ||
772 | ls_set_enabled(channel, false) | |
773 | ls_notice(numeric, "Game has been disabled.") | |
774 | end | |
775 | ||
1ea98fd5 | 776 | function ls_cmd_kill(numeric, victim) |
777 | if not victim then | |
778 | ls_notice(numeric, "Syntax: kill <nick>") | |
779 | return | |
780 | end | |
781 | ||
782 | local channel = ls_chan_for_numeric(numeric) | |
783 | ||
784 | if not channel then | |
785 | ls_notice(numeric, "You haven't joined any game lobby.") | |
786 | return | |
787 | end | |
788 | ||
789 | if ls_get_role(channel, numeric) ~= "scientist" then | |
790 | ls_notice(numeric, "You need to be a scientist to use this command.") | |
791 | return | |
792 | end | |
793 | ||
794 | if ls_get_state(channel) ~= "kill" then | |
795 | ls_notice(numeric, "Sorry, you can't use this command right now.") | |
796 | return | |
797 | end | |
798 | ||
799 | if not ls_get_active(channel, numeric) then | |
800 | ls_notice(numeric, "Sorry, it's not your turn to choose a victim.") | |
801 | return | |
802 | end | |
803 | ||
804 | local victimnick = irc_getnickbynick(victim) | |
805 | ||
806 | if not victimnick then | |
807 | ls_notice(numeric, "Sorry, I don't know who that is.") | |
808 | return | |
809 | end | |
810 | ||
811 | local victimnumeric = victimnick.numeric | |
812 | ||
813 | if not ls_get_role(channel, victimnumeric) then | |
814 | ls_notice(numeric, "Sorry, " .. ls_format_player(channel, victimnumeric) .. " isn't playing the game.") | |
815 | return | |
816 | end | |
817 | ||
818 | if math.random(100) > 85 then | |
fa947f3a | 819 | ls_incr_stats_user(numeric, "failed_chance") |
820 | ls_incr_stats_user(victimnumeric, "survived_chance") | |
821 | ||
1ea98fd5 | 822 | ls_chanmsg(channel, "The scientists' attack was not successful tonight. Nobody died.") |
04f34aee | 823 | elseif ls_get_guarded(channel, victimnumeric) then |
824 | for _, player in pairs(ls_get_players(channel)) do | |
825 | ls_set_trait(channel, player, "force", false) | |
826 | end | |
827 | ||
008a1b2a | 828 | ls_notice(victimnumeric, "You are no longer being protected by a \002force field\002.") |
04f34aee | 829 | ls_set_guarded(channel, victimnumeric, false) |
830 | ||
fa947f3a | 831 | ls_incr_stats_user(numeric, "failed_guarded") |
832 | ls_incr_stats_user(victimnumeric, "survived_guarded") | |
833 | ||
04f34aee | 834 | ls_chanmsg(channel, "The attack on " .. ls_format_player(channel, victimnumeric) .. " was deflected by a force field. The force field generator has now run out of power.") |
f96b460b | 835 | elseif ls_get_trait(channel, victimnumeric, "infested") then |
836 | ls_devoice_player(channel, numeric) | |
837 | ls_devoice_player(channel, victimnumeric) | |
838 | ||
730acd4f | 839 | ls_chanmsg(channel, "An alien bursts out of " .. ls_format_player(channel, victimnumeric, true) .. "'s chest just as " .. ls_format_player(channel, numeric, true) .. " was about to murder them, killing them both.") |
840 | ||
fa947f3a | 841 | ls_incr_stats_user(numeric, "kill_infested") |
842 | ls_incr_stats_user(numeric, "killed_scientist") | |
843 | ls_incr_stats_user(victimnumeric, "kill_scientist") | |
844 | ls_incr_stats_user(victimnumeric, "killed_infested") | |
845 | ||
f96b460b | 846 | ls_remove_player(channel, numeric, true) |
847 | ls_remove_player(channel, victimnumeric, true) | |
1ea98fd5 | 848 | else |
849 | ls_devoice_player(channel, victimnumeric) | |
850 | ||
851 | if numeric == victimnumeric then | |
fa947f3a | 852 | ls_incr_stats_user(numeric, "killed_suicide") |
853 | ||
1ea98fd5 | 854 | ls_chanmsg(channel, ls_format_player(channel, victimnumeric, true) .. " committed suicide.") |
855 | else | |
fa947f3a | 856 | ls_incr_stats_user(numeric, "kill_scientist") |
857 | ls_incr_stats_user(numeric, "killed_scientist") | |
858 | ||
1ea98fd5 | 859 | if ls_get_role(channel, victimnumeric) == "scientist" then |
860 | ls_chanmsg(channel, ls_format_player(channel, victimnumeric, true) .. " was brutally murdered. Oops.") | |
861 | else | |
862 | local killmessage = KILLMESSAGES[math.random(table.getn(KILLMESSAGES))] | |
863 | ||
85446399 | 864 | local custom_killmessage = ls_get_killmessage(victimnumeric) |
865 | ||
866 | if custom_killmessage and math.random(100) > 66 then | |
867 | killmessage = custom_killmessage | |
868 | end | |
869 | ||
e81c43c1 | 870 | local space = " " |
871 | ||
872 | if string.sub(killmessage, 1, 1) == "'" then | |
873 | space = "" | |
874 | end | |
875 | ||
876 | ls_chanmsg(channel, ls_format_player(channel, victimnumeric, true) .. space .. killmessage) | |
1ea98fd5 | 877 | end |
878 | end | |
879 | ||
880 | ls_remove_player(channel, victimnumeric, true) | |
881 | end | |
882 | ||
883 | ls_set_state(channel, "investigate") | |
884 | ls_advance_state(channel) | |
885 | ||
886 | ls_flush_modes(channel) | |
887 | end | |
888 | ||
889 | function ls_cmd_investigate(numeric, victim) | |
890 | if not victim then | |
891 | ls_notice(numeric, "Syntax: investigate <nick>") | |
892 | return | |
893 | end | |
894 | ||
895 | local channel = ls_chan_for_numeric(numeric) | |
896 | ||
897 | if not channel then | |
898 | ls_notice(numeric, "You haven't joined any game lobby.") | |
899 | return | |
900 | end | |
901 | ||
902 | if ls_get_role(channel, numeric) ~= "investigator" then | |
903 | ls_notice(numeric, "You need to be an investigator to use this command.") | |
904 | return | |
905 | end | |
906 | ||
907 | if ls_get_state(channel) ~= "investigate" then | |
908 | ls_notice(numeric, "Sorry, you can't use this command right now.") | |
909 | return | |
910 | end | |
911 | ||
72deecce | 912 | if not ls_get_active(channel, numeric) then |
913 | ls_notice(numeric, "Sorry, it's not your turn to choose an investigation target.") | |
914 | return | |
915 | end | |
916 | ||
1ea98fd5 | 917 | local victimnick = irc_getnickbynick(victim) |
918 | ||
919 | if not victimnick then | |
920 | ls_notice(numeric, "Sorry, I don't know who that is.") | |
921 | return | |
922 | end | |
923 | ||
924 | local victimnumeric = victimnick.numeric | |
925 | ||
926 | if not ls_get_role(channel, victimnumeric) then | |
927 | ls_notice(numeric, "Sorry, " .. ls_format_player(channel, victimnumeric) .. " isn't playing the game.") | |
928 | return | |
929 | end | |
930 | ||
931 | local investigators = ls_get_players(channel, "investigator") | |
932 | ||
933 | for _, investigator in pairs(investigators) do | |
934 | if investigator ~= numeric then | |
d6b0f776 | 935 | ls_notice(investigator, ls_format_player(channel, numeric) .. " investigated " .. ls_format_player(channel, victimnumeric) .. ". Their role is: " .. ls_format_role(ls_get_role(channel, victimnumeric))) |
1ea98fd5 | 936 | end |
937 | end | |
938 | ||
939 | if math.random(100) > 85 then | |
fa947f3a | 940 | ls_incr_stats_user(numeric, "investigate_revealed") |
941 | ||
1ea98fd5 | 942 | ls_chanmsg(channel, ls_format_player(channel, numeric) .. "'s fine detective work reveals " .. ls_format_player(channel, victimnumeric) .. "'s role: " .. ls_format_role(ls_get_role(channel, victimnumeric))) |
943 | end | |
944 | ||
fa947f3a | 945 | ls_incr_stats_user(numeric, "investigate_" .. ls_get_role(channel, victimnumeric)) |
946 | ls_incr_stats_user(victimnumeric, "investigate_target") | |
947 | ||
1ea98fd5 | 948 | if numeric == victimnumeric then |
949 | ls_notice(numeric, "You're the investigator. Excellent detective work!") | |
950 | else | |
951 | ls_notice(numeric, ls_format_player(channel, victimnumeric) .. "'s role is: " .. ls_format_role(ls_get_role(channel, victimnumeric))) | |
952 | end | |
953 | ||
954 | ls_set_state(channel, "vote") | |
955 | ls_advance_state(channel) | |
956 | ||
957 | ls_flush_modes(channel) | |
958 | end | |
959 | ||
960 | function ls_cmd_vote(numeric, victim) | |
961 | if not victim then | |
962 | ls_notice(numeric, "Syntax: vote <nick>") | |
963 | return | |
964 | end | |
965 | ||
966 | local channel = ls_chan_for_numeric(numeric) | |
967 | ||
968 | if not channel then | |
969 | ls_notice(numeric, "You haven't joined any game lobby.") | |
970 | return | |
971 | end | |
972 | ||
973 | if ls_get_state(channel) ~= "vote" then | |
974 | ls_notice(numeric, "Sorry, you can't use this command right now.") | |
975 | return | |
976 | end | |
977 | ||
978 | local victimnick = irc_getnickbynick(victim) | |
979 | ||
980 | if not victimnick then | |
981 | ls_notice(numeric, "Sorry, I don't know who that is.") | |
982 | return | |
983 | end | |
984 | ||
985 | local victimnumeric = victimnick.numeric | |
986 | ||
987 | if not ls_get_role(channel, victimnumeric) then | |
988 | ls_notice(numeric, "Sorry, " .. ls_format_player(channel, victimnumeric) .. " isn't playing the game.") | |
989 | return | |
990 | end | |
991 | ||
992 | if ls_get_vote(channel, numeric) == victimnumeric then | |
993 | ls_notice(numeric, "You already voted for " .. ls_format_player(channel, victimnumeric) .. ".") | |
994 | return | |
995 | end | |
996 | ||
40996a22 | 997 | ls_keepalive(channel, numeric) |
e85e0931 | 998 | |
1ea98fd5 | 999 | ls_set_vote(channel, numeric, victimnumeric) |
1000 | ls_notice(numeric, "Done.") | |
1001 | ||
1002 | ls_advance_state(channel) | |
1003 | ||
1004 | ls_flush_modes(channel) | |
1005 | end | |
1006 | ||
04f34aee | 1007 | function ls_cmd_guard(numeric, victim) |
1008 | if not victim then | |
99a09462 | 1009 | ls_notice(numeric, "Syntax: guard <nick>") |
04f34aee | 1010 | return |
1011 | end | |
1012 | ||
1013 | local channel = ls_chan_for_numeric(numeric) | |
1014 | ||
1015 | if not channel then | |
1016 | ls_notice(numeric, "You haven't joined any game lobby.") | |
1017 | return | |
1018 | end | |
1019 | ||
1020 | if not ls_get_trait(channel, numeric, "force") then | |
1021 | ls_notice(numeric, "Sorry, you need the force field generator to use this command.") | |
1022 | return | |
1023 | end | |
1024 | ||
04f34aee | 1025 | local victimnick = irc_getnickbynick(victim) |
1026 | ||
1027 | if not victimnick then | |
1028 | ls_notice(numeric, "Sorry, I don't know who that is.") | |
1029 | return | |
1030 | end | |
1031 | ||
1032 | local victimnumeric = victimnick.numeric | |
1033 | ||
1034 | if not ls_get_role(channel, victimnumeric) then | |
1035 | ls_notice(numeric, "Sorry, " .. ls_format_player(channel, victimnumeric) .. " isn't playing the game.") | |
1036 | return | |
1037 | end | |
1038 | ||
1039 | local target | |
1040 | ||
1041 | if victimnumeric == numeric then | |
1042 | target = "yourself" | |
1043 | else | |
1044 | target = ls_format_player(channel, victimnumeric) | |
1045 | end | |
1046 | ||
1047 | for _, player in pairs(ls_get_players(channel)) do | |
9e09e371 | 1048 | if ls_get_guarded(channel, player) then |
1049 | if player == victimnumeric then | |
c5b6021d | 1050 | ls_notice(numeric, "You are already protecting " .. target .. ".") |
9e09e371 | 1051 | return |
1052 | end | |
1053 | ||
f6544438 | 1054 | local previous_Target |
1055 | ||
1056 | if player == numeric then | |
1057 | previous_target = "yourself" | |
1058 | else | |
1059 | previous_target = ls_format_player(channel, player) | |
1060 | end | |
1061 | ||
1062 | ls_notice(numeric, "You are no longer protecting " .. previous_target .. ".") | |
c5b6021d | 1063 | |
1064 | if numeric ~= player then | |
1065 | ls_notice(player, "You are no longer being protected by a \002force field\002.") | |
1066 | end | |
9e09e371 | 1067 | end |
1068 | ||
04f34aee | 1069 | ls_set_guarded(channel, player, (player == victimnumeric)) |
1070 | end | |
1071 | ||
b517f6d2 | 1072 | ls_notice(victimnumeric, "A field of energy envelops you. You are now protected by a \002force field\002.") |
9e09e371 | 1073 | |
1074 | if numeric ~= victimnumeric then | |
1075 | ls_notice(numeric, "You are now protecting " .. target .. ".") | |
1076 | end | |
04f34aee | 1077 | end |
1078 | ||
fa947f3a | 1079 | function round(num, idp) |
1080 | local mult = 10^(idp or 0) | |
1081 | return math.floor(num * mult + 0.5) / mult | |
1082 | end | |
1083 | ||
1084 | function ls_cmd_stats(numeric, victim) | |
1085 | local getter | |
1086 | ||
1087 | if victim == "--all" then | |
1088 | local channel = ls_chan_for_numeric(numeric) | |
1089 | ||
1090 | if channel and ls_get_role(channel, numeric) ~= "lobby" then | |
1091 | ls_notice(numeric, "Sorry, you can't view aggregated statistics while you're playing the game.") | |
1092 | return | |
1093 | end | |
1094 | ||
1095 | getter = function(key) | |
1096 | return ls_get_stats_user_aggregate(key) | |
1097 | end | |
1098 | ||
1099 | ls_notice(numeric, "Showing aggregated statistics for all users.") | |
1100 | else | |
1101 | local victimnumeric | |
1102 | ||
1103 | if victim then | |
1104 | local victimnick = irc_getnickbynick(victim) | |
1105 | ||
1106 | if not victimnick then | |
1107 | ls_notice(numeric, "Sorry, I don't know who that is. Please specify a valid nick or try --all.") | |
1108 | return | |
1109 | end | |
1110 | ||
1111 | if not victimnick.accountid then | |
1112 | ls_notice(numeric, "Sorry, that user is not authenticated with Q.") | |
1113 | return | |
1114 | end | |
1115 | ||
1116 | for channel, _ in pairs(ls_gamestate) do | |
1117 | local role = ls_get_role(channel, victimnick.numeric) | |
1118 | ||
1119 | if role and role ~= "lobby" then | |
1120 | ls_notice(numeric, "Sorry, you can't view statistics for a user who is currently playing a game.") | |
1121 | return | |
1122 | end | |
1123 | end | |
1124 | ||
1125 | victimnumeric = victimnick.numeric | |
1126 | ||
1127 | ls_notice(numeric, "Showing statistics for '" .. victimnick.nick .. "'") | |
1128 | else | |
43fa6669 | 1129 | local victimnick = irc_getnickbynumeric(numeric) |
1130 | ||
1131 | if not victimnick.accountid then | |
1132 | ls_notice(numeric, "Sorry, you are not authenticated with Q.") | |
1133 | return | |
1134 | end | |
1135 | ||
fa947f3a | 1136 | victimnumeric = numeric |
1137 | ||
1138 | ls_notice(numeric, "Showing statistics for yourself.") | |
1139 | end | |
1140 | ||
1141 | getter = function(key) | |
1142 | return ls_get_stats_user(victimnumeric, key) | |
1143 | end | |
1144 | end | |
1145 | ||
bb527874 | 1146 | local time = getter("game_time") |
1147 | ||
1148 | if time > 3600 then | |
1149 | timeinfo = round(getter("game_time") / 3600, 2) .. " hours" | |
1150 | elseif time > 60 then | |
1151 | timeinfo = round(getter("game_time") / 60, 2) .. " minutes" | |
1152 | else | |
1153 | timeinfo = getter("game_time") .. " seconds" | |
1154 | end | |
1155 | ||
1156 | ls_notice(numeric, "Game time: " timeinfo) | |
fa947f3a | 1157 | |
1158 | ls_notice(numeric, "Roles: " .. | |
1159 | getter("role_scientist") .. "x " .. ls_format_role("scientist") .. ", " .. | |
1160 | getter("role_investigator") .. "x " .. ls_format_role("investigator") .. ", " .. | |
1161 | getter("role_citizen") .. "x " .. ls_format_role("citizen")) | |
1162 | ||
1163 | ls_notice(numeric, "Traits: " .. | |
1164 | getter("trait_teleporter") .. "x " .. ls_format_trait("teleporter") .. ", " .. | |
1165 | getter("trait_infested") .. "x " .. ls_format_trait("infested") .. ", " .. | |
1166 | getter("trait_force") .. "x " .. ls_format_trait("force")) | |
1167 | ||
1168 | ls_notice(numeric, "Won games as: " .. | |
1169 | getter("won_scientist") .. "x " .. ls_format_role("scientist") .. ", " .. | |
1170 | getter("won_investigator") .. "x " .. ls_format_role("investigator") .. ", " .. | |
1171 | getter("won_citizen") .. "x " .. ls_format_role("citizen")) | |
1172 | ||
1173 | ls_notice(numeric, "Survived attacks by: " .. | |
1174 | getter("survived_chance") .. "x chance, " .. | |
1175 | getter("survived_guarded") .. "x being guarded with a force field") | |
1176 | ||
7a0c3489 | 1177 | ls_notice(numeric, "Survived rounds: " .. getter("survived_round")) |
1178 | ||
fa947f3a | 1179 | ls_notice(numeric, "Active role: " .. |
1180 | getter("active_scientist") .. "x " .. ls_format_role("scientist") .. ", " .. | |
1181 | getter("active_investigator") .. "x " .. ls_format_role("investigator")) | |
1182 | ||
1183 | ls_notice(numeric, "Inactive role: " .. | |
1184 | getter("inactive_scientist") .. "x " .. ls_format_role("scientist") .. ", " .. | |
1185 | getter("inactive_investigator") .. "x " .. ls_format_role("investigator")) | |
1186 | ||
1187 | ls_notice(numeric, "Kills: " .. | |
1188 | getter("kill_scientist") .. "x scientist attack, " .. | |
1189 | getter("kill_infested") .. "x infestation attack, " .. | |
1190 | getter("kill_tied") .. "x teams tied, " .. | |
1191 | getter("kill_smite") .. "x lightning, " .. | |
1192 | getter("kill_bomb") .. "x bomb") | |
1193 | ||
1194 | ls_notice(numeric, "Failed kills: " .. | |
1195 | getter("failed_chance") .. "x by chance, " .. | |
1196 | getter("failed_guarded") .. "x because target was guarded by a force field") | |
1197 | ||
1198 | ls_notice(numeric, "Deaths: " .. | |
1199 | getter("killed_scientist") .. "x scientist attack, " .. | |
1200 | getter("killed_infested") .. "x infestation attack, " .. | |
1201 | getter("killed_lynch") .. "x lynched, " .. | |
1202 | getter("killed_suicide") .. "x suicide, " .. | |
1203 | getter("killed_tied") .. "x teams tied, " .. | |
1204 | getter("killed_afk") .. "x AFK, " .. | |
1205 | getter("killed_smite") .. "x lightning, " .. | |
1206 | getter("killed_bomb") .. "x bomb") | |
1207 | ||
1208 | ls_notice(numeric, "Revealed investigations: " .. getter("investigation_revealed")) | |
1209 | ||
1210 | ls_notice(numeric, "Investigated: " .. | |
1211 | getter("investigate_scientist") .. "x " .. ls_format_role("scientist") .. ", " .. | |
1212 | getter("investigate_investigator") .. "x " .. ls_format_role("investigator") .. ", " .. | |
1213 | getter("investigate_citizen") .. "x " .. ls_format_role("citizen")) | |
1214 | ||
1215 | ls_notice(numeric, "Was investigated: " .. getter("investigate_target")) | |
1216 | ||
1217 | ls_notice(numeric, "Votes by target team: " .. | |
1218 | getter("vote_team") .. "x own team, " .. | |
1219 | getter("vote_enemy") .. "x enemy team") | |
1220 | ||
1221 | ls_notice(numeric, "Votes by target role: " .. | |
1222 | getter("vote_scientist") .. "x " .. ls_format_role("scientist") .. ", " .. | |
1223 | getter("vote_investigator") .. "x " .. ls_format_role("investigator") .. ", " .. | |
1224 | getter("vote_citizen") .. "x " .. ls_format_role("citizen")) | |
1225 | ||
1226 | ls_notice(numeric, "Teleporter usage: " .. | |
1227 | getter("teleporter_activated") .. "x success (" .. getter("teleporter_intact") .. "x retained, " .. getter("teleporter_destroyed") .. "x destroyed), " .. | |
1228 | getter("teleporter_failed") .. "x failed") | |
1229 | end | |
1230 | ||
d6560b7b | 1231 | function ls_cmd_msghelp(numeric, victim) |
01d9c6ce | 1232 | ls_cmd_msgshowcommands(numeric, victim) |
d6560b7b | 1233 | end |
1234 | ||
1235 | function ls_cmd_msgshowcommands(numeric, victim) | |
1236 | ls_notice(numeric, "Commands available to you:") | |
1237 | ls_notice(numeric, "guard - Guards somebody.") | |
1238 | ls_notice(numeric, "help - Get help.") | |
1239 | ls_notice(numeric, "investigate - Investigate somebody.") | |
1240 | ls_notice(numeric, "kill - Kill somebody.") | |
1241 | ls_notice(numeric, "showcommands - Show this list.") | |
1242 | ls_notice(numeric, "stats - View stats about the game.") | |
1243 | ls_notice(numeric, "vote - Vote for somebody.") | |
1244 | ||
1245 | if onstaff(numeric) or ontlz(numeric) then | |
1246 | ls_notice(numeric, "smite - Remove someone from a game.") | |
1247 | ls_notice(numeric, "killgame - Cancel a game.") | |
8bb291d4 | 1248 | ls_notice(numeric, "killmessage - View or set someone's custom kill message.") |
d6560b7b | 1249 | end |
1250 | ||
1251 | if ontlz(numeric) then | |
1252 | ls_notice(numeric, "addchan - Adds me to a channel.") | |
1253 | ls_notice(numeric, "delchan - Removes me from a channel.") | |
1254 | end | |
1255 | end | |
1256 | ||
1ea98fd5 | 1257 | function ls_cmd_smite(numeric, victim) |
1258 | if not victim then | |
1259 | ls_notice(numeric, "Syntax: smite <nick>") | |
1260 | return | |
1261 | end | |
1262 | ||
1263 | local victimnick = irc_getnickbynick(victim) | |
1264 | ||
1265 | if not victimnick then | |
1266 | ls_notice(numeric, "Sorry, I don't know who that is.") | |
1267 | return | |
1268 | end | |
1269 | ||
1270 | local victimnumeric = victimnick.numeric | |
1271 | local channel = ls_chan_for_numeric(victimnumeric) | |
1272 | ||
1273 | if not channel then | |
1274 | ls_notice(numeric, "Sorry, " .. victimnick.nick .. " isn't playing the game.") | |
1275 | return | |
1276 | end | |
1277 | ||
fa947f3a | 1278 | ls_incr_stats_user(numeric, "kill_smite") |
1279 | ls_incr_stats_user(victimnumeric, "killed_smite") | |
1280 | ||
40996a22 | 1281 | ls_chanmsg(channel, ls_format_player(channel, victimnumeric, true, true) .. " was struck by lightning.") |
1ea98fd5 | 1282 | ls_remove_player(channel, victimnumeric, true) |
1283 | ||
1284 | ls_advance_state(channel) | |
1285 | ||
1286 | ls_flush_modes(channel) | |
1287 | end | |
1288 | ||
5601e62e | 1289 | function ls_cmd_killgame(numeric, channel) |
1290 | if not channel then | |
756c0083 | 1291 | ls_notice(numeric, "Syntax: killgame <channel>") |
5601e62e | 1292 | return |
1293 | end | |
1294 | ||
659137f7 | 1295 | channel = irctolower(channel) |
1296 | ||
5601e62e | 1297 | if not ls_is_game_channel(channel) then |
1298 | ls_notice(numeric, "I'm not on that channel.") | |
1299 | return | |
1300 | end | |
1301 | ||
1302 | if table.getn(ls_get_players(channel)) == 0 then | |
1303 | ls_notice(numeric, "There's nobody playing the game.") | |
1304 | return | |
1305 | end | |
1306 | ||
fa947f3a | 1307 | ls_incr_stats_user(numeric, "kill_bomb", table.getn(ls_get_players(channel))) |
1308 | ||
1309 | for _, player in pairs(ls_get_players(channel)) do | |
1310 | ls_incr_stats_user(player, "killed_bomb") | |
1311 | end | |
1312 | ||
5601e62e | 1313 | ls_chanmsg(channel, ls_format_player(channel, numeric) .. " set us up the bomb. Game over.") |
1314 | ls_stop_game(channel) | |
1315 | ||
1316 | ls_flush_modes(channel) | |
1317 | end | |
1318 | ||
1ea98fd5 | 1319 | function ls_cmd_addchan(numeric, channel) |
1320 | if not channel then | |
1321 | ls_notice(numeric, "Syntax: addchan <#channel>") | |
1322 | return | |
1323 | end | |
1324 | ||
659137f7 | 1325 | channel = irctolower(channel) |
1326 | ||
f8d31f49 | 1327 | if not irc_getchaninfo(channel) then |
1328 | ls_notice(numeric, "The specified channel does not exist.") | |
1329 | return | |
1330 | end | |
1331 | ||
1ea98fd5 | 1332 | if ls_is_game_channel(channel) then |
1333 | ls_notice(numeric, "The bot is already on that channel.") | |
1334 | return | |
1335 | end | |
1336 | ||
1337 | ls_add_channel(channel) | |
1338 | ||
1339 | ls_notice(numeric, "Done.") | |
1340 | end | |
1341 | ||
1342 | function ls_cmd_delchan(numeric, channel) | |
1343 | if not channel then | |
1344 | ls_notice(numeric, "Syntax: delchan <#channel>") | |
1345 | return | |
1346 | end | |
1347 | ||
659137f7 | 1348 | channel = irctolower(channel) |
1349 | ||
1ea98fd5 | 1350 | if not ls_is_game_channel(channel) then |
1351 | ls_notice(numeric, "The bot is not on that channel.") | |
1352 | return | |
1353 | end | |
1354 | ||
1355 | ls_remove_channel(channel, true) | |
1356 | ||
1357 | ls_notice(numeric, "Done.") | |
1358 | end | |
1359 | ||
85446399 | 1360 | function ls_cmd_killmessage(numeric, victim, message) |
1361 | if not victim then | |
1362 | ls_notice(numeric, "Syntax: killmessage <nick> ?message?") | |
1363 | return | |
1364 | end | |
1365 | ||
1366 | local victimnick = irc_getnickbynick(victim) | |
1367 | ||
1368 | if not victimnick then | |
1369 | ls_notice(numeric, "Sorry, I don't know who that is.") | |
1370 | return | |
1371 | end | |
1372 | ||
1373 | if not victimnick.accountid then | |
1374 | ls_notice(numeric, "Sorry, that user is not authenticated with Q.") | |
1375 | return | |
1376 | end | |
1377 | ||
1378 | local victimnumeric = victimnick.numeric | |
1379 | ||
1380 | if not message then | |
1381 | local current_message = ls_get_killmessage(victimnumeric) | |
1382 | ||
1383 | if not current_message then | |
1384 | ls_notice(numeric, victimnick.nick .. " does not have a custom kill message.") | |
1385 | else | |
1386 | ls_notice(numeric, "Current custom kill message for " .. victimnick.nick .. ": " .. current_message) | |
1387 | end | |
1388 | else | |
1389 | if message == "REMOVE" then | |
1390 | ls_set_killmessage(victimnumeric, nil) | |
1391 | ls_notice(numeric, "Custom kill message for " .. victimnick.nick .. " was removed.") | |
1392 | else | |
1393 | ls_set_killmessage(victimnumeric, message) | |
1394 | ls_notice(numeric, "Custom kill message for " .. victimnick.nick .. " was set to: " .. message) | |
1395 | end | |
1396 | end | |
1397 | end | |
1398 | ||
1ea98fd5 | 1399 | function ls_keepalive(channel, numeric) |
1400 | if ls_get_role(channel, numeric) then | |
1401 | ls_set_seen(channel, numeric, os.time()) | |
1402 | end | |
1403 | ||
1404 | -- extend lobby timeout if we don't have enough players yet | |
1405 | if ls_get_state(channel) == "lobby" and table.getn(ls_get_players(channel)) < MINPLAYERS then | |
1406 | ls_set_delay(channel, 90) | |
1407 | ls_set_timeout(channel, 150) | |
1408 | end | |
1409 | end | |
1410 | ||
1411 | function ls_timer_announce_players(channel) | |
1412 | ls_gamestate[channel]["announce_timer"] = nil | |
1413 | ||
1414 | local new_players = {} | |
1415 | ||
1416 | for _, numeric in pairs(ls_get_players(channel)) do | |
1417 | if not ls_get_announced(channel, numeric) then | |
1418 | table.insert(new_players, numeric) | |
1419 | ls_set_announced(channel, numeric, true) | |
1420 | ls_voice_player(channel, numeric) | |
1421 | end | |
1422 | end | |
1423 | ||
1424 | ls_flush_modes(channel) | |
1425 | ||
1426 | if table.getn(new_players) > 0 then | |
1427 | local count = table.getn(ls_get_players(channel)) | |
1428 | local subject | |
1429 | ||
1430 | if count ~= 1 then | |
1431 | subject = "players" | |
1432 | else | |
1433 | subject = "player" | |
1434 | end | |
1435 | ||
1436 | ls_chanmsg(channel, ls_format_players(channel, new_players) .. " joined the game (" .. count .. " " .. subject .. " in the lobby).") | |
1437 | end | |
1438 | end | |
1439 | ||
1440 | function ls_add_channel(channel) | |
93a44b51 | 1441 | ls_gamestate[channel] = { players = {}, state = "lobby", timeout = -1, delay = os.time() + 30, waitcount = 0, lasthl = 0, enabled = true } |
1ea98fd5 | 1442 | irc_localjoin(ls_bot, channel) |
103be8f6 | 1443 | irc_localsimplechanmode(ls_bot, channel, "-m") |
1ea98fd5 | 1444 | end |
1445 | ||
1446 | function ls_remove_channel(channel, part) | |
1447 | if ls_gamestate[channel]["announce_timer"] then | |
1448 | ls_sched:remove(ls_gamestate[channel]["announce_timer"]) | |
1449 | end | |
1450 | ||
1451 | ls_gamestate[channel] = nil | |
1452 | ||
1453 | if part then | |
1454 | irc_localpart(ls_bot, channel) | |
1455 | end | |
1456 | end | |
1457 | ||
1458 | function ls_dbload() | |
1459 | ls_db = loadtable(basepath() .. "db/" .. DB) | |
1460 | ||
1461 | if not ls_db then | |
1462 | ls_db = ls_dbdefaults() | |
1463 | end | |
1464 | end | |
1465 | ||
1466 | function ls_dbsave() | |
1467 | local channels = {} | |
1468 | ||
1469 | for channel, _ in pairs(ls_gamestate) do | |
1470 | table.insert(channels, channel) | |
1471 | end | |
1472 | ||
1473 | ls_db.channels = channels | |
1474 | ||
1475 | savetable(basepath() .. "db/" .. DB, ls_db) | |
1476 | end | |
1477 | ||
1478 | function ls_dbdefaults() | |
1479 | local db = {} | |
3b38a98a | 1480 | db.channels = { HOMECHANNEL } |
1ea98fd5 | 1481 | |
1482 | return db | |
1483 | end | |
1484 | ||
1485 | function ls_add_player(channel, numeric, forced) | |
1486 | local role = ls_get_role(channel, numeric) | |
1487 | ||
1488 | if role then | |
1489 | ls_chanmsg(channel, "\001ACTION slaps " .. ls_format_player(channel, numeric) .. "\001") | |
1490 | return | |
1491 | end | |
1492 | ||
1493 | if not forced then | |
93a44b51 | 1494 | if not ls_get_enabled(channel) then |
1495 | ls_notice(numeric, "Sorry, the game is currently disabled.") | |
1496 | return | |
1497 | end | |
1498 | ||
1ea98fd5 | 1499 | if ls_game_in_progress(channel) then |
1500 | ls_notice(numeric, "Sorry, you can't join the game right now.") | |
1501 | return | |
1502 | end | |
1503 | ||
e09c4835 | 1504 | local chanuser = irc_getuserchanmodes(numeric, channel) |
1505 | ||
1506 | if not chanuser then | |
1ea98fd5 | 1507 | ls_notice(numeric, "Sorry, you must be on the channel to use this command.") |
1508 | return | |
1509 | end | |
1510 | ||
e09c4835 | 1511 | if chanuser.opped then |
d3a23252 | 1512 | ls_notice(numeric, "You must not be opped to use this command.") |
e09c4835 | 1513 | return |
1514 | end | |
1515 | ||
1ea98fd5 | 1516 | if table.getn(ls_get_players(channel)) >= MAXPLAYERS then |
1517 | ls_notice(numeric, "Sorry, the game's lobby is full.") | |
1518 | return | |
1519 | end | |
1520 | ||
1521 | if ls_chan_for_numeric(numeric) then | |
1522 | ls_notice(numeric, "Sorry, you can't play on multiple channels at once.") | |
1523 | return | |
1524 | end | |
1525 | end | |
1526 | ||
1527 | ls_set_role(channel, numeric, "lobby") | |
1528 | ls_set_seen(channel, numeric, os.time()) | |
1529 | ||
1530 | if not forced then | |
1531 | ls_set_announced(channel, numeric, false) | |
1532 | ||
1533 | if ls_gamestate[channel]["announce_timer"] then | |
1534 | ls_sched:remove(ls_gamestate[channel]["announce_timer"]) | |
1535 | end | |
1536 | ls_gamestate[channel]["announce_timer"] = ls_sched:add(5, ls_timer_announce_players, channel) | |
1537 | ||
1538 | ls_notice(numeric, "You were added to the lobby.") | |
1539 | else | |
1540 | ls_set_announced(channel, numeric, true) | |
1541 | ls_voice_player(channel, numeric) | |
1542 | end | |
1543 | ||
1544 | ls_set_delay(channel, 30) | |
1545 | ls_set_timeout(channel, 90) | |
1546 | end | |
1547 | ||
1548 | function ls_voice_player(channel, numeric) | |
1549 | if not ls_gamestate[channel]["modes"] then | |
1550 | ls_gamestate[channel]["modes"] = {} | |
1551 | end | |
1552 | ||
1553 | table.insert(ls_gamestate[channel]["modes"], true) | |
1554 | table.insert(ls_gamestate[channel]["modes"], "v") | |
1555 | table.insert(ls_gamestate[channel]["modes"], numeric) | |
1556 | end | |
1557 | ||
1558 | function ls_devoice_player(channel, numeric) | |
1559 | if not ls_gamestate[channel]["modes"] then | |
1560 | ls_gamestate[channel]["modes"] = {} | |
1561 | end | |
1562 | ||
1563 | table.insert(ls_gamestate[channel]["modes"], false) | |
1564 | table.insert(ls_gamestate[channel]["modes"], "v") | |
1565 | table.insert(ls_gamestate[channel]["modes"], numeric) | |
1566 | end | |
1567 | ||
1568 | function ls_flush_modes(channel) | |
1569 | if ls_gamestate[channel]["modes"] then | |
1570 | irc_localovmode(ls_bot, channel, ls_gamestate[channel]["modes"]) | |
1571 | ls_gamestate[channel]["modes"] = nil | |
1572 | end | |
1573 | end | |
1574 | ||
1575 | function ls_remove_player(channel, numeric, forced) | |
1576 | local role = ls_get_role(channel, numeric) | |
1577 | ||
1578 | if not role then | |
1579 | return | |
1580 | end | |
1581 | ||
fa947f3a | 1582 | if role ~= "lobby" then |
1583 | ls_incr_stats_user(numeric, "game_time", os.time() - ls_get_startts(channel)) | |
1584 | end | |
1585 | ||
1ea98fd5 | 1586 | local announced = ls_get_announced(channel, numeric) |
1587 | ||
04f34aee | 1588 | local force_field = ls_get_trait(channel, numeric, "force") |
1589 | ||
1ea98fd5 | 1590 | ls_set_role(channel, numeric, nil) |
1591 | ||
1592 | ls_devoice_player(channel, numeric) | |
1593 | ||
1594 | for _, player in pairs(ls_get_players(channel)) do | |
1595 | if ls_get_vote(channel, player) == numeric then | |
1596 | ls_set_vote(channel, player, nil) | |
1597 | end | |
04f34aee | 1598 | |
008a1b2a | 1599 | if force_field and ls_get_guarded(channel, player) then |
1600 | ls_notice(player, "You are no longer being protected by a \002force field\002.") | |
04f34aee | 1601 | ls_set_guarded(channel, player, false) |
1602 | end | |
1ea98fd5 | 1603 | end |
1604 | ||
1605 | if not forced then | |
1606 | if announced then | |
1607 | if ls_game_in_progress(channel) then | |
1608 | ls_chanmsg(channel, ls_format_player(channel, numeric) .. " committed suicide. Goodbye, cruel world.") | |
1609 | else | |
1610 | ls_chanmsg(channel, ls_format_player(channel, numeric) .. " left the game (" .. table.getn(ls_get_players(channel)) .. " players in the lobby).") | |
1611 | end | |
1612 | end | |
1613 | ||
1614 | ls_notice(numeric, "You were removed from the lobby.") | |
1615 | ||
1616 | ls_set_delay(channel, 30) | |
1617 | ls_set_timeout(channel, 90) | |
1618 | end | |
1619 | end | |
1620 | ||
1621 | function ls_get_players(channel, role) | |
1622 | local players = {} | |
1623 | ||
1624 | for player, _ in pairs(ls_gamestate[channel]["players"]) do | |
1625 | if not role or ls_get_role(channel, player) == role then | |
1626 | table.insert(players, player) | |
1627 | end | |
1628 | end | |
1629 | ||
1630 | return players | |
1631 | end | |
1632 | ||
1633 | function ls_is_game_channel(channel) | |
1634 | return ls_gamestate[channel] | |
1635 | end | |
1636 | ||
1637 | function ls_get_role(channel, numeric) | |
1638 | if not ls_gamestate[channel]["players"][numeric] then | |
1639 | return nil | |
1640 | end | |
1641 | ||
1642 | return ls_gamestate[channel]["players"][numeric]["role"] | |
1643 | end | |
1644 | ||
1645 | function ls_set_role(channel, numeric, role) | |
04f34aee | 1646 | if not ls_gamestate[channel]["players"][numeric] or role == "lobby" then |
1647 | ls_gamestate[channel]["players"][numeric] = { | |
1648 | active = false, | |
1649 | announced = false, | |
1650 | traits = {}, | |
1651 | guarded = false | |
1652 | } | |
1ea98fd5 | 1653 | end |
1654 | ||
1655 | if role then | |
1656 | ls_gamestate[channel]["players"][numeric]["role"] = role | |
1657 | else | |
1658 | ls_gamestate[channel]["players"][numeric] = nil | |
1659 | end | |
1660 | ||
1661 | if role and role ~= "lobby" then | |
1662 | ls_notice(numeric, "Your role for this round is '" .. ls_format_role(role) .. "'.") | |
1663 | end | |
1664 | end | |
1665 | ||
87fa5be3 | 1666 | function ls_get_traits(channel, numeric) |
1667 | local traits = {} | |
1668 | ||
1669 | for trait, _ in pairs(ls_gamestate[channel]["players"][numeric]["traits"]) do | |
1670 | table.insert(traits, trait) | |
1671 | end | |
1672 | ||
1673 | return traits | |
1674 | end | |
1675 | ||
04f34aee | 1676 | function ls_get_trait(channel, numeric, trait) |
1677 | return ls_gamestate[channel]["players"][numeric]["traits"][trait] | |
1678 | end | |
1679 | ||
1680 | function ls_set_trait(channel, numeric, trait, enabled) | |
1681 | ls_gamestate[channel]["players"][numeric]["traits"][trait] = enabled | |
1682 | end | |
1683 | ||
1684 | function ls_get_guarded(channel, numeric, guarded) | |
1685 | return ls_gamestate[channel]["players"][numeric]["guarded"] | |
1686 | end | |
1687 | ||
1688 | function ls_set_guarded(channel, numeric, guarded) | |
1689 | ls_gamestate[channel]["players"][numeric]["guarded"] = guarded | |
1690 | end | |
1691 | ||
1ea98fd5 | 1692 | function ls_get_seen(channel, numeric) |
1693 | return ls_gamestate[channel]["players"][numeric]["seen"] | |
1694 | end | |
1695 | ||
1696 | function ls_set_seen(channel, numeric, seen) | |
1697 | ls_gamestate[channel]["players"][numeric]["seen"] = seen | |
1698 | end | |
1699 | ||
1700 | function ls_get_vote(channel, numeric) | |
1701 | if not ls_gamestate[channel]["players"][numeric] then | |
1702 | return nil | |
1703 | end | |
1704 | ||
1705 | return ls_gamestate[channel]["players"][numeric]["vote"] | |
1706 | end | |
1707 | ||
1708 | function ls_set_vote(channel, numeric, votenumeric) | |
1709 | if ls_get_vote(channel, numeric) == votenumeric then | |
1710 | return | |
1711 | end | |
1712 | ||
1713 | if votenumeric then | |
1714 | local count = 0 | |
1715 | for _, player in pairs(ls_get_players(channel)) do | |
1716 | if ls_get_vote(channel, player) == votenumeric then | |
1717 | count = count + 1 | |
1718 | end | |
1719 | end | |
1720 | ||
1721 | -- increase count for this new vote | |
1722 | count = count + 1 | |
1723 | ||
fa947f3a | 1724 | local plural_s |
1725 | ||
1726 | if count ~= 1 then | |
1727 | plural_s = "s" | |
1728 | else | |
1729 | plural_s = "" | |
1730 | end | |
1731 | ||
1ea98fd5 | 1732 | if numeric ~= votenumeric then |
1733 | if ls_get_vote(channel, numeric) then | |
fa947f3a | 1734 | ls_chanmsg(channel, ls_format_player(channel, numeric) .. " changed their vote to " .. ls_format_player(channel, votenumeric) .. " (" .. count .. " vote" .. plural_s .. ").") |
1ea98fd5 | 1735 | else |
fa947f3a | 1736 | ls_chanmsg(channel, ls_format_player(channel, numeric) .. " voted for " .. ls_format_player(channel, votenumeric) .. " (" .. count .. " vote" .. plural_s .. ").") |
1ea98fd5 | 1737 | end |
1738 | else | |
fa947f3a | 1739 | ls_chanmsg(channel, ls_format_player(channel, numeric) .. " voted for himself. Oops! (" .. count .. " vote" .. plural_s .. ")") |
1ea98fd5 | 1740 | end |
1741 | end | |
1742 | ||
1743 | if ls_gamestate[channel]["players"][numeric] then | |
1744 | ls_gamestate[channel]["players"][numeric]["vote"] = votenumeric | |
1745 | end | |
1746 | end | |
1747 | ||
23c269fd | 1748 | function ls_format_votes(votes, votees) |
1749 | local message = "" | |
1750 | ||
1751 | for _, votee in pairs(votees) do | |
1752 | if message ~= "" then | |
1753 | message = message .. ", " | |
1754 | end | |
1755 | ||
1756 | message = message .. votes[votee] .. "x " .. ls_format_player(channel, votee) | |
1757 | end | |
1758 | ||
1759 | return message | |
1760 | end | |
1761 | ||
1762 | -- Returns (.votes, .votees, .missing_votes) as the current vote results | |
1763 | function ls_get_vote_result(channel, countscore) | |
1764 | local result = { votes = {}, votees = {}, missing_votes = {} } | |
1765 | ||
1766 | for _, player in pairs(ls_get_players(channel)) do | |
1767 | local vote = ls_get_vote(channel, player) | |
1768 | ||
1769 | if vote then | |
1770 | if countscore then | |
1771 | if (ls_get_role(channel, player) == "scientist" and ls_get_role(channel, vote) == "scientist") or (ls_get_role(channel, player) ~= "scientist" and ls_get_role(channel, vote) ~= "scientist") then | |
1772 | ls_incr_stats_user(player, "vote_team") | |
1773 | else | |
1774 | ls_incr_stats_user(player, "vote_enemy") | |
1775 | end | |
1776 | ||
1777 | ls_incr_stats_user(player, "vote_" .. ls_get_role(channel, vote)) | |
1778 | end | |
1779 | ||
1780 | if not result.votes[vote] then | |
1781 | result.votes[vote] = 0 | |
1782 | table.insert(result.votees, vote) | |
1783 | end | |
1784 | result.votes[vote] = result.votes[vote] + 1 | |
1785 | else | |
1786 | table.insert(result.missing_votes, player) | |
1787 | end | |
1788 | ||
1789 | end | |
1790 | ||
1791 | local function votecomp(v1, v2) | |
1792 | if result.votes[v1] > result.votes[v2] then | |
1793 | return true | |
1794 | end | |
1795 | return false | |
1796 | end | |
1797 | ||
1798 | table.sort(result.votees, votecomp) | |
1799 | ||
1800 | return result | |
1801 | end | |
1802 | ||
1ea98fd5 | 1803 | function ls_get_active(channel, numeric) |
1804 | return ls_gamestate[channel]["players"][numeric]["active"] | |
1805 | end | |
1806 | ||
1807 | function ls_set_active(channel, numeric, active) | |
1808 | ls_gamestate[channel]["players"][numeric]["active"] = active | |
1809 | end | |
1810 | ||
1811 | function ls_get_announced(channel, numeric) | |
1812 | return ls_gamestate[channel]["players"][numeric]["announced"] | |
1813 | end | |
1814 | ||
1815 | function ls_set_announced(channel, numeric, announced) | |
1816 | ls_gamestate[channel]["players"][numeric]["announced"] = announced | |
1817 | end | |
1818 | ||
1819 | function ls_pick_player(players) | |
1820 | return players[math.random(table.getn(players))] | |
1821 | end | |
1822 | ||
1823 | function ls_number_scientists(numPlayers) | |
1824 | return math.ceil((numPlayers - 2) / 5.0) | |
1825 | end | |
1826 | ||
1827 | function ls_number_investigators(numPlayers) | |
1828 | return math.ceil((numPlayers - 5) / 6.0) | |
1829 | end | |
1830 | ||
1831 | function ls_start_game(channel) | |
1832 | local players = ls_get_players(channel) | |
1833 | ||
103be8f6 | 1834 | irc_localsimplechanmode(ls_bot, channel, "+m") |
1ea98fd5 | 1835 | |
1836 | for nick in channelusers_iter(channel, { nickpusher.numeric }) do | |
1837 | local numeric = nick[1] | |
1838 | ||
1839 | if ls_get_role(channel, numeric) then | |
1840 | ls_voice_player(channel, numeric) | |
40996a22 | 1841 | ls_set_seen(channel, numeric, os.time()) |
1ea98fd5 | 1842 | else |
1843 | ls_devoice_player(channel, numeric) | |
1844 | end | |
1845 | end | |
1846 | ||
1847 | ls_chanmsg(channel, "Starting the game...") | |
1848 | ||
fa947f3a | 1849 | ls_incr_stats_channel(channel, "game_count") |
1850 | ls_set_startts(channel, os.time()) | |
7a0c3489 | 1851 | ls_set_round(channel, 0) |
fa947f3a | 1852 | |
1ea98fd5 | 1853 | for _, player in pairs(players) do |
1854 | ls_set_role(channel, player, "lobby") | |
40996a22 | 1855 | ls_keepalive(channel, player) |
1ea98fd5 | 1856 | end |
1857 | ||
1858 | local players_count = table.getn(players) | |
1859 | local scientists_count = 0 | |
1860 | local scientists_needed = ls_number_scientists(players_count) | |
1861 | ||
1862 | -- pick scientists | |
1863 | while scientists_count < scientists_needed do | |
1864 | local scientist_index = math.random(table.getn(players)) | |
1865 | ls_set_role(channel, table.remove(players, scientist_index), "scientist") | |
1866 | scientists_count = scientists_count + 1 | |
1867 | end | |
1868 | ||
1869 | -- notify scientists about each other | |
1870 | for _, scientist in pairs(ls_get_players(channel, "scientist")) do | |
1871 | for _, scientist_notify in pairs(ls_get_players(channel, "scientist")) do | |
123d685d | 1872 | if scientist ~= scientist_notify then |
19d08a86 | 1873 | ls_notice(scientist_notify, ls_format_player(channel, scientist) .. " is also a scientist.") |
1ea98fd5 | 1874 | end |
1875 | end | |
1876 | end | |
1877 | ||
1878 | local investigators_count = 0 | |
1879 | local investigators_needed = ls_number_investigators(players_count) | |
1880 | ||
1881 | -- pick investigators | |
1882 | while investigators_count < investigators_needed do | |
1883 | local investigator_index = math.random(table.getn(players)) | |
1884 | ls_set_role(channel, table.remove(players, investigator_index), "investigator") | |
1885 | investigators_count = investigators_count + 1 | |
1886 | end | |
1887 | ||
d6b0f776 | 1888 | -- notify scientists about each other |
1889 | for _, investigator in pairs(ls_get_players(channel, "investigator")) do | |
1890 | for _, investigator_notify in pairs(ls_get_players(channel, "investigator")) do | |
1891 | if investigator ~= investigator_notify then | |
1892 | ls_notice(investigator_notify, ls_format_player(channel, investigator) .. " is also an investigator.") | |
1893 | end | |
1894 | end | |
1895 | end | |
1896 | ||
1ea98fd5 | 1897 | -- rest of the players are citizens |
1898 | for _, player in pairs(players) do | |
1899 | ls_set_role(channel, player, "citizen") | |
1900 | end | |
fa947f3a | 1901 | |
1902 | for _, player in pairs(ls_get_players(channel)) do | |
1903 | ls_incr_stats_user(player, "role_" .. ls_get_role(channel, player)) | |
1904 | end | |
1905 | ||
04f34aee | 1906 | -- give someone the force field generator |
1907 | local force_owner = players[math.random(table.getn(players))] | |
1908 | ls_set_trait(channel, force_owner, "force", true) | |
fa947f3a | 1909 | ls_incr_stats_user(force_owner, "trait_force") |
04f34aee | 1910 | ls_set_guarded(channel, force_owner, true) |
1911 | ls_notice(force_owner, "You've found the \002force field generator\002. Use /notice " .. BOTNICK .. " guard <nick> to protect someone.") | |
1912 | ls_notice(force_owner, "You are currently protecting yourself.") | |
1ea98fd5 | 1913 | |
f96b460b | 1914 | -- make someone infested if there are at least 6 citizens |
1915 | if table.getn(players) > 6 then | |
1916 | local infested_player = players[math.random(table.getn(players))] | |
1917 | ls_set_trait(channel, infested_player, "infested", true) | |
fa947f3a | 1918 | ls_incr_stats_user(infested_player, "trait_infested") |
f96b460b | 1919 | ls_notice(infested_player, "You're infested with an \002alien parasite\002.") |
f96b460b | 1920 | end |
1921 | ||
5601e62e | 1922 | -- give someone the teleporter |
1923 | local teleporter_candidates | |
1924 | ||
1925 | if math.random(100) > 75 then | |
1926 | teleporter_candidates = ls_get_players(channel) | |
1927 | else | |
1928 | teleporter_candidates = ls_get_players(channel, "scientist") | |
1929 | end | |
1930 | ||
1931 | local teleporter_owner = teleporter_candidates[math.random(table.getn(teleporter_candidates))] | |
1932 | ls_set_trait(channel, teleporter_owner, "teleporter", true) | |
fa947f3a | 1933 | ls_incr_stats_user(teleporter_owner, "trait_teleporter") |
5601e62e | 1934 | ls_notice(teleporter_owner, "You've found the \002personal teleporter\002 (50% chance to evade lynching).") |
1ea98fd5 | 1935 | |
1936 | ls_set_state(channel, "kill") | |
6e5b9638 | 1937 | ls_show_status(channel) |
1938 | ||
1ea98fd5 | 1939 | ls_advance_state(channel) |
1940 | end | |
1941 | ||
1942 | function ls_stop_game(channel) | |
fa947f3a | 1943 | if ls_get_state(channel) ~= "lobby" then |
1944 | ls_incr_stats_channel(channel, "game_time", os.time() - ls_get_startts(channel)) | |
1945 | end | |
1946 | ||
1ea98fd5 | 1947 | ls_set_state(channel, "lobby") |
1948 | ls_set_waitcount(channel, 0) | |
1949 | ||
1950 | for _, player in pairs(ls_get_players(channel)) do | |
1951 | ls_remove_player(channel, player, true) | |
1952 | end | |
1953 | ||
103be8f6 | 1954 | irc_localsimplechanmode(ls_bot, channel, "-m") |
1ea98fd5 | 1955 | end |
1956 | ||
6fbcf5fd | 1957 | -- makes sure people are not afk |
1958 | function ls_check_alive(channel) | |
c6819cdd | 1959 | local timeout |
1960 | ||
6fbcf5fd | 1961 | if not ls_game_in_progress(channel) then |
c6819cdd | 1962 | timeout = 300 |
1963 | else | |
1964 | timeout = 120 | |
1ea98fd5 | 1965 | end |
1966 | ||
6fbcf5fd | 1967 | local dead_players = {} |
1968 | local idle_players = {} | |
1ea98fd5 | 1969 | |
6fbcf5fd | 1970 | for _, player in pairs(ls_get_players(channel)) do |
1971 | local seen = ls_get_seen(channel, player) | |
1ea98fd5 | 1972 | |
980a55be | 1973 | if seen then |
c6819cdd | 1974 | if seen < os.time() - timeout then |
980a55be | 1975 | table.insert(dead_players, player) |
c6819cdd | 1976 | elseif seen < os.time() - timeout / 3 - 30 then |
980a55be | 1977 | table.insert(idle_players, player) |
1978 | end | |
1ea98fd5 | 1979 | end |
6fbcf5fd | 1980 | end |
1ea98fd5 | 1981 | |
6fbcf5fd | 1982 | if table.getn(dead_players) > 0 then |
1983 | local verb | |
1ea98fd5 | 1984 | |
6fbcf5fd | 1985 | if table.getn(dead_players) ~= 1 then |
1986 | verb = "seem" | |
1987 | else | |
1988 | verb = "seems" | |
1989 | end | |
1ea98fd5 | 1990 | |
87fa5be3 | 1991 | ls_chanmsg(channel, ls_format_players(channel, dead_players, true, true) .. " " .. verb .. " to be dead (AFK).") |
1ea98fd5 | 1992 | |
6fbcf5fd | 1993 | for _, player in pairs(dead_players) do |
fa947f3a | 1994 | ls_incr_stats_user(player, "killed_afk") |
6fbcf5fd | 1995 | ls_remove_player(channel, player, true) |
1ea98fd5 | 1996 | end |
6fbcf5fd | 1997 | end |
1ea98fd5 | 1998 | |
6fbcf5fd | 1999 | if table.getn(idle_players) > 0 then |
2000 | ls_chanmsg(channel, "Hi " .. ls_format_players(channel, idle_players) .. ", please say something if you're still alive.") | |
2001 | end | |
2002 | end | |
2003 | ||
2004 | function ls_advance_state(channel, delayed) | |
2005 | if delayed and not ls_delay_exceeded(channel) then | |
2006 | return | |
1ea98fd5 | 2007 | end |
2008 | ||
6fbcf5fd | 2009 | ls_debug(channel, "ls_advance_state") |
2010 | ||
2011 | ls_set_delay(channel, 30) | |
2012 | ||
1ea98fd5 | 2013 | local players = ls_get_players(channel) |
2014 | local scientists = ls_get_players(channel, "scientist") | |
2015 | local investigators = ls_get_players(channel, "investigator") | |
2016 | ||
2017 | -- game start condition | |
2018 | if not ls_game_in_progress(channel) then | |
2019 | if table.getn(players) < MINPLAYERS then | |
2020 | if table.getn(players) > 0 then | |
2021 | if ls_timeout_exceeded(channel) then | |
2022 | ls_chanmsg(channel, "Lobby was closed because there aren't enough players.") | |
2023 | ls_stop_game(channel) | |
2024 | else | |
2025 | ls_chanmsg(channel, "Game will start when there are at least " .. MINPLAYERS .. " players.") | |
2026 | end | |
2027 | end | |
2028 | else | |
2029 | ls_start_game(channel) | |
2030 | end | |
2031 | ||
2032 | return | |
2033 | end | |
2034 | ||
2035 | -- winning condition when everyone is dead | |
2036 | if table.getn(players) == 0 then | |
2037 | ls_chanmsg(channel, "Everyone is dead.") | |
2038 | ls_stop_game(channel) | |
2039 | return | |
2040 | end | |
2041 | ||
2042 | -- winning condition for scientists | |
2043 | if table.getn(scientists) >= table.getn(players) - table.getn(scientists) then | |
fa947f3a | 2044 | for _, player in pairs(players) do |
2045 | if ls_get_role(channel, player) == "scientist" then | |
2046 | ls_incr_stats_user(player, "kill_tied") | |
2047 | ls_incr_stats_user(player, "won_scientist") | |
2048 | else | |
2049 | ls_incr_stats_user(player, "killed_tied") | |
2050 | end | |
2051 | end | |
2052 | ||
87fa5be3 | 2053 | ls_chanmsg(channel, "There are equal to or more scientists than citizens. Science wins again: " .. ls_format_players(channel, scientists, true, true)) |
1ea98fd5 | 2054 | ls_stop_game(channel) |
2055 | return | |
2056 | end | |
2057 | ||
2058 | -- winning condition for citizen | |
2059 | if table.getn(scientists) == 0 then | |
fa947f3a | 2060 | for _, player in pairs(players) do |
2061 | ls_incr_stats_user(player, "won_" .. ls_get_role(channel, player)) | |
2062 | end | |
2063 | ||
87fa5be3 | 2064 | ls_chanmsg(channel, "All scientists have been eliminated. The citizens win this round: " .. ls_format_players(channel, players, true, true)) |
1ea98fd5 | 2065 | ls_stop_game(channel) |
2066 | return | |
2067 | end | |
2068 | ||
2069 | -- make sure there's progress towards the game's end | |
2070 | local state = ls_get_state(channel) | |
2071 | local timeout = ls_get_timeout(channel) | |
2072 | ||
2073 | if state == "kill" then | |
2074 | if timeout == -1 then | |
04f34aee | 2075 | local active_scientist = scientists[math.random(table.getn(scientists))] |
1ea98fd5 | 2076 | |
2077 | for _, scientist in pairs(scientists) do | |
2078 | if scientist == active_scientist then | |
2079 | ls_set_active(channel, scientist, true) | |
fa947f3a | 2080 | ls_incr_stats_user(scientist, "active_scientist") |
45dc5011 | 2081 | ls_notice(scientist, "It's your turn to select a citizen to kill. Use /notice " .. BOTNICK .. " kill <nick> to kill someone.") |
1ea98fd5 | 2082 | else |
2083 | ls_set_active(channel, scientist, false) | |
fa947f3a | 2084 | ls_incr_stats_user(scientist, "inactive_scientist") |
1ea98fd5 | 2085 | ls_notice(scientist, ls_format_player(channel, active_scientist) .. " is choosing a victim.") |
2086 | end | |
2087 | end | |
2088 | ||
7a0c3489 | 2089 | local round = ls_get_round(channel) + 1 |
2090 | ls_set_round(channel, round) | |
2091 | ||
2092 | if round > 1 then | |
2093 | for _, player in pairs(players) do | |
2094 | ls_incr_stats_user(player, "survived_round") | |
2095 | end | |
2096 | end | |
2097 | ||
2098 | local roundinfo = "Round #" .. round | |
2099 | ||
1ea98fd5 | 2100 | if table.getn(scientists) > 1 then |
7a0c3489 | 2101 | ls_chanmsg(channel, roundinfo .. ": The citizens are asleep while the mad scientists are choosing a target.") |
1ea98fd5 | 2102 | else |
7a0c3489 | 2103 | ls_chanmsg(channel, roundinfo .. ": The citizens are asleep while the mad scientist is choosing a target.") |
1ea98fd5 | 2104 | end |
2105 | ||
2106 | ls_set_timeout(channel, 120) | |
2107 | elseif ls_timeout_exceeded(channel) then | |
2108 | ls_chanmsg(channel, "The scientists failed to set their alarm clocks. Nobody dies tonight.") | |
2109 | ls_set_state(channel, "investigate") | |
2110 | ls_advance_state(channel) | |
2111 | else | |
2112 | ls_chanmsg(channel, "The scientists still need to pick someone to kill.") | |
2113 | end | |
2114 | end | |
2115 | ||
2116 | if state == "investigate" then | |
2117 | -- the investigators are already dead | |
2118 | if table.getn(investigators) == 0 then | |
2119 | ls_set_state(channel, "vote") | |
2120 | ls_advance_state(channel) | |
2121 | return | |
2122 | end | |
2123 | ||
2124 | if timeout == -1 then | |
04f34aee | 2125 | local active_investigator = investigators[math.random(table.getn(investigators))] |
1ea98fd5 | 2126 | |
2127 | for _, investigator in pairs(investigators) do | |
2128 | if investigator == active_investigator then | |
2129 | ls_set_active(channel, investigator, true) | |
fa947f3a | 2130 | ls_incr_stats_user(investigator, "active_investigator") |
45dc5011 | 2131 | ls_notice(investigator, "You need to choose someone to investigate: /notice " .. BOTNICK .. " investigate <nick>") |
1ea98fd5 | 2132 | else |
2133 | ls_set_active(channel, investigator, false) | |
fa947f3a | 2134 | ls_incr_stats_user(investigator, "inactive_investigator") |
1ea98fd5 | 2135 | ls_notice(investigator, "Another investigator is choosing a target.") |
2136 | end | |
2137 | end | |
2138 | ||
2139 | if table.getn(investigators) > 1 then | |
2140 | ls_chanmsg(channel, "It's now up to the investigators to find the mad scientists.") | |
2141 | else | |
2142 | ls_chanmsg(channel, "It's now up to the investigator to find the mad scientists.") | |
2143 | end | |
2144 | ||
2145 | ls_set_timeout(channel, 120) | |
2146 | elseif ls_timeout_exceeded(channel) then | |
2147 | ls_chanmsg(channel, "Looks like the investigator is still firmly asleep.") | |
2148 | ls_set_state(channel, "vote") | |
2149 | ls_advance_state(channel) | |
2150 | else | |
2151 | ls_chanmsg(channel, "The investigator still needs to do their job."); | |
2152 | end | |
2153 | end | |
2154 | ||
2155 | if state == "vote" then | |
23c269fd | 2156 | local voteresult = ls_get_vote_result(channel, true) |
1ea98fd5 | 2157 | |
2158 | if timeout == -1 then | |
2159 | for _, player in pairs(players) do | |
2160 | ls_set_vote(channel, player, nil) | |
2161 | end | |
2162 | ||
45dc5011 | 2163 | ls_chanmsg(channel, "It's now up to the citizens to vote who to lynch (via /notice " .. BOTNICK .. " vote <nick>).") |
1ea98fd5 | 2164 | ls_set_timeout(channel, 120) |
23c269fd | 2165 | elseif ls_timeout_exceeded(channel) or table.getn(voteresult.missing_votes) == 0 then |
1ea98fd5 | 2166 | local message_suffix, candidates |
2167 | ||
23c269fd | 2168 | if table.getn(voteresult.votees) > 0 then |
2169 | ls_show_votes(channel, voteresult, true) | |
1ea98fd5 | 2170 | |
23c269fd | 2171 | local most_votes = voteresult.votes[voteresult.votees[1]] |
1ea98fd5 | 2172 | candidates = {} |
2173 | ||
23c269fd | 2174 | for _, votee in pairs(voteresult.votees) do |
2175 | if voteresult.votes[votee] == most_votes then | |
1ea98fd5 | 2176 | table.insert(candidates, votee) |
2177 | end | |
2178 | end | |
2179 | ||
2180 | message_suffix = "was lynched by the angry mob." | |
2181 | else | |
2182 | candidates = players | |
2183 | message_suffix = "was hit by a stray high-energy laser beam." | |
2184 | end | |
2185 | ||
2186 | local victim_index = math.random(table.getn(candidates)) | |
2187 | local victim = candidates[victim_index] | |
96f013e2 | 2188 | local teleporter = ls_get_trait(channel, victim, "teleporter") |
1ea98fd5 | 2189 | |
96f013e2 | 2190 | if teleporter and math.random(100) > 50 then |
2191 | ls_notice(victim, "You press the button to activate the \002personal teleporter\002... and you safely escape!") | |
fa947f3a | 2192 | ls_incr_stats_user(victim, "teleporter_activated") |
37aed552 | 2193 | ls_chanmsg(channel, ls_format_player(channel, victim) .. " used their personal teleporter to escape the angry mob.") |
5601e62e | 2194 | |
2195 | if math.random(100) > 50 then | |
2196 | ls_set_trait(channel, victim, "teleporter", false) | |
fa947f3a | 2197 | ls_incr_stats_user(victim, "teleporter_destroyed") |
5601e62e | 2198 | ls_notice(victim, "Your \002personal teleporter\002 was destroyed.") |
fa947f3a | 2199 | else |
2200 | ls_incr_stats_user(victim, "teleporter_intact") | |
2201 | ls_notice(channel, victim, "You check your \002personal teleporter\002 after the escape and it is still intact.") | |
5601e62e | 2202 | end |
2203 | else | |
96f013e2 | 2204 | if teleporter then |
fa947f3a | 2205 | ls_incr_stats_user(victim, "teleporter_failed") |
96f013e2 | 2206 | ls_notice(victim, "You press the button to activate the \002personal teleporter\002... but nothing happens!") |
2207 | end | |
2208 | ||
fa947f3a | 2209 | ls_incr_stats_user(victim, "killed_lynch") |
5601e62e | 2210 | ls_devoice_player(channel, victim) |
1ea98fd5 | 2211 | |
5601e62e | 2212 | ls_chanmsg(channel, ls_format_player(channel, victim, true) .. " " .. message_suffix) |
2213 | ls_remove_player(channel, victim, true) | |
2214 | end | |
1ea98fd5 | 2215 | |
2216 | ls_set_state(channel, "kill") | |
2217 | ls_advance_state(channel) | |
2218 | elseif delayed then | |
23c269fd | 2219 | ls_show_votes(channel, voteresult, false) |
1ea98fd5 | 2220 | end |
2221 | end | |
2222 | end |