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