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