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