]> jfr.im git - irc/quakenet/lua-labspace.git/blobdiff - labspace.lua
Fix notice target for !add.
[irc/quakenet/lua-labspace.git] / labspace.lua
index 969fa4d7b3281da918f60795dda49ebebca86179..700026233700d2144774c647028776fc51439f2c 100644 (file)
@@ -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 <nick>")
@@ -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 <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>")
@@ -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 <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") .. ", " ..
@@ -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 <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.")
@@ -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 <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.")
@@ -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 <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 = {}