yl_snowball/game.lua

311 lines
8.3 KiB
Lua

local modname = "yl_snowball"
local modpath = core.get_modpath(modname)
local Object = dofile(modpath .. DIR_DELIM .. "classic.lua")
local snowgame = {}
local get_player_participation = yl_snowball.get_player_participation
-- game will end after this time when no one is shooting/hitting
yl_snowball.GAME_TIMEOUT = 10
-- If people continue shooting without hitting anyone, game time
-- will be extended, but only up to this time limit
yl_snowball.PLAYER_SUCK_TIME = 30
-- Time in seconds after which non-moving person is considered AFK and
-- hits stop counting
yl_snowball.AFK_TIME = 45
local active_games = {}
yl_snowball.active_games = active_games
local games_by_player = yl_snowball.games_by_player or {}
yl_snowball.games_by_player = games_by_player
local f = string.format
local game_hud = futil.define_hud(
"yl_snowball:hud_",
{
period = 0.567, -- FIXME not needed if updated manually
enabled_by_default = false,
get_hud_def = function(player)
return yl_snowball.update_hud(player)
end,
}
)
core.register_on_leaveplayer(
function(player, _timed_out)
local player_name = player:get_player_name()
if player_name ~= "" then
local game = games_by_player[player_name]
if game then
game:leave(player_name)
end
games_by_player[player_name] = nil
end
end
)
yl_snowball.update_hud = function(player)
local player_name = player:get_player_name()
local game = games_by_player[player_name]
local text = ""
if game then
if game.player_num > 1 then
text = game:get_info_string(true)
else
text = ""
end
else
game_hud:set_enabled(player, false)
end
return {
type = "text",
text = text,
number = 0xFFFFFF,
direction = -1, -- right to left?
position = { x = 0.7, y = 0.5 },
alignment = { x = 1, y = -1 },
offset = { x = -10, y = -10 },
style = 1,
}
end
local GamePlayer = Object:extend()
function GamePlayer:new(player_name)
self.player_name = player_name
self.active = true
self.hits_taken = 0
self.hits_given = 0
self.shots = 0
game_hud:set_enabled(core.get_player_by_name(player_name), true)
end
function GamePlayer:on_hit_take()
self.hits_taken = self.hits_taken + 1
end
function GamePlayer:on_hit_give()
self.hits_given = self.hits_given + 1
-- this is a bit weird: since we don't start counting shots until
-- first one hits, we may have more hits than shots :D
self.shots = math.max(self.shots, self.hits_given)
end
function GamePlayer:on_shoot()
self.shots = self.shots + 1
end
function GamePlayer:quit()
--game_hud:set_enabled(core.get_player_by_name(self.player_name), false) -- this crashes XD
end
local Game = Object:extend()
function Game:new()
local current_time = core.get_gametime()
self.time_started = current_time
self.time_last_shot = current_time
self.time_last_hit = current_time
self.players = {}
self.player_num = 0
active_games[self] = true
return self
end
function Game:get_time_left()
local current_time = core.get_gametime()
local time_left
if (self.time_last_shot - self.time_last_hit) < yl_snowball.PLAYER_SUCK_TIME then
time_left = yl_snowball.GAME_TIMEOUT - (current_time - self.time_last_shot)
else
time_left = yl_snowball.GAME_TIMEOUT + yl_snowball.PLAYER_SUCK_TIME - (current_time - self.time_last_hit)
end
return time_left > 0 and time_left or 0
end
function Game:get_info_string(show_time)
local top = {}
for p_name, player in pairs(self.players) do
table.insert(top, {p_name, player})
end
table.sort(top, function (a,b) return a[2].hits_given > b[2].hits_given end)
local lines = {}
if show_time then
table.insert(lines, f("Game ends in: %ds", self:get_time_left()))
end
for _, info in ipairs(top) do
local p_name = info[1]
local player = info[2]
local accuracy = player.hits_given/player.shots * 100
table.insert(lines, f("%03d|%03d|%05.1f%%|%s",
player.hits_given,
player.hits_taken,
accuracy == accuracy and accuracy or 0,
p_name))
end
return table.concat(lines, "\n")
end
function Game:add_player(player_name, game_player)
if self.players[player_name] then
if game_player and game_player.active then
self.players[player_name] = game_player
games_by_player[player_name] = self
end
-- Do nothing here: player merged their old scores from the
-- game where they weren't active in
else
if (not game_player) or (not game_player.active) then
game_player = GamePlayer(player_name)
end
self.players[player_name] = game_player
games_by_player[player_name] = self
-- this is a new player, increase player count
self.player_num = self.player_num + 1
end
end
function Game:get_player(player_name)
return self.players[player_name]
end
function Game:leave(player_name)
game_hud:set_enabled(core.get_player_by_name(player_name), false)
self.players[player_name].active = false
games_by_player[player_name] = nil
end
function Game:merge(other_game)
for name, game_player in pairs(other_game.players) do
self:add_player(name, game_player)
end
self.time_started = math.min(self.time_started, other_game.time_started)
self.time_last_hit = math.max(self.time_last_hit, other_game.time_last_hit)
self.time_last_shot = math.max(self.time_last_shot, other_game.time_last_shot)
active_games[other_game] = nil
end
function Game:register_hit(shooter_name, target_name)
local s_player = self:get_player(shooter_name)
s_player:on_hit_give()
local t_player = self:get_player(target_name)
t_player:on_hit_take()
local current_time = core.get_gametime()
self.time_last_hit = current_time
end
function Game:register_shot(shooter_name)
local s_player = self:get_player(shooter_name)
s_player:on_shoot()
local current_time = core.get_gametime()
self.time_last_shot = current_time
end
function Game:stop()
-- TODO show only top + your own scores
local lasted = self.time_last_shot - self.time_started
local lines = {
f("* Snowball Game Ended (lasted %ds)", lasted),
self:get_info_string(),
}
for player_name, player in pairs(self.players) do
if games_by_player[player_name] then
if self.player_num > 1 then
core.chat_send_player(player_name, table.concat(lines, "\n"))
end
player:quit()
games_by_player[player_name] = nil
end
end
active_games[self] = nil
end
function snowgame.register_hit(shooter_name, target_name)
local s_game = games_by_player[shooter_name]
local t_game = games_by_player[target_name]
if not s_game then
-- somehow landed a hit before being in a game
-- probably because left and rejoined?
s_game = Game()
s_game:add_player(shooter_name)
s_game:register_shot(shooter_name) -- if was in the game, then shot was registered in on_use
end
if afk_api.is_afk(target_name, yl_snowball.AFK_TIME) then
-- don't count hits on afk people
return
end
if s_game and t_game then
if (s_game ~= t_game) then
-- if both playing, but different games, then merge
s_game:merge(t_game)
end
-- either same game, or was merged into one
s_game:register_hit(shooter_name, target_name)
end
end
function snowgame.register_shot(shooter_name)
local s_game = games_by_player[shooter_name]
if not s_game then
-- started shooting first time, started a game
s_game = Game()
s_game:add_player(shooter_name)
games_by_player[shooter_name] = s_game
end
s_game:register_shot(shooter_name)
end
futil.register_globalstep({
period = 1.31,
func = function(dtime)
local current_time = core.get_gametime()
for game,_ in pairs(active_games) do
if game:get_time_left() <= 0 then
game:stop()
end
end
end,
})
yl_snowball.snowgame = snowgame
return snowgame