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