]> jfr.im git - irc/quakenet/lua-labspace.git/blob - labspace.lua
Implement notice handlers.
[irc/quakenet/lua-labspace.git] / labspace.lua
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
18 -- TODO
19 -- logging
20 -- make idle notifications independent from game delay
21
22 -- Ideas:
23 -- scientists vote on kills
24
25 local BOTNICK = "labspace"
26 local BOTACCOUNT = "labspace"
27 local BOTACCOUNTID = 5022574
28 local BOTCHANNELS = { "#labspace" }
29 local MINPLAYERS = 6
30 local MAXPLAYERS = 30
31 local DEBUG = false
32 local DB = "labspace.db"
33
34 local KILLMESSAGES = {
35 "was brutally murdered.",
36 "was vaporized by the scientist's death ray.",
37 "slipped into a coma after drinking their poison-laced morning coffee.",
38 "was crushed to death by a 5-ton boulder.",
39 "couldn't escape from the scientist's killbot army."
40 }
41
42 local ls_bot
43 local ls_gamestate = {}
44 local ls_db = {}
45 local ls_lastsave = 0
46 local ls_lastalivecheck = 0
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()
59 ls_bot = irc_localregisteruserid(BOTNICK, "lab", "space", "Labspace", BOTACCOUNT, BOTACCOUNTID, "+iXr", handler)
60 ls_join_channels()
61 end
62
63 function ls_join_channels()
64 for _, channel in pairs(BOTCHANNELS) do
65 ls_add_channel(channel)
66 end
67
68 for _, channel in pairs(ls_db.channels) do
69 if not ls_is_game_channel(channel) then
70 ls_add_channel(channel)
71 end
72 end
73 end
74
75 function ls_split_message(message)
76 message, _ = message:gsub("^ +", "")
77 message, _ = message:gsub(" +", " ")
78 message, _ = message:gsub(" +$", "")
79
80 local tokens = {}
81
82 for token in string.gmatch(message, "%S+") do
83 table.insert(tokens, token)
84 end
85
86 return tokens
87 end
88
89 function handler(target, revent, ...)
90 if revent == "irc_onchanmsg" then
91 local numeric, channel, message = ...
92
93 if not ls_is_game_channel(channel) then
94 return
95 end
96
97 ls_keepalive(channel, numeric)
98
99 local tokens = ls_split_message(message)
100 local command = tokens[1]
101
102 if command then
103 if command == "!add" then
104 ls_cmd_add(channel, numeric)
105 elseif command == "!remove" then
106 ls_cmd_remove(channel, numeric)
107 elseif command == "!wait" then
108 ls_cmd_wait(channel, numeric)
109 elseif command == "!start" then
110 ls_cmd_start(channel, numeric)
111 elseif command == "!status" then
112 ls_cmd_status(channel, numeric)
113 elseif command == "!hl" then
114 ls_cmd_hl(channel, numeric)
115 elseif command == "!enable" then
116 ls_cmd_enable(channel, numeric)
117 elseif command == "!disable" then
118 ls_cmd_disable(channel, numeric)
119 end
120
121 ls_flush_modes(channel)
122 end
123 elseif revent == "irc_onmsg" or revent == "irc_onnotice" then
124 local numeric, message = ...
125
126 local tokens = ls_split_message(message)
127
128 local command = tokens[1]
129 local argument = tokens[2]
130
131 if command then
132 if command == "kill" then
133 ls_cmd_kill(numeric, argument)
134 elseif command == "investigate" then
135 ls_cmd_investigate(numeric, argument)
136 elseif command == "vote" then
137 ls_cmd_vote(numeric, argument)
138 elseif command == "smite" and onstaff(numeric) then
139 ls_cmd_smite(numeric, argument)
140 elseif command == "addchan" and ontlz(numeric) then
141 ls_cmd_addchan(numeric, argument)
142 elseif command == "delchan" and ontlz(numeric) then
143 ls_cmd_delchan(numeric, argument)
144 end
145 end
146 elseif revent == "irc_onkilled" then
147 ls_bot = nil
148 ls_gamestate = {}
149 elseif revent == "irc_onkillreconnect" then
150 local numeric = ...
151
152 if numeric then
153 ls_bot = numeric
154 ls_join_channels()
155 end
156 end
157 end
158
159 function irc_onpart(channel, numeric, message)
160 if not ls_is_game_channel(channel) then
161 return
162 end
163
164 if ls_get_role(channel, numeric) then
165 ls_remove_player(channel, numeric)
166 ls_advance_state(channel)
167 end
168 end
169
170 function irc_onkick(channel, kicked_numeric, kicker_numeric, message)
171 if not ls_is_game_channel(channel) then
172 return
173 end
174
175 if ls_bot == kicked_numeric then
176 ls_remove_channel(channel)
177 return
178 end
179
180 if ls_get_role(channel, kicked_numeric) then
181 ls_remove_player(channel, kicked_numeric)
182 ls_advance_state(channel)
183 end
184 end
185 irc_onkickall = irc_onkick
186
187 function irc_onquit(numeric)
188 for channel, _ in pairs(ls_gamestate) do
189 if ls_get_role(channel, numeric) then
190 ls_remove_player(channel, numeric)
191 ls_advance_state(channel)
192 end
193 end
194 end
195
196 function ontick()
197 for channel, _ in pairs(ls_gamestate) do
198 ls_advance_state(channel, true)
199 ls_flush_modes(channel)
200 end
201
202 if ls_lastalivecheck < os.time() - 30 then
203 ls_lastalivecheck = os.time()
204
205 for channel, _ in pairs(ls_gamestate) do
206 ls_check_alive(channel)
207 end
208 end
209
210 if ls_lastsave < os.time() - 60 then
211 ls_lastsave = os.time()
212 ls_dbsave()
213 end
214 end
215
216 -- sends a debug message
217 function ls_debug(channel, message)
218 if DEBUG then
219 irc_localchanmsg(ls_bot, channel, "DEBUG: " .. message)
220 end
221 end
222
223 -- sends a notice to the specified target
224 function ls_notice(numeric, text)
225 irc_localnotice(ls_bot, numeric, text)
226 end
227
228 -- sends a message to the specified target
229 function ls_chanmsg(channel, text)
230 irc_localchanmsg(ls_bot, channel, text)
231 end
232
233 -- formats the specified role identifier for output in a message
234 function ls_format_role(role)
235 if role == "scientist" then
236 return "Mad Scientist"
237 elseif role == "investigator" then
238 return "Investigator"
239 elseif role == "citizen" then
240 return "Citizen"
241 elseif role == "lobby" then
242 return "Lobby"
243 else
244 return "Unknown Role"
245 end
246 end
247
248 -- formats the specified player name for output in a message (optionally
249 -- revealing that player's role in the game)
250 function ls_format_player(channel, numeric, reveal)
251 local nick = irc_getnickbynumeric(numeric)
252 local result = "\002" .. nick.nick .. "\002"
253
254 if reveal then
255 result = result .. " (" .. ls_format_role(ls_get_role(channel, numeric)) .. ")"
256 end
257
258 return result
259 end
260
261 -- formats a list of player names for output in a message (optionally
262 -- revealing their roles in the game)
263 function ls_format_players(channel, numerics, reveal, no_and)
264 local i = 0
265 local result = ""
266
267 for _, numeric in pairs(numerics) do
268 if i ~= 0 then
269 if not no_and and i == table.getn(numerics) - 1 then
270 result = result .. " and "
271 else
272 result = result .. ", "
273 end
274 end
275
276 result = result .. ls_format_player(channel, numeric, reveal)
277 i = i + 1
278 end
279
280 return result
281 end
282
283 -- returns the current state of the game
284 function ls_get_state(channel)
285 return ls_gamestate[channel]["state"]
286 end
287
288 -- gets the timeout for the current state
289 function ls_get_timeout(channel)
290 return ls_gamestate[channel]["timeout"]
291 end
292
293 -- gets the delay for the current state
294 function ls_get_delay(channel)
295 return ls_gamestate[channel]["delay"]
296 end
297
298 -- gets the ts when !hl was last used
299 function ls_get_lasthl(channel)
300 return ls_gamestate[channel]["lasthl"]
301 end
302
303 -- gets whether the bot is enabled
304 function ls_get_enabled(channel)
305 return ls_gamestate[channel]["enabled"]
306 end
307
308 -- returns true if the game state delay was exceeded, false otherwise
309 function ls_delay_exceeded(channel)
310 return ls_get_delay(channel) < os.time()
311 end
312
313 function ls_get_waitcount(channel)
314 return ls_gamestate[channel]["waitcount"]
315 end
316
317 -- sets the game state
318 function ls_set_state(channel, state)
319 ls_gamestate[channel]["state"] = state
320
321 ls_set_timeout(channel, -1)
322 ls_set_delay(channel, 30)
323 end
324
325 -- sets the game state timeout (in seconds)
326 function ls_set_timeout(channel, timeout)
327 if timeout == -1 then
328 ls_gamestate[channel]["timeout"] = -1
329 else
330 ls_gamestate[channel]["timeout"] = os.time() + timeout
331 end
332 end
333
334 -- sets the game state delay (in seconds)
335 function ls_set_delay(channel, delay)
336 ls_gamestate[channel]["delay"] = os.time() + delay
337 ls_debug(channel, "changed gamestate delay to " .. delay)
338 end
339
340 -- sets the !hl timestamp
341 function ls_set_lasthl(channel, ts)
342 ls_gamestate[channel]["lasthl"] = ts
343 end
344
345 -- sets whether the bot is enabled
346 function ls_set_enabled(channel, enabled)
347 ls_gamestate[channel]["enabled"] = enabled
348 end
349
350 function ls_set_waitcount(channel, count)
351 ls_gamestate[channel]["waitcount"] = count
352 end
353
354 -- returns true if the game state timeout was exceeded, false otherwise
355 function ls_timeout_exceeded(channel)
356 local timeout = ls_get_timeout(channel)
357
358 return timeout ~= -1 and timeout < os.time()
359 end
360
361 -- returns true if there's a game in progress, false otherwise
362 function ls_game_in_progress(channel)
363 return ls_get_state(channel) ~= "lobby"
364 end
365
366 -- returns the name of the channel the specified nick is playing on
367 -- if the nick isn't playing any games nil is returned instead
368 function ls_chan_for_numeric(numeric)
369 for channel, _ in pairs(ls_gamestate) do
370 if ls_get_role(channel, numeric) then
371 return channel
372 end
373 end
374
375 return nil
376 end
377
378 function ls_cmd_add(channel, numeric)
379 ls_add_player(channel, numeric)
380 end
381
382 function ls_cmd_remove(channel, numeric)
383 ls_remove_player(channel, numeric)
384 end
385
386 function ls_cmd_wait(channel, numeric)
387 if ls_game_in_progress(channel) then
388 ls_notice(numeric, "Sorry, there's no lobby at the moment.")
389 return
390 end
391
392 if table.getn(ls_get_players(channel)) >= MINPLAYERS then
393 local count = ls_get_waitcount(channel)
394
395 if count >= 2 then
396 ls_notice(numeric, "Sorry, the timeout can only be extended twice per game.")
397 return
398 end
399
400 ls_set_waitcount(channel, count + 1)
401 end
402
403 if not ls_get_role(channel, numeric) then
404 ls_notice(numeric, "Sorry, you need to be in the lobby to use this command.")
405 return
406 end
407
408 ls_set_timeout(channel, 120)
409 ls_set_delay(channel, 45)
410
411 ls_chanmsg(channel, "Lobby timeout was reset.")
412 end
413
414 function ls_cmd_start(channel, numeric)
415 if ls_game_in_progress(channel) then
416 ls_notice(numeric, "Sorry, there's no lobby at the moment.")
417 return
418 end
419
420 if not ls_get_role(channel, numeric) then
421 ls_notice(numeric, "Sorry, you need to be in the lobby to use this command.")
422 return
423 end
424
425 ls_advance_state(channel)
426
427 ls_flush_modes(channel)
428 end
429
430 function ls_cmd_status(channel, numeric)
431 if not ls_get_role(channel, numeric) then
432 ls_notice(numeric, "Sorry, you need to be in the lobby to use this command.")
433 return
434 end
435
436 ls_chanmsg(channel, "Players: " .. ls_format_players(channel, ls_get_players(channel)))
437
438 if ls_game_in_progress(channel) then
439 ls_chanmsg(channel, "Roles: " ..
440 table.getn(ls_get_players(channel, "scientist")) .. "x " .. ls_format_role("scientist") .. ", " ..
441 table.getn(ls_get_players(channel, "investigator")) .. "x " .. ls_format_role("investigator") .. ", " ..
442 table.getn(ls_get_players(channel, "citizen")) .. "x " .. ls_format_role("citizen"))
443 end
444 end
445
446 function ls_cmd_hl(channel, numeric)
447 if ls_game_in_progress(channel) then
448 ls_notice(numeric, "Sorry, there's no lobby at the moment.")
449 return
450 end
451
452 if not ls_get_role(channel, numeric) then
453 ls_notice(numeric, "Sorry, you need to be in the lobby to use this command.")
454 return
455 end
456
457 if ls_get_lasthl(channel) > os.time() - 300 then
458 ls_notice(numeric, "Sorry, you can only use that command once every 5 minute.")
459 return
460 end
461
462 ls_set_lasthl(channel, os.time())
463
464 local numerics = {}
465
466 for nick in channelusers_iter(channel, { nickpusher.numeric }) do
467 local numeric = nick[1]
468
469 if not ls_get_role(channel, numeric) then
470 table.insert(numerics, numeric)
471 end
472
473 if table.getn(numerics) > 10 then
474 ls_chanmsg(channel, "HL: " .. ls_format_players(channel, numerics, false, true))
475 numerics = {}
476 end
477 end
478
479 if table.getn(numerics) > 0 then
480 ls_chanmsg(channel, "HL: " .. ls_format_players(channel, numerics, false, true))
481 end
482 end
483
484 function ls_cmd_enable(channel, numeric)
485 local chanuser = irc_getuserchanmodes(numeric, channel)
486
487 if not chanuser or not chanuser.opped then
488 ls_notice(channel, "You need to be opped to use this command.")
489 return
490 end
491
492 ls_set_enabled(channel, false)
493 ls_notice(numeric, "Game has been enabled.")
494 end
495
496 function ls_cmd_disable(channel, numeric)
497 local chanuser = irc_getuserchanmodes(numeric, channel)
498
499 if not chanuser or not chanuser.opped then
500 ls_notice(channel, "You need to be opped to use this command.")
501 return
502 end
503
504 if ls_game_in_progress(channel) then
505 ls_chanmsg(channel, ls_format_player(channel, numeric) .. " disabled the game.")
506 end
507
508 ls_stop_game(channel)
509 ls_flush_modes(channel)
510
511 ls_set_enabled(channel, false)
512 ls_notice(numeric, "Game has been disabled.")
513 end
514
515 function ls_cmd_kill(numeric, victim)
516 if not victim then
517 ls_notice(numeric, "Syntax: kill <nick>")
518 return
519 end
520
521 local channel = ls_chan_for_numeric(numeric)
522
523 if not channel then
524 ls_notice(numeric, "You haven't joined any game lobby.")
525 return
526 end
527
528 ls_keepalive(channel, numeric)
529
530 if ls_get_role(channel, numeric) ~= "scientist" then
531 ls_notice(numeric, "You need to be a scientist to use this command.")
532 return
533 end
534
535 if ls_get_state(channel) ~= "kill" then
536 ls_notice(numeric, "Sorry, you can't use this command right now.")
537 return
538 end
539
540 if not ls_get_active(channel, numeric) then
541 ls_notice(numeric, "Sorry, it's not your turn to choose a victim.")
542 return
543 end
544
545 local victimnick = irc_getnickbynick(victim)
546
547 if not victimnick then
548 ls_notice(numeric, "Sorry, I don't know who that is.")
549 return
550 end
551
552 local victimnumeric = victimnick.numeric
553
554 if not ls_get_role(channel, victimnumeric) then
555 ls_notice(numeric, "Sorry, " .. ls_format_player(channel, victimnumeric) .. " isn't playing the game.")
556 return
557 end
558
559 if math.random(100) > 85 then
560 ls_chanmsg(channel, "The scientists' attack was not successful tonight. Nobody died.")
561 else
562 ls_devoice_player(channel, victimnumeric)
563
564 if numeric == victimnumeric then
565 ls_chanmsg(channel, ls_format_player(channel, victimnumeric, true) .. " committed suicide.")
566 else
567 if ls_get_role(channel, victimnumeric) == "scientist" then
568 ls_chanmsg(channel, ls_format_player(channel, victimnumeric, true) .. " was brutally murdered. Oops.")
569 else
570 local killmessage = KILLMESSAGES[math.random(table.getn(KILLMESSAGES))]
571
572 ls_chanmsg(channel, ls_format_player(channel, victimnumeric, true) .. " " .. killmessage)
573 end
574 end
575
576 ls_remove_player(channel, victimnumeric, true)
577 end
578
579 ls_set_state(channel, "investigate")
580 ls_advance_state(channel)
581
582 ls_flush_modes(channel)
583 end
584
585 function ls_cmd_investigate(numeric, victim)
586 if not victim then
587 ls_notice(numeric, "Syntax: investigate <nick>")
588 return
589 end
590
591 local channel = ls_chan_for_numeric(numeric)
592
593 if not channel then
594 ls_notice(numeric, "You haven't joined any game lobby.")
595 return
596 end
597
598 if ls_get_role(channel, numeric) ~= "investigator" then
599 ls_notice(numeric, "You need to be an investigator to use this command.")
600 return
601 end
602
603 ls_keepalive(channel, numeric)
604
605 if ls_get_state(channel) ~= "investigate" then
606 ls_notice(numeric, "Sorry, you can't use this command right now.")
607 return
608 end
609
610 local victimnick = irc_getnickbynick(victim)
611
612 if not victimnick then
613 ls_notice(numeric, "Sorry, I don't know who that is.")
614 return
615 end
616
617 local victimnumeric = victimnick.numeric
618
619 if not ls_get_role(channel, victimnumeric) then
620 ls_notice(numeric, "Sorry, " .. ls_format_player(channel, victimnumeric) .. " isn't playing the game.")
621 return
622 end
623
624 local investigators = ls_get_players(channel, "investigator")
625
626 for _, investigator in pairs(investigators) do
627 if investigator ~= numeric then
628 ls_notice(investigator, "Another investigator picked a target.")
629 end
630 end
631
632 if math.random(100) > 85 then
633 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)))
634 end
635
636 if numeric == victimnumeric then
637 ls_notice(numeric, "You're the investigator. Excellent detective work!")
638 else
639 ls_notice(numeric, ls_format_player(channel, victimnumeric) .. "'s role is: " .. ls_format_role(ls_get_role(channel, victimnumeric)))
640 end
641
642 ls_set_state(channel, "vote")
643 ls_advance_state(channel)
644
645 ls_flush_modes(channel)
646 end
647
648 function ls_cmd_vote(numeric, victim)
649 if not victim then
650 ls_notice(numeric, "Syntax: vote <nick>")
651 return
652 end
653
654 local channel = ls_chan_for_numeric(numeric)
655
656 if not channel then
657 ls_notice(numeric, "You haven't joined any game lobby.")
658 return
659 end
660
661 if ls_get_state(channel) ~= "vote" then
662 ls_notice(numeric, "Sorry, you can't use this command right now.")
663 return
664 end
665
666 ls_keepalive(channel, numeric)
667
668 local victimnick = irc_getnickbynick(victim)
669
670 if not victimnick then
671 ls_notice(numeric, "Sorry, I don't know who that is.")
672 return
673 end
674
675 local victimnumeric = victimnick.numeric
676
677 if not ls_get_role(channel, victimnumeric) then
678 ls_notice(numeric, "Sorry, " .. ls_format_player(channel, victimnumeric) .. " isn't playing the game.")
679 return
680 end
681
682 if ls_get_vote(channel, numeric) == victimnumeric then
683 ls_notice(numeric, "You already voted for " .. ls_format_player(channel, victimnumeric) .. ".")
684 return
685 end
686
687 ls_set_vote(channel, numeric, victimnumeric)
688 ls_notice(numeric, "Done.")
689
690 ls_advance_state(channel)
691
692 ls_flush_modes(channel)
693 end
694
695 function ls_cmd_smite(numeric, victim)
696 if not victim then
697 ls_notice(numeric, "Syntax: smite <nick>")
698 return
699 end
700
701 local victimnick = irc_getnickbynick(victim)
702
703 if not victimnick then
704 ls_notice(numeric, "Sorry, I don't know who that is.")
705 return
706 end
707
708 local victimnumeric = victimnick.numeric
709 local channel = ls_chan_for_numeric(victimnumeric)
710
711 if not channel then
712 ls_notice(numeric, "Sorry, " .. victimnick.nick .. " isn't playing the game.")
713 return
714 end
715
716 ls_chanmsg(channel, ls_format_player(channel, victimnumeric, true) .. " was struck by lightning.")
717 ls_remove_player(channel, victimnumeric, true)
718
719 ls_advance_state(channel)
720
721 ls_flush_modes(channel)
722 end
723
724 function ls_cmd_addchan(numeric, channel)
725 if not channel then
726 ls_notice(numeric, "Syntax: addchan <#channel>")
727 return
728 end
729
730 if not irc_getchaninfo(channel) then
731 ls_notice(numeric, "The specified channel does not exist.")
732 return
733 end
734
735 if ls_is_game_channel(channel) then
736 ls_notice(numeric, "The bot is already on that channel.")
737 return
738 end
739
740 ls_add_channel(channel)
741
742 ls_notice(numeric, "Done.")
743 end
744
745 function ls_cmd_delchan(numeric, channel)
746 if not channel then
747 ls_notice(numeric, "Syntax: delchan <#channel>")
748 return
749 end
750
751 if not ls_is_game_channel(channel) then
752 ls_notice(numeric, "The bot is not on that channel.")
753 return
754 end
755
756 ls_remove_channel(channel, true)
757
758 ls_notice(numeric, "Done.")
759 end
760
761 function ls_keepalive(channel, numeric)
762 if ls_get_role(channel, numeric) then
763 ls_set_seen(channel, numeric, os.time())
764 end
765
766 -- extend lobby timeout if we don't have enough players yet
767 if ls_get_state(channel) == "lobby" and table.getn(ls_get_players(channel)) < MINPLAYERS then
768 ls_set_delay(channel, 90)
769 ls_set_timeout(channel, 150)
770 end
771 end
772
773 function ls_timer_announce_players(channel)
774 ls_gamestate[channel]["announce_timer"] = nil
775
776 local new_players = {}
777
778 for _, numeric in pairs(ls_get_players(channel)) do
779 if not ls_get_announced(channel, numeric) then
780 table.insert(new_players, numeric)
781 ls_set_announced(channel, numeric, true)
782 ls_voice_player(channel, numeric)
783 end
784 end
785
786 ls_flush_modes(channel)
787
788 if table.getn(new_players) > 0 then
789 local count = table.getn(ls_get_players(channel))
790 local subject
791
792 if count ~= 1 then
793 subject = "players"
794 else
795 subject = "player"
796 end
797
798 ls_chanmsg(channel, ls_format_players(channel, new_players) .. " joined the game (" .. count .. " " .. subject .. " in the lobby).")
799 end
800 end
801
802 function ls_add_channel(channel)
803 ls_gamestate[channel] = { players = {}, state = "lobby", timeout = -1, delay = os.time() + 30, waitcount = 0, lasthl = 0, enabled = true }
804 irc_localjoin(ls_bot, channel)
805 irc_localsimplechanmode(ls_bot, channel, "-m")
806 end
807
808 function ls_remove_channel(channel, part)
809 if ls_gamestate[channel]["announce_timer"] then
810 ls_sched:remove(ls_gamestate[channel]["announce_timer"])
811 end
812
813 ls_gamestate[channel] = nil
814
815 if part then
816 irc_localpart(ls_bot, channel)
817 end
818 end
819
820 function ls_dbload()
821 ls_db = loadtable(basepath() .. "db/" .. DB)
822
823 if not ls_db then
824 ls_db = ls_dbdefaults()
825 end
826 end
827
828 function ls_dbsave()
829 local channels = {}
830
831 for channel, _ in pairs(ls_gamestate) do
832 table.insert(channels, channel)
833 end
834
835 ls_db.channels = channels
836
837 savetable(basepath() .. "db/" .. DB, ls_db)
838 end
839
840 function ls_dbdefaults()
841 local db = {}
842 db.channels = BOTCHANNELS
843
844 return db
845 end
846
847 function ls_add_player(channel, numeric, forced)
848 local role = ls_get_role(channel, numeric)
849
850 if role then
851 ls_chanmsg(channel, "\001ACTION slaps " .. ls_format_player(channel, numeric) .. "\001")
852 return
853 end
854
855 if not forced then
856 if not ls_get_enabled(channel) then
857 ls_notice(numeric, "Sorry, the game is currently disabled.")
858 return
859 end
860
861 if ls_game_in_progress(channel) then
862 ls_notice(numeric, "Sorry, you can't join the game right now.")
863 return
864 end
865
866 local chanuser = irc_getuserchanmodes(numeric, channel)
867
868 if not chanuser then
869 ls_notice(numeric, "Sorry, you must be on the channel to use this command.")
870 return
871 end
872
873 if chanuser.opped then
874 ls_notice(channel, "You must not be opped to use this command.")
875 return
876 end
877
878 if table.getn(ls_get_players(channel)) >= MAXPLAYERS then
879 ls_notice(numeric, "Sorry, the game's lobby is full.")
880 return
881 end
882
883 if ls_chan_for_numeric(numeric) then
884 ls_notice(numeric, "Sorry, you can't play on multiple channels at once.")
885 return
886 end
887 end
888
889 ls_set_role(channel, numeric, "lobby")
890 ls_set_seen(channel, numeric, os.time())
891
892 if not forced then
893 ls_set_announced(channel, numeric, false)
894
895 if ls_gamestate[channel]["announce_timer"] then
896 ls_sched:remove(ls_gamestate[channel]["announce_timer"])
897 end
898 ls_gamestate[channel]["announce_timer"] = ls_sched:add(5, ls_timer_announce_players, channel)
899
900 ls_notice(numeric, "You were added to the lobby.")
901 else
902 ls_set_announced(channel, numeric, true)
903 ls_voice_player(channel, numeric)
904 end
905
906 ls_set_delay(channel, 30)
907 ls_set_timeout(channel, 90)
908 end
909
910 function ls_voice_player(channel, numeric)
911 if not ls_gamestate[channel]["modes"] then
912 ls_gamestate[channel]["modes"] = {}
913 end
914
915 table.insert(ls_gamestate[channel]["modes"], true)
916 table.insert(ls_gamestate[channel]["modes"], "v")
917 table.insert(ls_gamestate[channel]["modes"], numeric)
918 end
919
920 function ls_devoice_player(channel, numeric)
921 if not ls_gamestate[channel]["modes"] then
922 ls_gamestate[channel]["modes"] = {}
923 end
924
925 table.insert(ls_gamestate[channel]["modes"], false)
926 table.insert(ls_gamestate[channel]["modes"], "v")
927 table.insert(ls_gamestate[channel]["modes"], numeric)
928 end
929
930 function ls_flush_modes(channel)
931 if ls_gamestate[channel]["modes"] then
932 irc_localovmode(ls_bot, channel, ls_gamestate[channel]["modes"])
933 ls_gamestate[channel]["modes"] = nil
934 end
935 end
936
937 function ls_remove_player(channel, numeric, forced)
938 local role = ls_get_role(channel, numeric)
939
940 if not role then
941 return
942 end
943
944 local announced = ls_get_announced(channel, numeric)
945
946 ls_set_role(channel, numeric, nil)
947
948 ls_devoice_player(channel, numeric)
949
950 for _, player in pairs(ls_get_players(channel)) do
951 if ls_get_vote(channel, player) == numeric then
952 ls_set_vote(channel, player, nil)
953 end
954 end
955
956 if not forced then
957 if announced then
958 if ls_game_in_progress(channel) then
959 ls_chanmsg(channel, ls_format_player(channel, numeric) .. " committed suicide. Goodbye, cruel world.")
960 else
961 ls_chanmsg(channel, ls_format_player(channel, numeric) .. " left the game (" .. table.getn(ls_get_players(channel)) .. " players in the lobby).")
962 end
963 end
964
965 ls_notice(numeric, "You were removed from the lobby.")
966
967 ls_set_delay(channel, 30)
968 ls_set_timeout(channel, 90)
969 end
970 end
971
972 function ls_get_players(channel, role)
973 local players = {}
974
975 for player, _ in pairs(ls_gamestate[channel]["players"]) do
976 if not role or ls_get_role(channel, player) == role then
977 table.insert(players, player)
978 end
979 end
980
981 return players
982 end
983
984 function ls_is_game_channel(channel)
985 return ls_gamestate[channel]
986 end
987
988 function ls_get_role(channel, numeric)
989 if not ls_gamestate[channel]["players"][numeric] then
990 return nil
991 end
992
993 return ls_gamestate[channel]["players"][numeric]["role"]
994 end
995
996 function ls_set_role(channel, numeric, role)
997 if not ls_gamestate[channel]["players"][numeric] then
998 ls_gamestate[channel]["players"][numeric] = { active = false, announced = false }
999 end
1000
1001 if role then
1002 ls_gamestate[channel]["players"][numeric]["role"] = role
1003 else
1004 ls_gamestate[channel]["players"][numeric] = nil
1005 end
1006
1007 if role and role ~= "lobby" then
1008 ls_notice(numeric, "Your role for this round is '" .. ls_format_role(role) .. "'.")
1009 end
1010 end
1011
1012 function ls_get_seen(channel, numeric)
1013 return ls_gamestate[channel]["players"][numeric]["seen"]
1014 end
1015
1016 function ls_set_seen(channel, numeric, seen)
1017 ls_gamestate[channel]["players"][numeric]["seen"] = seen
1018 end
1019
1020 function ls_get_vote(channel, numeric)
1021 if not ls_gamestate[channel]["players"][numeric] then
1022 return nil
1023 end
1024
1025 return ls_gamestate[channel]["players"][numeric]["vote"]
1026 end
1027
1028 function ls_set_vote(channel, numeric, votenumeric)
1029 if ls_get_vote(channel, numeric) == votenumeric then
1030 return
1031 end
1032
1033 if votenumeric then
1034 local count = 0
1035 for _, player in pairs(ls_get_players(channel)) do
1036 if ls_get_vote(channel, player) == votenumeric then
1037 count = count + 1
1038 end
1039 end
1040
1041 -- increase count for this new vote
1042 count = count + 1
1043
1044 if numeric ~= votenumeric then
1045 if ls_get_vote(channel, numeric) then
1046 ls_chanmsg(channel, ls_format_player(channel, numeric) .. " changed their vote to " .. ls_format_player(channel, votenumeric) .. " (" .. count .. " votes).")
1047 else
1048 ls_chanmsg(channel, ls_format_player(channel, numeric) .. " voted for " .. ls_format_player(channel, votenumeric) .. " (" .. count .. " votes).")
1049 end
1050 else
1051 ls_chanmsg(channel, ls_format_player(channel, numeric) .. " voted for himself. Oops! (" .. count .. " votes)")
1052 end
1053 end
1054
1055 if ls_gamestate[channel]["players"][numeric] then
1056 ls_gamestate[channel]["players"][numeric]["vote"] = votenumeric
1057 end
1058 end
1059
1060 function ls_get_active(channel, numeric)
1061 return ls_gamestate[channel]["players"][numeric]["active"]
1062 end
1063
1064 function ls_set_active(channel, numeric, active)
1065 ls_gamestate[channel]["players"][numeric]["active"] = active
1066 end
1067
1068 function ls_get_announced(channel, numeric)
1069 return ls_gamestate[channel]["players"][numeric]["announced"]
1070 end
1071
1072 function ls_set_announced(channel, numeric, announced)
1073 ls_gamestate[channel]["players"][numeric]["announced"] = announced
1074 end
1075
1076 function ls_pick_player(players)
1077 return players[math.random(table.getn(players))]
1078 end
1079
1080 function ls_number_scientists(numPlayers)
1081 return math.ceil((numPlayers - 2) / 5.0)
1082 end
1083
1084 function ls_number_investigators(numPlayers)
1085 return math.ceil((numPlayers - 5) / 6.0)
1086 end
1087
1088 function ls_start_game(channel)
1089 local players = ls_get_players(channel)
1090
1091 irc_localsimplechanmode(ls_bot, channel, "+m")
1092
1093 for nick in channelusers_iter(channel, { nickpusher.numeric }) do
1094 local numeric = nick[1]
1095
1096 if ls_get_role(channel, numeric) then
1097 ls_voice_player(channel, numeric)
1098 else
1099 ls_devoice_player(channel, numeric)
1100 end
1101 end
1102
1103 ls_chanmsg(channel, "Starting the game...")
1104
1105 for _, player in pairs(players) do
1106 ls_set_role(channel, player, "lobby")
1107 end
1108
1109 local players_count = table.getn(players)
1110 local scientists_count = 0
1111 local scientists_needed = ls_number_scientists(players_count)
1112
1113 -- pick scientists
1114 while scientists_count < scientists_needed do
1115 local scientist_index = math.random(table.getn(players))
1116 ls_set_role(channel, table.remove(players, scientist_index), "scientist")
1117 scientists_count = scientists_count + 1
1118 end
1119
1120 -- notify scientists about each other
1121 for _, scientist in pairs(ls_get_players(channel, "scientist")) do
1122 for _, scientist_notify in pairs(ls_get_players(channel, "scientist")) do
1123 if scientists ~= scientist_notify then
1124 ls_notice(scientists_notify, ls_format_player(channel, scientist) .. " is also a scientist.")
1125 end
1126 end
1127 end
1128
1129 local investigators_count = 0
1130 local investigators_needed = ls_number_investigators(players_count)
1131
1132 -- pick investigators
1133 while investigators_count < investigators_needed do
1134 local investigator_index = math.random(table.getn(players))
1135 ls_set_role(channel, table.remove(players, investigator_index), "investigator")
1136 investigators_count = investigators_count + 1
1137 end
1138
1139 -- rest of the players are citizens
1140 for _, player in pairs(players) do
1141 ls_set_role(channel, player, "citizen")
1142 end
1143
1144 ls_chanmsg(channel, "Roles have been assigned: " ..
1145 table.getn(ls_get_players(channel, "scientist")) .. "x " .. ls_format_role("scientist") .. ", " ..
1146 table.getn(ls_get_players(channel, "investigator")) .. "x " .. ls_format_role("investigator") .. ", " ..
1147 table.getn(ls_get_players(channel, "citizen")) .. "x " .. ls_format_role("citizen") .. " - Good luck!")
1148
1149 ls_set_state(channel, "kill")
1150 ls_advance_state(channel)
1151 end
1152
1153 function ls_stop_game(channel)
1154 ls_set_state(channel, "lobby")
1155 ls_set_waitcount(channel, 0)
1156
1157 for _, player in pairs(ls_get_players(channel)) do
1158 ls_remove_player(channel, player, true)
1159 end
1160
1161 irc_localsimplechanmode(ls_bot, channel, "-m")
1162 end
1163
1164 -- makes sure people are not afk
1165 function ls_check_alive(channel)
1166 if not ls_game_in_progress(channel) then
1167 return
1168 end
1169
1170 local dead_players = {}
1171 local idle_players = {}
1172
1173 for _, player in pairs(ls_get_players(channel)) do
1174 local seen = ls_get_seen(channel, player)
1175
1176 if seen < os.time() - 120 then
1177 table.insert(dead_players, player)
1178 elseif seen < os.time() - 60 then
1179 table.insert(idle_players, player)
1180 end
1181 end
1182
1183 if table.getn(dead_players) > 0 then
1184 local verb
1185
1186 if table.getn(dead_players) ~= 1 then
1187 verb = "seem"
1188 else
1189 verb = "seems"
1190 end
1191
1192 ls_chanmsg(channel, ls_format_players(channel, dead_players) .. " " .. verb .. " to be dead (AFK).")
1193
1194 for _, player in pairs(dead_players) do
1195 ls_remove_player(channel, player, true)
1196 end
1197 end
1198
1199 if table.getn(idle_players) > 0 then
1200 ls_chanmsg(channel, "Hi " .. ls_format_players(channel, idle_players) .. ", please say something if you're still alive.")
1201 end
1202 end
1203
1204 function ls_advance_state(channel, delayed)
1205 if delayed and not ls_delay_exceeded(channel) then
1206 return
1207 end
1208
1209 ls_debug(channel, "ls_advance_state")
1210
1211 ls_set_delay(channel, 30)
1212
1213 local players = ls_get_players(channel)
1214 local scientists = ls_get_players(channel, "scientist")
1215 local investigators = ls_get_players(channel, "investigator")
1216
1217 -- game start condition
1218 if not ls_game_in_progress(channel) then
1219 if table.getn(players) < MINPLAYERS then
1220 if table.getn(players) > 0 then
1221 if ls_timeout_exceeded(channel) then
1222 ls_chanmsg(channel, "Lobby was closed because there aren't enough players.")
1223 ls_stop_game(channel)
1224 else
1225 ls_chanmsg(channel, "Game will start when there are at least " .. MINPLAYERS .. " players.")
1226 end
1227 end
1228 else
1229 ls_start_game(channel)
1230 end
1231
1232 return
1233 end
1234
1235 -- winning condition when everyone is dead
1236 if table.getn(players) == 0 then
1237 ls_chanmsg(channel, "Everyone is dead.")
1238 ls_stop_game(channel)
1239 return
1240 end
1241
1242 -- winning condition for scientists
1243 if table.getn(scientists) >= table.getn(players) - table.getn(scientists) then
1244 ls_chanmsg(channel, "There are equal to or more scientists than citizens. Science wins again: " .. ls_format_players(channel, scientists, true))
1245 ls_stop_game(channel)
1246 return
1247 end
1248
1249 -- winning condition for citizen
1250 if table.getn(scientists) == 0 then
1251 ls_chanmsg(channel, "All scientists have been eliminated. The citizens win this round: " .. ls_format_players(channel, players, true))
1252 ls_stop_game(channel)
1253 return
1254 end
1255
1256 -- make sure there's progress towards the game's end
1257 local state = ls_get_state(channel)
1258 local timeout = ls_get_timeout(channel)
1259
1260 if state == "kill" then
1261 if timeout == -1 then
1262 local candidates = ls_get_players(channel, "scientist")
1263 local active_index = math.random(table.getn(candidates))
1264 local active_scientist = table.remove(candidates, active_index)
1265
1266 for _, scientist in pairs(scientists) do
1267 if scientist == active_scientist then
1268 ls_set_active(channel, scientist, true)
1269 ls_notice(scientist, "It's your turn to select a citizen to kill. Use /notice " .. BOTNICK .. " kill <nick> to kill someone.")
1270 else
1271 ls_set_active(channel, scientist, false)
1272 ls_notice(scientist, ls_format_player(channel, active_scientist) .. " is choosing a victim.")
1273 end
1274 end
1275
1276 if table.getn(scientists) > 1 then
1277 ls_chanmsg(channel, "The citizens are asleep while the mad scientists are choosing a target.")
1278 else
1279 ls_chanmsg(channel, "The citizens are asleep while the mad scientist is choosing a target.")
1280 end
1281
1282 ls_set_timeout(channel, 120)
1283 elseif ls_timeout_exceeded(channel) then
1284 ls_chanmsg(channel, "The scientists failed to set their alarm clocks. Nobody dies tonight.")
1285 ls_set_state(channel, "investigate")
1286 ls_advance_state(channel)
1287 else
1288 ls_chanmsg(channel, "The scientists still need to pick someone to kill.")
1289 end
1290 end
1291
1292 if state == "investigate" then
1293 -- the investigators are already dead
1294 if table.getn(investigators) == 0 then
1295 ls_set_state(channel, "vote")
1296 ls_advance_state(channel)
1297 return
1298 end
1299
1300 if timeout == -1 then
1301 local candidates = ls_get_players(channel, "investigator")
1302 local active_index = math.random(table.getn(candidates))
1303 local active_investigator = table.remove(candidates, active_index)
1304
1305 for _, investigator in pairs(investigators) do
1306 if investigator == active_investigator then
1307 ls_set_active(channel, investigator, true)
1308 ls_notice(investigator, "You need to choose someone to investigate: /notice " .. BOTNICK .. " investigate <nick>")
1309 else
1310 ls_set_active(channel, investigator, false)
1311 ls_notice(investigator, "Another investigator is choosing a target.")
1312 end
1313 end
1314
1315 if table.getn(investigators) > 1 then
1316 ls_chanmsg(channel, "It's now up to the investigators to find the mad scientists.")
1317 else
1318 ls_chanmsg(channel, "It's now up to the investigator to find the mad scientists.")
1319 end
1320
1321 ls_set_timeout(channel, 120)
1322 elseif ls_timeout_exceeded(channel) then
1323 ls_chanmsg(channel, "Looks like the investigator is still firmly asleep.")
1324 ls_set_state(channel, "vote")
1325 ls_advance_state(channel)
1326 else
1327 ls_chanmsg(channel, "The investigator still needs to do their job.");
1328 end
1329 end
1330
1331 if state == "vote" then
1332 local missing_votes = {}
1333
1334 for _, player in pairs(players) do
1335 if not ls_get_vote(channel, player) then
1336 table.insert(missing_votes, player)
1337 end
1338 end
1339
1340 if timeout == -1 then
1341 for _, player in pairs(players) do
1342 ls_set_vote(channel, player, nil)
1343 end
1344
1345 ls_chanmsg(channel, "It's now up to the citizens to vote who to lynch (via /notice " .. BOTNICK .. " vote <nick>).")
1346 ls_set_timeout(channel, 120)
1347 elseif ls_timeout_exceeded(channel) or table.getn(missing_votes) == 0 then
1348 local votes = {}
1349 local votees = {}
1350
1351 for _, player in pairs(players) do
1352 local vote = ls_get_vote(channel, player)
1353
1354 if vote then
1355 if not votes[vote] then
1356 votes[vote] = 0
1357 table.insert(votees, vote)
1358 end
1359
1360 votes[vote] = votes[vote] + 1
1361 end
1362 end
1363
1364 local function votecomp(v1, v2)
1365 if votes[v1] > votes[v2] then
1366 return true
1367 end
1368 end
1369
1370 table.sort(votees, votecomp)
1371
1372 local message_suffix, candidates
1373
1374 if table.getn(votees) > 0 then
1375 local message = ""
1376
1377 for _, votee in pairs(votees) do
1378 if message ~= "" then
1379 message = message .. ", "
1380 end
1381
1382 message = message .. votes[votee] .. "x " .. ls_format_player(channel, votee)
1383 end
1384
1385 ls_chanmsg(channel, "Votes: " .. message)
1386
1387 local most_votes = votes[votees[1]]
1388 candidates = {}
1389
1390 for _, votee in pairs(votees) do
1391 if votes[votee] == most_votes then
1392 table.insert(candidates, votee)
1393 end
1394 end
1395
1396 message_suffix = "was lynched by the angry mob."
1397 else
1398 candidates = players
1399 message_suffix = "was hit by a stray high-energy laser beam."
1400 end
1401
1402 local victim_index = math.random(table.getn(candidates))
1403 local victim = candidates[victim_index]
1404
1405 ls_devoice_player(channel, victim)
1406
1407 ls_chanmsg(channel, ls_format_player(channel, victim, true) .. " " .. message_suffix)
1408 ls_remove_player(channel, victim, true)
1409
1410 ls_set_state(channel, "kill")
1411 ls_advance_state(channel)
1412 elseif delayed then
1413 ls_chanmsg(channel, "Some of the citizens still need to vote: " .. ls_format_players(channel, missing_votes))
1414 end
1415 end
1416 end