X-Git-Url: https://jfr.im/git/irc/quakenet/lua-labspace.git/blobdiff_plain/1ea98fd520da223076a7cac232b9182bf4fece7d..d3a2325215f9ea76f9980c788a99992caa61c429:/labspace.lua diff --git a/labspace.lua b/labspace.lua index 969fa4d..7000262 100644 --- a/labspace.lua +++ b/labspace.lua @@ -17,6 +17,7 @@ -- TODO -- logging +-- make idle notifications independent from game delay -- Ideas: -- scientists vote on kills @@ -42,6 +43,7 @@ local ls_bot local ls_gamestate = {} local ls_db = {} local ls_lastsave = 0 +local ls_lastalivecheck = 0 local ls_sched = Scheduler() function onload() @@ -110,11 +112,15 @@ function handler(target, revent, ...) ls_cmd_status(channel, numeric) elseif command == "!hl" then ls_cmd_hl(channel, numeric) + elseif command == "!enable" then + ls_cmd_enable(channel, numeric) + elseif command == "!disable" then + ls_cmd_disable(channel, numeric) end ls_flush_modes(channel) end - elseif revent == "irc_onmsg" then + elseif revent == "irc_onmsg" or revent == "irc_onnotice" then local numeric, message = ... local tokens = ls_split_message(message) @@ -129,6 +135,8 @@ function handler(target, revent, ...) ls_cmd_investigate(numeric, argument) elseif command == "vote" then ls_cmd_vote(numeric, argument) + elseif command == "guard" then + ls_cmd_guard(numeric, argument) elseif command == "smite" and onstaff(numeric) then ls_cmd_smite(numeric, argument) elseif command == "addchan" and ontlz(numeric) then @@ -193,6 +201,14 @@ function ontick() ls_flush_modes(channel) end + if ls_lastalivecheck < os.time() - 30 then + ls_lastalivecheck = os.time() + + for channel, _ in pairs(ls_gamestate) do + ls_check_alive(channel) + end + end + if ls_lastsave < os.time() - 60 then ls_lastsave = os.time() ls_dbsave() @@ -281,6 +297,16 @@ function ls_get_delay(channel) return ls_gamestate[channel]["delay"] end +-- gets the ts when !hl was last used +function ls_get_lasthl(channel) + return ls_gamestate[channel]["lasthl"] +end + +-- gets whether the bot is enabled +function ls_get_enabled(channel) + return ls_gamestate[channel]["enabled"] +end + -- returns true if the game state delay was exceeded, false otherwise function ls_delay_exceeded(channel) return ls_get_delay(channel) < os.time() @@ -313,6 +339,16 @@ function ls_set_delay(channel, delay) ls_debug(channel, "changed gamestate delay to " .. delay) end +-- sets the !hl timestamp +function ls_set_lasthl(channel, ts) + ls_gamestate[channel]["lasthl"] = ts +end + +-- sets whether the bot is enabled +function ls_set_enabled(channel, enabled) + ls_gamestate[channel]["enabled"] = enabled +end + function ls_set_waitcount(channel, count) ls_gamestate[channel]["waitcount"] = count end @@ -420,6 +456,17 @@ function ls_cmd_hl(channel, numeric) return end + if ls_get_lasthl(channel) > os.time() - 300 then + ls_notice(numeric, "Sorry, you can only use that command once every 5 minute.") + return + end + + if string.lower(channel) == "#labspace" then + ls_notice(numeric, "Sorry, you can't use this command here.") + end + + ls_set_lasthl(channel, os.time()) + local numerics = {} for nick in channelusers_iter(channel, { nickpusher.numeric }) do @@ -440,6 +487,37 @@ function ls_cmd_hl(channel, numeric) end end +function ls_cmd_enable(channel, numeric) + local chanuser = irc_getuserchanmodes(numeric, channel) + + if not chanuser or not chanuser.opped then + ls_notice(channel, "You need to be opped to use this command.") + return + end + + ls_set_enabled(channel, true) + ls_notice(numeric, "Game has been enabled.") +end + +function ls_cmd_disable(channel, numeric) + local chanuser = irc_getuserchanmodes(numeric, channel) + + if not chanuser or not chanuser.opped then + ls_notice(channel, "You need to be opped to use this command.") + return + end + + if ls_game_in_progress(channel) then + ls_chanmsg(channel, ls_format_player(channel, numeric) .. " disabled the game.") + end + + ls_stop_game(channel) + ls_flush_modes(channel) + + ls_set_enabled(channel, false) + ls_notice(numeric, "Game has been disabled.") +end + function ls_cmd_kill(numeric, victim) if not victim then ls_notice(numeric, "Syntax: kill ") @@ -453,6 +531,8 @@ function ls_cmd_kill(numeric, victim) return end + ls_keepalive(channel, numeric) + if ls_get_role(channel, numeric) ~= "scientist" then ls_notice(numeric, "You need to be a scientist to use this command.") return @@ -484,6 +564,22 @@ function ls_cmd_kill(numeric, victim) if math.random(100) > 85 then ls_chanmsg(channel, "The scientists' attack was not successful tonight. Nobody died.") + elseif ls_get_guarded(channel, victimnumeric) then + for _, player in pairs(ls_get_players(channel)) do + ls_set_trait(channel, player, "force", false) + end + + ls_set_guarded(channel, victimnumeric, false) + + 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.") + elseif ls_get_trait(channel, victimnumeric, "infested") then + ls_devoice_player(channel, numeric) + ls_devoice_player(channel, victimnumeric) + + ls_remove_player(channel, numeric, true) + ls_remove_player(channel, victimnumeric, true) + + 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.") else ls_devoice_player(channel, victimnumeric) @@ -495,7 +591,7 @@ function ls_cmd_kill(numeric, victim) else local killmessage = KILLMESSAGES[math.random(table.getn(KILLMESSAGES))] - ls_chanmsg(channel, ls_format_player(channel, victimnumeric, true) .. killmessage) + ls_chanmsg(channel, ls_format_player(channel, victimnumeric, true) .. " " .. killmessage) end end @@ -526,6 +622,8 @@ function ls_cmd_investigate(numeric, victim) return end + ls_keepalive(channel, numeric) + if ls_get_state(channel) ~= "investigate" then ls_notice(numeric, "Sorry, you can't use this command right now.") return @@ -587,6 +685,8 @@ function ls_cmd_vote(numeric, victim) return end + ls_keepalive(channel, numeric) + local victimnick = irc_getnickbynick(victim) if not victimnick then @@ -614,6 +714,55 @@ function ls_cmd_vote(numeric, victim) ls_flush_modes(channel) end +function ls_cmd_guard(numeric, victim) + if not victim then + ls_notice(numeric, "Syntax: vote ") + return + end + + local channel = ls_chan_for_numeric(numeric) + + if not channel then + ls_notice(numeric, "You haven't joined any game lobby.") + return + end + + if not ls_get_trait(channel, numeric, "force") then + ls_notice(numeric, "Sorry, you need the force field generator to use this command.") + return + end + + ls_keepalive(channel, numeric) + + local victimnick = irc_getnickbynick(victim) + + if not victimnick then + ls_notice(numeric, "Sorry, I don't know who that is.") + return + end + + local victimnumeric = victimnick.numeric + + if not ls_get_role(channel, victimnumeric) then + ls_notice(numeric, "Sorry, " .. ls_format_player(channel, victimnumeric) .. " isn't playing the game.") + return + end + + local target + + if victimnumeric == numeric then + target = "yourself" + else + target = ls_format_player(channel, victimnumeric) + end + + for _, player in pairs(ls_get_players(channel)) do + ls_set_guarded(channel, player, (player == victimnumeric)) + end + + ls_notice(numeric, "You are now protecting " .. target .. ".") +end + function ls_cmd_smite(numeric, victim) if not victim then ls_notice(numeric, "Syntax: smite ") @@ -649,6 +798,11 @@ function ls_cmd_addchan(numeric, channel) return end + if not irc_getchaninfo(channel) then + ls_notice(numeric, "The specified channel does not exist.") + return + end + if ls_is_game_channel(channel) then ls_notice(numeric, "The bot is already on that channel.") return @@ -717,9 +871,9 @@ function ls_timer_announce_players(channel) end function ls_add_channel(channel) - ls_gamestate[channel] = { players = {}, state = "lobby", timeout = -1, delay = os.time() + 30, waitcount = 0 } + ls_gamestate[channel] = { players = {}, state = "lobby", timeout = -1, delay = os.time() + 30, waitcount = 0, lasthl = 0, enabled = true } irc_localjoin(ls_bot, channel) - irc_simplechanmode(channel, "-m") + irc_localsimplechanmode(ls_bot, channel, "-m") end function ls_remove_channel(channel, part) @@ -770,16 +924,28 @@ function ls_add_player(channel, numeric, forced) end if not forced then + if not ls_get_enabled(channel) then + ls_notice(numeric, "Sorry, the game is currently disabled.") + return + end + if ls_game_in_progress(channel) then ls_notice(numeric, "Sorry, you can't join the game right now.") return end - if not irc_nickonchan(numeric, channel) then + local chanuser = irc_getuserchanmodes(numeric, channel) + + if not chanuser then ls_notice(numeric, "Sorry, you must be on the channel to use this command.") return end + if chanuser.opped then + ls_notice(numeric, "You must not be opped to use this command.") + return + end + if table.getn(ls_get_players(channel)) >= MAXPLAYERS then ls_notice(numeric, "Sorry, the game's lobby is full.") return @@ -848,6 +1014,8 @@ function ls_remove_player(channel, numeric, forced) local announced = ls_get_announced(channel, numeric) + local force_field = ls_get_trait(channel, numeric, "force") + ls_set_role(channel, numeric, nil) ls_devoice_player(channel, numeric) @@ -856,6 +1024,10 @@ function ls_remove_player(channel, numeric, forced) if ls_get_vote(channel, player) == numeric then ls_set_vote(channel, player, nil) end + + if force_field then + ls_set_guarded(channel, player, false) + end end if not forced then @@ -899,8 +1071,13 @@ function ls_get_role(channel, numeric) end function ls_set_role(channel, numeric, role) - if not ls_gamestate[channel]["players"][numeric] then - ls_gamestate[channel]["players"][numeric] = { active = false, announced = false } + if not ls_gamestate[channel]["players"][numeric] or role == "lobby" then + ls_gamestate[channel]["players"][numeric] = { + active = false, + announced = false, + traits = {}, + guarded = false + } end if role then @@ -914,6 +1091,22 @@ function ls_set_role(channel, numeric, role) end end +function ls_get_trait(channel, numeric, trait) + return ls_gamestate[channel]["players"][numeric]["traits"][trait] +end + +function ls_set_trait(channel, numeric, trait, enabled) + ls_gamestate[channel]["players"][numeric]["traits"][trait] = enabled +end + +function ls_get_guarded(channel, numeric, guarded) + return ls_gamestate[channel]["players"][numeric]["guarded"] +end + +function ls_set_guarded(channel, numeric, guarded) + ls_gamestate[channel]["players"][numeric]["guarded"] = guarded +end + function ls_get_seen(channel, numeric) return ls_gamestate[channel]["players"][numeric]["seen"] end @@ -993,13 +1186,14 @@ end function ls_start_game(channel) local players = ls_get_players(channel) - irc_simplechanmode(channel, "+m") + irc_localsimplechanmode(ls_bot, channel, "+m") for nick in channelusers_iter(channel, { nickpusher.numeric }) do local numeric = nick[1] if ls_get_role(channel, numeric) then ls_voice_player(channel, numeric) + ls_keepalive(channel, numeric) else ls_devoice_player(channel, numeric) end @@ -1025,8 +1219,8 @@ function ls_start_game(channel) -- notify scientists about each other for _, scientist in pairs(ls_get_players(channel, "scientist")) do for _, scientist_notify in pairs(ls_get_players(channel, "scientist")) do - if scientists ~= scientist_notify then - ls_notice(scientists_notify, ls_format_player(channel, scientist) .. " is also a scientist.") + if scientist ~= scientist_notify then + ls_notice(scientist_notify, ls_format_player(channel, scientist) .. " is also a scientist.") end end end @@ -1045,7 +1239,22 @@ function ls_start_game(channel) for _, player in pairs(players) do ls_set_role(channel, player, "citizen") end - + + -- give someone the force field generator + local force_owner = players[math.random(table.getn(players))] + ls_set_trait(channel, force_owner, "force", true) + ls_set_guarded(channel, force_owner, true) + ls_notice(force_owner, "You've found the \002force field generator\002. Use /notice " .. BOTNICK .. " guard to protect someone.") + ls_notice(force_owner, "You are currently protecting yourself.") + + -- make someone infested if there are at least 6 citizens + if table.getn(players) > 6 then + local infested_player = players[math.random(table.getn(players))] + ls_set_trait(channel, infested_player, "infested", true) + ls_notice(infested_player, "You're infested with an \002alien parasite\002.") + ls_chanmsg(channel, "It's " .. ls_format_player(channel, infested_player) .. ".") + end + ls_chanmsg(channel, "Roles have been assigned: " .. table.getn(ls_get_players(channel, "scientist")) .. "x " .. ls_format_role("scientist") .. ", " .. table.getn(ls_get_players(channel, "investigator")) .. "x " .. ls_format_role("investigator") .. ", " .. @@ -1063,54 +1272,60 @@ function ls_stop_game(channel) ls_remove_player(channel, player, true) end - irc_simplechanmode(channel, "-m") + irc_localsimplechanmode(ls_bot, channel, "-m") end -function ls_advance_state(channel, delayed) - if delayed and not ls_delay_exceeded(channel) then +-- makes sure people are not afk +function ls_check_alive(channel) + if not ls_game_in_progress(channel) then return end - ls_debug(channel, "ls_advance_state") - - ls_set_delay(channel, 30) - - -- make sure people are not afk - if delayed and ls_game_in_progress(channel) then - local dead_players = {} - local idle_players = {} + local dead_players = {} + local idle_players = {} - for _, player in pairs(ls_get_players(channel)) do - local seen = ls_get_seen(channel, player) + for _, player in pairs(ls_get_players(channel)) do + local seen = ls_get_seen(channel, player) + if seen then if seen < os.time() - 120 then table.insert(dead_players, player) elseif seen < os.time() - 60 then table.insert(idle_players, player) end end + end - if table.getn(dead_players) > 0 then - local verb + if table.getn(dead_players) > 0 then + local verb - if table.getn(dead_players) ~= 1 then - verb = "seem" - else - verb = "seems" - end + if table.getn(dead_players) ~= 1 then + verb = "seem" + else + verb = "seems" + end - ls_chanmsg(channel, ls_format_players(channel, dead_players) .. " " .. verb .. " to be dead (AFK).") + ls_chanmsg(channel, ls_format_players(channel, dead_players) .. " " .. verb .. " to be dead (AFK).") - for _, player in pairs(dead_players) do - ls_remove_player(channel, player, true) - end + for _, player in pairs(dead_players) do + ls_remove_player(channel, player, true) end + end - if table.getn(idle_players) > 0 then - ls_chanmsg(channel, "Hi " .. ls_format_players(channel, idle_players) .. ", please say something if you're still alive.") - end + if table.getn(idle_players) > 0 then + ls_chanmsg(channel, "Hi " .. ls_format_players(channel, idle_players) .. ", please say something if you're still alive.") + end +end + +function ls_advance_state(channel, delayed) + if delayed and not ls_delay_exceeded(channel) then + return end + ls_debug(channel, "ls_advance_state") + + ls_set_delay(channel, 30) + local players = ls_get_players(channel) local scientists = ls_get_players(channel, "scientist") local investigators = ls_get_players(channel, "investigator") @@ -1160,14 +1375,12 @@ function ls_advance_state(channel, delayed) if state == "kill" then if timeout == -1 then - local candidates = ls_get_players(channel, "scientist") - local active_index = math.random(table.getn(candidates)) - local active_scientist = table.remove(candidates, active_index) + local active_scientist = scientists[math.random(table.getn(scientists))] for _, scientist in pairs(scientists) do if scientist == active_scientist then ls_set_active(channel, scientist, true) - ls_notice(scientist, "It's your turn to select a citizen to kill. Use /msg " .. BOTNICK .. " kill to kill someone.") + ls_notice(scientist, "It's your turn to select a citizen to kill. Use /notice " .. BOTNICK .. " kill to kill someone.") else ls_set_active(channel, scientist, false) ls_notice(scientist, ls_format_player(channel, active_scientist) .. " is choosing a victim.") @@ -1199,14 +1412,12 @@ function ls_advance_state(channel, delayed) end if timeout == -1 then - local candidates = ls_get_players(channel, "investigator") - local active_index = math.random(table.getn(candidates)) - local active_investigator = table.remove(candidates, active_index) + local active_investigator = investigators[math.random(table.getn(investigators))] for _, investigator in pairs(investigators) do if investigator == active_investigator then ls_set_active(channel, investigator, true) - ls_notice(investigator, "You need to choose someone to investigate: /msg " .. BOTNICK .. " investigate ") + ls_notice(investigator, "You need to choose someone to investigate: /notice " .. BOTNICK .. " investigate ") else ls_set_active(channel, investigator, false) ls_notice(investigator, "Another investigator is choosing a target.") @@ -1243,7 +1454,7 @@ function ls_advance_state(channel, delayed) ls_set_vote(channel, player, nil) end - ls_chanmsg(channel, "It's now up to the citizens to vote who to lynch (via /msg " .. BOTNICK .. " vote ).") + ls_chanmsg(channel, "It's now up to the citizens to vote who to lynch (via /notice " .. BOTNICK .. " vote ).") ls_set_timeout(channel, 120) elseif ls_timeout_exceeded(channel) or table.getn(missing_votes) == 0 then local votes = {}