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