]> jfr.im git - irc/quakenet/lua-labspace.git/blame - labspace.lua
Stats: use hours, minutes and seconds when appropriate.
[irc/quakenet/lua-labspace.git] / labspace.lua
CommitLineData
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 18local BOTNICK = "labspace"
19local BOTACCOUNT = "labspace"
20local BOTACCOUNTID = 5022574
3b38a98a 21local HOMECHANNEL = "#labspace"
1ea98fd5 22local MINPLAYERS = 6
23local MAXPLAYERS = 30
24local DEBUG = false
25local DB = "labspace.db"
26
27local 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
41local ls_bot
3b38a98a 42local ls_hlbot
1ea98fd5 43local ls_gamestate = {}
44local ls_db = {}
45local ls_lastsave = 0
6fbcf5fd 46local ls_lastalivecheck = 0
1ea98fd5 47local ls_sched = Scheduler()
48
49function onload()
50 ls_dbload()
51 onconnect()
52end
53
54function onunload()
55 ls_dbsave()
56end
57
58function 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 64end
65
66function 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
75end
76
77function 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
89end
90
3b38a98a 91function 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
179end
180
3b38a98a 181function 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
188end
189
1ea98fd5 190function 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
205end
206
207function 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
227end
228irc_onkickall = irc_onkick
229
230function 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
241end
242
243function 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
261end
262
263-- sends a debug message
264function ls_debug(channel, message)
265 if DEBUG then
266 irc_localchanmsg(ls_bot, channel, "DEBUG: " .. message)
267 end
268end
269
270-- sends a notice to the specified target
271function ls_notice(numeric, text)
272 irc_localnotice(ls_bot, numeric, text)
273end
274
275-- sends a message to the specified target
276function ls_chanmsg(channel, text)
277 irc_localchanmsg(ls_bot, channel, text)
278end
279
280-- formats the specified role identifier for output in a message
281function 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
293end
294
87fa5be3 295-- formats the specified trait identifier for output in a message
296function 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
306end
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)
310function 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
329end
330
331-- formats a list of player names for output in a message (optionally
87fa5be3 332-- revealing their roles and traits in the game)
333function 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
351end
352
353-- returns the current state of the game
354function ls_get_state(channel)
355 return ls_gamestate[channel]["state"]
356end
357
fa947f3a 358function ls_get_startts(channel)
359 return ls_gamestate[channel]["startts"]
360end
361
1ea98fd5 362-- gets the timeout for the current state
363function ls_get_timeout(channel)
364 return ls_gamestate[channel]["timeout"]
365end
366
367-- gets the delay for the current state
368function ls_get_delay(channel)
369 return ls_gamestate[channel]["delay"]
370end
371
f6812645 372-- gets the ts when !hl was last used
373function ls_get_lasthl(channel)
374 return ls_gamestate[channel]["lasthl"]
375end
376
93a44b51 377-- gets whether the bot is enabled
378function ls_get_enabled(channel)
379 return ls_gamestate[channel]["enabled"]
380end
381
1ea98fd5 382-- returns true if the game state delay was exceeded, false otherwise
383function ls_delay_exceeded(channel)
384 return ls_get_delay(channel) < os.time()
385end
386
387function ls_get_waitcount(channel)
388 return ls_gamestate[channel]["waitcount"]
389end
390
7a0c3489 391function ls_get_round(channel)
392 return ls_gamestate[channel]["round"]
393end
394
1ea98fd5 395-- sets the game state
396function ls_set_state(channel, state)
397 ls_gamestate[channel]["state"] = state
398
399 ls_set_timeout(channel, -1)
400 ls_set_delay(channel, 30)
401end
402
fa947f3a 403function ls_set_startts(channel, startts)
404 ls_gamestate[channel]["startts"] = startts
405end
406
1ea98fd5 407-- sets the game state timeout (in seconds)
408function 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
414end
415
416-- sets the game state delay (in seconds)
417function ls_set_delay(channel, delay)
418 ls_gamestate[channel]["delay"] = os.time() + delay
419 ls_debug(channel, "changed gamestate delay to " .. delay)
420end
421
f6812645 422-- sets the !hl timestamp
423function ls_set_lasthl(channel, ts)
424 ls_gamestate[channel]["lasthl"] = ts
425end
426
93a44b51 427-- sets whether the bot is enabled
428function ls_set_enabled(channel, enabled)
429 ls_gamestate[channel]["enabled"] = enabled
430end
431
1ea98fd5 432function ls_set_waitcount(channel, count)
433 ls_gamestate[channel]["waitcount"] = count
434end
435
7a0c3489 436function ls_set_round(channel, number)
437 ls_gamestate[channel]["round"] = number
438end
439
1ea98fd5 440-- returns true if the game state timeout was exceeded, false otherwise
441function ls_timeout_exceeded(channel)
442 local timeout = ls_get_timeout(channel)
443
444 return timeout ~= -1 and timeout < os.time()
445end
446
447-- returns true if there's a game in progress, false otherwise
448function ls_game_in_progress(channel)
449 return ls_get_state(channel) ~= "lobby"
450end
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
454function 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
462end
463
fa947f3a 464function 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]
474end
475
476function 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
498end
499
500function 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]
521end
522
523function 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
537end
538
539function 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
568end
569
85446399 570function 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]
583end
584
585function 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
598end
599
1ea98fd5 600function ls_cmd_add(channel, numeric)
601 ls_add_player(channel, numeric)
602end
603
604function ls_cmd_remove(channel, numeric)
605 ls_remove_player(channel, numeric)
606end
607
608function 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.")
634end
635
636function 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)
650end
651
652function 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 659end
660
661
662function 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
678end
679
23c269fd 680function 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
695end
696
223b51b5 697function 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 700end
701
1ea98fd5 702function 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
743end
744
93a44b51 745function 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.")
755end
756
757function 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.")
774end
775
1ea98fd5 776function 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)
887end
888
889function 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)
958end
959
960function 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)
1005end
1006
04f34aee 1007function 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 1077end
1078
fa947f3a 1079function round(num, idp)
1080 local mult = 10^(idp or 0)
1081 return math.floor(num * mult + 0.5) / mult
1082end
1083
1084function 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")
1229end
1230
d6560b7b 1231function ls_cmd_msghelp(numeric, victim)
01d9c6ce 1232 ls_cmd_msgshowcommands(numeric, victim)
d6560b7b 1233end
1234
1235function 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
1255end
1256
1ea98fd5 1257function 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)
1287end
1288
5601e62e 1289function 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)
1317end
1318
1ea98fd5 1319function 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.")
1340end
1341
1342function 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.")
1358end
1359
85446399 1360function 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
1397end
1398
1ea98fd5 1399function 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
1409end
1410
1411function 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
1438end
1439
1440function 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 1444end
1445
1446function 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
1456end
1457
1458function ls_dbload()
1459 ls_db = loadtable(basepath() .. "db/" .. DB)
1460
1461 if not ls_db then
1462 ls_db = ls_dbdefaults()
1463 end
1464end
1465
1466function 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)
1476end
1477
1478function ls_dbdefaults()
1479 local db = {}
3b38a98a 1480 db.channels = { HOMECHANNEL }
1ea98fd5 1481
1482 return db
1483end
1484
1485function 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)
1546end
1547
1548function 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)
1556end
1557
1558function 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)
1566end
1567
1568function 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
1573end
1574
1575function 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
1619end
1620
1621function 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
1631end
1632
1633function ls_is_game_channel(channel)
1634 return ls_gamestate[channel]
1635end
1636
1637function 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"]
1643end
1644
1645function 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
1664end
1665
87fa5be3 1666function 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
1674end
1675
04f34aee 1676function ls_get_trait(channel, numeric, trait)
1677 return ls_gamestate[channel]["players"][numeric]["traits"][trait]
1678end
1679
1680function ls_set_trait(channel, numeric, trait, enabled)
1681 ls_gamestate[channel]["players"][numeric]["traits"][trait] = enabled
1682end
1683
1684function ls_get_guarded(channel, numeric, guarded)
1685 return ls_gamestate[channel]["players"][numeric]["guarded"]
1686end
1687
1688function ls_set_guarded(channel, numeric, guarded)
1689 ls_gamestate[channel]["players"][numeric]["guarded"] = guarded
1690end
1691
1ea98fd5 1692function ls_get_seen(channel, numeric)
1693 return ls_gamestate[channel]["players"][numeric]["seen"]
1694end
1695
1696function ls_set_seen(channel, numeric, seen)
1697 ls_gamestate[channel]["players"][numeric]["seen"] = seen
1698end
1699
1700function 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"]
1706end
1707
1708function 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
1746end
1747
23c269fd 1748function 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
1760end
1761
1762-- Returns (.votes, .votees, .missing_votes) as the current vote results
1763function 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
1801end
1802
1ea98fd5 1803function ls_get_active(channel, numeric)
1804 return ls_gamestate[channel]["players"][numeric]["active"]
1805end
1806
1807function ls_set_active(channel, numeric, active)
1808 ls_gamestate[channel]["players"][numeric]["active"] = active
1809end
1810
1811function ls_get_announced(channel, numeric)
1812 return ls_gamestate[channel]["players"][numeric]["announced"]
1813end
1814
1815function ls_set_announced(channel, numeric, announced)
1816 ls_gamestate[channel]["players"][numeric]["announced"] = announced
1817end
1818
1819function ls_pick_player(players)
1820 return players[math.random(table.getn(players))]
1821end
1822
1823function ls_number_scientists(numPlayers)
1824 return math.ceil((numPlayers - 2) / 5.0)
1825end
1826
1827function ls_number_investigators(numPlayers)
1828 return math.ceil((numPlayers - 5) / 6.0)
1829end
1830
1831function 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)
1940end
1941
1942function 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 1955end
1956
6fbcf5fd 1957-- makes sure people are not afk
1958function 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
2002end
2003
2004function 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
2222end