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