-- TODO
-- logging
+-- make idle notifications independent from game delay
-- Ideas:
-- scientists vote on kills
local ls_gamestate = {}
local ls_db = {}
local ls_lastsave = 0
+local ls_lastalivecheck = 0
local ls_sched = Scheduler()
function onload()
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)
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
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()
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()
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
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
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 <nick>")
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
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)
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
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
return
end
+ ls_keepalive(channel, numeric)
+
local victimnick = irc_getnickbynick(victim)
if not victimnick then
ls_flush_modes(channel)
end
+function ls_cmd_guard(numeric, victim)
+ if not victim then
+ ls_notice(numeric, "Syntax: vote <nick>")
+ 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 <nick>")
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
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)
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
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)
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
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
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
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
-- 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
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 <nick> 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") .. ", " ..
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")
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 <nick> to kill someone.")
+ ls_notice(scientist, "It's your turn to select a citizen to kill. Use /notice " .. BOTNICK .. " kill <nick> to kill someone.")
else
ls_set_active(channel, scientist, false)
ls_notice(scientist, ls_format_player(channel, active_scientist) .. " is choosing a victim.")
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 <nick>")
+ ls_notice(investigator, "You need to choose someone to investigate: /notice " .. BOTNICK .. " investigate <nick>")
else
ls_set_active(channel, investigator, false)
ls_notice(investigator, "Another investigator is choosing a target.")
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 <nick>).")
+ ls_chanmsg(channel, "It's now up to the citizens to vote who to lynch (via /notice " .. BOTNICK .. " vote <nick>).")
ls_set_timeout(channel, 120)
elseif ls_timeout_exceeded(channel) or table.getn(missing_votes) == 0 then
local votes = {}