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