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