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