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