diff --git a/.gitmodules b/.gitmodules index 184272e..034060a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "mods/ctf_pvp_engine"] - path = mods/ctf_pvp_engine - url = https://github.com/rubenwardy/ctf_pvp_engine [submodule "mods/crafting"] path = mods/crafting url = https://github.com/rubenwardy/crafting diff --git a/mods/ctf_pvp_engine b/mods/ctf_pvp_engine deleted file mode 160000 index ef6e358..0000000 --- a/mods/ctf_pvp_engine +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ef6e358de558617181c5acf52299212c8fcb9bce diff --git a/mods/ctf_pvp_engine/.gitignore b/mods/ctf_pvp_engine/.gitignore new file mode 100644 index 0000000..b9d6bd9 --- /dev/null +++ b/mods/ctf_pvp_engine/.gitignore @@ -0,0 +1,215 @@ +################# +## Eclipse +################# + +*.pydevproject +.project +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.classpath +.settings/ +.loadpath + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# CDT-specific +.cproject + +# PDT-specific +.buildpath + + +################# +## Visual Studio +################# + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results + +[Dd]ebug/ +[Rr]elease/ +x64/ +build/ +[Bb]in/ +[Oo]bj/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +*_i.c +*_p.c +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.log +*.scc + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +*.ncrunch* +.*crunch*.local.xml + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.Publish.xml +*.pubxml + +# NuGet Packages Directory +## TODO: If you have NuGet Package Restore enabled, uncomment the next line +#packages/ + +# Windows Azure Build Output +csx +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.[Pp]ublish.xml +*.pfx +*.publishsettings + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +App_Data/*.mdf +App_Data/*.ldf + +############# +## Windows detritus +############# + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Mac crap +.DS_Store + + +############# +## Python +############# + +*.py[co] + +# Packages +*.egg +*.egg-info +dist/ +build/ +eggs/ +parts/ +var/ +sdist/ +develop-eggs/ +.installed.cfg + +# Installer logs +pip-log.txt + +# Unit test / coverage reports +.coverage +.tox + +#Translations +*.mo + +#Mr Developer +.mr.developer.cfg diff --git a/mods/ctf_pvp_engine/README.md b/mods/ctf_pvp_engine/README.md new file mode 100644 index 0000000..40684c1 --- /dev/null +++ b/mods/ctf_pvp_engine/README.md @@ -0,0 +1,23 @@ +CTF PvP Engine +============== + +A highly modular framework for the Minetest game engine, in order to allow +the development of Capture the Flag / City vs City games. Good for any +sort of game where players can join teams - flags are optional, everything +is highly configurable. + +Licenses +======== + +Created by: [rubenwardy](http://rubenwardy.com/). +Copyright (c) 2013 - 2015 +**Code:** LGPL 2.1 or later. +**Textures:** CC-BY-SA 3.0 + +ctf_flag/sounds/trumpet* by tobyk, license: CC-BY 3.0 +from: http://freesound.org/people/tobyk/sounds/26198/ + +Documentation +============= + +See the doc_* files, starting with doc_project_overview.md diff --git a/mods/ctf_pvp_engine/ctf/core.lua b/mods/ctf_pvp_engine/ctf/core.lua new file mode 100644 index 0000000..ebc67e4 --- /dev/null +++ b/mods/ctf_pvp_engine/ctf/core.lua @@ -0,0 +1,269 @@ +-- Awaiting core support. +local function __genOrderedIndex( t ) + local orderedIndex = {} + for key in pairs(t) do + table.insert( orderedIndex, key ) + end + table.sort( orderedIndex ) + return orderedIndex +end + +local function orderedNext(t, state) + -- Equivalent of the next function, but returns the keys in the alphabetic + -- order. We use a temporary ordered key table that is stored in the + -- table being iterated. + + local key = nil + if state == nil then + t.__orderedIndex = __genOrderedIndex( t ) + key = t.__orderedIndex[1] + else + for i = 1,table.getn(t.__orderedIndex) do + if t.__orderedIndex[i] == state then + key = t.__orderedIndex[i+1] + end + end + end + + if key then + return key, t[key] + end + + -- no more value to return, cleanup + t.__orderedIndex = nil + return +end + +function orderedPairs(t) + -- Equivalent of the pairs() function on tables. Allows to iterate + -- in order + return orderedNext, t, nil +end + + + +-- Registered +ctf.registered_on_load = {} +function ctf.register_on_load(func) + if ctf._mt_loaded then + error("You can't register callbacks at game time!") + end + table.insert(ctf.registered_on_load, func) + if ctf._loaddata then + func(ctf._loaddata) + end +end +ctf.registered_on_save = {} +function ctf.register_on_save(func) + if ctf._mt_loaded then + error("You can't register callbacks at game time!") + end + table.insert(ctf.registered_on_save, func) +end +ctf.registered_on_init = {} +function ctf.register_on_init(func) + if ctf._mt_loaded then + error("You can't register callbacks at game time!") + end + table.insert(ctf.registered_on_init, func) + if ctf._inited then + func() + end +end +ctf.registered_on_new_team = {} +function ctf.register_on_new_team(func) + if ctf._mt_loaded then + error("You can't register callbacks at game time!") + end + table.insert(ctf.registered_on_new_team, func) +end +ctf.registered_on_territory_query = {} +function ctf.register_on_territory_query(func) + if ctf._mt_loaded then + error("You can't register callbacks at game time!") + end + table.insert(ctf.registered_on_territory_query, func) +end +ctf.registered_on_new_game = {} +function ctf.register_on_new_game(func) + if ctf._mt_loaded then + error("You can't register callbacks at game time!") + end + table.insert(ctf.registered_on_new_game, func) + if ctf._new_game then + func() + end +end + +function vector.distanceSQ(p1, p2) + local x = p1.x - p2.x + local y = p1.y - p2.y + local z = p1.z - p2.z + return x*x + y*y + z*z +end + + + +-- Debug helpers +function ctf.error(area, msg) + minetest.log("error", "CTF::" .. area .. " - " ..msg) +end +function ctf.log(area, msg) + if area and area ~= "" then + print("[CaptureTheFlag] (" .. area .. ") " .. msg) + else + print("[CaptureTheFlag] " .. msg) + end +end +function ctf.action(area, msg) + if area and area ~= "" then + minetest.log("action", "[CaptureTheFlag] (" .. area .. ") " .. msg) + else + minetest.log("action", "[CaptureTheFlag] " .. msg) + end +end +function ctf.warning(area, msg) + print("WARNING: [CaptureTheFlag] (" .. area .. ") " .. msg) +end + +function ctf.init() + ctf._inited = true + ctf.log("init", "Initialising!") + + -- Set up structures + ctf._defsettings = {} + ctf.teams = {} + ctf.players = {} + + -- See minetest.conf.example in the root of this subgame + + ctf.log("init", "Creating Default Settings") + ctf._set("diplomacy", true) + ctf._set("players_can_change_team", true) + ctf._set("allocate_mode", 0) + ctf._set("maximum_in_team", -1) + ctf._set("default_diplo_state", "war") + ctf._set("hud", true) + ctf._set("autoalloc_on_joinplayer", true) + ctf._set("friendly_fire", true) + ctf._set("spawn_offset", "0,0,0") + + + for i = 1, #ctf.registered_on_init do + ctf.registered_on_init[i]() + end + + ctf.load() +end + +function ctf.reset() + ctf.log("io", "Deleting CTF save data...") + os.remove(minetest.get_worldpath().."/ctf.txt") + ctf.player_last_team = {} + ctf.init() +end + +-- Set default setting value +function ctf._set(setting, default) + if ctf._defsettings[setting] then + ctf.warning("settings", "Setting " .. dump(setting) .. " redeclared!") + ctf.warning("settings", debug.traceback()) + end + ctf._defsettings[setting] = default + + if minetest.settings:get("ctf."..setting) then + ctf.log("settings", "- " .. setting .. ": " .. minetest.settings:get("ctf."..setting)) + elseif minetest.settings:get("ctf_"..setting) then + ctf.log("settings", "- " .. setting .. ": " .. minetest.settings:get("ctf_"..setting)) + ctf.warning("settings", "deprecated setting ctf_"..setting.. + " used, use ctf."..setting.." instead.") + end +end + +function ctf.setting(name) + local set = minetest.settings:get("ctf."..name) or + minetest.settings:get("ctf_"..name) + local dset = ctf._defsettings[name] + if dset == nil then + ctf.error("setting", "No such setting - " .. name) + return nil + end + + if set ~= nil then + if type(dset) == "number" then + return tonumber(set) + elseif type(dset) == "boolean" then + return minetest.is_yes(set) + else + return set + end + else + return dset + end +end + +function ctf.load() + ctf.log("io", "Loading CTF state") + local file = io.open(minetest.get_worldpath().."/ctf.txt", "r") + if file then + local table = minetest.deserialize(file:read("*all")) + if type(table) == "table" then + ctf.teams = table.teams + ctf.players = table.players + + for i = 1, #ctf.registered_on_load do + ctf.registered_on_load[i](table) + end + return + end + ctf._loaddata = table + else + ctf.log("io", "ctf.txt is not present in the world folder") + ctf._new_game = true + for i = 1, #ctf.registered_on_new_game do + ctf.registered_on_new_game[i]() + end + end +end + +minetest.after(0, function() + ctf._loaddata = nil + ctf._mt_loaded = true +end) + +function ctf.check_save() + if ctf_flag and ctf_flag.assert_flags then + ctf_flag.assert_flags() + end + if ctf.needs_save then + ctf.save() + end + minetest.after(10, ctf.check_save) +end +minetest.after(10, ctf.check_save) + +function ctf.save() + local file = io.open(minetest.get_worldpath().."/ctf.txt", "w") + if file then + local out = { + teams = ctf.teams, + players = ctf.players + } + + for i = 1, #ctf.registered_on_save do + local res = ctf.registered_on_save[i]() + + if res then + for key, value in pairs(res) do + out[key] = value + end + end + end + + file:write(minetest.serialize(out)) + file:close() + ctf.needs_save = false + else + ctf.error("io", "CTF file failed to save!") + end +end diff --git a/mods/ctf_pvp_engine/ctf/depends.txt b/mods/ctf_pvp_engine/ctf/depends.txt new file mode 100644 index 0000000..5588f85 --- /dev/null +++ b/mods/ctf_pvp_engine/ctf/depends.txt @@ -0,0 +1,2 @@ +chatplus? +hudkit diff --git a/mods/ctf_pvp_engine/ctf/diplomacy.lua b/mods/ctf_pvp_engine/ctf/diplomacy.lua new file mode 100644 index 0000000..a753c8c --- /dev/null +++ b/mods/ctf_pvp_engine/ctf/diplomacy.lua @@ -0,0 +1,83 @@ +-- diplo states: war, peace, alliance +ctf.diplo = { + diplo = {} +} + +ctf.register_on_load(function(table) + ctf.diplo.diplo = table.diplo +end) + +ctf.register_on_save(function() + return { diplo = ctf.diplo.diplo } +end) + +function ctf.diplo.get(one,two) + if not ctf.diplo.diplo then + return ctf.setting("default_diplo_state") + end + + for i = 1, #ctf.diplo.diplo do + local dip = ctf.diplo.diplo[i] + if (dip.one == one and dip.two == two) or + (dip.one == two and dip.two == one) then + return dip.state + end + end + + return ctf.setting("default_diplo_state") +end + +function ctf.diplo.set(one, two, state) + if ctf.diplo.diplo then + -- Check the table for an existing diplo state + for i = 1, #ctf.diplo.diplo do + local dip = ctf.diplo.diplo[i] + if (dip.one == one and dip.two == two) or + (dip.one == two and dip.two == one) then + dip.state = state + return + end + end + else + ctf.diplo.diplo = {} + end + + table.insert(ctf.diplo.diplo,{one=one,two=two,state=state}) +end + +function ctf.diplo.check_requests(one, two) + local team = ctf.team(two) + + if not team.log then + return nil + end + + for i=1,#team.log do + if team.log[i].team == one and + team.log[i].type == "request" and + team.log[i].mode == "diplo" then + return team.log[i].msg + end + end + + return nil +end + +function ctf.diplo.cancel_requests(one, two) + local team = ctf.team(two) + + if not team.log then + return + end + + for i=1,#team.log do + if team.log[i].team == one and + team.log[i].type == "request" and + team.log[i].mode == "diplo" then + table.remove(team.log,i) + return + end + end + + return +end diff --git a/mods/ctf_pvp_engine/ctf/gui.lua b/mods/ctf_pvp_engine/ctf/gui.lua new file mode 100644 index 0000000..58a5964 --- /dev/null +++ b/mods/ctf_pvp_engine/ctf/gui.lua @@ -0,0 +1,353 @@ +ctf.gui = { + tabs = {} +} + +ctf.register_on_init(function() + ctf._set("gui", true) + ctf._set("gui.team", true) + ctf._set("gui.team.initial", "news") + + for name, tab in pairs(ctf.gui.tabs) do + ctf._set("gui.tab." .. name, true) + end +end) + +function ctf.gui.register_tab(name, title, func) + ctf.gui.tabs[name] = { + name = name, + title = title, + func = func + } + + if ctf._defsettings and ctf._defsettings["gui.tab." .. name] == nil then + ctf._set("gui.tab." .. name, true) + end +end + +function ctf.gui.show(name, tab, tname) + if not tab then + tab = ctf.setting("gui.team.initial") or "news" + end + + if not tab or not ctf.gui.tabs[tab] or not name or name == "" then + ctf.log("gui", "Invalid tab or name given to ctf.gui.show") + return + end + + if not ctf.setting("gui.team") or not ctf.setting("gui") then + return + end + + if not ctf.team(tname) then + tname = ctf.player(name).team + end + + if ctf.team(tname) then + ctf.action("gui", name .. " views " .. tname .. "'s " .. tab .. " page") + ctf.gui.tabs[tab].func(name, tname) + else + ctf.log("gui", "Invalid team given to ctf.gui.show") + end +end + +-- Get tab buttons +function ctf.gui.get_tabs(name, tname) + local result = "" + local id = 1 + local function addtab(name,text) + result = result .. "button[" .. (id*2-1) .. ",0;2,1;" .. name .. ";" .. text .. "]" + id = id + 1 + end + + for name, tab in pairs(ctf.gui.tabs) do + if ctf.setting("gui.tab." .. name) then + addtab(name, tab.title) + end + end + + return result +end + +-- Team interface +ctf.gui.register_tab("news", "News", function(name, tname) + local result = "" + local team = ctf.team(tname).log + + if not team then + team = {} + end + + local amount = 0 + + for i = 1, #team do + if team[i].type == "request" then + if ctf.can_mod(name, tname) then + amount = amount + 2 + local height = (amount*0.5) + 0.5 + amount = amount + 1 + + if team[i].mode == "diplo" then + result = result .. "background[0.5," .. height .. ";8.3,1;diplo_" .. team[i].msg .. ".png]" + if team[i].msg == "alliance" then + result = result .. "label[1," .. height .. ";" .. + team[i].team .. " offers an " .. + minetest.formspec_escape(team[i].msg) .. " treaty]" + else + result = result .. "label[1," .. height .. ";" .. + team[i].team .. " offers a " .. + minetest.formspec_escape(team[i].msg) .. " treaty]" + end + result = result .. "button[6," .. height .. ";1,1;btn_y" .. i .. ";Yes]" + result = result .. "button[7," .. height .. ";1,1;btn_n" .. i .. ";No]" + else + result = result .. "label[0.5," .. height .. ";RANDOM REQUEST TYPE]" + end + end + else + amount = amount + 1 + local height = (amount*0.5) + 0.5 + + if height > 5 then + break + end + + result = result .. "label[0.5," .. height .. ";" .. + minetest.formspec_escape(team[i].msg) .. "]" + end + end + + if ctf.can_mod(name, tname) then + result = result .. "button[4,6;2,1;clear;Clear all]" + end + + if amount == 0 then + result = "label[0.5,1;Welcome to the news panel]" .. + "label[0.5,1.5;News such as attacks will appear here]" + end + + minetest.show_formspec(name, "ctf:news", + "size[10,7]" .. + ctf.gui.get_tabs(name, tname) .. + result) +end) + +-- Team interface +ctf.gui.register_tab("diplo", "Diplomacy", function(name, tname) + local result = "" + local data = {} + + local amount = 0 + + for key, value in pairs(ctf.teams) do + if key ~= tname then + table.insert(data,{ + team = key, + state = ctf.diplo.get(tname, key), + to = ctf.diplo.check_requests(tname, key), + from = ctf.diplo.check_requests(key, tname) + }) + end + end + + result = result .. "label[1,1;Diplomacy from the perspective of " .. tname .. "]" + + for i = 1, #data do + amount = i + local height = (i*1)+0.5 + + if height > 5 then + break + end + + result = result .. "background[1," .. height .. ";8.2,1;diplo_" .. + data[i].state .. ".png]" + result = result .. "button[1.25," .. height .. ";2,1;team_" .. + data[i].team .. ";" .. data[i].team .. "]" + result = result .. "label[3.75," .. height .. ";" .. data[i].state + .. "]" + + if ctf.can_mod(name, tname) and ctf.player(name).team == tname then + if not data[i].from and not data[i].to then + if data[i].state == "war" then + result = result .. "button[7.5," .. height .. + ";1.5,1;peace_" .. data[i].team .. ";Peace]" + elseif data[i].state == "peace" then + result = result .. "button[6," .. height .. + ";1.5,1;war_" .. data[i].team .. ";War]" + result = result .. "button[7.5," .. height .. + ";1.5,1;alli_" .. data[i].team .. ";Alliance]" + elseif data[i].state == "alliance" then + result = result .. "button[6," .. height .. + ";1.5,1;peace_" .. data[i].team .. ";Peace]" + end + elseif data[i].from ~= nil then + result = result .. "label[6," .. height .. + ";request recieved]" + elseif data[i].to ~= nil then + result = result .. "label[5.5," .. height .. + ";request sent]" + result = result .. "button[7.5," .. height .. + ";1.5,1;cancel_" .. data[i].team .. ";Cancel]" + end + end + end + + minetest.show_formspec(name, "ctf:diplo", + "size[10,7]" .. + ctf.gui.get_tabs(name, tname) .. + result + ) +end) + +local function formspec_is_ctf_tab(fsname) + for name, tab in pairs(ctf.gui.tabs) do + if fsname == "ctf:" .. name then + return true + end + end + return false +end + +minetest.register_on_player_receive_fields(function(player, formname, fields) + if not formspec_is_ctf_tab(formname) then + return false + end + + local name = player:get_player_name() + local tplayer = ctf.player(name) + local tname = tplayer.team + local team = ctf.team(tname) + + if not team then + return false + end + + -- Do navigation + for tabname, tab in pairs(ctf.gui.tabs) do + if fields[tabname] then + ctf.gui.show(name, tabname) + return true + end + end + + -- Todo: move callbacks + -- News page + if fields.clear then + team.log = {} + ctf.needs_save = true + ctf.gui.show(name, "news") + return true + end +end) + +minetest.register_on_player_receive_fields(function(player, formname, fields) + local name = player:get_player_name() + local tplayer = ctf.player(name) + local tname = tplayer.team + local team = ctf.team(tname) + + if not team then + return false + end + + if formname == "ctf:news" then + for key, field in pairs(fields) do + local ok, id = string.match(key, "btn_([yn])([0123456789]+)") + if ok and id then + if ok == "y" then + ctf.diplo.set(tname, team.log[tonumber(id)].team, team.log[tonumber(id)].msg) + + -- Post to acceptor's log + ctf.post(tname, { + msg = "You have accepted the " .. + team.log[tonumber(id)].msg .. " request from " .. + team.log[tonumber(id)].team }) + + -- Post to request's log + ctf.post(team.log[tonumber(id)].team, { + msg = tname .. " has accepted your " .. + team.log[tonumber(id)].msg .. " request" }) + + id = id + 1 + end + + table.remove(team.log, id) + ctf.needs_save = true + ctf.gui.show(name, "news") + return true + end + end + end +end) + +minetest.register_on_player_receive_fields(function(player, formname, fields) + local name = player:get_player_name() + local tplayer = ctf.player(name) + local tname = tplayer.team + local team = ctf.team(tname) + + if not team or formname ~= "ctf:diplo" then + return false + end + + for key, field in pairs(fields) do + local tname2 = string.match(key, "team_(.+)") + if tname2 and ctf.team(tname2) then + ctf.gui.show(name, "diplo", tname2) + return true + end + + if ctf.can_mod(name, tname) then + tname2 = string.match(key, "peace_(.+)") + if tname2 then + if ctf.diplo.get(tname, tname2) == "war" then + ctf.post(tname2, { + type = "request", + msg = "peace", + team = tname, + mode = "diplo" }) + else + ctf.diplo.set(tname, tname2, "peace") + ctf.post(tname, { + msg = "You have cancelled the alliance treaty with " .. tname2 }) + ctf.post(tname2, { + msg = tname .. " has cancelled the alliance treaty" }) + end + + ctf.gui.show(name, "diplo") + return true + end + + tname2 = string.match(key, "war_(.+)") + if tname2 then + ctf.diplo.set(tname, tname2, "war") + ctf.post(tname, { + msg = "You have declared war on " .. tname2 }) + ctf.post(tname2, { + msg = tname .. " has declared war on you" }) + + ctf.gui.show(name, "diplo") + return true + end + + tname2 = string.match(key, "alli_(.+)") + if tname2 then + ctf.post(tname2, { + type = "request", + msg = "alliance", + team = tname, + mode = "diplo" }) + + ctf.gui.show(name, "diplo") + return true + end + + tname2 = string.match(key, "cancel_(.+)") + if tname2 then + ctf.diplo.cancel_requests(tname, tname2) + ctf.gui.show(name, "diplo") + return true + end + end -- end if can mod + end -- end for each field +end) diff --git a/mods/ctf_pvp_engine/ctf/hud.lua b/mods/ctf_pvp_engine/ctf/hud.lua new file mode 100644 index 0000000..32da7f9 --- /dev/null +++ b/mods/ctf_pvp_engine/ctf/hud.lua @@ -0,0 +1,50 @@ +ctf.hud = hudkit() +ctf.hud.parts = {} +function ctf.hud.register_part(func) + table.insert(ctf.hud.parts, func) +end + +minetest.register_on_leaveplayer(function(player) + ctf.hud.players[player:get_player_name()] = nil +end) + +ctf.register_on_join_team(function(name, tname) + if ctf.setting("hud") then + ctf.hud.update(minetest.get_player_by_name(name)) + end +end) + +function ctf.hud.update(player) + if not player then + return + end + + local name = player:get_player_name() + local tplayer = ctf.player(name) + + if not tplayer or not tplayer.team or not ctf.team(tplayer.team) then + return + end + + -- Team Identifier + for i = 1, #ctf.hud.parts do + ctf.hud.parts[i](player, name, tplayer) + end +end + +function ctf.hud.updateAll() + if not ctf.setting("hud") then + return + end + + local players = minetest.get_connected_players() + for i = 1, #players do + ctf.hud.update(players[i]) + end +end + +local function tick() + ctf.hud.updateAll() + minetest.after(10, tick) +end +minetest.after(1, tick) diff --git a/mods/ctf_pvp_engine/ctf/init.lua b/mods/ctf_pvp_engine/ctf/init.lua new file mode 100644 index 0000000..61d3f8f --- /dev/null +++ b/mods/ctf_pvp_engine/ctf/init.lua @@ -0,0 +1,33 @@ +-- CAPTURE THE FLAG +-- by Andrew "rubenwardy" Ward +----------------------------------------- + +ctf = {} + +-- Fix for https://github.com/minetest/minetest/issues/2383 +local csa = minetest.chat_send_all +function minetest.chat_send_all(msg) + minetest.after(0, function() + csa(msg) + end) +end + +-- Privs +minetest.register_privilege("ctf_team_mgr", { + description = "Team manager", +}) + +minetest.register_privilege("ctf_admin", { + description = "Can create teams, manage players, assign team owners.", +}) + +-- Modules +dofile(minetest.get_modpath("ctf") .. "/core.lua") +dofile(minetest.get_modpath("ctf") .. "/teams.lua") +dofile(minetest.get_modpath("ctf") .. "/diplomacy.lua") +dofile(minetest.get_modpath("ctf") .. "/gui.lua") +dofile(minetest.get_modpath("ctf") .. "/hud.lua") + +-- Init +ctf.init() +ctf.clean_player_lists() diff --git a/mods/ctf_pvp_engine/ctf/teams.lua b/mods/ctf_pvp_engine/ctf/teams.lua new file mode 100644 index 0000000..362fd6f --- /dev/null +++ b/mods/ctf_pvp_engine/ctf/teams.lua @@ -0,0 +1,516 @@ +-- Get or add a team +function ctf.team(name) + if name == nil then + return nil + end + if type(name) == "table" then + if not name.add_team then + ctf.error("team", "Invalid table given to ctf.team") + return + end + + return ctf.create_team(name.name, name) + else + local team = ctf.teams[name] + if team then + if not team.data or not team.players then + ctf.warning("team", "Assertion failed, data{} or players{} not " .. + "found in team{}") + end + return team + else + if name and name:trim() ~= "" then + ctf.warning("team", dump(name) .. " does not exist!") + end + return nil + end + end +end + +function ctf.create_team(name, data) + ctf.log("team", "Creating team " .. name) + + ctf.teams[name] = { + data = data, + spawn = nil, + players = {} + } + + for i = 1, #ctf.registered_on_new_team do + ctf.registered_on_new_team[i](ctf.teams[name]) + end + + ctf.needs_save = true + + return ctf.teams[name] +end + +function ctf.remove_team(name) + local team = ctf.team(name) + if team then + for username, player in pairs(team.players) do + player.team = nil + end + for i = 1, #team.flags do + team.flags[i].team = nil + end + ctf.teams[name] = nil + ctf.needs_save = true + return true + else + return false + end +end + +function ctf.list_teams(name) + minetest.chat_send_player(name, "Teams:") + for tname, team in pairs(ctf.teams) do + if team and team.players then + local details = "" + + local numPlayers = ctf.count_players_in_team(tname) + details = numPlayers .. " members" + + if team.flags then + local numFlags = 0 + for flagid, flag in pairs(team.flags) do + numFlags = numFlags + 1 + end + details = details .. ", " .. numFlags .. " flags" + end + + minetest.chat_send_player(name, ">> " .. tname .. + " (" .. details .. ")") + end + end +end + +-- Count number of players in a team +function ctf.count_players_in_team(team) + local count = 0 + for name, player in pairs(ctf.team(team).players) do + count = count + 1 + end + return count +end + +function ctf.new_player(name) + if name then + ctf.players[name] = { + name = name + } + ctf.needs_save = true + else + ctf.error("team", "Can't create a blank player") + ctf.log("team", debug.traceback()) + end +end + +-- get a player +function ctf.player(name) + if not ctf.players[name] then + ctf.new_player(name) + end + return ctf.players[name] +end + +function ctf.player_or_nil(name) + return ctf.players[name] +end + +function ctf.chat_send_team(team, msg) + if type(team) == "string" then + team = ctf.team(team) + end + + for pname, _ in pairs(team.players) do + minetest.chat_send_player(pname, msg) + end +end + +function ctf.remove_player(name) + ctf.log("team", "Removing player ".. dump(name)) + local player = ctf.players[name] + if player then + local team = ctf.team(player.team) + if team then + team.players[name] = nil + end + ctf.players[name] = nil + ctf.needs_save = true + return true + else + return false + end +end + +ctf.registered_on_join_team = {} +function ctf.register_on_join_team(func) + if ctf._mt_loaded then + error("You can't register callbacks at game time!") + end + table.insert(ctf.registered_on_join_team, func) +end + +ctf.player_last_team = {} + +-- Player joins team +-- Called by /join, /team join or auto allocate. +function ctf.join(name, team, force, by) + if not name or name == "" or not team or team == "" then + ctf.log("team", "Missing parameters to ctf.join") + return false + end + + local player = ctf.player(name) + + if not force and not ctf.setting("players_can_change_team") + and player.team and ctf.team(player.team) then + if by then + if by == name then + ctf.action("teams", name .. " attempted to change to " .. team) + minetest.chat_send_player(by, "You are not allowed to switch teams, traitor!") + else + ctf.action("teams", by .. " attempted to change " .. name .. " to " .. team) + minetest.chat_send_player(by, "Failed to add " .. name .. " to " .. team .. + " as players_can_change_team = false") + end + else + ctf.log("teams", "failed to add " .. name .. " to " .. team .. + " as players_can_change_team = false") + end + return false + end + + local team_data = ctf.team(team) + if not team_data then + if by then + minetest.chat_send_player(by, "No such team.") + ctf.list_teams(by) + if by == name then + minetest.log("action", by .. " tried to move " .. name .. " to " .. team .. ", which doesn't exist") + else + minetest.log("action", name .. " attempted to join " .. team .. ", which doesn't exist") + end + else + ctf.log("teams", "failed to add " .. name .. " to " .. team .. + " as team does not exist") + end + return false + end + + if player.team then + local oldteam = ctf.team(player.team) + if oldteam then + oldteam.players[player.name] = nil + end + end + + player.team = team + team_data.players[player.name] = player + ctf.player_last_team[name] = team + + ctf.needs_save = true + + minetest.log("action", name .. " joined team " .. team) + minetest.chat_send_all(name.." has joined team "..team) + + for i = 1, #ctf.registered_on_join_team do + ctf.registered_on_join_team[i](name, team) + end + return true +end + +-- Cleans up the player lists +function ctf.clean_player_lists() + ctf.log("utils", "Cleaning player lists") + for _, str in pairs(ctf.players) do + if str and str.team and ctf.teams[str.team] then + ctf.log("utils", " - Adding player "..str.name.." to team "..str.team) + ctf.teams[str.team].players[str.name] = str + else + ctf.log("utils", " - Skipping player "..str.name) + end + end + + ctf.needs_save = true +end + +-- Sees if the player can change stuff in a team +function ctf.can_mod(player,team) + local privs = minetest.get_player_privs(player) + + if privs then + if privs.ctf_admin == true then + return true + end + end + + if player and ctf.teams[team] and ctf.teams[team].players and ctf.teams[team].players[player] then + if ctf.teams[team].players[player].auth == true then + return true + end + end + return false +end + +-- post a message to a team board +function ctf.post(team, msg) + if not ctf.team(team) then + return false + end + + if not ctf.team(team).log then + ctf.team(team).log = {} + end + + + ctf.log("team", "message posted to team board") + + table.insert(ctf.team(team).log, 1, msg) + ctf.needs_save = true + + return true +end + +-- Automatic Allocation +function ctf.autoalloc(name, alloc_mode) + alloc_mode = alloc_mode or ctf.setting("allocate_mode") + if alloc_mode == 0 then + return + end + local last_team = ctf.player_last_team[name] + if last_team then + return last_team + end + + local max_players = ctf.setting("maximum_in_team") + + local mtot = false -- more than one team + for key, team in pairs(ctf.teams) do + mtot = true + break + end + if not mtot then + ctf.error("autoalloc", "No teams to allocate " .. name .. " to!") + return + end + + if alloc_mode == 1 then + local index = {} + + for key, team in pairs(ctf.teams) do + if team.data.allow_joins ~= false and (max_players == -1 or + ctf.count_players_in_team(key) < max_players) then + table.insert(index, key) + end + end + + if #index == 0 then + ctf.error("autoalloc", "No teams to join!") + else + return index[math.random(1, #index)] + end + elseif alloc_mode == 2 then + local one = nil + local one_count = -1 + local two = nil + local two_count = -1 + for key, team in pairs(ctf.teams) do + local count = ctf.count_players_in_team(key) + if team.data.allow_joins ~= false and + (max_players == -1 or count < max_players) then + if count > one_count then + two = one + two_count = one_count + one = key + one_count = count + end + + if count > two_count then + two = key + two_count = count + end + end + end + + if not one and not two then + ctf.error("autoalloc", "No teams to join!") + elseif one and two then + if math.random() > 0.5 then + return one + else + return two + end + else + if one then + return one + else + return two + end + end + elseif alloc_mode == 3 then + local smallest = nil + local smallest_count = 1000 + for key, team in pairs(ctf.teams) do + local count = ctf.count_players_in_team(key) + if team.data.allow_joins ~= false and + (not smallest or count < smallest_count) then + smallest = key + smallest_count = count + end + end + + if not smallest then + ctf.error("autoalloc", "No teams to join!") + else + return smallest + end + elseif alloc_mode == 4 then + return ctf.custom_alloc(name) + else + ctf.error("autoalloc", + "Unknown allocation mode: " .. alloc_mode) + end +end + +-- Custom team allocation function. Throws error +-- if unimplemented, and autoalloc mode 4 is selected +function ctf.custom_alloc() + error("Allocation mode set to custom while " .. + "ctf.custom_alloc hasn't been overridden!") +end + +-- updates the spawn position for a team +function ctf.get_spawn(team) + if ctf.team(team) then + local spawn = ctf.team(team).spawn + if not spawn then + return nil + end + return vector.add(spawn, minetest.string_to_pos(ctf.setting("spawn_offset"))) + else + return nil + end +end + +function ctf.move_to_spawn(name) + local player = minetest.get_player_by_name(name) + local tplayer = ctf.player(name) + if ctf.team(tplayer.team) then + local spawn = ctf.get_spawn(tplayer.team) + if spawn then + player:move_to(spawn, false) + return true + end + end + return false +end + +minetest.register_on_respawnplayer(function(player) + if not player then + return false + end + + return ctf.move_to_spawn(player:get_player_name()) +end) + +function ctf.get_territory_owner(pos) + local largest = nil + local largest_weight = 0 + for i = 1, #ctf.registered_on_territory_query do + local team, weight = ctf.registered_on_territory_query[i](pos) + if team and weight then + if weight == -1 then + return team + end + if weight > largest_weight then + largest = team + largest_weight = weight + end + end + end + return largest +end + +minetest.register_on_newplayer(function(player) + local name = player:get_player_name() + local team = ctf.autoalloc(name) + if team then + ctf.log("autoalloc", name .. " was allocated to " .. team) + ctf.join(name, team) + ctf.move_to_spawn(player:get_player_name()) + end +end) + + +minetest.register_on_joinplayer(function(player) + if not ctf.setting("autoalloc_on_joinplayer") then + return + end + + local name = player:get_player_name() + if ctf.team(ctf.player(name).team) then + return + end + + local team = ctf.autoalloc(name) + if team then + ctf.log("autoalloc", name .. " was allocated to " .. team) + ctf.join(name, team) + ctf.move_to_spawn(player:get_player_name()) + end +end) + +-- Disable friendly fire. +ctf.registered_on_killedplayer = {} +function ctf.register_on_killedplayer(func) + if ctf._mt_loaded then + error("You can't register callbacks at game time!") + end + table.insert(ctf.registered_on_killedplayer, func) +end +local dead_players = {} +minetest.register_on_respawnplayer(function(player) + dead_players[player:get_player_name()] = nil +end) +minetest.register_on_joinplayer(function(player) + dead_players[player:get_player_name()] = nil +end) +minetest.register_on_punchplayer(function(player, hitter, + time_from_last_punch, tool_capabilities, dir, damage) + if player and hitter then + local pname = player:get_player_name() + local hname = hitter:get_player_name() + + local to = ctf.player(pname) + local from = ctf.player(hname) + + if dead_players[pname] then + return + end + + if to.team == from.team and to.team ~= "" and + to.team ~= nil and to.name ~= from.name then + minetest.chat_send_player(hname, pname .. " is on your team!") + if not ctf.setting("friendly_fire") then + return true + end + end + + local hp = player:get_hp() + if hp == 0 then + return false + end + + if hp - damage <= 0 then + dead_players[pname] = true + local wielded = hitter:get_wielded_item() + for i = 1, #ctf.registered_on_killedplayer do + ctf.registered_on_killedplayer[i](pname, hname, + wielded, tool_capabilities) + end + return false + end + end +end) diff --git a/mods/ctf_pvp_engine/ctf/textures/diplo_alliance.png b/mods/ctf_pvp_engine/ctf/textures/diplo_alliance.png new file mode 100644 index 0000000..be56c42 Binary files /dev/null and b/mods/ctf_pvp_engine/ctf/textures/diplo_alliance.png differ diff --git a/mods/ctf_pvp_engine/ctf/textures/diplo_peace.png b/mods/ctf_pvp_engine/ctf/textures/diplo_peace.png new file mode 100644 index 0000000..6f0da5b Binary files /dev/null and b/mods/ctf_pvp_engine/ctf/textures/diplo_peace.png differ diff --git a/mods/ctf_pvp_engine/ctf/textures/diplo_war.png b/mods/ctf_pvp_engine/ctf/textures/diplo_war.png new file mode 100644 index 0000000..4183fe2 Binary files /dev/null and b/mods/ctf_pvp_engine/ctf/textures/diplo_war.png differ diff --git a/mods/ctf_pvp_engine/ctf_chat/depends.txt b/mods/ctf_pvp_engine/ctf_chat/depends.txt new file mode 100644 index 0000000..bc3bd76 --- /dev/null +++ b/mods/ctf_pvp_engine/ctf_chat/depends.txt @@ -0,0 +1,4 @@ +ctf +ctf_colors +chatplus? +irc? diff --git a/mods/ctf_pvp_engine/ctf_chat/init.lua b/mods/ctf_pvp_engine/ctf_chat/init.lua new file mode 100644 index 0000000..8ccc192 --- /dev/null +++ b/mods/ctf_pvp_engine/ctf_chat/init.lua @@ -0,0 +1,411 @@ +ctf.register_on_init(function() + ctf.log("chat", "Initialising...") + + -- Settings: Chat + ctf._set("chat.team_channel", true) + ctf._set("chat.global_channel", true) + ctf._set("chat.default", "global") +end) + +function minetest.is_player_name_valid(name) + return name:match("^[%a%d_-]+$") +end + +local function team_console_help(name) + minetest.chat_send_player(name, "Try:") + minetest.chat_send_player(name, "/team - show team panel") + minetest.chat_send_player(name, "/team all - list all teams") + minetest.chat_send_player(name, "/team - show details about team 'name'") + minetest.chat_send_player(name, "/team - get which team 'player' is in") + minetest.chat_send_player(name, "/team player - get which team 'player' is in") + + local privs = minetest.get_player_privs(name) + if privs and privs.ctf_admin == true then + minetest.chat_send_player(name, "/team add - add a team called name (ctf_admin only)") + minetest.chat_send_player(name, "/team remove - add a team called name (ctf_admin only)") + end + if privs and privs.ctf_team_mgr == true then + minetest.chat_send_player(name, "/team lock - closes a team to new players (ctf_team_mgr only)") + minetest.chat_send_player(name, "/team unlock - opens a team to new players (ctf_team_mgr only)") + minetest.chat_send_player(name, "/team bjoin - Command is * for all players, playername for one, !playername to remove (ctf_team_mgr only)") + minetest.chat_send_player(name, "/team join - add 'player' to team 'team' (ctf_team_mgr only)") + minetest.chat_send_player(name, "/team removeply - add 'player' to team 'team' (ctf_team_mgr only)") + end +end + +minetest.register_chatcommand("team", { + description = "Open the team console, or run team command (see /team help)", + func = function(name, param) + local test = string.match(param, "^player ([%a%d_-]+)") + local create = string.match(param, "^add ([%a%d_-]+)") + local remove = string.match(param, "^remove ([%a%d_-]+)") + local lock = string.match(param, "^lock ([%a%d_-]+)") + local unlock = string.match(param, "^unlock ([%a%d_-]+)") + local j_name, j_tname = string.match(param, "^join ([%a%d_-]+) ([%a%d_]+)") + local b_tname, b_pattern = string.match(param, "^bjoin ([%a%d_-]+) ([%a%d_-%*%! ]+)") + local l_name = string.match(param, "^removeplr ([%a%d_-]+)") + if create then + local privs = minetest.get_player_privs(name) + if privs and privs.ctf_admin then + if ( + string.match(create, "([%a%b_]-)") + and create ~= "" + and create ~= nil + and ctf.team({name=create, add_team=true}) + ) then + return true, "Added team '"..create.."'" + else + return false, "Error adding team '"..create.."'" + end + else + return false, "You are not a ctf_admin!" + end + elseif remove then + local privs = minetest.get_player_privs(name) + if privs and privs.ctf_admin then + if ctf.remove_team(remove) then + return true, "Removed team '" .. remove .. "'" + else + return false, "Error removing team '" .. remove .. "'" + end + else + return false, "You are not a ctf_admin!" + end + elseif lock then + local privs = minetest.get_player_privs(name) + if privs and privs.ctf_team_mgr then + local team = ctf.team(lock) + if team then + team.data.allow_joins = false + return true, "Locked team to new members" + else + return false, "Unable to find that team!" + end + else + return false, "You are not a ctf_team_mgr!" + end + elseif unlock then + local privs = minetest.get_player_privs(name) + if privs and privs.ctf_team_mgr then + local team = ctf.team(unlock) + if team then + team.data.allow_joins = true + return true, "Unlocked team to new members" + else + return false, "Unable to find that team!" + end + else + return false, "You are not a ctf_team_mgr!" + end + elseif param == "all" then + ctf.list_teams(name) + elseif ctf.team(param) then + minetest.chat_send_player(name, "Team "..param..":") + local count = 0 + for _, value in pairs(ctf.team(param).players) do + count = count + 1 + if value.auth then + minetest.chat_send_player(name, count .. ">> " .. value.name + .. " (team owner)") + else + minetest.chat_send_player(name, count .. ">> " .. value.name) + end + end + elseif ctf.player_or_nil(param) or test then + if not test then + test = param + end + if ctf.player(test).team then + if ctf.player(test).auth then + return true, test .. + " is in team " .. ctf.player(test).team.." (team owner)" + else + return true, test .. + " is in team " .. ctf.player(test).team + end + else + return true, test.." is not in a team" + end + elseif j_name and j_tname then + local privs = minetest.get_player_privs(name) + if privs and privs.ctf_team_mgr then + if ctf.join(j_name, j_tname, true, name) then + return true, "Successfully added " .. j_name .. " to " .. j_tname + else + return false, "Failed to add " .. j_name .. " to " .. j_tname + end + else + return true, "You are not a ctf_team_mgr!" + end + elseif b_pattern and b_tname then + local privs = minetest.get_player_privs(name) + if privs and privs.ctf_team_mgr then + local tokens = string.split(b_pattern, " ") + local players = {} + + for _, token in pairs(tokens) do + print(token) + if token == "*" then + for _, player in pairs(minetest.get_connected_players()) do + players[player:get_player_name()] = true + end + elseif token:sub(1, 1) == "!" then + players[token:sub(2, #token)] = nil + elseif minetest.is_player_name_valid(token) then + players[token] = true + else + return false, "Invalid token: " .. token .. "\nExpecting *, playername, or !playername." + end + end + + for pname, _ in pairs(players) do + ctf.join(pname, b_tname, true, name) + end + return true, "Success!" + else + return false, "You are not a ctf_team_mgr!" + end + elseif l_name then + local privs = minetest.get_player_privs(name) + if privs and privs.ctf_team_mgr then + if ctf.remove_player(l_name) then + return true, "Removed player " .. l_name + else + return false, "Failed to remove player." + end + else + return false, "You are not a ctf_team_mgr!" + end + elseif param=="help" then + team_console_help(name) + else + if param ~= "" and param ~= nil then + minetest.chat_send_player(name, "'"..param.."' is an invalid parameter to /team") + team_console_help(name) + end + if ctf.setting("gui") then + if (ctf and + ctf.players and + ctf.players[name] and + ctf.players[name].team) then + print("showing") + ctf.gui.show(name) + return true, "Showing the team window" + else + return false, "You're not part of a team!" + end + else + return false, "GUI is disabled!" + end + end + return false, "Nothing could be done" + end +}) + +minetest.register_chatcommand("join", { + params = "team name", + description = "Add to team", + func = function(name, param) + if ctf.join(name, param, false, name) then + return true, "Joined team " .. param .. "!" + else + return false, "Failed to join team!" + end + end +}) + +minetest.register_chatcommand("ctf_clean", { + description = "Do admin cleaning stuff", + privs = {ctf_admin=true}, + func = function(name, param) + ctf.log("chat", "Cleaning CTF...") + ctf.clean_player_lists() + if ctf_flag and ctf_flag.assert_flags then + ctf_flag.assert_flags() + end + return true, "CTF cleaned!" + end +}) + +minetest.register_chatcommand("ctf_reset", { + description = "Delete all CTF saved states and start again.", + privs = {ctf_admin=true}, + func = function(name, param) + minetest.chat_send_all("The CTF core was reset by the admin. All team memberships," .. + "flags, land ownerships etc have been deleted.") + ctf.reset() + return true, "Reset CTF core." + end, +}) + +minetest.register_chatcommand("ctf_reload", { + description = "reload the ctf main frame and get settings", + privs = {ctf_admin=true}, + func = function(name, param) + ctf.needs_save = true + ctf.init() + return true, "CTF core reloaded!" + end +}) + +minetest.register_chatcommand("ctf_ls", { + description = "ctf: list settings", + privs = {ctf_admin=true}, + func = function(name, param) + minetest.chat_send_player(name, "Settings:") + for set, def in orderedPairs(ctf._defsettings) do + minetest.chat_send_player(name, " - " .. set .. ": " .. dump(ctf.setting(set))) + print("\"" .. set .. "\" " .. dump(ctf.setting(set))) + end + return true + end +}) + +minetest.register_chatcommand("team_owner", { + params = "player name", + description = "Make player team owner", + privs = {ctf_admin=true}, + func = function(name, param) + if ctf and ctf.players and ctf.player(param) and ctf.player(param).team and ctf.team(ctf.player(param).team) then + if ctf.player(param).auth == true then + ctf.player(param).auth = false + return true, param.." was downgraded from team admin status" + else + ctf.player(param).auth = true + return true, param.." was upgraded to an admin of "..ctf.player(name).team + end + ctf.needs_save = true + else + return false, "Unable to do that :/ "..param.." does not exist, or is not part of a valid team." + end + end +}) + +minetest.register_chatcommand("post", { + params = "message", + description = "Post a message on your team's message board", + func = function(name, param) + + if ctf and ctf.players and ctf.players[name] and ctf.players[name].team and ctf.teams[ctf.players[name].team] then + if not ctf.player(name).auth then + minetest.chat_send_player(name, "You do not own that team") + end + + if not ctf.teams[ctf.players[name].team].log then + ctf.teams[ctf.players[name].team].log = {} + end + + table.insert(ctf.teams[ctf.players[name].team].log,{msg=param}) + + minetest.chat_send_player(name, "Posted: "..param) + else + minetest.chat_send_player(name, "Could not post message") + end + end, +}) + +minetest.register_chatcommand("all", { + params = "msg", + description = "Send a message on the global channel", + func = function(name, param) + if not ctf.setting("chat.global_channel") then + minetest.chat_send_player(name, "The global channel is disabled") + return + end + + if ctf.player(name).team then + local tosend = ctf.player(name).team .. + " <" .. name .. "> " .. param + minetest.chat_send_all(tosend) + if minetest.global_exists("chatplus") then + chatplus.log(tosend) + end + else + minetest.chat_send_all("<"..name.."> "..param) + end + end +}) + +minetest.register_chatcommand("t", { + params = "msg", + description = "Send a message on the team channel", + func = function(name, param) + if not ctf.setting("chat.team_channel") then + minetest.chat_send_player(name, "The team channel is disabled.") + return + end + + local tname = ctf.player(name).team + local team = ctf.team(tname) + if team then + minetest.log("action", tname .. "<" .. name .. "> ** ".. param .. " **") + if minetest.global_exists("chatplus") then + chatplus.log("<" .. name .. "> ** ".. param .. " **") + end + + tcolor = ctf_colors.get_color(ctf.player(name)) + for username, to in pairs(team.players) do + minetest.chat_send_player(username, + minetest.colorize(tcolor.css, "<" .. name .. "> ** " .. param .. " **")) + end + if minetest.global_exists("irc") and irc.feature_mod_channel then + irc:say(irc.config.channel, tname .. "<" .. name .. "> ** " .. param .. " **", true) + end + else + minetest.chat_send_player(name, + "You're not in a team, so you have no team to talk to.") + end + end +}) + +if minetest.global_exists("irc") then + function irc.playerMessage(name, message) + local tname = ctf.player(name).team + local color = ctf_colors.get_irc_color(ctf.player(name)) + local clear = "\x0F" + if color then + color = "\x03" .. color + else + color = "" + clear = "" + end + local abrace = color .. "<" .. clear + local bbrace = color .. ">" .. clear + return ("%s%s%s %s"):format(abrace, name, bbrace, message) + end +end + +local handler +handler = function(name, message) + if ctf.player(name).team then + for i = 1, #minetest.registered_on_chat_messages do + local func = minetest.registered_on_chat_messages[i] + if func ~= handler and func(name, message) then + return true + end + end + + if not minetest.check_player_privs(name, {shout = true}) then + minetest.chat_send_player(name, "-!- You don't have permission to shout.") + return true + end + local tcolor = ctf_colors.get_color(ctf.player(name)) + minetest.chat_send_all(minetest.colorize(tcolor.css, + "<" .. name .. "> ") .. message) + return true + else + return nil + end +end +table.insert(minetest.registered_on_chat_messages, 1, handler) + +minetest.registered_chatcommands["me"].func = function(name, param) + if ctf.player(name).team then + local tcolor = ctf_colors.get_color(ctf.player(name)) + name = minetest.colorize(tcolor.css, "* " .. name) + else + name = "* ".. name + end + + minetest.chat_send_all(name .. " " .. param) +end diff --git a/mods/ctf_pvp_engine/ctf_colors/depends.txt b/mods/ctf_pvp_engine/ctf_colors/depends.txt new file mode 100644 index 0000000..10733cb --- /dev/null +++ b/mods/ctf_pvp_engine/ctf_colors/depends.txt @@ -0,0 +1,2 @@ +ctf +3d_armor? diff --git a/mods/ctf_pvp_engine/ctf_colors/gui.lua b/mods/ctf_pvp_engine/ctf_colors/gui.lua new file mode 100644 index 0000000..6a0beed --- /dev/null +++ b/mods/ctf_pvp_engine/ctf_colors/gui.lua @@ -0,0 +1,60 @@ + +ctf.gui.register_tab("settings", "Settings", function(name, team) + local color = "" + if ctf.team(team).data.color then + color = ctf.team(team).data.color + end + + local result = "field[3,2;4,1;color;Team Color;" .. color .. "]" .. + "button[4,6;2,1;save;Save]" + + + if not ctf.can_mod(name,team) then + result = "label[0.5,1;You do not own this team!" + end + + minetest.show_formspec(name, "ctf:settings", + "size[10,7]" .. + ctf.gui.get_tabs(name, team) .. + result + ) +end) + +minetest.register_on_player_receive_fields(function(player, formname, fields) + if formname ~= "ctf:settings" then + return false + end + + -- Settings page + if fields.save then + local name = player:get_player_name() + local pdata = ctf.player(name) + local team = ctf.team(pdata.team) + + ctf.gui.show(name, "settings") + if team and ctf.can_mod(name, pdata.team) then + if ctf.flag_colors[fields.color] then + team.data.color = fields.color + ctf.needs_save = true + + minetest.chat_send_player(name, "Team color set to " .. fields.color) + else + local colors = "" + for color, code in pairs(ctf.flag_colors) do + if colors ~= "" then + colors = colors .. ", " + end + colors = colors .. color + end + minetest.chat_send_player(name, "Color " .. fields.color .. + " does not exist! Available: " .. colors) + end + elseif team then + minetest.chat_send_player(name, "You don't have the rights to change settings.") + else + minetest.chat_send_player(name, "You don't appear to be in a team") + end + + return true + end +end) diff --git a/mods/ctf_pvp_engine/ctf_colors/hud.lua b/mods/ctf_pvp_engine/ctf_colors/hud.lua new file mode 100644 index 0000000..40d6f47 --- /dev/null +++ b/mods/ctf_pvp_engine/ctf_colors/hud.lua @@ -0,0 +1,88 @@ +function ctf_colors.get_color(tplayer) + local team = ctf.team(tplayer.team) + local tcolor_text = nil + if team then + tcolor_text = team.data.color + end + local tcolor_hex = ctf.flag_colors[tcolor_text] + if not tcolor_hex then + tcolor_hex = "0x000000" + end + + local tcolor_css = "#" .. tcolor_hex:sub(3, 8) + + return { + text = tcolor_text, + hex = tcolor_hex, + css = tcolor_css + } +end + +function ctf_colors.get_irc_color(tplayer) + local team = ctf.team(tplayer.team) + local tcolor_text = nil + if team then + tcolor_text = team.data.color + end + return ctf_colors.irc_colors[tcolor_text] +end + +function ctf_colors.get_nametag_color(name, tplayer, tcolor_text, tcolor_hex) + if ctf.setting("colors.nametag.tcolor") then + return "0xFF" .. string.sub(tcolor_hex, 3) + else + return "0xFFFFFFFF" + end +end + +function ctf_colors.set_skin(player, color) + if minetest.global_exists("armor") then + -- TODO: how should support for skin mods be done? + armor.textures[name].skin = "ctf_colors_skin_" .. color .. ".png" + armor:update_player_visuals(player) + else + player:set_properties({ + textures = {"ctf_colors_skin_" .. color .. ".png"} + }) + end +end + +function ctf_colors.update(player, name, tplayer) + if not player then + player = minetest.get_player_by_name(name) + end + + local tcolor = ctf_colors.get_color(tplayer) + + if ctf.setting("colors.hudtint") then + if tcolor.text == "red" or tcolor.text == "blue" then + player:hud_set_hotbar_image("ctf_colors_hotbar_" .. tcolor.text .. ".png") + player:hud_set_hotbar_selected_image("ctf_colors_hotbar_selected_" .. tcolor.text .. ".png") + else + ctf.error("ctf_colors", "Hint color not supported for " .. tcolor.text) + end + end + + if ctf.setting("colors.skins") and tcolor.text then + ctf_colors.set_skin(player, tcolor.text) + end + + if ctf.setting("hud.teamname") then + if not ctf.hud:exists(player, "ctf:hud_team") then + ctf.hud:add(player, "ctf:hud_team", { + hud_elem_type = "text", + position = {x = 1, y = 0}, + scale = {x = 100, y = 100}, + text = "Team " .. tplayer.team, + number = tcolor.hex, + offset = {x = -20, y = 20}, + alignment = {x = -1, y = 0} + }) + else + ctf.hud:change(player, "ctf:hud_team", "text", "Team " .. tplayer.team) + ctf.hud:change(player, "ctf:hud_team", "number", tcolor.hex) + end + end +end + +ctf.hud.register_part(ctf_colors.update) diff --git a/mods/ctf_pvp_engine/ctf_colors/init.lua b/mods/ctf_pvp_engine/ctf_colors/init.lua new file mode 100644 index 0000000..ac6e445 --- /dev/null +++ b/mods/ctf_pvp_engine/ctf_colors/init.lua @@ -0,0 +1,31 @@ +-- Supported colors +ctf_colors = {} +ctf_colors.colors = { + red = "0xFF4444", + cyan = "0x00FFFF", + blue = "0x4466FF", + purple = "0x800080", + yellow = "0xFFFF00", + green = "0x00FF00", + pink = "0xFF00FF", + silver = "0xC0C0C0", + gray = "0x808080", + black = "0x000000", + orange = "0xFFA500", + gold = "0x808000" +} +ctf_colors.irc_colors = { + red = "4", + blue = "2", +} +ctf.flag_colors = ctf_colors.colors + +ctf.register_on_init(function() + ctf.log("colors", "Initialising...") + ctf._set("colors.skins", false) + ctf._set("colors.hudtint", true) + ctf._set("hud.teamname", false) +end) + +dofile(minetest.get_modpath("ctf_colors") .. "/hud.lua") +dofile(minetest.get_modpath("ctf_colors") .. "/gui.lua") diff --git a/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_hotbar_blue.png b/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_hotbar_blue.png new file mode 100644 index 0000000..b0e7b20 Binary files /dev/null and b/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_hotbar_blue.png differ diff --git a/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_hotbar_red.png b/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_hotbar_red.png new file mode 100644 index 0000000..1cb2e8c Binary files /dev/null and b/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_hotbar_red.png differ diff --git a/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_hotbar_selected_blue.png b/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_hotbar_selected_blue.png new file mode 100644 index 0000000..760257c Binary files /dev/null and b/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_hotbar_selected_blue.png differ diff --git a/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_hotbar_selected_red.png b/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_hotbar_selected_red.png new file mode 100644 index 0000000..0d39112 Binary files /dev/null and b/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_hotbar_selected_red.png differ diff --git a/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_skin_black.png b/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_skin_black.png new file mode 100644 index 0000000..40c52b7 Binary files /dev/null and b/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_skin_black.png differ diff --git a/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_skin_blue.png b/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_skin_blue.png new file mode 100644 index 0000000..6b711ed Binary files /dev/null and b/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_skin_blue.png differ diff --git a/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_skin_cyan.png b/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_skin_cyan.png new file mode 100644 index 0000000..2acfdb8 Binary files /dev/null and b/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_skin_cyan.png differ diff --git a/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_skin_gold.png b/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_skin_gold.png new file mode 100644 index 0000000..447cfa3 Binary files /dev/null and b/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_skin_gold.png differ diff --git a/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_skin_gray.png b/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_skin_gray.png new file mode 100644 index 0000000..8966626 Binary files /dev/null and b/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_skin_gray.png differ diff --git a/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_skin_green.png b/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_skin_green.png new file mode 100644 index 0000000..0502178 Binary files /dev/null and b/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_skin_green.png differ diff --git a/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_skin_orange.png b/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_skin_orange.png new file mode 100644 index 0000000..2414cdd Binary files /dev/null and b/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_skin_orange.png differ diff --git a/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_skin_pink.png b/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_skin_pink.png new file mode 100644 index 0000000..a13562c Binary files /dev/null and b/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_skin_pink.png differ diff --git a/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_skin_purple.png b/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_skin_purple.png new file mode 100644 index 0000000..cd428d0 Binary files /dev/null and b/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_skin_purple.png differ diff --git a/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_skin_red.png b/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_skin_red.png new file mode 100644 index 0000000..8214fb5 Binary files /dev/null and b/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_skin_red.png differ diff --git a/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_skin_silver.png b/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_skin_silver.png new file mode 100644 index 0000000..291e8f6 Binary files /dev/null and b/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_skin_silver.png differ diff --git a/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_skin_yellow.png b/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_skin_yellow.png new file mode 100644 index 0000000..99b2301 Binary files /dev/null and b/mods/ctf_pvp_engine/ctf_colors/textures/ctf_colors_skin_yellow.png differ diff --git a/mods/ctf_pvp_engine/ctf_flag/api.lua b/mods/ctf_pvp_engine/ctf_flag/api.lua new file mode 100644 index 0000000..478a5c1 --- /dev/null +++ b/mods/ctf_pvp_engine/ctf_flag/api.lua @@ -0,0 +1,242 @@ +ctf_flag.registered_on_capture = {} +function ctf_flag.register_on_capture(func) + if ctf._mt_loaded then + error("You can't register callbacks at game time!") + end + table.insert(ctf_flag.registered_on_capture, func) +end + +ctf_flag.registered_on_pick_up = {} +function ctf_flag.register_on_pick_up(func) + if ctf._mt_loaded then + error("You can't register callbacks at game time!") + end + table.insert(ctf_flag.registered_on_pick_up, func) +end + +ctf_flag.registered_on_drop = {} +function ctf_flag.register_on_drop(func) + if ctf._mt_loaded then + error("You can't register callbacks at game time!") + end + table.insert(ctf_flag.registered_on_drop, func) +end + +ctf_flag.registered_on_precapture = {} +function ctf_flag.register_on_precapture(func) + if ctf._mt_loaded then + error("You can't register callbacks at game time!") + end + table.insert(ctf_flag.registered_on_precapture, func) +end + +ctf_flag.registered_on_prepick_up = {} +function ctf_flag.register_on_prepick_up(func) + if ctf._mt_loaded then + error("You can't register callbacks at game time!") + end + table.insert(ctf_flag.registered_on_prepick_up, func) +end + +function ctf_flag.collect_claimed() + local claimed = {} + for _, team in pairs(ctf.teams) do + for i = 1, #team.flags do + if team.flags[i].claimed then + table.insert(claimed, team.flags[i]) + end + end + end + return claimed +end + +function ctf_flag.get_claimed_by_player(name) + local claimed = ctf_flag.collect_claimed() + for _, flag in pairs(claimed) do + if flag.claimed.player == name then + return name + end + end +end + +function ctf_flag.player_drop_flag(name) + if not name then + return + end + + local claimed = ctf_flag.collect_claimed() + for i = 1, #claimed do + local flag = claimed[i] + if flag.claimed.player == name then + flag.claimed = nil + + local flag_name = "" + if flag.name then + flag_name = flag.name .. " " + end + flag_name = flag.team .. "'s " .. flag_name .. "flag" + + ctf.hud.updateAll() + + ctf.action("flag", name .. " dropped " .. flag_name) + minetest.chat_send_all(flag_name.." has returned.") + + for i = 1, #ctf_flag.registered_on_drop do + ctf_flag.registered_on_drop[i](name, flag) + end + end + end +end + +-- add a flag to a team +function ctf_flag.add(team, pos) + if not team or team == "" then + return + end + + ctf.log("flag", "Adding flag to " .. team .. " at (" .. pos.x .. + ", " .. pos.y .. ", " .. pos.z .. ")") + + if not ctf.team(team).flags then + ctf.team(team).flags = {} + end + + pos.team = team + table.insert(ctf.team(team).flags,pos) + ctf.needs_save = true +end + +function ctf_flag.update(pos) + if minetest.get_node(pos).name ~= "ctf_flag:flag" then + return + end + + local top = {x=pos.x,y=pos.y+1,z=pos.z} + local flagmeta = minetest.get_meta(pos) + + if not flagmeta then + return + end + + local flag_team_data = ctf_flag.get(pos) + if not flag_team_data or not ctf.team(flag_team_data.team)then + ctf.log("flag", "Flag does not exist! Deleting nodes. "..dump(pos)) + minetest.set_node(pos,{name="air"}) + minetest.set_node(top,{name="air"}) + return + end + local topmeta = minetest.get_meta(top) + local flag_name = flag_team_data.name + if flag_name and flag_name ~= "" then + flagmeta:set_string("infotext", flag_name.." - "..flag_team_data.team) + else + flagmeta:set_string("infotext", flag_team_data.team.."'s flag") + end + + if not ctf.team(flag_team_data.team).data.color then + ctf.team(flag_team_data.team).data.color = "red" + ctf.needs_save = true + end + + if flag_team_data.claimed then + minetest.set_node(top,{name="ctf_flag:flag_captured_top"}) + else + minetest.set_node(top,{name="ctf_flag:flag_top_"..ctf.team(flag_team_data.team).data.color}) + end + + topmeta = minetest.get_meta(top) + if flag_name and flag_name ~= "" then + topmeta:set_string("infotext", flag_name.." - "..flag_team_data.team) + else + topmeta:set_string("infotext", flag_team_data.team.."'s flag") + end +end + +function ctf_flag.flag_tick(pos) + ctf_flag.update(pos) + minetest.get_node_timer(pos):start(5) +end + +-- get a flag from a team +function ctf_flag.get(pos) + if not pos then + return + end + + local result = nil + for _, team in pairs(ctf.teams) do + for i = 1, #team.flags do + if ( + team.flags[i].x == pos.x and + team.flags[i].y == pos.y and + team.flags[i].z == pos.z + ) then + if result then + minetest.chat_send_all("[CTF ERROR] Multiple teams have same flag. Please report this to the server operator / admin") + print("CTF ERROR DATA") + print("Multiple teams have same flag.") + print("This is a sign of ctf.txt corruption.") + print("----------------") + print(dump(result)) + print(dump(team.flags[i])) + print("----------------") + else + result = team.flags[i] + end + end + end + end + return result +end + +-- delete a flag from a team +function ctf_flag.delete(team, pos) + if not team or team == "" then + return + end + + ctf.log("flag", "Deleting flag from " .. team .. " at (" .. pos.x .. + ", " .. pos.y .. ", " .. pos.z .. ")") + + for i = 1, #ctf.team(team).flags do + if ( + ctf.team(team).flags[i].x == pos.x and + ctf.team(team).flags[i].y == pos.y and + ctf.team(team).flags[i].z == pos.z + ) then + table.remove(ctf.team(team).flags,i) + return + end + end +end + +function ctf_flag.assert_flag(flag) + minetest.get_voxel_manip(flag, { x = flag.x + 1, y = flag.y + 1, z = flag.z + 1}) + local nodename = minetest.get_node(flag).name + if nodename ~= "ctf_flag:flag" then + ctf.log("flag", flag.team .. " has wrong node at flag position, " .. nodename .. ", correcting...") + minetest.set_node(flag, { name = "ctf_flag:flag"}) + ctf_flag.update(flag) + end +end + +function ctf_flag.assert_flags() + for tname, team in pairs(ctf.teams) do + ctf_flag.assert_flags_team(tname) + end +end + +function ctf_flag.assert_flags_team(tname) + local team = ctf.team(tname) + if not tname or not team then + return false + end + + if not team.flags then + team.flags = {} + end + + for i=1, #team.flags do + ctf_flag.assert_flag(team.flags[i]) + end +end diff --git a/mods/ctf_pvp_engine/ctf_flag/depends.txt b/mods/ctf_pvp_engine/ctf_flag/depends.txt new file mode 100644 index 0000000..ff56a63 --- /dev/null +++ b/mods/ctf_pvp_engine/ctf_flag/depends.txt @@ -0,0 +1,3 @@ +ctf +ctf_colors +chatplus? diff --git a/mods/ctf_pvp_engine/ctf_flag/flag_func.lua b/mods/ctf_pvp_engine/ctf_flag/flag_func.lua new file mode 100644 index 0000000..b6542c4 --- /dev/null +++ b/mods/ctf_pvp_engine/ctf_flag/flag_func.lua @@ -0,0 +1,253 @@ +local function do_capture(attname, flag, returned) + local team = flag.team + local attacker = ctf.player(attname) + + local flag_name = "" + if flag.name then + flag_name = flag.name .. " " + end + flag_name = team .. "'s " .. flag_name .. "flag" + + + if ctf.setting("flag.capture_take") and not returned then + for i = 1, #ctf_flag.registered_on_prepick_up do + if not ctf_flag.registered_on_prepick_up[i](attname, flag) then + return + end + end + + minetest.chat_send_all(flag_name.." has been picked up by ".. + attname.." (team "..attacker.team..")") + + ctf.action("flag", attname .. " picked up " .. flag_name) + + -- Post to flag owner's board + ctf.post(team, { + msg = flag_name .. " has been taken by " .. attname .. " of ".. attacker.team, + icon="flag_red" }) + + -- Post to attacker's board + ctf.post(attacker.team, { + msg = attname .. " snatched '" .. flag_name .. "' from " .. team, + icon="flag_green"}) + + -- Add to claimed list + flag.claimed = { + team = attacker.team, + player = attname + } + + ctf.hud.updateAll() + + ctf_flag.update(flag) + + for i = 1, #ctf_flag.registered_on_pick_up do + ctf_flag.registered_on_pick_up[i](attname, flag) + end + else + for i = 1, #ctf_flag.registered_on_precapture do + if not ctf_flag.registered_on_precapture[i](attname, flag) then + return + end + end + + minetest.chat_send_all(flag_name.." has been captured ".. + " by "..attname.." (team "..attacker.team..")") + + ctf.action("flag", attname .. " captured " .. flag_name) + + -- Post to flag owner's board + ctf.post(team, { + msg = flag_name .. " has been captured by " .. attacker.team, + icon="flag_red"}) + + -- Post to attacker's board + ctf.post(attacker.team, { + msg = attname .. " captured '" .. flag_name .. "' from " .. team, + icon="flag_green"}) + + -- Take flag + if ctf.setting("flag.allow_multiple") then + ctf_flag.delete(team, vector.new(flag)) + ctf_flag.add(attacker.team, vector.new(flag)) + else + minetest.set_node(pos,{name="air"}) + ctf_flag.delete(team,pos) + end + + for i = 1, #ctf_flag.registered_on_capture do + ctf_flag.registered_on_capture[i](attname, flag) + end + end + + ctf.needs_save = true +end + +local function player_drop_flag(player) + return ctf_flag.player_drop_flag(player:get_player_name()) +end +minetest.register_on_dieplayer(player_drop_flag) +minetest.register_on_leaveplayer(player_drop_flag) + + +ctf_flag = { + on_punch_top = function(pos, node, puncher) + pos.y = pos.y - 1 + ctf_flag.on_punch(pos, node, puncher) + end, + on_rightclick_top = function(pos, node, clicker) + pos.y = pos.y - 1 + ctf_flag.on_rightclick(pos, node, clicker) + end, + on_rightclick = function(pos, node, clicker) + local name = clicker:get_player_name() + local flag = ctf_flag.get(pos) + if not flag then + return + end + + if flag.claimed then + if ctf.setting("flag.capture_take") then + minetest.chat_send_player(name, "This flag has been taken by "..flag.claimed.player) + minetest.chat_send_player(name, "who is a member of team "..flag.claimed.team) + return + else + minetest.chat_send_player(name, "Oops! This flag should not be captured. Reverting...") + flag.claimed = nil + end + end + ctf.gui.flag_board(name, pos) + end, + on_punch = function(pos, node, puncher) + local name = puncher:get_player_name() + if not puncher or not name then + return + end + + local flag = ctf_flag.get(pos) + if not flag then + return + end + + if flag.claimed then + if ctf.setting("flag.capture_take") then + minetest.chat_send_player(name, "This flag has been taken by " .. flag.claimed.player) + minetest.chat_send_player(name, "who is a member of team " .. flag.claimed.team) + return + else + minetest.chat_send_player(name, "Oops! This flag should not be captured. Reverting.") + flag.claimed = nil + end + end + + local team = flag.team + if not team then + return + end + + if ctf.team(team) and ctf.player(name).team then + if ctf.player(name).team == team then + -- Clicking on their team's flag + if ctf.setting("flag.capture_take") then + ctf_flag._flagret(name) + end + else + -- Clicked on another team's flag + local diplo = ctf.diplo.get(team, ctf.player(name).team) or + ctf.setting("default_diplo_state") + + if diplo ~= "war" then + minetest.chat_send_player(name, "You are at peace with this team!") + return + end + + do_capture(name, flag) + end + else + minetest.chat_send_player(name, "You are not part of a team!") + end + end, + _flagret = function(name) + local claimed = ctf_flag.collect_claimed() + for i = 1, #claimed do + local flag = claimed[i] + if flag.claimed.player == name then + do_capture(name, flag, true) + end + end + end, + on_construct = function(pos) + local meta = minetest.get_meta(pos) + meta:set_string("infotext", "Unowned flag") + minetest.get_node_timer(pos):start(5) + end, + after_place_node = function(pos, placer) + local name = placer:get_player_name() + if not pos or not name then + minetest.set_node(pos, {name="air"}) + return + end + + local meta = minetest.get_meta(pos) + if not meta then + minetest.set_node(pos, {name="air"}) + return + end + + local tplayer = ctf.player_or_nil(name) + if tplayer and ctf.team(tplayer.team) then + if not minetest.check_player_privs(name, {ctf_place_flag=true}) then + minetest.chat_send_player(name, "You're not allowed to place flags! Reported to admin for investigation.") + minetest.set_node(pos, {name="air"}) + if minetest.global_exists("chatplus") then + chatplus.send_mail("*SERVER*", minetest.settings:get("name"), + "player " .. name .. " attempted to place flag!") + end + return + end + + local tname = tplayer.team + local team = ctf.team(tplayer.team) + meta:set_string("infotext", tname.."'s flag") + + -- add flag + ctf_flag.add(tname, pos) + + -- TODO: fix this hackiness + if team.spawn and not ctf.setting("flag.allow_multiple") and + minetest.get_node(team.spawn).name == "ctf_flag:flag" then + -- send message + minetest.chat_send_all(tname .. "'s flag has been moved") + minetest.set_node(team.spawn, {name="air"}) + minetest.set_node({ + x = team.spawn.x, + y = team.spawn.y+1, + z = team.spawn.z + }, {name="air"}) + team.spawn = pos + end + + ctf.needs_save = true + + local pos2 = { + x = pos.x, + y = pos.y + 1, + z = pos.z + } + + if not team.data.color then + team.data.color = "red" + ctf.needs_save = true + end + + minetest.set_node(pos2, {name="ctf_flag:flag_top_"..team.data.color}) + + local meta2 = minetest.get_meta(pos2) + + meta2:set_string("infotext", tname.."'s flag") + else + minetest.chat_send_player(name, "You are not part of a team!") + minetest.set_node(pos, {name="air"}) + end + end +} diff --git a/mods/ctf_pvp_engine/ctf_flag/flags.lua b/mods/ctf_pvp_engine/ctf_flag/flags.lua new file mode 100644 index 0000000..a7a9d8d --- /dev/null +++ b/mods/ctf_pvp_engine/ctf_flag/flags.lua @@ -0,0 +1,90 @@ +-- The flag +minetest.register_node("ctf_flag:flag", { + description = "Flag", + drawtype="nodebox", + paramtype = "light", + walkable = false, + inventory_image = "flag_silver2.png", + tiles = { + "default_wood.png", + "default_wood.png", + "default_wood.png", + "default_wood.png", + "default_wood.png", + "default_wood.png" + }, + node_box = { + type = "fixed", + fixed = { + {0.250000,-0.500000,0.000000,0.312500,0.500000,0.062500} + } + }, + groups = {immortal=1,is_flag=1,flag_bottom=1}, + on_punch = ctf_flag.on_punch, + on_rightclick = ctf_flag.on_rightclick, + on_construct = ctf_flag.on_construct, + after_place_node = ctf_flag.after_place_node, + on_timer = ctf_flag.flag_tick +}) + +for color, _ in pairs(ctf.flag_colors) do + minetest.register_node("ctf_flag:flag_top_"..color,{ + description = "You are not meant to have this! - flag top", + drawtype="nodebox", + paramtype = "light", + walkable = false, + tiles = { + "default_wood.png", + "default_wood.png", + "default_wood.png", + "default_wood.png", + "flag_"..color.."2.png", + "flag_"..color..".png" + }, + node_box = { + type = "fixed", + fixed = { + {0.250000,-0.500000,0.000000,0.312500,0.500000,0.062500}, + {-0.5,0,0.000000,0.250000,0.500000,0.062500} + } + }, + groups = {immortal=1,is_flag=1,flag_top=1,not_in_creative_inventory=1}, + on_punch = ctf_flag.on_punch_top, + on_rightclick = ctf_flag.on_rightclick_top + }) +end + +minetest.register_node("ctf_flag:flag_captured_top",{ + description = "You are not meant to have this! - flag captured", + drawtype = "nodebox", + paramtype = "light", + walkable = false, + tiles = { + "default_wood.png", + "default_wood.png", + "default_wood.png", + "default_wood.png", + "default_wood.png", + "default_wood.png" + }, + node_box = { + type = "fixed", + fixed = { + {0.250000,-0.500000,0.000000,0.312500,0.500000,0.062500} + } + }, + groups = {immortal=1,is_flag=1,flag_top=1,not_in_creative_inventory=1}, + on_punch = ctf_flag.on_punch_top, + on_rightclick = ctf_flag.on_rightclick_top +}) + +if ctf.setting("flag.crafting") then + minetest.register_craft({ + output = "ctf_flag:flag", + recipe = { + {"default:stick", "group:wool"}, + {"default:stick", "",}, + {"default:stick", ""} + } +}) +end diff --git a/mods/ctf_pvp_engine/ctf_flag/gui.lua b/mods/ctf_pvp_engine/ctf_flag/gui.lua new file mode 100644 index 0000000..71d497f --- /dev/null +++ b/mods/ctf_pvp_engine/ctf_flag/gui.lua @@ -0,0 +1,184 @@ +-- Team interface +ctf.gui.register_tab("flags", "Flags", function(name, team) + local result = "" + local t = ctf.team(team) + + if not t then + return + end + + local x = 1 + local y = 2 + result = result .. "label[1,1;Click a flag button to go there]" + + if ctf.setting("gui.team.teleport_to_spawn") and minetest.get_setting("static_spawnpoint") then + local x,y,z = string.match(minetest.get_setting("static_spawnpoint"), "(%d+),(%d+),(%d+)") + + result = result .. + "button[" .. x .. "," .. y .. ";2,1;goto_" + ..f.x.."_"..f.y.."_"..f.z..";" + + result = result .. "Spawn]" + x = x + 2 + end + + for i=1, #t.flags do + local f = t.flags[i] + + if x > 8 then + x = 1 + y = y + 1 + end + + if y > 6 then + break + end + + result = result .. + "button[" .. x .. "," .. y .. ";2,1;goto_" + ..f.x.."_"..f.y.."_"..f.z..";" + + if f.name then + result = result .. f.name .. "]" + else + result = result .. "("..f.x..","..f.y..","..f.z..")]" + end + + x = x + 2 + end + + minetest.show_formspec(name, "ctf:flags", + "size[10,7]".. + ctf.gui.get_tabs(name,team).. + result) +end) + +minetest.register_on_player_receive_fields(function(player, formname, fields) + -- Todo: fix security issue here + -- local name = player:get_player_name() + -- if formname == "ctf:flags" then + -- for key, field in pairs(fields) do + -- local x,y,z = string.match(key, "goto_([%d-]+)_([%d-]+)_([%d-]+)") + -- if x and y and z then + -- player:setpos({ x=tonumber(x), y=tonumber(y), z=tonumber(z) }) + -- return true + -- end + -- end + -- end +end) + +-- Flag interface +function ctf.gui.flag_board(name, pos) + local flag = ctf_flag.get(pos) + if not flag then + return + end + + local team = flag.team + if not team then + return + end + + if not ctf.can_mod(name, team) then + if ctf.player(name).team and ctf.player(name).team == team then + ctf.gui.show(name) + end + return + end + + ctf.log("gui", name .. " views flag board") + + local flag_name = flag.name + + if not ctf.setting("flag.names") then + flag.name = nil + return + end + + if not ctf.setting("gui") then + return + end + + if not flag_name then + flag_name = "" + end + + if not ctf.gui.flag_data then + ctf.gui.flag_data = {} + end + + ctf.gui.flag_data[name] = {pos=pos} + + minetest.show_formspec(name, "ctf:flag_board", + "size[6,3]".. + "field[1,1;4,1;flag_name;Flag Name;"..flag_name.."]".. + "button_exit[1,2;2,1;save;Save]".. + "button_exit[3,2;2,1;delete;Delete]" + ) +end +minetest.register_on_player_receive_fields(function(player, formname, fields) + local name = player:get_player_name() + + if not formname=="ctf:flag_board" then + return false + end + + if fields.save and fields.flag_name then + local flag = ctf_flag.get(ctf.gui.flag_data[name].pos) + if not flag then + return false + end + + local team = flag.team + if not team then + return false + end + + if ctf.can_mod(name,team) == false then + return false + end + + local flag_name = flag.name + if not flag_name then + flag_name = "" + end + + flag.name = fields.flag_name + + local msg = flag_name.." was renamed to "..fields.flag_name + + if flag_name=="" then + msg = "A flag was named "..fields.flag_name.." at ("..ctf.gui.flag_data[name].pos.x..","..ctf.gui.flag_data[name].pos.z..")" + end + + ctf.post(team,{msg=msg,icon="flag_info"}) + + return true + elseif fields.delete then + local pos = ctf.gui.flag_data[name].pos + + local flag = ctf_flag.get(ctf.gui.flag_data[name].pos) + + if not flag then + return + end + + local team = flag.team + if not team then + return + end + + if ctf.can_mod(name,team) == false then + return false + end + + ctf_flag.delete(team,pos) + + minetest.set_node(pos,{name="air"}) + pos.y=pos.y+1 + minetest.set_node(pos,{name="air"}) + player:get_inventory():add_item("main", "ctf_flag:flag") + + return true + end +end) diff --git a/mods/ctf_pvp_engine/ctf_flag/hud.lua b/mods/ctf_pvp_engine/ctf_flag/hud.lua new file mode 100644 index 0000000..2eee563 --- /dev/null +++ b/mods/ctf_pvp_engine/ctf_flag/hud.lua @@ -0,0 +1,102 @@ +-- TODO: delete flags if they are removed (ctf.next, or captured) +ctf.hud.register_part(function(player, name, tplayer) + if ctf.setting("flag.waypoints") then + for tname, team in pairs(ctf.teams) do + for _, flag in pairs(team.flags) do + local hud = "ctf:hud_" .. tname + local flag_name = flag.name or tname .. "'s base" + local color = ctf.flag_colors[team.data.color] + if not color then + color = "0x000000" + end + + if ctf.hud:exists(player, hud) then + ctf.hud:change(player, hud, "world_pos", { + x = flag.x, + y = flag.y, + z = flag.z + }) + else + ctf.hud:add(player, hud, { + hud_elem_type = "waypoint", + name = flag_name, + number = color, + world_pos = { + x = flag.x, + y = flag.y, + z = flag.z + } + }) + end + end + end + end +end) + +ctf.hud.register_part(function(player, name, tplayer) + -- Check all flags + local alert = nil + local color = "0xFFFFFF" + if ctf.setting("flag.alerts") then + if ctf.setting("flag.alerts.neutral_alert") then + alert = "Punch the enemy flag! Protect your flag!" + end + local claimed = ctf_flag.collect_claimed() + local enemyHolder = nil + local teamHolder = nil + for _, flag in pairs(claimed) do + if flag.team == tplayer.team then + enemyHolder = flag.claimed.player + else + teamHolder = flag.claimed.player + end + end + + if teamHolder == name then + if enemyHolder then + alert = "You can't capture the flag until " .. enemyHolder .. " is killed!" + color = "0xFF0000" + else + alert = "You've got the flag! Run back and punch your flag!" + color = "0xFF0000" + end + elseif teamHolder then + if enemyHolder then + alert = "Kill " .. enemyHolder .. " to allow " .. teamHolder .. " to capture the flag!" + color = "0xFF0000" + else + alert = "Protect " .. teamHolder .. ", they've got the enemy flag!" + color = "0xFF0000" + end + elseif enemyHolder then + alert = "Kill " .. enemyHolder .. ", they've got your flag!" + color = "0xFF0000" + end + end + + -- Display alert + if alert then + if ctf.hud:exists(player, "ctf:hud_team_alert") then + ctf.hud:change(player, "ctf:hud_team_alert", "text", alert) + ctf.hud:change(player, "ctf:hud_team_alert", "number", color) + else + local y + if ctf.setting("hud.teamname") then + y = 50 + else + y = 20 + end + ctf.hud:add(player, "ctf:hud_team_alert", { + hud_elem_type = "text", + position = {x = 1, y = 0}, + scale = {x = 100, y = 100}, + text = alert, + number = color, + offset = {x = -10, y = y}, + alignment = {x = -1, y = 0} + }) + end + else + ctf.hud:remove(player, "ctf:hud_team_alert") + end +end) diff --git a/mods/ctf_pvp_engine/ctf_flag/init.lua b/mods/ctf_pvp_engine/ctf_flag/init.lua new file mode 100644 index 0000000..1022d5e --- /dev/null +++ b/mods/ctf_pvp_engine/ctf_flag/init.lua @@ -0,0 +1,146 @@ +-- Initialise +ctf.register_on_init(function() + ctf.log("flag", "Initialising...") + ctf._set("flag.allow_multiple", true) + ctf._set("flag.capture_take", false) + ctf._set("flag.names", true) + ctf._set("flag.waypoints", true) + ctf._set("flag.protect_distance", 25) + ctf._set("flag.nobuild_radius", 3) + ctf._set("flag.drop_time", 7*60) + ctf._set("flag.drop_warn_time", 60) + ctf._set("flag.crafting", false) + ctf._set("flag.alerts", true) + ctf._set("flag.alerts.neutral_alert", true) + ctf._set("gui.team.teleport_to_flag", true) + ctf._set("gui.team.teleport_to_spawn", false) +end) + +minetest.register_privilege("ctf_place_flag", { + description = "can place flag" +}) + +dofile(minetest.get_modpath("ctf_flag") .. "/hud.lua") +dofile(minetest.get_modpath("ctf_flag") .. "/gui.lua") +dofile(minetest.get_modpath("ctf_flag") .. "/flag_func.lua") +dofile(minetest.get_modpath("ctf_flag") .. "/api.lua") +dofile(minetest.get_modpath("ctf_flag") .. "/flags.lua") + +ctf.register_on_new_team(function(team) + team.flags = {} +end) + +function ctf_flag.get_nearest(pos) + local closest = nil + local closest_distSQ = 1000000 + local pd = ctf.setting("flag.protect_distance") + local pdSQ = pd * pd + + for tname, team in pairs(ctf.teams) do + for i = 1, #team.flags do + local distSQ = vector.distanceSQ(pos, team.flags[i]) + if distSQ < pdSQ and distSQ < closest_distSQ then + closest = team.flags[i] + closest_distSQ = distSQ + end + end + end + + return closest, closest_distSQ +end + +function ctf_flag.get_nearest_team_dist(pos) + local flag, distSQ = ctf_flag.get_nearest(pos) + if flag then + return flag.team, distSQ + end +end + +ctf.register_on_territory_query(ctf_flag.get_nearest_team_dist) + +function ctf.get_spawn(team) + if not ctf.team(team) then + return nil + end + + if ctf.team(team).spawn then + return ctf.team(team).spawn + end + + -- Get spawn from first flag + ctf_flag.assert_flags(team) + if #ctf.team(team).flags > 0 then + return ctf.team(team).flags[1] + else + return nil + end +end + +-- Add minimum build range +local old_is_protected = minetest.is_protected +local r = ctf.setting("flag.nobuild_radius") +local rs = r * r +function minetest.is_protected(pos, name) + if r <= 0 or rs == 0 then + return old_is_protected(pos, name) + end + + local flag, distSQ = ctf_flag.get_nearest(pos) + if flag and pos.y >= flag.y - 1 and distSQ < rs then + minetest.chat_send_player(name, + "Too close to the flag to build! Leave at least " .. r .. " blocks around the flag.") + return true + else + return old_is_protected(pos, name) + end +end + +-- Play sound +ctf_flag.register_on_pick_up(function(attname, flag) + local vteam = ctf.team(flag.team) + for name, player in pairs(vteam.players) do + minetest.sound_play({name="trumpet_lose"}, { + to_player = name, + gain = 1.0, -- default + }) + end + + local ateam = ctf.team(ctf.player(attname).team) + for name, player in pairs(ateam.players) do + minetest.sound_play({name="trumpet_win"}, { + to_player = name, + gain = 1.0, -- default + }) + end +end) + +-- Drop after time +local pickup_times = {} +ctf_flag.register_on_pick_up(function(attname, flag) + pickup_times[attname] = minetest.get_gametime() +end) +ctf_flag.register_on_drop(function(attname, flag) + pickup_times[attname] = nil +end) +ctf_flag.register_on_capture(function(attname, flag) + pickup_times[attname] = nil +end) +ctf.register_on_new_game(function() + pickup_times = {} +end) +local function update_flag_drops() + local time = minetest.get_gametime() + local drop_time = ctf.setting("flag.drop_time") + for name, start in pairs(pickup_times) do + local remaining = drop_time - time + start + if remaining < 0 then + ctf_flag.player_drop_flag(name) + minetest.chat_send_player(name, "You took too long to capture the flag, so it returned!") + elseif remaining < ctf.setting("flag.drop_warn_time") then + minetest.chat_send_player(name, "You have " .. remaining .. + " seconds to capture the flag before it returns.") + end + end + minetest.after(5, update_flag_drops) +end +minetest.after(5, update_flag_drops) diff --git a/mods/ctf_pvp_engine/ctf_flag/sounds/trumpet_lose.ogg b/mods/ctf_pvp_engine/ctf_flag/sounds/trumpet_lose.ogg new file mode 100644 index 0000000..0d8bcdb Binary files /dev/null and b/mods/ctf_pvp_engine/ctf_flag/sounds/trumpet_lose.ogg differ diff --git a/mods/ctf_pvp_engine/ctf_flag/sounds/trumpet_win.ogg b/mods/ctf_pvp_engine/ctf_flag/sounds/trumpet_win.ogg new file mode 100644 index 0000000..e0d61b4 Binary files /dev/null and b/mods/ctf_pvp_engine/ctf_flag/sounds/trumpet_win.ogg differ diff --git a/mods/ctf_pvp_engine/ctf_flag/textures/flag_black.png b/mods/ctf_pvp_engine/ctf_flag/textures/flag_black.png new file mode 100644 index 0000000..47e9a9a Binary files /dev/null and b/mods/ctf_pvp_engine/ctf_flag/textures/flag_black.png differ diff --git a/mods/ctf_pvp_engine/ctf_flag/textures/flag_black2.png b/mods/ctf_pvp_engine/ctf_flag/textures/flag_black2.png new file mode 100644 index 0000000..72bacfc Binary files /dev/null and b/mods/ctf_pvp_engine/ctf_flag/textures/flag_black2.png differ diff --git a/mods/ctf_pvp_engine/ctf_flag/textures/flag_blue.png b/mods/ctf_pvp_engine/ctf_flag/textures/flag_blue.png new file mode 100644 index 0000000..0cf5abe Binary files /dev/null and b/mods/ctf_pvp_engine/ctf_flag/textures/flag_blue.png differ diff --git a/mods/ctf_pvp_engine/ctf_flag/textures/flag_blue2.png b/mods/ctf_pvp_engine/ctf_flag/textures/flag_blue2.png new file mode 100644 index 0000000..e1254cf Binary files /dev/null and b/mods/ctf_pvp_engine/ctf_flag/textures/flag_blue2.png differ diff --git a/mods/ctf_pvp_engine/ctf_flag/textures/flag_cyan.png b/mods/ctf_pvp_engine/ctf_flag/textures/flag_cyan.png new file mode 100644 index 0000000..9cfc186 Binary files /dev/null and b/mods/ctf_pvp_engine/ctf_flag/textures/flag_cyan.png differ diff --git a/mods/ctf_pvp_engine/ctf_flag/textures/flag_cyan2.png b/mods/ctf_pvp_engine/ctf_flag/textures/flag_cyan2.png new file mode 100644 index 0000000..4ee3a29 Binary files /dev/null and b/mods/ctf_pvp_engine/ctf_flag/textures/flag_cyan2.png differ diff --git a/mods/ctf_pvp_engine/ctf_flag/textures/flag_gold.png b/mods/ctf_pvp_engine/ctf_flag/textures/flag_gold.png new file mode 100644 index 0000000..0969ba7 Binary files /dev/null and b/mods/ctf_pvp_engine/ctf_flag/textures/flag_gold.png differ diff --git a/mods/ctf_pvp_engine/ctf_flag/textures/flag_gold2.png b/mods/ctf_pvp_engine/ctf_flag/textures/flag_gold2.png new file mode 100644 index 0000000..c5cec33 Binary files /dev/null and b/mods/ctf_pvp_engine/ctf_flag/textures/flag_gold2.png differ diff --git a/mods/ctf_pvp_engine/ctf_flag/textures/flag_gray.png b/mods/ctf_pvp_engine/ctf_flag/textures/flag_gray.png new file mode 100644 index 0000000..eef45d5 Binary files /dev/null and b/mods/ctf_pvp_engine/ctf_flag/textures/flag_gray.png differ diff --git a/mods/ctf_pvp_engine/ctf_flag/textures/flag_gray2.png b/mods/ctf_pvp_engine/ctf_flag/textures/flag_gray2.png new file mode 100644 index 0000000..3a71ca8 Binary files /dev/null and b/mods/ctf_pvp_engine/ctf_flag/textures/flag_gray2.png differ diff --git a/mods/ctf_pvp_engine/ctf_flag/textures/flag_green.png b/mods/ctf_pvp_engine/ctf_flag/textures/flag_green.png new file mode 100644 index 0000000..7800464 Binary files /dev/null and b/mods/ctf_pvp_engine/ctf_flag/textures/flag_green.png differ diff --git a/mods/ctf_pvp_engine/ctf_flag/textures/flag_green2.png b/mods/ctf_pvp_engine/ctf_flag/textures/flag_green2.png new file mode 100644 index 0000000..d4ce6ef Binary files /dev/null and b/mods/ctf_pvp_engine/ctf_flag/textures/flag_green2.png differ diff --git a/mods/ctf_pvp_engine/ctf_flag/textures/flag_orange.png b/mods/ctf_pvp_engine/ctf_flag/textures/flag_orange.png new file mode 100644 index 0000000..9d99418 Binary files /dev/null and b/mods/ctf_pvp_engine/ctf_flag/textures/flag_orange.png differ diff --git a/mods/ctf_pvp_engine/ctf_flag/textures/flag_orange2.png b/mods/ctf_pvp_engine/ctf_flag/textures/flag_orange2.png new file mode 100644 index 0000000..b135b9d Binary files /dev/null and b/mods/ctf_pvp_engine/ctf_flag/textures/flag_orange2.png differ diff --git a/mods/ctf_pvp_engine/ctf_flag/textures/flag_pink.png b/mods/ctf_pvp_engine/ctf_flag/textures/flag_pink.png new file mode 100644 index 0000000..36b85d3 Binary files /dev/null and b/mods/ctf_pvp_engine/ctf_flag/textures/flag_pink.png differ diff --git a/mods/ctf_pvp_engine/ctf_flag/textures/flag_pink2.png b/mods/ctf_pvp_engine/ctf_flag/textures/flag_pink2.png new file mode 100644 index 0000000..b5899cd Binary files /dev/null and b/mods/ctf_pvp_engine/ctf_flag/textures/flag_pink2.png differ diff --git a/mods/ctf_pvp_engine/ctf_flag/textures/flag_purple.png b/mods/ctf_pvp_engine/ctf_flag/textures/flag_purple.png new file mode 100644 index 0000000..86bb33d Binary files /dev/null and b/mods/ctf_pvp_engine/ctf_flag/textures/flag_purple.png differ diff --git a/mods/ctf_pvp_engine/ctf_flag/textures/flag_purple2.png b/mods/ctf_pvp_engine/ctf_flag/textures/flag_purple2.png new file mode 100644 index 0000000..6930dde Binary files /dev/null and b/mods/ctf_pvp_engine/ctf_flag/textures/flag_purple2.png differ diff --git a/mods/ctf_pvp_engine/ctf_flag/textures/flag_red.png b/mods/ctf_pvp_engine/ctf_flag/textures/flag_red.png new file mode 100644 index 0000000..bfd504e Binary files /dev/null and b/mods/ctf_pvp_engine/ctf_flag/textures/flag_red.png differ diff --git a/mods/ctf_pvp_engine/ctf_flag/textures/flag_red2.png b/mods/ctf_pvp_engine/ctf_flag/textures/flag_red2.png new file mode 100644 index 0000000..010f068 Binary files /dev/null and b/mods/ctf_pvp_engine/ctf_flag/textures/flag_red2.png differ diff --git a/mods/ctf_pvp_engine/ctf_flag/textures/flag_silver.png b/mods/ctf_pvp_engine/ctf_flag/textures/flag_silver.png new file mode 100644 index 0000000..bede48d Binary files /dev/null and b/mods/ctf_pvp_engine/ctf_flag/textures/flag_silver.png differ diff --git a/mods/ctf_pvp_engine/ctf_flag/textures/flag_silver2.png b/mods/ctf_pvp_engine/ctf_flag/textures/flag_silver2.png new file mode 100644 index 0000000..fe856e6 Binary files /dev/null and b/mods/ctf_pvp_engine/ctf_flag/textures/flag_silver2.png differ diff --git a/mods/ctf_pvp_engine/ctf_flag/textures/flag_yellow.png b/mods/ctf_pvp_engine/ctf_flag/textures/flag_yellow.png new file mode 100644 index 0000000..80e1b29 Binary files /dev/null and b/mods/ctf_pvp_engine/ctf_flag/textures/flag_yellow.png differ diff --git a/mods/ctf_pvp_engine/ctf_flag/textures/flag_yellow2.png b/mods/ctf_pvp_engine/ctf_flag/textures/flag_yellow2.png new file mode 100644 index 0000000..642f527 Binary files /dev/null and b/mods/ctf_pvp_engine/ctf_flag/textures/flag_yellow2.png differ diff --git a/mods/ctf_pvp_engine/ctf_protect/depends.txt b/mods/ctf_pvp_engine/ctf_protect/depends.txt new file mode 100644 index 0000000..a7176f2 --- /dev/null +++ b/mods/ctf_pvp_engine/ctf_protect/depends.txt @@ -0,0 +1 @@ +ctf \ No newline at end of file diff --git a/mods/ctf_pvp_engine/ctf_protect/init.lua b/mods/ctf_pvp_engine/ctf_protect/init.lua new file mode 100644 index 0000000..25c3809 --- /dev/null +++ b/mods/ctf_pvp_engine/ctf_protect/init.lua @@ -0,0 +1,28 @@ +-- This mod is used to protect nodes in the capture the flag game +ctf.register_on_init(function() + ctf.log("chat", "Initialising...") + + -- Settings: Chat + ctf._set("node_ownership", true) +end) + +local old_is_protected = minetest.is_protected + +function minetest.is_protected(pos, name) + if not ctf.setting("node_ownership") then + return old_is_protected(pos, name) + end + + local team = ctf.get_territory_owner(pos) + + if not team or not ctf.team(team) then + return old_is_protected(pos, name) + end + + if ctf.player(name).team == team then + return old_is_protected(pos, name) + else + minetest.chat_send_player(name, "You cannot dig on team "..team.."'s land") + return true + end +end diff --git a/mods/ctf_pvp_engine/doc_data.md b/mods/ctf_pvp_engine/doc_data.md new file mode 100644 index 0000000..eca89bd --- /dev/null +++ b/mods/ctf_pvp_engine/doc_data.md @@ -0,0 +1,80 @@ +# Data Formats + +This file documents the contents of ctf.txt +Values are added to the file using ctf.register_on_save and ctf.register_on_load. +Here are the default values: + +```lua +{ + players = ctf.players, + teams = ctf.teams, + diplo = ctf.diplo.diplo +} +``` + +## Players + +Commonly called tplayer (may be called data or player in old code). +Player name is commonly called name (but may be called other things in older code). + +```lua +ctf.players = { + username = (player_table) +} + +(player_table) = { + name = "username", + team = "teamname", + auth = false + -- true if the player is a team admin. Team admins can change team settings. + -- See ctf.can_mod() + -- Note that priv:ctf_admin can also change team settings +} +``` + +## Teams + +Commonly called team. +Team name is commonly called tname (but may be called team in old code). + +```lua +ctf.teams = { + teamname = (team_table) +} + +(team_table) = { + data = { + name = "teamname", + color = "teamcolor" -- see ctf_colors + }, + flags = { + (flag_table), (flag_table) + }, + players = { + username1 = (player_table), + username2 = (player_table) + }, + spawn = { x=0, y=0, z=0 } + -- fallback team spawn. Read by ctf.get_spawn() and overriding functions + -- Don't use directly, instead call ctf.get_spawn("teamname") +} + +(flag_table) = { + x=0, y=0, z=0, + flag_name = "Capital" -- human readable name +} +``` + +## Diplomacy + +```lua +ctf.diplo.diplo = { + (diplo_table), (diplo_table) +} + +(diplo_table) = { + one = "teamname1", + two = "teamname2", + state = "war" / "peace" / "alliance" +} +``` diff --git a/mods/ctf_pvp_engine/doc_project_overview.md b/mods/ctf_pvp_engine/doc_project_overview.md new file mode 100644 index 0000000..99902b8 --- /dev/null +++ b/mods/ctf_pvp_engine/doc_project_overview.md @@ -0,0 +1,70 @@ +# Welcome + +The aim of CTF_PvP_Engine is to provide a base to any subgame which uses the +concepts of teams. Flags are a plugin mod, so it isn't CTF as such. + +# Modules in CTF_PvP_Engine + +## hudkit + +A support library to make the HUD API nicer. +WTFPL. + +## ctf + +Requires hudkit. Support for chatplus. +Core framework, players, teams, diplomacy, hud and gui. + +* core - adds saving, loading and settings. All modules depend on this. +* teams - add the concepts of teams and players. All modules except core depend on this. +* diplomacy - adds inter team states of war, peace and alliances. +* gui - adds the team gui on /team. Allows tabs to be registered. +* hud - adds the name of the team in the TR of the screen, and sets the color + +## ctf_chat + +Requires ctf. Support for chatplus. +Chat commands and chat channels. + +## ctf_colors + +Requires ctf. Support for 3d_armor. +Adds player colors. + +* gui - settings form +* hud - team name color, player skin color, nametag color +* init - table of colors + +## ctf_flag + +Requires ctf and ctf_colors. Support for chatplus. +Adds flags and flag taking. + +* api - flag callbacks, flag management (adding, capturing, updating), flag checking (asserts) +* flag_func - functions for flag node definitions. +* flags - flag node definitions. +* gui - flag naming GUI, flag teleport GUI. +* hud - waypoints, alerts ("Punch the enemy flag!" etc in top right) +* init - get nearest flag, overrides ctf.get_spawn(), minimum build range, pick up sound, flag capture timeout. + +## ctf_protect + +Adds node ownership / protection to teams. +Requires ctf_flag. + +# Past/Other Mods + +Please look + +## ctf_turret + +Adds auto-firing turrets that fire on enemies. +See git history. + +## Capture the flag + +more mods available in [capture the flag](http://github.com/rubenwardy/capturetheflag/). + +* ctf_match - adds the concept of winning, match build time, + and reseting the map / setting up a new game. + Requires ctf_flag diff --git a/mods/ctf_pvp_engine/doc_settings.md b/mods/ctf_pvp_engine/doc_settings.md new file mode 100644 index 0000000..68e999a --- /dev/null +++ b/mods/ctf_pvp_engine/doc_settings.md @@ -0,0 +1,60 @@ +# ctf + +| name | default value | description | +| -------------------------- | ------------- | ---------------------------------------------------------------- | +| allocate_mode | 0 | 0=off, 1=firstnonfullteam, 2=RandomOfSmallestTwo, 3=SmallestTeam | +| autoalloc_on_joinplayer | true | Trigger auto alloc on join player | +| default_diplo_state | "war" | "war", "alliance" or "peace" | +| diplomacy | true | Is diplomacy enabled | +| friendly_fire | true | True if players can't hit other players on their team | +| maximum_in_team | -1 | Player cap | +| players_can_change_team | true | | +| hud | true | Enable HUD | +| gui | true | Enable GUI | +| gui.team | true | Whether to show team gui (/team) | +| gui.team.initial | "news" | Initial tab | +| gui.tab.diplo | true | Show diplo tab | +| gui.tab.news | true | Show news tab | +| gui.tab.settings | true | Show settings tab | +| spawn_offset | {x=0, y=0, z=0} | Offset of static spawn-point from team-base | + +# ctf_chat + +| name | default value | description | +| -------------------------- | ------------- | ---------------------------------------------------------------- | +| chat.default | "global" | "global" or "team" | +| chat.global_channel | true | | +| chat.team_channel | true | | + +# ctf_colors + +| name | default value | description | +| -------------------------- | ------------- | ---------------------------------------------------------------- | +| colors.nametag | true | Whether to colour the name tagColour nametag | +| colors.nametag.tcolor | false | Base nametag colour on team colour | +| colors.skins | false | Team skins are coloured | + +# ctf_flag + +| name | default value | description | +| -------------------------- | ------------- | ---------------------------------------------------------------- | +| flag.alerts | true | Prompts like "X has captured your flag" | +| flag.alerts.neutral_alert | true | Show prompt in neutral state, ie: "attack and defend!" | +| flag.allow_multiple | true | Teams can have multiple flags | +| flag.capture_take | false | Whether a player needs to return flag to base to capture | +| flag.drop_time | 420 | Time in seconds before a player drops the flag they're holding | +| flag.drop_warn_time | 60 | Warning time before drop | +| flag.nobuild_radius | 3 | Area around flag where you can't build | +| flag.names | true | Enable naming flags | +| flag.protect_distance | 25 | Area protection distance | +| flag.waypoints | true | Enable waypoints to flags | +| flag.crafting | false | Enable the crafting of flags | +| gui.tab.flags | true | Show flags tab | +| gui.team.teleport_to_flag | true | Enable teleport to flag button in flags tab | +| gui.team.teleport_to_spawn | false | Enable teleport to spawn button in flags tab | + +# ctf_protect + +| name | default value | description | +| -------------------------- | ------------- | ---------------------------------------------------------------- | +| node_ownership | true | Whether node protection per team is enabled | diff --git a/mods/ctf_pvp_engine/hudkit/init.lua b/mods/ctf_pvp_engine/hudkit/init.lua new file mode 100644 index 0000000..c4633c1 --- /dev/null +++ b/mods/ctf_pvp_engine/hudkit/init.lua @@ -0,0 +1,67 @@ +function hudkit() + return { + players = {}, + + add = function(self, player, id, def) + local name = player:get_player_name() + local elements = self.players[name] + + if not elements then + self.players[name] = {} + elements = self.players[name] + end + + elements[id] = { + id = player:hud_add(def), + def = def + } + return true + end, + + exists = function(self, player, id) + if not player then + return false + end + + local name = player:get_player_name() + local elements = self.players[name] + + if not elements or not elements[id] then + return false + end + return true + end, + + change = function(self, player, id, stat, value) + if not player then + return false + end + + local name = player:get_player_name() + local elements = self.players[name] + + if not elements or not elements[id] or not elements[id].id then + return false + end + + if elements[id].def[stat] ~= value then + elements[id].def[stat] = value + player:hud_change(elements[id].id, stat, value) + end + return true + end, + + remove = function(self, player, id) + local name = player:get_player_name() + local elements = self.players[name] + + if not elements or not elements[id] or not elements[id].id then + return false + end + + player:hud_remove(elements[id].id) + elements[id] = nil + return true + end + } +end diff --git a/mods/ctf_pvp_engine/lib_chatcmdbuilder/.gitignore b/mods/ctf_pvp_engine/lib_chatcmdbuilder/.gitignore new file mode 100644 index 0000000..9cac31c --- /dev/null +++ b/mods/ctf_pvp_engine/lib_chatcmdbuilder/.gitignore @@ -0,0 +1,80 @@ + +# Created by https://www.gitignore.io/api/lin,linux,windows,lua + +#!! ERROR: lin is undefined. Use list command to see defined gitignore types !!# + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + + +### Windows ### +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + + +### Lua ### +# Compiled Lua sources +luac.out + +# luarocks build files +*.src.rock +*.zip +*.tar.gz + +# Object files +*.o +*.os +*.ko +*.obj +*.elf + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo +*.def +*.exp + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex diff --git a/mods/ctf_pvp_engine/lib_chatcmdbuilder/LICENSE b/mods/ctf_pvp_engine/lib_chatcmdbuilder/LICENSE new file mode 100644 index 0000000..588da15 --- /dev/null +++ b/mods/ctf_pvp_engine/lib_chatcmdbuilder/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2016-17 rubenwardy + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/mods/ctf_pvp_engine/lib_chatcmdbuilder/README.md b/mods/ctf_pvp_engine/lib_chatcmdbuilder/README.md new file mode 100644 index 0000000..7d52585 --- /dev/null +++ b/mods/ctf_pvp_engine/lib_chatcmdbuilder/README.md @@ -0,0 +1,89 @@ +# ChatCmdBuilder + +Easily create complex chat commands with no regex. +Created by rubenwardy +License: MIT + +# Usage + +## Registering Chat Commands + +`ChatCmdBuilder.new(name, setup)` registers a new chat command called `name`. +Setup is called immediately after calling `new` to initialise subcommands. + +You can set values in the chat command definition by using def: +`ChatCmdBuilder.new(name, setup, def)`. + +Here is an example: + +```Lua +ChatCmdBuilder.new("admin", function(cmd) + cmd:sub("kill :target", function(name, target) + local player = minetest.get_player_by_name(target) + if player then + player:set_hp(0) + return true, "Killed " .. target + else + return false, "Unable to find " .. target + end + end) + + cmd:sub("move :target to :pos:pos", function(name, target, pos) + local player = minetest.get_player_by_name(target) + if player then + player:setpos(pos) + return true, "Moved " .. target .. " to " .. minetest.pos_to_string(pos) + else + return false, "Unable to find " .. target + end + end) +end, { + description = "Admin tools", + privs = { + kick = true, + ban = true + } +}) +``` + +A player could then do `/admin kill player1` to kill player1, +or `/admin move player1 to 0,0,0` to teleport a user. + +## Introduction to Routing + +A route is a string. Let's look at `move :target to :pos:pos`: + +* `move` and `to` are constants. They need to be there in order to match. +* `:target` and `:pos:pos` are parameters. They're passed to the function. +* The second `pos` in `:pos:pos` after `:` is the param type. `:target` has an implicit + type of `word`. + +## Param Types + +* `word` - default. Any string without spaces. +* `number` - Any number, including decimals +* `int` - Any integer, no decimals +* `text` - Any string +* `pos` - 1,2,3 or 1.1,2,3.4567 or (1,2,3) or 1.2, 2 ,3.2 + +## Build chat command function + +If you don't want to register the chatcommand at this point, you can just generate +a function using `ChatCmdBuilder.build`. + +For example, this is the full definition of ChatCmdBuilder.new: + +```Lua +function ChatCmdBuilder.new(name, func, def) + def = def or {} + def.func = ChatCmdBuilder.build(name, func) + minetest.register_chatcommand(name, def) +end +``` + +## Run tests + +```Bash +sudo apt-get install luajit +luajit init.lua +``` diff --git a/mods/ctf_pvp_engine/lib_chatcmdbuilder/description.txt b/mods/ctf_pvp_engine/lib_chatcmdbuilder/description.txt new file mode 100644 index 0000000..6e5d637 --- /dev/null +++ b/mods/ctf_pvp_engine/lib_chatcmdbuilder/description.txt @@ -0,0 +1 @@ +A library to make registering chat commands easier diff --git a/mods/ctf_pvp_engine/lib_chatcmdbuilder/init.lua b/mods/ctf_pvp_engine/lib_chatcmdbuilder/init.lua new file mode 100644 index 0000000..400d250 --- /dev/null +++ b/mods/ctf_pvp_engine/lib_chatcmdbuilder/init.lua @@ -0,0 +1,230 @@ +ChatCmdBuilder = {} + +function ChatCmdBuilder.new(name, func, def) + def = def or {} + local cmd = ChatCmdBuilder.build(func) + cmd.def = def + def.func = cmd.run + minetest.register_chatcommand(name, def) + return cmd +end + +local STATE_READY = 1 +local STATE_PARAM = 2 +local STATE_PARAM_TYPE = 3 +local bad_chars = {} +bad_chars["("] = true +bad_chars[")"] = true +bad_chars["."] = true +bad_chars["%"] = true +bad_chars["+"] = true +bad_chars["-"] = true +bad_chars["*"] = true +bad_chars["?"] = true +bad_chars["["] = true +bad_chars["^"] = true +bad_chars["$"] = true +local function escape(char) + if bad_chars[char] then + return "%" .. char + else + return char + end +end + +function ChatCmdBuilder.build(func) + local cmd = { + _subs = {} + } + function cmd:sub(route, func, def) + print("Parsing " .. route) + + def = def or {} + if string.trim then + route = string.trim(route) + end + + local sub = { + pattern = "^", + params = {}, + func = func + } + + -- End of param reached: add it to the pattern + local param = "" + local param_type = "" + local should_be_eos = false + local function finishParam() + if param ~= "" and param_type ~= "" then + print(" - Found param " .. param .. " type " .. param_type) + + if param_type == "pos" then + sub.pattern = sub.pattern .. "%(? *(%-?[%d.]+) *, *(%-?[%d.]+) *, *(%-?[%d.]+) *%)?" + elseif param_type == "text" then + sub.pattern = sub.pattern .. "(*+)" + should_be_eos = true + elseif param_type == "number" then + sub.pattern = sub.pattern .. "([%d.]+)" + elseif param_type == "int" then + sub.pattern = sub.pattern .. "([%d]+)" + else + if param_type ~= "word" then + print("Unrecognised param_type=" .. param_type .. ", using 'word' type instead") + param_type = "word" + end + sub.pattern = sub.pattern .. "([^ ]+)" + end + + table.insert(sub.params, param_type) + + param = "" + param_type = "" + end + end + + -- Iterate through the route to find params + local state = STATE_READY + for i = 1, #route do + local c = route:sub(i, i) + if should_be_eos then + error("Should be end of string. Nothing is allowed after a param of type text.") + end + + if state == STATE_READY then + if c == ":" then + print(" - Found :, entering param") + state = STATE_PARAM + param_type = "word" + else + sub.pattern = sub.pattern .. escape(c) + end + elseif state == STATE_PARAM then + if c == ":" then + print(" - Found :, entering param type") + state = STATE_PARAM_TYPE + param_type = "" + elseif c:match("%W") then + print(" - Found nonalphanum, leaving param") + state = STATE_READY + finishParam() + sub.pattern = sub.pattern .. escape(c) + else + param = param .. c + end + elseif state == STATE_PARAM_TYPE then + if c:match("%W") then + print(" - Found nonalphanum, leaving param type") + state = STATE_READY + finishParam() + sub.pattern = sub.pattern .. escape(c) + else + param_type = param_type .. c + end + end + end + print(" - End of route") + finishParam() + sub.pattern = sub.pattern .. "$" + print("Pattern: " .. sub.pattern) + + table.insert(self._subs, sub) + end + + if func then + func(cmd) + end + + cmd.run = function(name, param) + for i = 1, #cmd._subs do + local sub = cmd._subs[i] + local res = { string.match(param, sub.pattern) } + if #res > 0 then + local pointer = 1 + local params = { name } + for j = 1, #sub.params do + local param = sub.params[j] + if param == "pos" then + local pos = { + x = tonumber(res[pointer]), + y = tonumber(res[pointer + 1]), + z = tonumber(res[pointer + 2]) + } + table.insert(params, pos) + pointer = pointer + 3 + elseif param == "number" or param == "int" then + table.insert(params, tonumber(res[pointer])) + pointer = pointer + 1 + else + table.insert(params, res[pointer]) + pointer = pointer + 1 + end + end + return sub.func(unpack(params)) + end + end + print("No matches") + end + + return cmd +end + +local function run_tests() + if not (ChatCmdBuilder.build(function(cmd) + cmd:sub("bar :one and :two:word", function(name, one, two) + if name == "singleplayer" and one == "abc" and two == "def" then + return true + end + end) + end))("singleplayer", "bar abc and def") then + error("Test 1 failed") + end + + local move = ChatCmdBuilder.build(function(cmd) + cmd:sub("move :target to :pos:pos", function(name, target, pos) + if name == "singleplayer" and target == "player1" and + pos.x == 0 and pos.y == 1 and pos.z == 2 then + return true + end + end) + end) + if not move("singleplayer", "move player1 to 0,1,2") then + error("Test 2 failed") + end + if not move("singleplayer", "move player1 to (0,1,2)") then + error("Test 3 failed") + end + if not move("singleplayer", "move player1 to 0, 1,2") then + error("Test 4 failed") + end + if not move("singleplayer", "move player1 to 0 ,1, 2") then + error("Test 5 failed") + end + if not move("singleplayer", "move player1 to 0, 1, 2") then + error("Test 6 failed") + end + if not move("singleplayer", "move player1 to 0 ,1 ,2") then + error("Test 7 failed") + end + if not move("singleplayer", "move player1 to ( 0 ,1 ,2)") then + error("Test 7 failed") + end + if move("singleplayer", "move player1 to abc,def,sdosd") then + error("Test 8 failed") + end + if move("singleplayer", "move player1 to abc def sdosd") then + error("Test 8 failed") + end + + if not (ChatCmdBuilder.build(function(cmd) + cmd:sub("does :one:int plus :two:int equal :three:int", function(name, one, two, three) + if name == "singleplayer" and one + two == three then + return true + end + end) + end))("singleplayer", "does 1 plus 2 equal 3") then + error("Test 9 failed") + end +end +if not minetest then + run_tests() +end diff --git a/mods/ctf_pvp_engine/lib_chatcmdbuilder/mod.conf b/mods/ctf_pvp_engine/lib_chatcmdbuilder/mod.conf new file mode 100644 index 0000000..1172971 --- /dev/null +++ b/mods/ctf_pvp_engine/lib_chatcmdbuilder/mod.conf @@ -0,0 +1,7 @@ +name = lib_chatcmdbuilder +title = Chat Command Builder +author = rubenwardy +description = A library to make registering chat commands easier +license = MIT +forum = https://forum.minetest.net/viewtopic.php?t=14899 +version = 0.1.0 diff --git a/mods/ctf_pvp_engine/modpack.txt b/mods/ctf_pvp_engine/modpack.txt new file mode 100644 index 0000000..e69de29