eggwars/init.lua

1462 lines
38 KiB
Lua
Executable File

----------------------------------------------------------------------
-- Eggwars by wilkgr --
-- with additional code by shivajiva101@hotmail.com --
-- Licensed under the AGPL v3 --
-- You MUST make any changes you make open source --
-- even if you just run it on your server without publishing it --
-- Supports a maximum of 8 players per instance and 8 concurrent --
-- instances for a max of 64 players --
----------------------------------------------------------------------
minetest.set_mapgen_params({mgname = "singlenode"})
math.randomseed(os.time())
eggwars = {}
eggwars.arena = {}
eggwars.armor = minetest.get_modpath("3d_armor") ~= nil
eggwars.bows = minetest.get_modpath("bows") ~= nil
eggwars.playertag = minetest.get_modpath("playertag") ~= nil
eggwars.gauges = minetest.get_modpath("gauges") ~= nil
eggwars.match = {}
eggwars.player = {}
---------------------
-- Local Variables --
---------------------
local mod_data = minetest.get_mod_storage() -- mod storage
local arena_pos = {
{x = 0, y = 0, z = 0},
{x = 1000, y = 0, z = 0},
{x = 0, y = 0, z = 1000},
{x = 1000, y = 0, z = 1000},
{x = -1000, y = 0, z = 0},
{x = 0, y = 0, z = -1000},
{x = -1000, y = 0, z = -1000},
{x = -2000, y = 0, z = 0}
}
local loaded = minetest.deserialize(mod_data:get_string('loaded')) or {arena = {}}
local lobby = {
insert_offset = {x = -42, y = -14, z = -30},
pos = {x = 0, y = 1000, z = 0},
region = {
p1 = {x = -83, y = 980, z = -83},
p2 = {x = 83, y = 1030, z = 83}
}
}
local min_match_players = 4 -- min players needed for a match (default = 4)
local owner = minetest.settings:get('name')
local MP = minetest.get_modpath("eggwars")
local WP = minetest.get_worldpath()
local registered_players = {} -- temp prematch buffer
local schempath = MP.."/schems/"
local stats = minetest.deserialize(mod_data:get_string('statistics')) or {}
local dev = false -- set this to true if testing locally
local reload = dev == false
local tmp_tbl, tmp_hud = {}, {}
local r_rate = 5
stats.rankings = stats.rankings or {}
if reload then loaded = {
arena = {}
}
end
dofile(MP .. "/register_arena.lua")
dofile(MP.."/register_nodes.lua")
dofile(MP.."/shop.lua")
dofile(MP .. "/extender.lua")
dofile(MP .. "/tools.lua")
dofile(MP .. "/sprint.lua")
----------------------
-- Helper Functions --
----------------------
--- Sets server conf settings
-- @return nothing
local function set_settings()
minetest.settings:set('player_transfer_distance', 12)
minetest.settings:set('time_speed', 0)
minetest.settings:write()
minetest.set_timeofday(0.5) -- noon
end
--- Remove objects and replace region with air
-- @param pos1 - vector table
-- @param pos2 - vector table
-- @return nothing
local function clean(pos1, pos2)
eggwars.clear_nodes(pos1, pos2)
eggwars.fixlight(pos1, pos2)
eggwars.clear_objects(pos1, pos2)
end
--- Remove objects and replace region with air
-- @param pos - vector table
-- @param region - vector pair table
-- @return nothing
-- local function clear(pos, region)
-- local p1 = vector.add(pos, region.p1)
-- local p2 = vector.add(pos, region.p2)
-- clean(p1, p2)
-- end
--- Save persistant data
-- @return nothing
local function save_persistant()
mod_data:set_string('statistics', minetest.serialize(stats))
mod_data:set_string('dirty', reload)
mod_data:set_string('loaded', minetest.serialize(loaded))
end
--- Create and return a unique integer
-- @param tbl temp table
-- @param from - start integer
-- @param to - end integer
-- @return random unique integer between from & to
local function my_random(tbl, from, to)
local num = math.random (from, to)
if tbl[num] then num = my_random(tbl, from, to) end
tbl[num] = num
return num
end
--- Generate a random unique list for the trader name order
-- @return table of unique random integers
local function gen_trader_order()
local tbl = {}
for i = 1, 8 do
tbl[#tbl + 1] = my_random(tmp_tbl, 1, 8)
end
tmp_tbl = {}
return tbl
end
--- Finds first unused arena index
-- @return integer index or nil if all arenas are in use!
local function next_index()
for i, _ in ipairs(arena_pos) do
local used
for _, v in pairs(eggwars.match) do
if v.id == i then
used = true
break -- jump to the outer loop
end
end
if not used then return i end
end
end
-- Check path for correct file presence
-- @param path: folder to check
-- @param name: filename without extension
-- @return truth table including count
local function check_files(path, name)
local extension, file, err
local list = {}
list.n = 0
extension = {"mts", "ewm"}
for _, entry in ipairs(extension) do
local filename = path .. name .. "." .. entry
file, err = io.open(filename, "rb")
if err then
list[entry] = false
else
file:close()
list[entry] = true
list.n = list.n + 1
end
end
return list
end
--- Inserts & protects the lobby in a new instance
-- @return nothing
local function add_lobby()
-- calculate insertion pos, place schema, protect & save to settings
local pos = vector.add(lobby.pos, lobby.insert_offset)
minetest.place_schematic(pos, schempath.."lobby.mts")
eggwars.protect(lobby.region.p1, lobby.region.p2, false)
loaded.lobby = true
save_persistant()
end
-- lobby loading
if not loaded.lobby then
minetest.after(0, add_lobby)
end
--- Add match status flags to players HUD
-- @param key - match key string
-- @return nothing
local function gen_match_hud(key)
local match = eggwars.match[key]
local pos = - (0.5 * (#match.player * 20) - 2)
local result = {}
for _, def in pairs(match.hud_img) do
local ndef = {
hud_elem_type = 'image',
position = {x = 0.5, y = 1},
scale = {x = 1, y = 1},
text = def[1],
alignment = {x = 0, y = 0}, -- table reqd
offset = {x = pos, y = -100}
}
table.insert(result, ndef)
pos = pos + 20
end
for k, def in pairs(match.player) do
local player = minetest.get_player_by_name(k)
for i, v in ipairs(result) do
def.hud_id[i] = player:hud_add(v)
end
-- Add Lozenge
def.pil = player:hud_add({
hud_elem_type = 'image',
position = {x = 0, y = 1},
scale = {x = 1, y = 0.5},
text = 'eggwars_pil.png',
alignment = {x = 0, y = 0}, -- table reqd
offset = {x = 70, y = -20}
})
-- Add match time element
def.remaining = player:hud_add({
hud_elem_type = 'text',
position = {x = 0, y = 1},
scale = {x = 128, y = 64},
text = 'Remaining: ' .. match.hud_time .. 'm',
number = '0x00FF00',
alignment = {x = 0, y = 0}, -- table reqd
offset = {x = 70, y = -20}
})
end
end
--- Remove player hud match elements
-- @param name - players name string
-- @param key - match key string
-- @return nothing
local function remove_player_hud(name)
local obj = minetest.get_player_by_name(name)
if not obj then return end
local key = eggwars.player[name]
local def = eggwars.match[key].player[name]
for _, v in ipairs(def.hud_id) do
obj:hud_remove(v)
end
obj:hud_remove(def.remaining)
obj:hud_remove(def.pil)
obj:hud_remove(def.waypoint)
end
--- Remove match status flags from players HUD
-- @param key - arena key string
-- @return nothing
local function remove_match_hud(key)
for k, _ in pairs(eggwars.match[key].player) do
remove_player_hud(k)
end
end
--- Add an image to a players HUD that removes itself
-- @param player - object
-- @param image_string - filename of image
-- @param timer - time in seconds it displays
-- @return nothing
function eggwars.add_tmp_image(name, image_string, timer)
local player = minetest.get_player_by_name(name)
tmp_hud[name] = player:hud_add({
hud_elem_type = 'image',
position = {x = 0.5, y = 0.5},
scale = {x = 1, y = 1},
text = image_string,
alignment = {x = 0, y = 0}, -- table reqd
offset = {x = 0, y = 0}
})
minetest.after(timer, function(pname)
eggwars.remove_tmp_image(pname)
end, name)
end
--- Remove recipes blocked in arena defs
-- NOTE: this function MUST NOT be run with minetest.after it will
-- cause a SIGSEGV error on server shutdown!!!
-- @return nothing
local function modify_recipes()
for i, arena in ipairs(eggwars.arena) do
for j, v in ipairs(arena.blocked_recipes) do
minetest.clear_craft(v)
end
end
end
modify_recipes()
--- Make non expempt nodes unbreakable
-- @return nothing
local function modify_game()
-- create a list of exempt nodes from the registered arenas
local exempt_node = {}
for _, arena in ipairs(eggwars.arena) do
for _, v in ipairs(arena.exempt_nodes) do
table.insert(exempt_node, v)
end
end
-- parse registered nodes
for item, def in pairs(minetest.registered_nodes) do
local modify = true
for i, v in ipairs(exempt_node) do
if string.find(item, v) then
modify = false
break
end
end
if modify then
minetest.override_item(item, {groups = {unbreakable = 1}, })
end
end
end
--- Provides match events based on time
-- @return nothing
local function match_timer()
-- process each match
for key, def in pairs(eggwars.match) do
def.tmr = def.tmr + r_rate
if not def.sd and def.tmr >= def.suddendeath then
def.sd = true
for k, v in pairs(def.player) do
-- remove remaining eggs
if v.egg then
minetest.remove_node(v.eggpos)
v.egg = false
minetest.sound_play("eggwars_out", {
to_player = k,
gain = 1.0,
})
eggwars.update_hud(key, v.id)
end
minetest.sound_play("eggwars_sudden", {
to_player = k,
gain = 1.0,
})
eggwars.add_tmp_image(k, 'eggwars_suddendeath.png', 5)
end
end
if def.sd and def.tmr >= def.max_time then
for k, v in pairs(def.player) do
minetest.sound_play("eggwars_time_over", {
to_player = k,
gain = 1.0,
})
eggwars.add_tmp_image(k, 'eggwars_timeover.png', 5)
end
eggwars.end_match(key)
end
for k, v in pairs(def.player) do
local player = minetest.get_player_by_name(k)
local hp = player:get_hp()
if hp > 0 and v.alive then
local res = hp + 1
local max = player:get_properties().hp_max
if res > max then
res = max
end
if hp ~= res then
player:set_hp(res)
end
end
end
end
-- finally retrigger
minetest.after(r_rate, match_timer)
end
--- Initialises stats for a new player
-- @param name - players name string
-- @return nothing
local function initialise_stats(name)
stats.player = stats.player or {}
if not stats.player[name] then
stats.player[name] = {
damage = 0,
eggs = 0,
falls = 0,
kills = 0,
pb = {
time = 0, -- utc
kills = 0,
falls = 0,
damage = 0
},
plays = 0,
rank = 0,
wins = 0,
}
end
end
--- Displays match results as a formspec
-- @param match - table of ranked match players
-- @param arena_id - integer index of arena
-- @return nothing
local function display_match_results(match, arena_id)
local get_player_by_name = minetest.get_player_by_name
local fs = {
'size[8,6]',
'label[0,0;Rank]',
'label[1,0;Name]',
'label[4,0;Kills]',
'label[5,0;Points]',
'label[6.1,0;Eggs]',
'label[7,0;Falls]'
}
for i, v in ipairs(match) do
local c = eggwars.arena[arena_id].colour[v.id]
fs[#fs + 1] = 'label[0,'..(0.5 * i)..';'..eggwars.colorize(c, i)..']'
fs[#fs + 1] = 'label[1,'..(0.5 * i)..';'..eggwars.colorize(c, v.name)..']'
fs[#fs + 1] = 'label[4,'..(0.5 * i)..';'..eggwars.colorize(c, v.kills)..']'
fs[#fs + 1] = 'label[5,'..(0.5 * i)..';'..eggwars.colorize(c, v.damage)..']'
fs[#fs + 1] = 'label[6.1,'..(0.5 * i)..';'..eggwars.colorize(c, v.eggs)..']'
fs[#fs + 1] = 'label[7,'..(0.5 * i)..';'..eggwars.colorize(c, v.falls)..']'
end
fs[#fs + 1] = 'button_exit[3,5;2,1;btn_e;OK]'
local res = table.concat(fs)
for i, v in ipairs(match) do
local player = get_player_by_name(v.name)
if player then
minetest.show_formspec(v.name, '', res)
end
end
end
--- Displays stats as a formspec
-- @param name - name of player requesting info
-- @return nothing
local function display_stats(name)
local fs = {
'size[9,7]',
'label[0,0;Rank]',
'label[1,0;Name]',
'label[4,0;Wins]',
'label[5,0;Kills]',
'label[6,0;Points]',
'label[7.1,0;Eggs]',
'label[8,0;Falls]'
}
for i,v in ipairs(stats.rankings) do
local c = 'lime'
fs[#fs + 1] = 'label[0,'..(0.5 * i)..';'.. minetest.colorize(c, i) ..']'
fs[#fs + 1] = 'label[1,'..(0.5 * i)..';'.. minetest.colorize(c, v.name) ..']'
fs[#fs + 1] = 'label[4,'..(0.5 * i)..';'.. minetest.colorize(c, v.wins) ..']'
fs[#fs + 1] = 'label[5,'..(0.5 * i)..';'.. minetest.colorize(c, v.kills) ..']'
fs[#fs + 1] = 'label[6,'..(0.5 * i)..';'.. minetest.colorize(c, v.damage) ..']'
fs[#fs + 1] = 'label[7.1,'..(0.5 * i)..';'.. minetest.colorize(c, v.eggs) ..']'
fs[#fs + 1] = 'label[8,'..(0.5 * i)..';'.. minetest.colorize(c, v.falls) ..']'
if i == 10 then break end
end
fs[#fs + 1] = 'button_exit[3.5,6;2,1;btn_e;OK]'
local res = table.concat(fs)
local player = minetest.get_player_by_name(name)
if player then
minetest.show_formspec(name, '', res)
end
end
--- Updates match players game time hud display every minute
-- @return nothing
local function update_hud_time()
for key, match in pairs(eggwars.match) do
match.hud_time = match.hud_time - 1
eggwars.update_hud(key)
end
minetest.after(60, update_hud_time)
end
--- Spawns a player in the first pair of vertical air nodes above minp
-- @return vector
local function safe_spawn(minp)
local maxp = vector.new(minp.x, minp.y + 20, minp.z)
local pos = minetest.find_nodes_in_area(minp, maxp, 'air')
local res = minp
for i, v in ipairs(pos) do
if i > 1 and res.y == v.y - 1 then
return res -- 2 vertical air nodes
else
res = v
end
end
return minp -- failed search
end
--- Removes a player from a match
-- @param name - player name strings
-- @return nothing
local function remove_match_player(name)
if eggwars.player[name] then
local key, match, player, count
key = eggwars.player[name]
match = eggwars.match[key]
player = match.player[name]
if match.alive == 1 then
eggwars.end_match(key)
else
if player.egg then
minetest.remove_node(player.eggpos)
end
remove_player_hud(name)
count = match.alive - 1
match.alive = count
match.player[name].alive = false
match.player[name].egg = false
eggwars.match[key] = match
eggwars.player[name] = nil
player = minetest.get_player_by_name(name)
player:set_pos(lobby.pos)
local msg = name .. " quit the match!"
eggwars.chat_send_match(key, msg)
end
end
end
--- Return length of a keypair table
-- @param list - keypair table
-- @return integer
local function list_count(list)
local count = 0
for k, _ in pairs(list) do
count = count + 1
end
return count
end
--- Add entities to a match
-- @param key - match key string
-- @return nothing
local function add_match_entities(key)
local uid = os.time()
eggwars.match[key].uid = uid
local match = eggwars.match[key]
local def = eggwars.arena[match.arena]
local rnd_list = gen_trader_order()
local pos = arena_pos[match.id]
for name, data in pairs(match.player) do
local id = data.id
local sp = vector.add(pos, def.island[id])
-- Add spawner bot
local adj = vector.add(sp, def.bot.offset[id])
local staticdata = minetest.serialize({uid = uid, owner = name})
local obj = minetest.add_entity(adj, 'eggwars:bot', staticdata)
local yaw = ((math.pi * def.bot.yaw[id]) / 180)
obj:set_yaw(yaw)
-- Add trader
local trader_name = 'Trader '.. def.trader.names[rnd_list[id]]
staticdata = minetest.serialize({owner = name, nametag = trader_name, uid = uid})
adj = vector.add(sp, def.trader.offset[id])
obj = minetest.add_entity(adj, 'eggwars:trader', staticdata)
yaw = ((math.pi * def.trader.yaw[id]) / 180)
obj:set_yaw(yaw)
end
end
-------------------
-- API Functions --
-------------------
--- Removes a temp player hud image
-- @param name - players name
-- @return nothing
eggwars.remove_tmp_image = function(name)
if tmp_hud[name] then
local player = minetest.get_player_by_name(name)
player:hud_remove(tmp_hud[name])
tmp_hud[name] = nil
end
end
--- Update match status in players HUD
-- @param key - arena key string
-- @param id - hud element index, if omitted time is updated
-- @return nothing
eggwars.update_hud = function(key, id)
local match = eggwars.match[key]
for k, def in pairs(match.player) do
local obj = minetest.get_player_by_name(k)
if not id then
obj:hud_change(def.remaining, 'text', 'Remaining: ' ..
eggwars.match[key].hud_time .. 'm')
else
obj:hud_change(def.hud_id[id], 'text', match.hud_img[id][2])
end
end
end
--- Reset arena by index
-- @param arena integer index of registered arena
-- @param n integer index of arena instance
-- @return nothing
eggwars.reset = function(arena, n)
local def = eggwars.arena[arena]
local pos = arena_pos[n]
local p1 = vector.add(pos, def.region.p1)
local p2 = vector.add(pos, def.region.p2)
clean(p1, p2)
end
--- Spawn hub islands for an arena instance
-- @param arena integer index of registered arena
-- @param n integer index of arena
-- @return nothing
eggwars.centrespawn = function(id, n)
local def = eggwars.arena[id]
local spwn = arena_pos[n]
local p1 = vector.add(spwn, def.hub.offset)
minetest.place_schematic(p1, schempath .. def.hub.schem)
-- place satellites
for i, v in ipairs(def.satellite.pos) do
p1 = vector.add(vector.add(spwn, v), def.satellite.offset)
minetest.place_schematic(p1, schempath ..
def.satellite.schem)
end
end
--- Spawn island at pos, indexed by n
-- @param arena integer index of registered arena
-- @param pos vector table
-- @param n integer index of island coords
-- @return spawn vector
eggwars.islandspawn = function(arena, pos, n)
local def = eggwars.arena[arena].island
local p1 = vector.add(pos, def.offset)
minetest.place_schematic(p1, schempath .. def.schem, def.rotate[n])
return pos
end
--- Creates & protects an arena type in the world
-- @param id - arena index
-- @param n - arena position indexeggwars.match[key]
-- @return nothing
eggwars.create_arena = function(id, n)
local def = eggwars.arena[id]
local pos = arena_pos[n]
eggwars.centrespawn(id, n)
for i = 1, #def.island do
local sp = vector.add(pos, def.island[i])
eggwars.islandspawn(id, sp, i)
end
local v = {
p1 = vector.add(pos, def.region.p1),
p2 = vector.add(pos, def.region.p2)
}
eggwars.protect(v.p1, v.p2, false)
end
--- Deletes an arena from the world
-- @param id - arena index
-- @param n - arena position index
-- @return nothing
eggwars.delete_arena = function(arena, n)
local def = eggwars.arena[arena]
local pos = arena_pos[n]
local v = {
p1 = vector.add(pos, def.region.p1),
p2 = vector.add(pos, def.region.p2)
}
eggwars.reset(arena, n)
eggwars.delete_area(v.p1, v.p2)
end
--- Creates a match instance from registered_players table
-- @return nothing
eggwars.begin_match = function ()
-- A match consists of an instance of a registered arena between 4-8 players
-- match.alive - players alive count
-- match.arena - registered arena index
-- match.hud_img - egg images table
-- match.hud_time - remaining time in minutes
-- match.id - arena instance index
-- match.player[name]
-- alive = bool status
-- color = {r,g,b}
-- damage = hp damage
-- egg = bool - false if dug
-- eggpos = pos of players egg
-- eggs = eggs destroyed count
-- falls = fall count
-- hud_id = hud element ref table
-- id = integer - player index in the instance
-- kills = kill count
-- rate = gold spawner rate in seconds
-- shop_items = table of available items
-- spawn = spawn point
-- spawner = pos of gold spawner
-- trader = entity obj
-- winner = integer
--
-- match.spawners - diamond and ruby spawner positions
-- match.stats - match statistics
-- match.tmr - match timer
-- match.uid - match id
local match = {}
local n = next_index() -- arena pos index
local pos = arena_pos[n] -- base vector
local arena, def, key, spwnr, adj, hud_img
-- Load nonexistant arena into the map or use saved type
if not loaded.arena[n] then
arena = math.random(1, #eggwars.arena)
def = {
arena = arena,
pos = pos,
id = n
}
eggwars.create_arena(arena, n)
loaded.arena[n] = def
else
arena = loaded.arena[n].arena
end
def = eggwars.arena[arena]
key = 'a' .. n
match.alive = #registered_players
match.arena = arena
match.id = n
match.player = {}
match.spawners = {}
match.hud_img = {}
for id, name in ipairs(registered_players) do
local player = minetest.get_player_by_name(name)
local sp = vector.add(pos, def.island[id])
local colour = def.colour[id]
adj = sp
spwnr = def.spawners.gold.rate
eggwars.player[name] = key
hud_img = {}
hud_img[1] = ('eggwars_%s.png'):format(def.cs[id][3])
hud_img[2] = ('eggwars_out_%s.png'):format(def.cs[id][3])
table.insert(match.hud_img, hud_img)
match.player[name] = {}
match.player[name].alive = true
match.player[name].color = colour
match.player[name].damage = 0
match.player[name].egg = true
match.player[name].eggs = 0
match.player[name].falls = 0
match.player[name].hud_id = {}
match.player[name].id = id
match.player[name].kills = 0
match.player[name].rate = spwnr
match.player[name].shop_items = {}
match.player[name].spawn = sp
match.player[name].winner = 0
initialise_stats(name)
if not eggwars.playertag then
player:set_nametag_attributes({color = colour})
end
adj.y = adj.y + 2
player:set_pos(adj) -- set player position first
-- Add gold spawner and initialise node timer
adj = vector.add(sp, def.spawners.gold[id])
minetest.set_node(adj, {name = 'eggwars:gold_spawner'})
minetest.get_node_timer(adj):start(spwnr)
match.player[name].spawner = adj
-- Add egg
adj = vector.add(sp, def.egg_offset[id])
minetest.set_node(adj, {name = 'eggwars:egg' .. id})
match.player[name].eggpos = adj
-- set egg metadata
local meta = minetest.get_meta(adj)
meta:set_string('owner', name)
meta:set_string('infotext', name .. "'s egg")
if eggwars.gauges then gauges.add(player) end -- luacheck: ignore
-- Create players shop items table
match.player[name].shop_items = {
-- Add players wool colour
{
name = {
name = "wool:" .. def.cs[id][3],
count = 20,
wear = 0,
metadata = ""
},
description = def.cs[id][1] .. " Wool",
image = 'wool_' .. def.cs[id][3] .. '.png',
cost = {name = "default:gold_ingot",
count = 5,
wear = 0,
metadata = ""
},
entry = 0,
}
}
-- Add the registered shop items
for j = 1, #eggwars.shop_items do -- add the rest
table.insert(match.player[name].shop_items, eggwars.shop_items[j])
end
-- Give arena privs
minetest.set_player_privs(name, {interact = true, shout = true})
-- Set playertag colour
if eggwars.playertag then
playertag.set(player, 1, def.cs[id][2]) -- luacheck: ignore
end
-- Add home waypoint
sp.y = sp.y - 4
match.player[name].waypoint = player:hud_add({
hud_elem_type = "waypoint",
name = 'Home',
text = "m",
number = def.cs[id][4],
world_pos = sp,
})
end
-- Add diamond spawners
spwnr = def.spawners.diamond.rate
for idx, v in ipairs(def.spawners.diamond) do
adj = vector.add(pos, v)
minetest.set_node(adj, {name = 'eggwars:diamond_spawner'})
minetest.get_node_timer(adj):start(spwnr)
table.insert(match.spawners, adj)
end
-- Add ruby spawners
spwnr = def.spawners.ruby.rate
for idx, v in ipairs(def.spawners.ruby) do
adj = vector.add(pos, v)
minetest.set_node(adj, {name = 'eggwars:ruby_spawner'})
minetest.get_node_timer(adj):start(spwnr)
table.insert(match.spawners, adj)
end
-- initialise match timer vars
match.tmr = 0
match.max_time = def.timer.max_time
match.hud_time = match.max_time / 60 -- minutes
match.suddendeath = def.timer.suddendeath
-- store match
eggwars.match[key] = match
-- Add hud to match players
gen_match_hud(key)
-- persist dirty state in case of crash
reload = true
save_persistant()
minetest.after(5, function(param)
add_match_entities(param)
end, key)
registered_players = {} -- reset
end
--- Ends and cleans up a match instance
-- @param key - match key string
-- @return nothing
eggwars.end_match = function(key)
local def = eggwars.match[key]
local match = {}
remove_match_hud(key)
eggwars.match[key].uid = 0
for _, pos in ipairs(def.spawners) do
minetest.get_node_timer(pos):stop()
local w = minetest.get_objects_inside_radius(pos, 2)
for _, obj in ipairs(w) do
if not obj:is_player() then
obj:remove()
end
end
minetest.remove_node(pos)
end
for name, pdef in pairs(def.player) do
--stop gold spawner
minetest.get_node_timer(pdef.spawner):stop()
-- remove objects around spawner
local w = minetest.get_objects_inside_radius(pdef.spawner, 2)
for _, obj in ipairs(w) do
if not obj:is_player() then
obj:remove()
end
end
-- remove spawner
minetest.remove_node(pdef.spawner)
-- Update global stats
local s = stats.player[name]
s.kills = s.kills + pdef.kills
s.falls = s.falls + pdef.falls
s.damage = s.damage + pdef.damage
s.plays = s.plays + 1
s.wins = s.wins + pdef.winner
-- rank match player
local record = {
damage = pdef.damage,
eggs = pdef.eggs,
falls = pdef.falls,
id = pdef.id, -- colour ref
kills = pdef.kills,
name = name,
win = pdef.winner
}
table.insert(match, record)
local player = minetest.get_player_by_name(name)
if player then
-- reset spectator
if not pdef.alive then
minetest.sound_play("eggwars_game_over", {
to_player = name,
gain = 0.5
})
if not eggwars.playertag then
player:set_nametag_attributes({
color = {a = 255, r = 255, g = 255, b = 255}}) --Make nametag visible
else
playertag.set(player, 1) -- luacheck: ignore
end
player:set_properties({visual_size = {x = 1, y = 1, z = 1}}) --Make player visible
else
-- reset player
if eggwars.armor then eggwars.clear_armor(player) end
eggwars.clear_inventory(player)
if pdef.winner == 1 then
minetest.chat_send_all(minetest.colorize(
"green", "*** " .. name .. " won their match!")
)
eggwars.add_tmp_image(name, 'eggwars_winner.png', 5)
minetest.sound_play("eggwars_winner", {
to_player = pdef.name,
gain = 0.5
})
end
end
minetest.set_player_privs(name, {shout = true}) -- set lobby privs
player:set_pos(lobby.pos) -- move player
end
stats.player[name] = s
eggwars.player[name] = nil
end
-- rank winner
table.sort(match, function(a, b)
return a.winner > b.winner
end)
if match[1].winner == 0 then
-- on kills
table.sort(stats.rankings, function(a, b)
return a.kills > b.kills
end)
-- check winner
if match[1].kills == 0 then
-- resort for damage
table.sort(stats.rankings, function(a, b)
return a.damage > b.damage
end)
end
end
local tmp = match[1]
if tmp.winner == 1 then
local rank = {
damage = tmp.damage,
eggs = tmp.eggs,
falls = tmp.falls,
kills = tmp.kills,
name = tmp.name,
wins = stats.player[tmp.name].wins
}
local new_entry = true
for i,v in ipairs(stats.rankings) do
if v.name == rank.name then
-- update entry
new_entry = false
v.damage = v.damage + rank.damage
v.eggs = v.eggs + rank.eggs
v.falls = v.falls + rank.falls
v.kills = v.kills + rank.kills
v.wins = rank.wins
end
end
if new_entry then
table.insert(stats.rankings, rank)
end
if #stats.rankings > 1 then
table.sort(stats.rankings, function(a, b)
return a.wins > b.wins
end)
if #stats.rankings > 20 then
table.remove(stats.rankings, #stats.rankings)
end
end
end
eggwars.reset(def.arena, def.id)
-- remove match
eggwars.match[key] = nil
-- check if this is the last match
if list_count(eggwars.match) == 0 then reload = false end
-- store data
save_persistant()
-- finally
display_match_results(match, def.arena)
end
--- Colour a message string using rgb
-- @param rgb - rgb table
-- @param msg - string to colour
-- @return a minetest colorized string
eggwars.colorize = function(rgb, msg)
local red = string.format('%x', rgb.r)
local green = string.format('%x', rgb.g)
local blue = string.format('%x', rgb.b)
-- modify if reqd
if red:len() == 1 then red = 0 .. red end
if green:len() == 1 then green = 0 .. green end
if blue:len() == 1 then blue = 0 .. blue end
-- And now concatenate it too
local colour = "#" .. red .. green .. blue
return minetest.colorize(colour, msg)
end
--- Broadcast match chat
-- @return nothing
eggwars.chat_send_match = function(key, msg)
local def = eggwars.match[key].player
for name, _ in pairs(def) do
minetest.chat_send_player(name, msg)
end
end
-----------------------------------------
-- Registered chat commands --
-----------------------------------------
minetest.register_chatcommand('admin', {
description = 'gamehub management tool',
params = '{load|save} [name]',
func = function(name, param)
-- secure access
if not minetest.get_player_privs(name).server and name == owner then
return false, "Insufficient privs!"
end
local cmd, fn, pos1, pos2, helper, list, player
helper = [[Usage:
/admin load <filename>
/admin save <filename> <pos1> <pos2>
]]
list = {}
player = minetest.get_player_by_name(name)
if not player then
return false, "You need to be playing to use this command!"
end
for word in param:gmatch("%S+") do
list[#list + 1] = word
end
local qty = #list
if qty < 2 or qty == 3 or qty > 4 then
return false
elseif qty == 4 then
pos1 = minetest.string_to_pos(list[3])
pos2 = minetest.string_to_pos(list[4])
end
cmd = list[1]
fn = list[2]
if cmd == 'load' then
-- last entry takes precedence
local folders = {
MP .. "/schems/", -- read-only
WP .. "/schems/"
}
local path, folder, file, err, msg
msg = {}
for i, v in ipairs(folders) do
local check = check_files(v, fn)
if check.n > 0 then
folder = v
msg[#msg + 1] = "file(s) found in " .. v
elseif check.n == 0 then
msg[#msg + 1] = "no match found in " .. v
end
minetest.chat_send_player(name, table.concat(msg, "\n"))
check.n = nil -- reset
end
if not folder then return end
path = folder .. fn .. ".mts"
-- add mts using player current pos
local pos = vector.round(player:get_pos())
err = minetest.place_schematic(pos, path, nil, nil, true)
if err == nil then
minetest.chat_send_player(name, "could not open file " .. path)
return
end
-- add nodes with metadata
path = folder .. fn .. ".ewm"
file, err = io.open(path, "rb")
if err then
minetest.chat_send_player(name, "could not open meta file "
.. fn .. ".ewm")
return
end
local value = file:read("*a")
file:close()
local count = eggwars.deserialize(pos, value)
minetest.chat_send_player(name, "replaced " .. count ..
" nodes...")
elseif cmd == 'save' then
-- serialize metadata
local result, count = eggwars.serialize_meta(pos1, pos2)
local path = WP .. "/schems"
local filename = path .. "/" .. fn .. ".ewm"
if count > 0 then
local file, err = io.open(filename, "wb")
if err ~= nil then
minetest.log(name, "Could not save file to \"" .. filename .. "\"")
return
end
file:write(result)
file:flush()
file:close()
minetest.chat_send_player(name, "Saved " .. count ..
" nodes to \"" .. filename .. "\"")
end
-- create schematic
filename = path .. "/" .. fn .. ".mts"
minetest.create_schematic(pos1, pos2, nil, filename)
minetest.chat_send_player(name, "Saved \"" .. filename .. "\"")
else
return true, helper
end
end
})
-- register for a match
minetest.register_chatcommand("r", {
params = "",
description = "Join match",
func = function(name, param)
if #registered_players < 8 then -- max 8 players in a match
if #eggwars.match < 8 then -- max 8 matches on the server
for i, v in ipairs(registered_players) do
if registered_players[i] == name then
return true, "You have already registered"
end
end
eggwars.remove_tmp_image(name)
registered_players[#registered_players + 1] = name
if #registered_players == 8 then
eggwars.begin_match();
else
minetest.chat_send_all(#registered_players ..
"/8 players have registered! Use /register to join.");
end
else
return true, "Sorry the max number of games are running." ..
"Please use /start once a match finishes."
end
else
return true, "Sorry. 8 players have already registered." ..
"Try registering after their game has begun."
end
end,
})
-- start match
minetest.register_chatcommand("s", {
params = "",
description = "Starts match if min match players is satisfied",
func = function(name, param)
if #registered_players >= min_match_players or name == owner then
eggwars.begin_match()
end
end
})
-- quit match
minetest.register_chatcommand("q", {
params = "",
description = "Quit the match you are playing",
func = function(name, param)
-- queued?
if #registered_players > 0 then
local idx
for i, v in ipairs(registered_players) do
if v.name == name then
idx = i
break
end
end
table.remove(registered_players, idx)
return true, "you have been removed from the queue!"
end
remove_match_player(name)
end
})
-- show stats formspec
minetest.register_chatcommand("stats", {
params = "",
description = "Shows server game statistics",
func = function(name, param)
display_stats(name)
end
})
-- end match - development
minetest.register_chatcommand("e", {
params = "",
description = "End the game",
func = function(name, param)
if name == owner then
local key = eggwars.player[name]
if key then
eggwars.end_match(key)
end
end
end
})
-- list players
minetest.register_chatcommand("who", {
params = "",
description = "List players in matches",
func = function(name, param)
local text = {}
for k, v in pairs(eggwars.match) do
text[#text + 1] = "Players in match: " .. k .. " "
for key, def in pairs(v.player) do
text[#text + 1] = eggwars.colorize(def.color, key)
end
text[#text + 1] = '\n'
end
return true, table.concat(text)
end
})
-------------------------------------
-- Registered callbacks --
-------------------------------------
minetest.register_on_dieplayer(function(player, reason)
local name = player:get_player_name()
local key = eggwars.player[name]
local def = eggwars.match[key]
if def then
if def.player[name].alive and not def.player[name].egg then
minetest.chat_send_all("*** "..name.." is " ..
minetest.colorize('red', 'OUT'))
-- set privs for spectating
minetest.set_player_privs(name, {fly = true, fast = true, shout = true})
eggwars.add_tmp_image(name, 'eggwars_out.png', 5)
-- record the kill
local killer
if reason.object then
killer = reason.object:get_player_name()
local upd = def.player[killer].kills + 1
def.player[killer].kills = upd
end
-- Make nametag invisible
if not eggwars.playertag then
player:set_nametag_attributes({color = {a = 0, r = 0, g = 0, b = 0}})
else
playertag.set(player, 0, {a=0,r=0,g=0,b=0}) -- luacheck: ignore
end
player:set_properties({visual_size = {x = 0, y = 0}}) --Make player invisible
if eggwars.gauges then gauges.remove(name) end -- luacheck: ignore
if eggwars.armor then eggwars.clear_armor(player) end
def.player[name].alive = false
def.alive = def.alive - 1
eggwars.clear_inventory(player)
eggwars.match[key] = def
eggwars.update_hud(key, def.player[name].id)
-- Are we down to 1 player alive yet?
if def.alive == 1 then
if killer then def.player[killer].winner = 1 end
eggwars.end_match(key)
end
elseif def.player[name].alive and def.player[name].egg then
-- Clean inventory & announce
eggwars.clear_inventory(player)
minetest.chat_send_all("*** " ..
name.." paid Hades a visit and was revived by their egg.")
end
end
end)
minetest.register_on_respawnplayer(function(player)
local name = player:get_player_name()
local pos = lobby.pos -- initialise with lobby vector
-- match override
if eggwars.player[name] then
local key = eggwars.player[name]
local match = eggwars.match[key]
pos = match.player[name].spawn
end
player:set_pos(safe_spawn(pos))
return true
-- Wait for respawn before moving
--minetest.after(0.1, function () player:set_pos(pos) end)
end)
minetest.register_on_joinplayer(function(player)
-- handle the player - no items or interact in the hub
if eggwars.armor then eggwars.clear_armor(player) end
eggwars.clear_inventory(player)
local name = player:get_player_name()
minetest.set_player_privs(name, {shout = true}) --
player:set_pos(lobby.pos)
eggwars.add_tmp_image(name, 'eggwars_welcome.png', 10)
end)
minetest.register_on_leaveplayer(function(player)
-- Handle players exiting during a match
local name = player:get_player_name()
remove_match_player(name)
if tmp_hud[name] then tmp_hud[name] = nil end
end)
minetest.register_on_chat_message(function(name, message)
-- Let's colour the chat!
local txt = "<" .. name .. "> " .. message
if eggwars.player[name] then
local key = eggwars.player[name]
local def = eggwars.match[key]
txt = eggwars.colorize(def.player[name].color, message)
eggwars.chat_send_match(key, txt)
else
-- player in lobby
minetest.chat_send_all(txt) -- broadcast
end
return true -- return as handled!
end)
minetest.register_on_player_hpchange(function(player, hp_change, reason)
if player then
local name = player:get_player_name()
local key = eggwars.player[name]
local match = eggwars.match[key]
if eggwars.player[name] and reason.object then
local pname = reason.object:get_player_name()
local damage = match.player[pname].damage - hp_change
eggwars.match[key].player[pname].damage = damage
elseif eggwars.player[name] and reason.type == 'fall'
and match.player[name].alive then
local falls = match.player[name].falls + 1
eggwars.match[key].player[name].falls = falls
end
end
end, false)
-- knockback override for match players
local old_calculate_knockback = minetest.calculate_knockback
function minetest.calculate_knockback(player, ...) -- luacheck: ignore
if eggwars.player[player:get_player_name()] then
return 3
end
return old_calculate_knockback(player, ...)
end
-- run functions after all mods are loaded!
minetest.after(0, modify_game)
minetest.after(0, set_settings)
minetest.after(0, match_timer)
minetest.after(0, update_hud_time)
minetest.log('action', '[LOADED] Eggwars mod')