2 -- Copyright (C) 2011 Gunnar Beutner
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.
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.
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.
20 -- vote info in !status
23 -- scientists vote on kills
25 local BOTNICK = "labspace"
26 local BOTACCOUNT = "labspace"
27 local BOTACCOUNTID = 5022574
28 local HOMECHANNEL = "#labspace"
32 local DB = "labspace.db"
34 local KILLMESSAGES = {
35 "was brutally murdered.",
36 "was vaporized by the scientist's death ray.",
37 "slipped into a coma after drinking their poison-laced morning coffee.",
38 "was crushed to death by a 5-ton boulder.",
39 "couldn't escape from the scientist's killbot army."
44 local ls_gamestate = {}
47 local ls_lastalivecheck = 0
48 local ls_sched = Scheduler()
60 ls_bot = irc_localregisteruserid(BOTNICK, "labspace", "brought.to.you.by.science", "For science!", BOTACCOUNT, BOTACCOUNTID, "+iXr", gamehandler)
63 ls_hlbot = irc_localregisteruser("hl-" .. BOTNICK, "will.spam", "for.food", "Got some change?", "labspace-hl", "+iX", highlighthandler)
64 irc_localjoin(ls_hlbot, HOMECHANNEL)
67 function ls_join_channels()
68 ls_add_channel(HOMECHANNEL)
70 for _, channel in pairs(ls_db.channels) do
71 if not ls_is_game_channel(channel) then
72 ls_add_channel(channel)
77 function ls_split_message(message)
78 message, _ = message:gsub("^ +", "")
79 message, _ = message:gsub(" +", " ")
80 message, _ = message:gsub(" +$", "")
84 for token in string.gmatch(message, "%S+") do
85 table.insert(tokens, token)
91 function gamehandler(target, revent, ...)
92 if revent == "irc_onchanmsg" then
93 local numeric, channel, message = ...
95 if not ls_is_game_channel(channel) then
99 ls_keepalive(channel, numeric)
101 local tokens = ls_split_message(message)
102 local command = tokens[1]
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)
115 elseif command == "!help" then
116 ls_cmd_help(channel, numeric)
117 elseif command == "!hl" then
118 ls_cmd_hl(channel, numeric)
119 elseif command == "!enable" then
120 ls_cmd_enable(channel, numeric)
121 elseif command == "!disable" then
122 ls_cmd_disable(channel, numeric)
125 ls_flush_modes(channel)
127 elseif revent == "irc_onmsg" or revent == "irc_onnotice" then
128 local numeric, message = ...
130 local tokens = ls_split_message(message)
132 local command = tokens[1]
133 local argument = tokens[2]
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)
142 elseif command == "guard" then
143 ls_cmd_guard(numeric, argument)
144 elseif command == "smite" and onstaff(numeric) then
145 ls_cmd_smite(numeric, argument)
146 elseif command == "killgame" and onstaff(numeric) then
147 ls_cmd_killgame(numeric, argument)
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)
154 elseif revent == "irc_onkilled" then
157 elseif revent == "irc_onkillreconnect" then
163 function highlighthandler(target, revent, ...)
164 if revent == "irc_onkilled" then
166 elseif revent == "irc_onkillreconnect" then
168 ls_localjoin(ls_hlbot, HOMECHANNEL)
172 function irc_onpart(channel, numeric, message)
173 if not ls_is_game_channel(channel) then
177 if ls_get_role(channel, numeric) then
178 ls_remove_player(channel, numeric)
179 ls_advance_state(channel)
183 function irc_onkick(channel, kicked_numeric, kicker_numeric, message)
184 if not ls_is_game_channel(channel) then
188 if ls_bot == kicked_numeric then
189 ls_remove_channel(channel)
193 if ls_get_role(channel, kicked_numeric) then
194 ls_remove_player(channel, kicked_numeric)
195 ls_advance_state(channel)
198 irc_onkickall = irc_onkick
200 function 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)
210 for channel, _ in pairs(ls_gamestate) do
211 ls_advance_state(channel, true)
212 ls_flush_modes(channel)
215 if ls_lastalivecheck < os.time() - 30 then
216 ls_lastalivecheck = os.time()
218 for channel, _ in pairs(ls_gamestate) do
219 ls_check_alive(channel)
223 if ls_lastsave < os.time() - 60 then
224 ls_lastsave = os.time()
229 -- sends a debug message
230 function ls_debug(channel, message)
232 irc_localchanmsg(ls_bot, channel, "DEBUG: " .. message)
236 -- sends a notice to the specified target
237 function ls_notice(numeric, text)
238 irc_localnotice(ls_bot, numeric, text)
241 -- sends a message to the specified target
242 function ls_chanmsg(channel, text)
243 irc_localchanmsg(ls_bot, channel, text)
246 -- formats the specified role identifier for output in a message
247 function 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
254 elseif role == "lobby" then
257 return "Unknown Role"
261 -- formats the specified trait identifier for output in a message
262 function ls_format_trait(trait)
263 if trait == "teleporter" then
264 return "Personal Teleporter"
265 elseif trait == "infested" then
266 return "Alien Parasite"
267 elseif trait == "force" then
268 return "Force Field Generator"
270 return "Unknown Trait"
274 -- formats the specified player name for output in a message (optionally
275 -- revealing that player's role and their traits in the game)
276 function ls_format_player(channel, numeric, reveal_role, reveal_traits)
277 local nick = irc_getnickbynumeric(numeric)
278 local result = "\002" .. nick.nick .. "\002"
281 result = result .. " (" .. ls_format_role(ls_get_role(channel, numeric))
283 if reveal_traits then
284 for _, trait in pairs(ls_get_traits(channel, numeric)) do
285 if ls_get_trait(channel, numeric, trait) then
286 result = result .. ", " .. ls_format_trait(trait)
291 result = result .. ")"
297 -- formats a list of player names for output in a message (optionally
298 -- revealing their roles and traits in the game)
299 function ls_format_players(channel, numerics, reveal_role, reveal_traits, no_and)
303 for _, numeric in pairs(numerics) do
305 if not no_and and i == table.getn(numerics) - 1 then
306 result = result .. " and "
308 result = result .. ", "
312 result = result .. ls_format_player(channel, numeric, reveal_role, reveal_traits)
319 -- returns the current state of the game
320 function ls_get_state(channel)
321 return ls_gamestate[channel]["state"]
324 -- gets the timeout for the current state
325 function ls_get_timeout(channel)
326 return ls_gamestate[channel]["timeout"]
329 -- gets the delay for the current state
330 function ls_get_delay(channel)
331 return ls_gamestate[channel]["delay"]
334 -- gets the ts when !hl was last used
335 function ls_get_lasthl(channel)
336 return ls_gamestate[channel]["lasthl"]
339 -- gets whether the bot is enabled
340 function ls_get_enabled(channel)
341 return ls_gamestate[channel]["enabled"]
344 -- returns true if the game state delay was exceeded, false otherwise
345 function ls_delay_exceeded(channel)
346 return ls_get_delay(channel) < os.time()
349 function ls_get_waitcount(channel)
350 return ls_gamestate[channel]["waitcount"]
353 -- sets the game state
354 function ls_set_state(channel, state)
355 ls_gamestate[channel]["state"] = state
357 ls_set_timeout(channel, -1)
358 ls_set_delay(channel, 30)
361 -- sets the game state timeout (in seconds)
362 function ls_set_timeout(channel, timeout)
363 if timeout == -1 then
364 ls_gamestate[channel]["timeout"] = -1
366 ls_gamestate[channel]["timeout"] = os.time() + timeout
370 -- sets the game state delay (in seconds)
371 function ls_set_delay(channel, delay)
372 ls_gamestate[channel]["delay"] = os.time() + delay
373 ls_debug(channel, "changed gamestate delay to " .. delay)
376 -- sets the !hl timestamp
377 function ls_set_lasthl(channel, ts)
378 ls_gamestate[channel]["lasthl"] = ts
381 -- sets whether the bot is enabled
382 function ls_set_enabled(channel, enabled)
383 ls_gamestate[channel]["enabled"] = enabled
386 function ls_set_waitcount(channel, count)
387 ls_gamestate[channel]["waitcount"] = count
390 -- returns true if the game state timeout was exceeded, false otherwise
391 function ls_timeout_exceeded(channel)
392 local timeout = ls_get_timeout(channel)
394 return timeout ~= -1 and timeout < os.time()
397 -- returns true if there's a game in progress, false otherwise
398 function ls_game_in_progress(channel)
399 return ls_get_state(channel) ~= "lobby"
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
404 function ls_chan_for_numeric(numeric)
405 for channel, _ in pairs(ls_gamestate) do
406 if ls_get_role(channel, numeric) then
414 function ls_cmd_add(channel, numeric)
415 ls_add_player(channel, numeric)
418 function ls_cmd_remove(channel, numeric)
419 ls_remove_player(channel, numeric)
422 function 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.")
428 if table.getn(ls_get_players(channel)) >= MINPLAYERS then
429 local count = ls_get_waitcount(channel)
432 ls_notice(numeric, "Sorry, the timeout can only be extended twice per game.")
436 ls_set_waitcount(channel, count + 1)
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.")
444 ls_set_timeout(channel, 120)
445 ls_set_delay(channel, 45)
447 ls_chanmsg(channel, "Lobby timeout was reset.")
450 function 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.")
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.")
461 ls_advance_state(channel)
463 ls_flush_modes(channel)
466 function 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.")
472 ls_chanmsg(channel, "Players: " .. ls_format_players(channel, ls_get_players(channel)))
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"))
482 function 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 " .. HOMECHANNEL)
487 function 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.")
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.")
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.")
503 if string.lower(channel) ~= string.lower(HOMECHANNEL) then
504 ls_notice(numeric, "Sorry, you can't use this command here.")
508 ls_set_lasthl(channel, os.time())
512 for nick in channelusers_iter(channel, { nickpusher.numeric }) do
513 local numeric = nick[1]
515 if not ls_get_role(channel, numeric) then
516 table.insert(numerics, numeric)
519 if table.getn(numerics) > 10 then
520 irc_localchanmsg(ls_hlbot, channel, "HL: " .. ls_format_players(channel, numerics, false, false, true))
525 if table.getn(numerics) > 0 then
526 irc_localchanmsg(ls_hlbot, "HL: " .. ls_format_players(channel, numerics, false, false, true))
530 function ls_cmd_enable(channel, numeric)
531 local chanuser = irc_getuserchanmodes(numeric, channel)
533 if not chanuser or not chanuser.opped then
534 ls_notice(channel, "You need to be opped to use this command.")
538 ls_set_enabled(channel, true)
539 ls_notice(numeric, "Game has been enabled.")
542 function ls_cmd_disable(channel, numeric)
543 local chanuser = irc_getuserchanmodes(numeric, channel)
545 if not chanuser or not chanuser.opped then
546 ls_notice(channel, "You need to be opped to use this command.")
550 if ls_game_in_progress(channel) then
551 ls_chanmsg(channel, ls_format_player(channel, numeric) .. " disabled the game.")
554 ls_stop_game(channel)
555 ls_flush_modes(channel)
557 ls_set_enabled(channel, false)
558 ls_notice(numeric, "Game has been disabled.")
561 function ls_cmd_kill(numeric, victim)
563 ls_notice(numeric, "Syntax: kill <nick>")
567 local channel = ls_chan_for_numeric(numeric)
570 ls_notice(numeric, "You haven't joined any game lobby.")
574 if ls_get_role(channel, numeric) ~= "scientist" then
575 ls_notice(numeric, "You need to be a scientist to use this command.")
579 if ls_get_state(channel) ~= "kill" then
580 ls_notice(numeric, "Sorry, you can't use this command right now.")
584 if not ls_get_active(channel, numeric) then
585 ls_notice(numeric, "Sorry, it's not your turn to choose a victim.")
589 local victimnick = irc_getnickbynick(victim)
591 if not victimnick then
592 ls_notice(numeric, "Sorry, I don't know who that is.")
596 local victimnumeric = victimnick.numeric
598 if not ls_get_role(channel, victimnumeric) then
599 ls_notice(numeric, "Sorry, " .. ls_format_player(channel, victimnumeric) .. " isn't playing the game.")
603 if math.random(100) > 85 then
604 ls_chanmsg(channel, "The scientists' attack was not successful tonight. Nobody died.")
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)
610 ls_set_guarded(channel, victimnumeric, false)
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.")
613 elseif ls_get_trait(channel, victimnumeric, "infested") then
614 ls_devoice_player(channel, numeric)
615 ls_devoice_player(channel, victimnumeric)
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.")
619 ls_remove_player(channel, numeric, true)
620 ls_remove_player(channel, victimnumeric, true)
622 ls_devoice_player(channel, victimnumeric)
624 if numeric == victimnumeric then
625 ls_chanmsg(channel, ls_format_player(channel, victimnumeric, true) .. " committed suicide.")
627 if ls_get_role(channel, victimnumeric) == "scientist" then
628 ls_chanmsg(channel, ls_format_player(channel, victimnumeric, true) .. " was brutally murdered. Oops.")
630 local killmessage = KILLMESSAGES[math.random(table.getn(KILLMESSAGES))]
632 ls_chanmsg(channel, ls_format_player(channel, victimnumeric, true) .. " " .. killmessage)
636 ls_remove_player(channel, victimnumeric, true)
639 ls_set_state(channel, "investigate")
640 ls_advance_state(channel)
642 ls_flush_modes(channel)
645 function ls_cmd_investigate(numeric, victim)
647 ls_notice(numeric, "Syntax: investigate <nick>")
651 local channel = ls_chan_for_numeric(numeric)
654 ls_notice(numeric, "You haven't joined any game lobby.")
658 if ls_get_role(channel, numeric) ~= "investigator" then
659 ls_notice(numeric, "You need to be an investigator to use this command.")
663 if ls_get_state(channel) ~= "investigate" then
664 ls_notice(numeric, "Sorry, you can't use this command right now.")
668 if not ls_get_active(channel, numeric) then
669 ls_notice(numeric, "Sorry, it's not your turn to choose an investigation target.")
673 local victimnick = irc_getnickbynick(victim)
675 if not victimnick then
676 ls_notice(numeric, "Sorry, I don't know who that is.")
680 local victimnumeric = victimnick.numeric
682 if not ls_get_role(channel, victimnumeric) then
683 ls_notice(numeric, "Sorry, " .. ls_format_player(channel, victimnumeric) .. " isn't playing the game.")
687 local investigators = ls_get_players(channel, "investigator")
689 for _, investigator in pairs(investigators) do
690 if investigator ~= numeric then
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)))
695 if ls_get_role(channel, victimnumeric) ~= "scientist" then
696 ls_notice(victimnumeric, ls_format_player(channel, numeric) .. " investigated you.")
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)))
703 if numeric == victimnumeric then
704 ls_notice(numeric, "You're the investigator. Excellent detective work!")
706 ls_notice(numeric, ls_format_player(channel, victimnumeric) .. "'s role is: " .. ls_format_role(ls_get_role(channel, victimnumeric)))
709 ls_set_state(channel, "vote")
710 ls_advance_state(channel)
712 ls_flush_modes(channel)
715 function ls_cmd_vote(numeric, victim)
717 ls_notice(numeric, "Syntax: vote <nick>")
721 local channel = ls_chan_for_numeric(numeric)
724 ls_notice(numeric, "You haven't joined any game lobby.")
728 if ls_get_state(channel) ~= "vote" then
729 ls_notice(numeric, "Sorry, you can't use this command right now.")
733 local victimnick = irc_getnickbynick(victim)
735 if not victimnick then
736 ls_notice(numeric, "Sorry, I don't know who that is.")
740 local victimnumeric = victimnick.numeric
742 if not ls_get_role(channel, victimnumeric) then
743 ls_notice(numeric, "Sorry, " .. ls_format_player(channel, victimnumeric) .. " isn't playing the game.")
747 if ls_get_vote(channel, numeric) == victimnumeric then
748 ls_notice(numeric, "You already voted for " .. ls_format_player(channel, victimnumeric) .. ".")
752 ls_keepalive(channel, numeric)
754 ls_set_vote(channel, numeric, victimnumeric)
755 ls_notice(numeric, "Done.")
757 ls_advance_state(channel)
759 ls_flush_modes(channel)
762 function ls_cmd_guard(numeric, victim)
764 ls_notice(numeric, "Syntax: guard <nick>")
768 local channel = ls_chan_for_numeric(numeric)
771 ls_notice(numeric, "You haven't joined any game lobby.")
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.")
780 local victimnick = irc_getnickbynick(victim)
782 if not victimnick then
783 ls_notice(numeric, "Sorry, I don't know who that is.")
787 local victimnumeric = victimnick.numeric
789 if not ls_get_role(channel, victimnumeric) then
790 ls_notice(numeric, "Sorry, " .. ls_format_player(channel, victimnumeric) .. " isn't playing the game.")
796 if victimnumeric == numeric then
799 target = ls_format_player(channel, victimnumeric)
802 for _, player in pairs(ls_get_players(channel)) do
803 if ls_get_guarded(channel, player) then
804 if player == victimnumeric then
805 ls_notice(numeric, "You are already protecting " .. target)
809 ls_notice(numeric, "You are no longer protecting " .. target)
812 ls_set_guarded(channel, player, (player == victimnumeric))
815 ls_notice(victimnumeric, "A field of energy envelopes you. You are now protected by a \002force field\002.")
817 if numeric ~= victimnumeric then
818 ls_notice(numeric, "You are now protecting " .. target .. ".")
822 function ls_cmd_smite(numeric, victim)
824 ls_notice(numeric, "Syntax: smite <nick>")
828 local victimnick = irc_getnickbynick(victim)
830 if not victimnick then
831 ls_notice(numeric, "Sorry, I don't know who that is.")
835 local victimnumeric = victimnick.numeric
836 local channel = ls_chan_for_numeric(victimnumeric)
839 ls_notice(numeric, "Sorry, " .. victimnick.nick .. " isn't playing the game.")
843 ls_chanmsg(channel, ls_format_player(channel, victimnumeric, true, true) .. " was struck by lightning.")
844 ls_remove_player(channel, victimnumeric, true)
846 ls_advance_state(channel)
848 ls_flush_modes(channel)
851 function ls_cmd_killgame(numeric, channel)
853 ls_notice(numeric, "Syntax: smite <channel>")
857 if not ls_is_game_channel(channel) then
858 ls_notice(numeric, "I'm not on that channel.")
862 if table.getn(ls_get_players(channel)) == 0 then
863 ls_notice(numeric, "There's nobody playing the game.")
867 ls_chanmsg(channel, ls_format_player(channel, numeric) .. " set us up the bomb. Game over.")
868 ls_stop_game(channel)
870 ls_flush_modes(channel)
873 function ls_cmd_addchan(numeric, channel)
875 ls_notice(numeric, "Syntax: addchan <#channel>")
879 if not irc_getchaninfo(channel) then
880 ls_notice(numeric, "The specified channel does not exist.")
884 if ls_is_game_channel(channel) then
885 ls_notice(numeric, "The bot is already on that channel.")
889 ls_add_channel(channel)
891 ls_notice(numeric, "Done.")
894 function ls_cmd_delchan(numeric, channel)
896 ls_notice(numeric, "Syntax: delchan <#channel>")
900 if not ls_is_game_channel(channel) then
901 ls_notice(numeric, "The bot is not on that channel.")
905 ls_remove_channel(channel, true)
907 ls_notice(numeric, "Done.")
910 function ls_keepalive(channel, numeric)
911 if ls_get_role(channel, numeric) then
912 ls_set_seen(channel, numeric, os.time())
915 -- extend lobby timeout if we don't have enough players yet
916 if ls_get_state(channel) == "lobby" and table.getn(ls_get_players(channel)) < MINPLAYERS then
917 ls_set_delay(channel, 90)
918 ls_set_timeout(channel, 150)
922 function ls_timer_announce_players(channel)
923 ls_gamestate[channel]["announce_timer"] = nil
925 local new_players = {}
927 for _, numeric in pairs(ls_get_players(channel)) do
928 if not ls_get_announced(channel, numeric) then
929 table.insert(new_players, numeric)
930 ls_set_announced(channel, numeric, true)
931 ls_voice_player(channel, numeric)
935 ls_flush_modes(channel)
937 if table.getn(new_players) > 0 then
938 local count = table.getn(ls_get_players(channel))
947 ls_chanmsg(channel, ls_format_players(channel, new_players) .. " joined the game (" .. count .. " " .. subject .. " in the lobby).")
951 function ls_add_channel(channel)
952 ls_gamestate[channel] = { players = {}, state = "lobby", timeout = -1, delay = os.time() + 30, waitcount = 0, lasthl = 0, enabled = true }
953 irc_localjoin(ls_bot, channel)
954 irc_localsimplechanmode(ls_bot, channel, "-m")
957 function ls_remove_channel(channel, part)
958 if ls_gamestate[channel]["announce_timer"] then
959 ls_sched:remove(ls_gamestate[channel]["announce_timer"])
962 ls_gamestate[channel] = nil
965 irc_localpart(ls_bot, channel)
970 ls_db = loadtable(basepath() .. "db/" .. DB)
973 ls_db = ls_dbdefaults()
980 for channel, _ in pairs(ls_gamestate) do
981 table.insert(channels, channel)
984 ls_db.channels = channels
986 savetable(basepath() .. "db/" .. DB, ls_db)
989 function ls_dbdefaults()
991 db.channels = { HOMECHANNEL }
996 function ls_add_player(channel, numeric, forced)
997 local role = ls_get_role(channel, numeric)
1000 ls_chanmsg(channel, "\001ACTION slaps " .. ls_format_player(channel, numeric) .. "\001")
1005 if not ls_get_enabled(channel) then
1006 ls_notice(numeric, "Sorry, the game is currently disabled.")
1010 if ls_game_in_progress(channel) then
1011 ls_notice(numeric, "Sorry, you can't join the game right now.")
1015 local chanuser = irc_getuserchanmodes(numeric, channel)
1017 if not chanuser then
1018 ls_notice(numeric, "Sorry, you must be on the channel to use this command.")
1022 if chanuser.opped then
1023 ls_notice(numeric, "You must not be opped to use this command.")
1027 if table.getn(ls_get_players(channel)) >= MAXPLAYERS then
1028 ls_notice(numeric, "Sorry, the game's lobby is full.")
1032 if ls_chan_for_numeric(numeric) then
1033 ls_notice(numeric, "Sorry, you can't play on multiple channels at once.")
1038 ls_set_role(channel, numeric, "lobby")
1039 ls_set_seen(channel, numeric, os.time())
1042 ls_set_announced(channel, numeric, false)
1044 if ls_gamestate[channel]["announce_timer"] then
1045 ls_sched:remove(ls_gamestate[channel]["announce_timer"])
1047 ls_gamestate[channel]["announce_timer"] = ls_sched:add(5, ls_timer_announce_players, channel)
1049 ls_notice(numeric, "You were added to the lobby.")
1051 ls_set_announced(channel, numeric, true)
1052 ls_voice_player(channel, numeric)
1055 ls_set_delay(channel, 30)
1056 ls_set_timeout(channel, 90)
1059 function ls_voice_player(channel, numeric)
1060 if not ls_gamestate[channel]["modes"] then
1061 ls_gamestate[channel]["modes"] = {}
1064 table.insert(ls_gamestate[channel]["modes"], true)
1065 table.insert(ls_gamestate[channel]["modes"], "v")
1066 table.insert(ls_gamestate[channel]["modes"], numeric)
1069 function ls_devoice_player(channel, numeric)
1070 if not ls_gamestate[channel]["modes"] then
1071 ls_gamestate[channel]["modes"] = {}
1074 table.insert(ls_gamestate[channel]["modes"], false)
1075 table.insert(ls_gamestate[channel]["modes"], "v")
1076 table.insert(ls_gamestate[channel]["modes"], numeric)
1079 function ls_flush_modes(channel)
1080 if ls_gamestate[channel]["modes"] then
1081 irc_localovmode(ls_bot, channel, ls_gamestate[channel]["modes"])
1082 ls_gamestate[channel]["modes"] = nil
1086 function ls_remove_player(channel, numeric, forced)
1087 local role = ls_get_role(channel, numeric)
1093 local announced = ls_get_announced(channel, numeric)
1095 local force_field = ls_get_trait(channel, numeric, "force")
1097 ls_set_role(channel, numeric, nil)
1099 ls_devoice_player(channel, numeric)
1101 for _, player in pairs(ls_get_players(channel)) do
1102 if ls_get_vote(channel, player) == numeric then
1103 ls_set_vote(channel, player, nil)
1107 ls_set_guarded(channel, player, false)
1113 if ls_game_in_progress(channel) then
1114 ls_chanmsg(channel, ls_format_player(channel, numeric) .. " committed suicide. Goodbye, cruel world.")
1116 ls_chanmsg(channel, ls_format_player(channel, numeric) .. " left the game (" .. table.getn(ls_get_players(channel)) .. " players in the lobby).")
1120 ls_notice(numeric, "You were removed from the lobby.")
1122 ls_set_delay(channel, 30)
1123 ls_set_timeout(channel, 90)
1127 function ls_get_players(channel, role)
1130 for player, _ in pairs(ls_gamestate[channel]["players"]) do
1131 if not role or ls_get_role(channel, player) == role then
1132 table.insert(players, player)
1139 function ls_is_game_channel(channel)
1140 return ls_gamestate[channel]
1143 function ls_get_role(channel, numeric)
1144 if not ls_gamestate[channel]["players"][numeric] then
1148 return ls_gamestate[channel]["players"][numeric]["role"]
1151 function ls_set_role(channel, numeric, role)
1152 if not ls_gamestate[channel]["players"][numeric] or role == "lobby" then
1153 ls_gamestate[channel]["players"][numeric] = {
1162 ls_gamestate[channel]["players"][numeric]["role"] = role
1164 ls_gamestate[channel]["players"][numeric] = nil
1167 if role and role ~= "lobby" then
1168 ls_notice(numeric, "Your role for this round is '" .. ls_format_role(role) .. "'.")
1172 function ls_get_traits(channel, numeric)
1175 for trait, _ in pairs(ls_gamestate[channel]["players"][numeric]["traits"]) do
1176 table.insert(traits, trait)
1182 function ls_get_trait(channel, numeric, trait)
1183 return ls_gamestate[channel]["players"][numeric]["traits"][trait]
1186 function ls_set_trait(channel, numeric, trait, enabled)
1187 ls_gamestate[channel]["players"][numeric]["traits"][trait] = enabled
1190 function ls_get_guarded(channel, numeric, guarded)
1191 return ls_gamestate[channel]["players"][numeric]["guarded"]
1194 function ls_set_guarded(channel, numeric, guarded)
1195 ls_gamestate[channel]["players"][numeric]["guarded"] = guarded
1198 function ls_get_seen(channel, numeric)
1199 return ls_gamestate[channel]["players"][numeric]["seen"]
1202 function ls_set_seen(channel, numeric, seen)
1203 ls_gamestate[channel]["players"][numeric]["seen"] = seen
1206 function ls_get_vote(channel, numeric)
1207 if not ls_gamestate[channel]["players"][numeric] then
1211 return ls_gamestate[channel]["players"][numeric]["vote"]
1214 function ls_set_vote(channel, numeric, votenumeric)
1215 if ls_get_vote(channel, numeric) == votenumeric then
1221 for _, player in pairs(ls_get_players(channel)) do
1222 if ls_get_vote(channel, player) == votenumeric then
1227 -- increase count for this new vote
1230 if numeric ~= votenumeric then
1231 if ls_get_vote(channel, numeric) then
1232 ls_chanmsg(channel, ls_format_player(channel, numeric) .. " changed their vote to " .. ls_format_player(channel, votenumeric) .. " (" .. count .. " votes).")
1234 ls_chanmsg(channel, ls_format_player(channel, numeric) .. " voted for " .. ls_format_player(channel, votenumeric) .. " (" .. count .. " votes).")
1237 ls_chanmsg(channel, ls_format_player(channel, numeric) .. " voted for himself. Oops! (" .. count .. " votes)")
1241 if ls_gamestate[channel]["players"][numeric] then
1242 ls_gamestate[channel]["players"][numeric]["vote"] = votenumeric
1246 function ls_get_active(channel, numeric)
1247 return ls_gamestate[channel]["players"][numeric]["active"]
1250 function ls_set_active(channel, numeric, active)
1251 ls_gamestate[channel]["players"][numeric]["active"] = active
1254 function ls_get_announced(channel, numeric)
1255 return ls_gamestate[channel]["players"][numeric]["announced"]
1258 function ls_set_announced(channel, numeric, announced)
1259 ls_gamestate[channel]["players"][numeric]["announced"] = announced
1262 function ls_pick_player(players)
1263 return players[math.random(table.getn(players))]
1266 function ls_number_scientists(numPlayers)
1267 return math.ceil((numPlayers - 2) / 5.0)
1270 function ls_number_investigators(numPlayers)
1271 return math.ceil((numPlayers - 5) / 6.0)
1274 function ls_start_game(channel)
1275 local players = ls_get_players(channel)
1277 irc_localsimplechanmode(ls_bot, channel, "+m")
1279 for nick in channelusers_iter(channel, { nickpusher.numeric }) do
1280 local numeric = nick[1]
1282 if ls_get_role(channel, numeric) then
1283 ls_voice_player(channel, numeric)
1284 ls_set_seen(channel, numeric, os.time())
1286 ls_devoice_player(channel, numeric)
1290 ls_chanmsg(channel, "Starting the game...")
1292 for _, player in pairs(players) do
1293 ls_set_role(channel, player, "lobby")
1294 ls_keepalive(channel, player)
1297 local players_count = table.getn(players)
1298 local scientists_count = 0
1299 local scientists_needed = ls_number_scientists(players_count)
1302 while scientists_count < scientists_needed do
1303 local scientist_index = math.random(table.getn(players))
1304 ls_set_role(channel, table.remove(players, scientist_index), "scientist")
1305 scientists_count = scientists_count + 1
1308 -- notify scientists about each other
1309 for _, scientist in pairs(ls_get_players(channel, "scientist")) do
1310 for _, scientist_notify in pairs(ls_get_players(channel, "scientist")) do
1311 if scientist ~= scientist_notify then
1312 ls_notice(scientist_notify, ls_format_player(channel, scientist) .. " is also a scientist.")
1317 local investigators_count = 0
1318 local investigators_needed = ls_number_investigators(players_count)
1320 -- pick investigators
1321 while investigators_count < investigators_needed do
1322 local investigator_index = math.random(table.getn(players))
1323 ls_set_role(channel, table.remove(players, investigator_index), "investigator")
1324 investigators_count = investigators_count + 1
1327 -- notify scientists about each other
1328 for _, investigator in pairs(ls_get_players(channel, "investigator")) do
1329 for _, investigator_notify in pairs(ls_get_players(channel, "investigator")) do
1330 if investigator ~= investigator_notify then
1331 ls_notice(investigator_notify, ls_format_player(channel, investigator) .. " is also an investigator.")
1336 -- rest of the players are citizens
1337 for _, player in pairs(players) do
1338 ls_set_role(channel, player, "citizen")
1341 -- give someone the force field generator
1342 local force_owner = players[math.random(table.getn(players))]
1343 ls_set_trait(channel, force_owner, "force", true)
1344 ls_set_guarded(channel, force_owner, true)
1345 ls_notice(force_owner, "You've found the \002force field generator\002. Use /notice " .. BOTNICK .. " guard <nick> to protect someone.")
1346 ls_notice(force_owner, "You are currently protecting yourself.")
1348 -- make someone infested if there are at least 6 citizens
1349 if table.getn(players) > 6 then
1350 local infested_player = players[math.random(table.getn(players))]
1351 ls_set_trait(channel, infested_player, "infested", true)
1352 ls_notice(infested_player, "You're infested with an \002alien parasite\002.")
1355 -- give someone the teleporter
1356 local teleporter_candidates
1358 if math.random(100) > 75 then
1359 teleporter_candidates = ls_get_players(channel)
1361 teleporter_candidates = ls_get_players(channel, "scientist")
1364 local teleporter_owner = teleporter_candidates[math.random(table.getn(teleporter_candidates))]
1365 ls_set_trait(channel, teleporter_owner, "teleporter", true)
1366 ls_notice(teleporter_owner, "You've found the \002personal teleporter\002 (50% chance to evade lynching).")
1368 ls_chanmsg(channel, "Roles have been assigned: " ..
1369 table.getn(ls_get_players(channel, "scientist")) .. "x " .. ls_format_role("scientist") .. ", " ..
1370 table.getn(ls_get_players(channel, "investigator")) .. "x " .. ls_format_role("investigator") .. ", " ..
1371 table.getn(ls_get_players(channel, "citizen")) .. "x " .. ls_format_role("citizen") .. " - Good luck!")
1373 ls_set_state(channel, "kill")
1374 ls_advance_state(channel)
1377 function ls_stop_game(channel)
1378 ls_set_state(channel, "lobby")
1379 ls_set_waitcount(channel, 0)
1381 for _, player in pairs(ls_get_players(channel)) do
1382 ls_remove_player(channel, player, true)
1385 irc_localsimplechanmode(ls_bot, channel, "-m")
1388 -- makes sure people are not afk
1389 function ls_check_alive(channel)
1390 if not ls_game_in_progress(channel) then
1394 local dead_players = {}
1395 local idle_players = {}
1397 for _, player in pairs(ls_get_players(channel)) do
1398 local seen = ls_get_seen(channel, player)
1401 if seen < os.time() - 120 then
1402 table.insert(dead_players, player)
1403 elseif seen < os.time() - 60 then
1404 table.insert(idle_players, player)
1409 if table.getn(dead_players) > 0 then
1412 if table.getn(dead_players) ~= 1 then
1418 ls_chanmsg(channel, ls_format_players(channel, dead_players, true, true) .. " " .. verb .. " to be dead (AFK).")
1420 for _, player in pairs(dead_players) do
1421 ls_remove_player(channel, player, true)
1425 if table.getn(idle_players) > 0 then
1426 ls_chanmsg(channel, "Hi " .. ls_format_players(channel, idle_players) .. ", please say something if you're still alive.")
1430 function ls_advance_state(channel, delayed)
1431 if delayed and not ls_delay_exceeded(channel) then
1435 ls_debug(channel, "ls_advance_state")
1437 ls_set_delay(channel, 30)
1439 local players = ls_get_players(channel)
1440 local scientists = ls_get_players(channel, "scientist")
1441 local investigators = ls_get_players(channel, "investigator")
1443 -- game start condition
1444 if not ls_game_in_progress(channel) then
1445 if table.getn(players) < MINPLAYERS then
1446 if table.getn(players) > 0 then
1447 if ls_timeout_exceeded(channel) then
1448 ls_chanmsg(channel, "Lobby was closed because there aren't enough players.")
1449 ls_stop_game(channel)
1451 ls_chanmsg(channel, "Game will start when there are at least " .. MINPLAYERS .. " players.")
1455 ls_start_game(channel)
1461 -- winning condition when everyone is dead
1462 if table.getn(players) == 0 then
1463 ls_chanmsg(channel, "Everyone is dead.")
1464 ls_stop_game(channel)
1468 -- winning condition for scientists
1469 if table.getn(scientists) >= table.getn(players) - table.getn(scientists) then
1470 ls_chanmsg(channel, "There are equal to or more scientists than citizens. Science wins again: " .. ls_format_players(channel, scientists, true, true))
1471 ls_stop_game(channel)
1475 -- winning condition for citizen
1476 if table.getn(scientists) == 0 then
1477 ls_chanmsg(channel, "All scientists have been eliminated. The citizens win this round: " .. ls_format_players(channel, players, true, true))
1478 ls_stop_game(channel)
1482 -- make sure there's progress towards the game's end
1483 local state = ls_get_state(channel)
1484 local timeout = ls_get_timeout(channel)
1486 if state == "kill" then
1487 if timeout == -1 then
1488 local active_scientist = scientists[math.random(table.getn(scientists))]
1490 for _, scientist in pairs(scientists) do
1491 if scientist == active_scientist then
1492 ls_set_active(channel, scientist, true)
1493 ls_notice(scientist, "It's your turn to select a citizen to kill. Use /notice " .. BOTNICK .. " kill <nick> to kill someone.")
1495 ls_set_active(channel, scientist, false)
1496 ls_notice(scientist, ls_format_player(channel, active_scientist) .. " is choosing a victim.")
1500 if table.getn(scientists) > 1 then
1501 ls_chanmsg(channel, "The citizens are asleep while the mad scientists are choosing a target.")
1503 ls_chanmsg(channel, "The citizens are asleep while the mad scientist is choosing a target.")
1506 ls_set_timeout(channel, 120)
1507 elseif ls_timeout_exceeded(channel) then
1508 ls_chanmsg(channel, "The scientists failed to set their alarm clocks. Nobody dies tonight.")
1509 ls_set_state(channel, "investigate")
1510 ls_advance_state(channel)
1512 ls_chanmsg(channel, "The scientists still need to pick someone to kill.")
1516 if state == "investigate" then
1517 -- the investigators are already dead
1518 if table.getn(investigators) == 0 then
1519 ls_set_state(channel, "vote")
1520 ls_advance_state(channel)
1524 if timeout == -1 then
1525 local active_investigator = investigators[math.random(table.getn(investigators))]
1527 for _, investigator in pairs(investigators) do
1528 if investigator == active_investigator then
1529 ls_set_active(channel, investigator, true)
1530 ls_notice(investigator, "You need to choose someone to investigate: /notice " .. BOTNICK .. " investigate <nick>")
1532 ls_set_active(channel, investigator, false)
1533 ls_notice(investigator, "Another investigator is choosing a target.")
1537 if table.getn(investigators) > 1 then
1538 ls_chanmsg(channel, "It's now up to the investigators to find the mad scientists.")
1540 ls_chanmsg(channel, "It's now up to the investigator to find the mad scientists.")
1543 ls_set_timeout(channel, 120)
1544 elseif ls_timeout_exceeded(channel) then
1545 ls_chanmsg(channel, "Looks like the investigator is still firmly asleep.")
1546 ls_set_state(channel, "vote")
1547 ls_advance_state(channel)
1549 ls_chanmsg(channel, "The investigator still needs to do their job.");
1553 if state == "vote" then
1554 local missing_votes = {}
1556 for _, player in pairs(players) do
1557 if not ls_get_vote(channel, player) then
1558 table.insert(missing_votes, player)
1562 if timeout == -1 then
1563 for _, player in pairs(players) do
1564 ls_set_vote(channel, player, nil)
1567 ls_chanmsg(channel, "It's now up to the citizens to vote who to lynch (via /notice " .. BOTNICK .. " vote <nick>).")
1568 ls_set_timeout(channel, 120)
1569 elseif ls_timeout_exceeded(channel) or table.getn(missing_votes) == 0 then
1573 for _, player in pairs(players) do
1574 local vote = ls_get_vote(channel, player)
1577 if not votes[vote] then
1579 table.insert(votees, vote)
1582 votes[vote] = votes[vote] + 1
1586 local function votecomp(v1, v2)
1587 if votes[v1] > votes[v2] then
1592 table.sort(votees, votecomp)
1594 local message_suffix, candidates
1596 if table.getn(votees) > 0 then
1599 for _, votee in pairs(votees) do
1600 if message ~= "" then
1601 message = message .. ", "
1604 message = message .. votes[votee] .. "x " .. ls_format_player(channel, votee)
1607 ls_chanmsg(channel, "Votes: " .. message)
1609 local most_votes = votes[votees[1]]
1612 for _, votee in pairs(votees) do
1613 if votes[votee] == most_votes then
1614 table.insert(candidates, votee)
1618 message_suffix = "was lynched by the angry mob."
1620 candidates = players
1621 message_suffix = "was hit by a stray high-energy laser beam."
1624 local victim_index = math.random(table.getn(candidates))
1625 local victim = candidates[victim_index]
1626 local teleporter = ls_get_trait(channel, victim, "teleporter")
1628 if teleporter and math.random(100) > 50 then
1629 ls_notice(victim, "You press the button to activate the \002personal teleporter\002... and you safely escape!")
1630 ls_chanmsg(channel, ls_format_player(channel, victim) .. " used his personal teleporter to escape the angry mob.")
1632 if math.random(100) > 50 then
1633 ls_set_trait(channel, victim, "teleporter", false)
1634 ls_notice(victim, "Your \002personal teleporter\002 was destroyed.")
1638 ls_notice(victim, "You press the button to activate the \002personal teleporter\002... but nothing happens!")
1641 ls_devoice_player(channel, victim)
1643 ls_chanmsg(channel, ls_format_player(channel, victim, true) .. " " .. message_suffix)
1644 ls_remove_player(channel, victim, true)
1647 ls_set_state(channel, "kill")
1648 ls_advance_state(channel)
1650 ls_chanmsg(channel, "Some of the citizens still need to vote: " .. ls_format_players(channel, missing_votes))