├── .gitattributes ├── .gitignore ├── README.md ├── ctf ├── core.lua ├── diplomacy.lua ├── gui.lua ├── hud.lua ├── init.lua ├── mod.conf ├── teams.lua └── textures │ ├── diplo_alliance.png │ ├── diplo_peace.png │ └── diplo_war.png ├── ctf_chat ├── init.lua └── mod.conf ├── ctf_colors ├── gui.lua ├── hud.lua ├── init.lua ├── mod.conf └── textures │ ├── ctf_colors_hotbar_blue.png │ ├── ctf_colors_hotbar_red.png │ ├── ctf_colors_hotbar_selected_blue.png │ ├── ctf_colors_hotbar_selected_red.png │ ├── ctf_colors_skin_black.png │ ├── ctf_colors_skin_blue.png │ ├── ctf_colors_skin_cyan.png │ ├── ctf_colors_skin_gold.png │ ├── ctf_colors_skin_gray.png │ ├── ctf_colors_skin_green.png │ ├── ctf_colors_skin_orange.png │ ├── ctf_colors_skin_pink.png │ ├── ctf_colors_skin_purple.png │ ├── ctf_colors_skin_red.png │ ├── ctf_colors_skin_silver.png │ └── ctf_colors_skin_yellow.png ├── ctf_flag ├── api.lua ├── flag_func.lua ├── flags.lua ├── gui.lua ├── hud.lua ├── init.lua ├── mod.conf ├── sounds │ ├── trumpet_lose.ogg │ └── trumpet_win.ogg └── textures │ ├── flag_black.png │ ├── flag_black2.png │ ├── flag_blue.png │ ├── flag_blue2.png │ ├── flag_cyan.png │ ├── flag_cyan2.png │ ├── flag_gold.png │ ├── flag_gold2.png │ ├── flag_gray.png │ ├── flag_gray2.png │ ├── flag_green.png │ ├── flag_green2.png │ ├── flag_orange.png │ ├── flag_orange2.png │ ├── flag_pink.png │ ├── flag_pink2.png │ ├── flag_purple.png │ ├── flag_purple2.png │ ├── flag_red.png │ ├── flag_red2.png │ ├── flag_silver.png │ ├── flag_silver2.png │ ├── flag_yellow.png │ └── flag_yellow2.png ├── ctf_protect ├── init.lua └── mod.conf ├── doc_data.md ├── doc_project_overview.md ├── doc_settings.md ├── hudkit ├── init.lua └── mod.conf ├── lib_chatcmdbuilder ├── .gitignore ├── LICENSE ├── README.md ├── description.txt ├── init.lua └── mod.conf └── modpack.conf /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .classpath 16 | .settings/ 17 | .loadpath 18 | 19 | # External tool builders 20 | .externalToolBuilders/ 21 | 22 | # Locally stored "Eclipse launch configurations" 23 | *.launch 24 | 25 | # CDT-specific 26 | .cproject 27 | 28 | # PDT-specific 29 | .buildpath 30 | 31 | 32 | ################# 33 | ## Visual Studio 34 | ################# 35 | 36 | ## Ignore Visual Studio temporary files, build results, and 37 | ## files generated by popular Visual Studio add-ons. 38 | 39 | # User-specific files 40 | *.suo 41 | *.user 42 | *.sln.docstates 43 | 44 | # Build results 45 | 46 | [Dd]ebug/ 47 | [Rr]elease/ 48 | x64/ 49 | build/ 50 | [Bb]in/ 51 | [Oo]bj/ 52 | 53 | # MSTest test Results 54 | [Tt]est[Rr]esult*/ 55 | [Bb]uild[Ll]og.* 56 | 57 | *_i.c 58 | *_p.c 59 | *.ilk 60 | *.meta 61 | *.obj 62 | *.pch 63 | *.pdb 64 | *.pgc 65 | *.pgd 66 | *.rsp 67 | *.sbr 68 | *.tlb 69 | *.tli 70 | *.tlh 71 | *.tmp 72 | *.tmp_proj 73 | *.log 74 | *.vspscc 75 | *.vssscc 76 | .builds 77 | *.pidb 78 | *.log 79 | *.scc 80 | 81 | # Visual C++ cache files 82 | ipch/ 83 | *.aps 84 | *.ncb 85 | *.opensdf 86 | *.sdf 87 | *.cachefile 88 | 89 | # Visual Studio profiler 90 | *.psess 91 | *.vsp 92 | *.vspx 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | 101 | # TeamCity is a build add-in 102 | _TeamCity* 103 | 104 | # DotCover is a Code Coverage Tool 105 | *.dotCover 106 | 107 | # NCrunch 108 | *.ncrunch* 109 | .*crunch*.local.xml 110 | 111 | # Installshield output folder 112 | [Ee]xpress/ 113 | 114 | # DocProject is a documentation generator add-in 115 | DocProject/buildhelp/ 116 | DocProject/Help/*.HxT 117 | DocProject/Help/*.HxC 118 | DocProject/Help/*.hhc 119 | DocProject/Help/*.hhk 120 | DocProject/Help/*.hhp 121 | DocProject/Help/Html2 122 | DocProject/Help/html 123 | 124 | # Click-Once directory 125 | publish/ 126 | 127 | # Publish Web Output 128 | *.Publish.xml 129 | *.pubxml 130 | 131 | # NuGet Packages Directory 132 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 133 | #packages/ 134 | 135 | # Windows Azure Build Output 136 | csx 137 | *.build.csdef 138 | 139 | # Windows Store app package directory 140 | AppPackages/ 141 | 142 | # Others 143 | sql/ 144 | *.Cache 145 | ClientBin/ 146 | [Ss]tyle[Cc]op.* 147 | ~$* 148 | *~ 149 | *.dbmdl 150 | *.[Pp]ublish.xml 151 | *.pfx 152 | *.publishsettings 153 | 154 | # RIA/Silverlight projects 155 | Generated_Code/ 156 | 157 | # Backup & report files from converting an old project file to a newer 158 | # Visual Studio version. Backup files are not needed, because we have git ;-) 159 | _UpgradeReport_Files/ 160 | Backup*/ 161 | UpgradeLog*.XML 162 | UpgradeLog*.htm 163 | 164 | # SQL Server files 165 | App_Data/*.mdf 166 | App_Data/*.ldf 167 | 168 | ############# 169 | ## Windows detritus 170 | ############# 171 | 172 | # Windows image file caches 173 | Thumbs.db 174 | ehthumbs.db 175 | 176 | # Folder config file 177 | Desktop.ini 178 | 179 | # Recycle Bin used on file shares 180 | $RECYCLE.BIN/ 181 | 182 | # Mac crap 183 | .DS_Store 184 | 185 | 186 | ############# 187 | ## Python 188 | ############# 189 | 190 | *.py[co] 191 | 192 | # Packages 193 | *.egg 194 | *.egg-info 195 | dist/ 196 | build/ 197 | eggs/ 198 | parts/ 199 | var/ 200 | sdist/ 201 | develop-eggs/ 202 | .installed.cfg 203 | 204 | # Installer logs 205 | pip-log.txt 206 | 207 | # Unit test / coverage reports 208 | .coverage 209 | .tox 210 | 211 | #Translations 212 | *.mo 213 | 214 | #Mr Developer 215 | .mr.developer.cfg 216 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | CTF PvP Engine 2 | ============== 3 | 4 | A highly modular framework for the Minetest game engine, in order to allow 5 | the development of Capture the Flag / City vs City games. Good for any 6 | sort of game where players can join teams - flags are optional, everything 7 | is highly configurable. 8 | 9 | Licenses 10 | ======== 11 | 12 | Created by: [rubenwardy](http://rubenwardy.com/). 13 | Copyright (c) 2013 - 2015 14 | **Code:** LGPL 2.1 or later. 15 | **Textures:** CC-BY-SA 3.0 16 | 17 | ctf_flag/sounds/trumpet* by tobyk, license: CC-BY 3.0 18 | from: http://freesound.org/people/tobyk/sounds/26198/ 19 | 20 | Documentation 21 | ============= 22 | 23 | See the doc_* files, starting with doc_project_overview.md 24 | -------------------------------------------------------------------------------- /ctf/core.lua: -------------------------------------------------------------------------------- 1 | -- Awaiting core support. 2 | local function __genOrderedIndex( t ) 3 | local orderedIndex = {} 4 | for key in pairs(t) do 5 | table.insert( orderedIndex, key ) 6 | end 7 | table.sort( orderedIndex ) 8 | return orderedIndex 9 | end 10 | 11 | local function orderedNext(t, state) 12 | -- Equivalent of the next function, but returns the keys in the alphabetic 13 | -- order. We use a temporary ordered key table that is stored in the 14 | -- table being iterated. 15 | 16 | local key = nil 17 | if state == nil then 18 | t.__orderedIndex = __genOrderedIndex( t ) 19 | key = t.__orderedIndex[1] 20 | else 21 | for i = 1,table.getn(t.__orderedIndex) do 22 | if t.__orderedIndex[i] == state then 23 | key = t.__orderedIndex[i+1] 24 | end 25 | end 26 | end 27 | 28 | if key then 29 | return key, t[key] 30 | end 31 | 32 | -- no more value to return, cleanup 33 | t.__orderedIndex = nil 34 | return 35 | end 36 | 37 | function orderedPairs(t) 38 | -- Equivalent of the pairs() function on tables. Allows to iterate 39 | -- in order 40 | return orderedNext, t, nil 41 | end 42 | 43 | 44 | 45 | -- Registered 46 | ctf.registered_on_load = {} 47 | function ctf.register_on_load(func) 48 | if ctf._mt_loaded then 49 | error("You can't register callbacks at game time!") 50 | end 51 | table.insert(ctf.registered_on_load, func) 52 | if ctf._loaddata then 53 | func(ctf._loaddata) 54 | end 55 | end 56 | ctf.registered_on_save = {} 57 | function ctf.register_on_save(func) 58 | if ctf._mt_loaded then 59 | error("You can't register callbacks at game time!") 60 | end 61 | table.insert(ctf.registered_on_save, func) 62 | end 63 | ctf.registered_on_init = {} 64 | function ctf.register_on_init(func) 65 | if ctf._mt_loaded then 66 | error("You can't register callbacks at game time!") 67 | end 68 | table.insert(ctf.registered_on_init, func) 69 | if ctf._inited then 70 | func() 71 | end 72 | end 73 | ctf.registered_on_new_team = {} 74 | function ctf.register_on_new_team(func) 75 | if ctf._mt_loaded then 76 | error("You can't register callbacks at game time!") 77 | end 78 | table.insert(ctf.registered_on_new_team, func) 79 | end 80 | ctf.registered_on_territory_query = {} 81 | function ctf.register_on_territory_query(func) 82 | if ctf._mt_loaded then 83 | error("You can't register callbacks at game time!") 84 | end 85 | table.insert(ctf.registered_on_territory_query, func) 86 | end 87 | ctf.registered_on_new_game = {} 88 | function ctf.register_on_new_game(func) 89 | if ctf._mt_loaded then 90 | error("You can't register callbacks at game time!") 91 | end 92 | table.insert(ctf.registered_on_new_game, func) 93 | if ctf._new_game then 94 | func() 95 | end 96 | end 97 | 98 | function vector.distanceSQ(p1, p2) 99 | local x = p1.x - p2.x 100 | local y = p1.y - p2.y 101 | local z = p1.z - p2.z 102 | return x*x + y*y + z*z 103 | end 104 | 105 | 106 | 107 | -- Debug helpers 108 | function ctf.error(area, msg) 109 | minetest.log("error", "CTF::" .. area .. " - " ..msg) 110 | end 111 | function ctf.log(area, msg) 112 | if area and area ~= "" then 113 | print("[CaptureTheFlag] (" .. area .. ") " .. msg) 114 | else 115 | print("[CaptureTheFlag] " .. msg) 116 | end 117 | end 118 | function ctf.action(area, msg) 119 | if area and area ~= "" then 120 | minetest.log("action", "[CaptureTheFlag] (" .. area .. ") " .. msg) 121 | else 122 | minetest.log("action", "[CaptureTheFlag] " .. msg) 123 | end 124 | end 125 | function ctf.warning(area, msg) 126 | print("WARNING: [CaptureTheFlag] (" .. area .. ") " .. msg) 127 | end 128 | 129 | function ctf.init() 130 | ctf._inited = true 131 | ctf.log("init", "Initialising!") 132 | 133 | -- Set up structures 134 | ctf._defsettings = {} 135 | ctf.teams = {} 136 | ctf.players = {} 137 | 138 | -- See minetest.conf.example in the root of this subgame 139 | 140 | ctf.log("init", "Creating Default Settings") 141 | ctf._set("diplomacy", true) 142 | ctf._set("players_can_change_team", true) 143 | ctf._set("allocate_mode", 0) 144 | ctf._set("maximum_in_team", -1) 145 | ctf._set("default_diplo_state", "war") 146 | ctf._set("hud", true) 147 | ctf._set("autoalloc_on_joinplayer", true) 148 | ctf._set("friendly_fire", true) 149 | ctf._set("spawn_offset", "0,0,0") 150 | 151 | 152 | for i = 1, #ctf.registered_on_init do 153 | ctf.registered_on_init[i]() 154 | end 155 | 156 | ctf.load() 157 | end 158 | 159 | function ctf.reset() 160 | ctf.log("io", "Deleting CTF save data...") 161 | os.remove(minetest.get_worldpath().."/ctf.txt") 162 | ctf.player_last_team = {} 163 | ctf.init() 164 | end 165 | 166 | -- Set default setting value 167 | function ctf._set(setting, default) 168 | if ctf._defsettings[setting] then 169 | ctf.warning("settings", "Setting " .. dump(setting) .. " redeclared!") 170 | ctf.warning("settings", debug.traceback()) 171 | end 172 | ctf._defsettings[setting] = default 173 | 174 | if minetest.settings:get("ctf."..setting) then 175 | ctf.log("settings", "- " .. setting .. ": " .. minetest.settings:get("ctf."..setting)) 176 | elseif minetest.settings:get("ctf_"..setting) then 177 | ctf.log("settings", "- " .. setting .. ": " .. minetest.settings:get("ctf_"..setting)) 178 | ctf.warning("settings", "deprecated setting ctf_"..setting.. 179 | " used, use ctf."..setting.." instead.") 180 | end 181 | end 182 | 183 | function ctf.setting(name) 184 | local set = minetest.settings:get("ctf."..name) or 185 | minetest.settings:get("ctf_"..name) 186 | local dset = ctf._defsettings[name] 187 | if dset == nil then 188 | ctf.error("setting", "No such setting - " .. name) 189 | return nil 190 | end 191 | 192 | if set ~= nil then 193 | if type(dset) == "number" then 194 | return tonumber(set) 195 | elseif type(dset) == "boolean" then 196 | return minetest.is_yes(set) 197 | else 198 | return set 199 | end 200 | else 201 | return dset 202 | end 203 | end 204 | 205 | function ctf.load() 206 | ctf.log("io", "Loading CTF state") 207 | local file = io.open(minetest.get_worldpath().."/ctf.txt", "r") 208 | if file then 209 | local table = minetest.deserialize(file:read("*all")) 210 | if type(table) == "table" then 211 | ctf.teams = table.teams 212 | ctf.players = table.players 213 | 214 | for i = 1, #ctf.registered_on_load do 215 | ctf.registered_on_load[i](table) 216 | end 217 | return 218 | end 219 | ctf._loaddata = table 220 | else 221 | ctf.log("io", "ctf.txt is not present in the world folder") 222 | ctf._new_game = true 223 | for i = 1, #ctf.registered_on_new_game do 224 | ctf.registered_on_new_game[i]() 225 | end 226 | end 227 | end 228 | 229 | minetest.after(0, function() 230 | ctf._loaddata = nil 231 | ctf._mt_loaded = true 232 | end) 233 | 234 | function ctf.check_save() 235 | if ctf_flag and ctf_flag.assert_flags then 236 | ctf_flag.assert_flags() 237 | end 238 | if ctf.needs_save then 239 | ctf.save() 240 | end 241 | minetest.after(10, ctf.check_save) 242 | end 243 | minetest.after(10, ctf.check_save) 244 | 245 | function ctf.save() 246 | local file = io.open(minetest.get_worldpath().."/ctf.txt", "w") 247 | if file then 248 | local out = { 249 | teams = ctf.teams, 250 | players = ctf.players 251 | } 252 | 253 | for i = 1, #ctf.registered_on_save do 254 | local res = ctf.registered_on_save[i]() 255 | 256 | if res then 257 | for key, value in pairs(res) do 258 | out[key] = value 259 | end 260 | end 261 | end 262 | 263 | file:write(minetest.serialize(out)) 264 | file:close() 265 | ctf.needs_save = false 266 | else 267 | ctf.error("io", "CTF file failed to save!") 268 | end 269 | end 270 | -------------------------------------------------------------------------------- /ctf/diplomacy.lua: -------------------------------------------------------------------------------- 1 | -- diplo states: war, peace, alliance 2 | ctf.diplo = { 3 | diplo = {} 4 | } 5 | 6 | ctf.register_on_load(function(table) 7 | ctf.diplo.diplo = table.diplo 8 | end) 9 | 10 | ctf.register_on_save(function() 11 | return { diplo = ctf.diplo.diplo } 12 | end) 13 | 14 | function ctf.diplo.get(one,two) 15 | if not ctf.diplo.diplo then 16 | return ctf.setting("default_diplo_state") 17 | end 18 | 19 | for i = 1, #ctf.diplo.diplo do 20 | local dip = ctf.diplo.diplo[i] 21 | if (dip.one == one and dip.two == two) or 22 | (dip.one == two and dip.two == one) then 23 | return dip.state 24 | end 25 | end 26 | 27 | return ctf.setting("default_diplo_state") 28 | end 29 | 30 | function ctf.diplo.set(one, two, state) 31 | if ctf.diplo.diplo then 32 | -- Check the table for an existing diplo state 33 | for i = 1, #ctf.diplo.diplo do 34 | local dip = ctf.diplo.diplo[i] 35 | if (dip.one == one and dip.two == two) or 36 | (dip.one == two and dip.two == one) then 37 | dip.state = state 38 | return 39 | end 40 | end 41 | else 42 | ctf.diplo.diplo = {} 43 | end 44 | 45 | table.insert(ctf.diplo.diplo,{one=one,two=two,state=state}) 46 | end 47 | 48 | function ctf.diplo.check_requests(one, two) 49 | local team = ctf.team(two) 50 | 51 | if not team.log then 52 | return nil 53 | end 54 | 55 | for i=1,#team.log do 56 | if team.log[i].team == one and 57 | team.log[i].type == "request" and 58 | team.log[i].mode == "diplo" then 59 | return team.log[i].msg 60 | end 61 | end 62 | 63 | return nil 64 | end 65 | 66 | function ctf.diplo.cancel_requests(one, two) 67 | local team = ctf.team(two) 68 | 69 | if not team.log then 70 | return 71 | end 72 | 73 | for i=1,#team.log do 74 | if team.log[i].team == one and 75 | team.log[i].type == "request" and 76 | team.log[i].mode == "diplo" then 77 | table.remove(team.log,i) 78 | return 79 | end 80 | end 81 | 82 | return 83 | end 84 | -------------------------------------------------------------------------------- /ctf/gui.lua: -------------------------------------------------------------------------------- 1 | ctf.gui = { 2 | tabs = {} 3 | } 4 | 5 | ctf.register_on_init(function() 6 | ctf._set("gui", true) 7 | ctf._set("gui.team", true) 8 | ctf._set("gui.team.initial", "news") 9 | 10 | for name, tab in pairs(ctf.gui.tabs) do 11 | ctf._set("gui.tab." .. name, true) 12 | end 13 | end) 14 | 15 | function ctf.gui.register_tab(name, title, func) 16 | ctf.gui.tabs[name] = { 17 | name = name, 18 | title = title, 19 | func = func 20 | } 21 | 22 | if ctf._defsettings and ctf._defsettings["gui.tab." .. name] == nil then 23 | ctf._set("gui.tab." .. name, true) 24 | end 25 | end 26 | 27 | function ctf.gui.show(name, tab, tname) 28 | if not tab then 29 | tab = ctf.setting("gui.team.initial") or "news" 30 | end 31 | 32 | if not tab or not ctf.gui.tabs[tab] or not name or name == "" then 33 | ctf.log("gui", "Invalid tab or name given to ctf.gui.show") 34 | return 35 | end 36 | 37 | if not ctf.setting("gui.team") or not ctf.setting("gui") then 38 | return 39 | end 40 | 41 | if not ctf.team(tname) then 42 | tname = ctf.player(name).team 43 | end 44 | 45 | if ctf.team(tname) then 46 | ctf.action("gui", name .. " views " .. tname .. "'s " .. tab .. " page") 47 | ctf.gui.tabs[tab].func(name, tname) 48 | else 49 | ctf.log("gui", "Invalid team given to ctf.gui.show") 50 | end 51 | end 52 | 53 | -- Get tab buttons 54 | function ctf.gui.get_tabs(name, tname) 55 | local result = "" 56 | local id = 1 57 | local function addtab(name,text) 58 | result = result .. "button[" .. (id*2-1) .. ",0;2,1;" .. name .. ";" .. text .. "]" 59 | id = id + 1 60 | end 61 | 62 | for name, tab in pairs(ctf.gui.tabs) do 63 | if ctf.setting("gui.tab." .. name) then 64 | addtab(name, tab.title) 65 | end 66 | end 67 | 68 | return result 69 | end 70 | 71 | -- Team interface 72 | ctf.gui.register_tab("news", "News", function(name, tname) 73 | local result = "" 74 | local team = ctf.team(tname).log 75 | 76 | if not team then 77 | team = {} 78 | end 79 | 80 | local amount = 0 81 | 82 | for i = 1, #team do 83 | if team[i].type == "request" then 84 | if ctf.can_mod(name, tname) then 85 | amount = amount + 2 86 | local height = (amount*0.5) + 0.5 87 | amount = amount + 1 88 | 89 | if team[i].mode == "diplo" then 90 | result = result .. "background[0.5," .. height .. ";8.3,1;diplo_" .. team[i].msg .. ".png]" 91 | if team[i].msg == "alliance" then 92 | result = result .. "label[1," .. height .. ";" .. 93 | team[i].team .. " offers an " .. 94 | minetest.formspec_escape(team[i].msg) .. " treaty]" 95 | else 96 | result = result .. "label[1," .. height .. ";" .. 97 | team[i].team .. " offers a " .. 98 | minetest.formspec_escape(team[i].msg) .. " treaty]" 99 | end 100 | result = result .. "button[6," .. height .. ";1,1;btn_y" .. i .. ";Yes]" 101 | result = result .. "button[7," .. height .. ";1,1;btn_n" .. i .. ";No]" 102 | else 103 | result = result .. "label[0.5," .. height .. ";RANDOM REQUEST TYPE]" 104 | end 105 | end 106 | else 107 | amount = amount + 1 108 | local height = (amount*0.5) + 0.5 109 | 110 | if height > 5 then 111 | break 112 | end 113 | 114 | result = result .. "label[0.5," .. height .. ";" .. 115 | minetest.formspec_escape(team[i].msg) .. "]" 116 | end 117 | end 118 | 119 | if ctf.can_mod(name, tname) then 120 | result = result .. "button[4,6;2,1;clear;Clear all]" 121 | end 122 | 123 | if amount == 0 then 124 | result = "label[0.5,1;Welcome to the news panel]" .. 125 | "label[0.5,1.5;News such as attacks will appear here]" 126 | end 127 | 128 | minetest.show_formspec(name, "ctf:news", 129 | "size[10,7]" .. 130 | ctf.gui.get_tabs(name, tname) .. 131 | result) 132 | end) 133 | 134 | -- Team interface 135 | ctf.gui.register_tab("diplo", "Diplomacy", function(name, tname) 136 | local result = "" 137 | local data = {} 138 | 139 | local amount = 0 140 | 141 | for key, value in pairs(ctf.teams) do 142 | if key ~= tname then 143 | table.insert(data,{ 144 | team = key, 145 | state = ctf.diplo.get(tname, key), 146 | to = ctf.diplo.check_requests(tname, key), 147 | from = ctf.diplo.check_requests(key, tname) 148 | }) 149 | end 150 | end 151 | 152 | result = result .. "label[1,1;Diplomacy from the perspective of " .. tname .. "]" 153 | 154 | for i = 1, #data do 155 | amount = i 156 | local height = (i*1)+0.5 157 | 158 | if height > 5 then 159 | break 160 | end 161 | 162 | result = result .. "background[1," .. height .. ";8.2,1;diplo_" .. 163 | data[i].state .. ".png]" 164 | result = result .. "button[1.25," .. height .. ";2,1;team_" .. 165 | data[i].team .. ";" .. data[i].team .. "]" 166 | result = result .. "label[3.75," .. height .. ";" .. data[i].state 167 | .. "]" 168 | 169 | if ctf.can_mod(name, tname) and ctf.player(name).team == tname then 170 | if not data[i].from and not data[i].to then 171 | if data[i].state == "war" then 172 | result = result .. "button[7.5," .. height .. 173 | ";1.5,1;peace_" .. data[i].team .. ";Peace]" 174 | elseif data[i].state == "peace" then 175 | result = result .. "button[6," .. height .. 176 | ";1.5,1;war_" .. data[i].team .. ";War]" 177 | result = result .. "button[7.5," .. height .. 178 | ";1.5,1;alli_" .. data[i].team .. ";Alliance]" 179 | elseif data[i].state == "alliance" then 180 | result = result .. "button[6," .. height .. 181 | ";1.5,1;peace_" .. data[i].team .. ";Peace]" 182 | end 183 | elseif data[i].from ~= nil then 184 | result = result .. "label[6," .. height .. 185 | ";request recieved]" 186 | elseif data[i].to ~= nil then 187 | result = result .. "label[5.5," .. height .. 188 | ";request sent]" 189 | result = result .. "button[7.5," .. height .. 190 | ";1.5,1;cancel_" .. data[i].team .. ";Cancel]" 191 | end 192 | end 193 | end 194 | 195 | minetest.show_formspec(name, "ctf:diplo", 196 | "size[10,7]" .. 197 | ctf.gui.get_tabs(name, tname) .. 198 | result 199 | ) 200 | end) 201 | 202 | local function formspec_is_ctf_tab(fsname) 203 | for name, tab in pairs(ctf.gui.tabs) do 204 | if fsname == "ctf:" .. name then 205 | return true 206 | end 207 | end 208 | return false 209 | end 210 | 211 | minetest.register_on_player_receive_fields(function(player, formname, fields) 212 | if not formspec_is_ctf_tab(formname) then 213 | return false 214 | end 215 | 216 | local name = player:get_player_name() 217 | local tplayer = ctf.player(name) 218 | local tname = tplayer.team 219 | local team = ctf.team(tname) 220 | 221 | if not team then 222 | return false 223 | end 224 | 225 | -- Do navigation 226 | for tabname, tab in pairs(ctf.gui.tabs) do 227 | if fields[tabname] then 228 | ctf.gui.show(name, tabname) 229 | return true 230 | end 231 | end 232 | 233 | -- Todo: move callbacks 234 | -- News page 235 | if fields.clear then 236 | team.log = {} 237 | ctf.needs_save = true 238 | ctf.gui.show(name, "news") 239 | return true 240 | end 241 | end) 242 | 243 | minetest.register_on_player_receive_fields(function(player, formname, fields) 244 | local name = player:get_player_name() 245 | local tplayer = ctf.player(name) 246 | local tname = tplayer.team 247 | local team = ctf.team(tname) 248 | 249 | if not team then 250 | return false 251 | end 252 | 253 | if formname == "ctf:news" then 254 | for key, field in pairs(fields) do 255 | local ok, id = string.match(key, "btn_([yn])([0123456789]+)") 256 | if ok and id then 257 | if ok == "y" then 258 | ctf.diplo.set(tname, team.log[tonumber(id)].team, team.log[tonumber(id)].msg) 259 | 260 | -- Post to acceptor's log 261 | ctf.post(tname, { 262 | msg = "You have accepted the " .. 263 | team.log[tonumber(id)].msg .. " request from " .. 264 | team.log[tonumber(id)].team }) 265 | 266 | -- Post to request's log 267 | ctf.post(team.log[tonumber(id)].team, { 268 | msg = tname .. " has accepted your " .. 269 | team.log[tonumber(id)].msg .. " request" }) 270 | 271 | id = id + 1 272 | end 273 | 274 | table.remove(team.log, id) 275 | ctf.needs_save = true 276 | ctf.gui.show(name, "news") 277 | return true 278 | end 279 | end 280 | end 281 | end) 282 | 283 | minetest.register_on_player_receive_fields(function(player, formname, fields) 284 | local name = player:get_player_name() 285 | local tplayer = ctf.player(name) 286 | local tname = tplayer.team 287 | local team = ctf.team(tname) 288 | 289 | if not team or formname ~= "ctf:diplo" then 290 | return false 291 | end 292 | 293 | for key, field in pairs(fields) do 294 | local tname2 = string.match(key, "team_(.+)") 295 | if tname2 and ctf.team(tname2) then 296 | ctf.gui.show(name, "diplo", tname2) 297 | return true 298 | end 299 | 300 | if ctf.can_mod(name, tname) then 301 | tname2 = string.match(key, "peace_(.+)") 302 | if tname2 then 303 | if ctf.diplo.get(tname, tname2) == "war" then 304 | ctf.post(tname2, { 305 | type = "request", 306 | msg = "peace", 307 | team = tname, 308 | mode = "diplo" }) 309 | else 310 | ctf.diplo.set(tname, tname2, "peace") 311 | ctf.post(tname, { 312 | msg = "You have cancelled the alliance treaty with " .. tname2 }) 313 | ctf.post(tname2, { 314 | msg = tname .. " has cancelled the alliance treaty" }) 315 | end 316 | 317 | ctf.gui.show(name, "diplo") 318 | return true 319 | end 320 | 321 | tname2 = string.match(key, "war_(.+)") 322 | if tname2 then 323 | ctf.diplo.set(tname, tname2, "war") 324 | ctf.post(tname, { 325 | msg = "You have declared war on " .. tname2 }) 326 | ctf.post(tname2, { 327 | msg = tname .. " has declared war on you" }) 328 | 329 | ctf.gui.show(name, "diplo") 330 | return true 331 | end 332 | 333 | tname2 = string.match(key, "alli_(.+)") 334 | if tname2 then 335 | ctf.post(tname2, { 336 | type = "request", 337 | msg = "alliance", 338 | team = tname, 339 | mode = "diplo" }) 340 | 341 | ctf.gui.show(name, "diplo") 342 | return true 343 | end 344 | 345 | tname2 = string.match(key, "cancel_(.+)") 346 | if tname2 then 347 | ctf.diplo.cancel_requests(tname, tname2) 348 | ctf.gui.show(name, "diplo") 349 | return true 350 | end 351 | end -- end if can mod 352 | end -- end for each field 353 | end) 354 | -------------------------------------------------------------------------------- /ctf/hud.lua: -------------------------------------------------------------------------------- 1 | ctf.hud = hudkit() 2 | ctf.hud.parts = {} 3 | function ctf.hud.register_part(func) 4 | table.insert(ctf.hud.parts, func) 5 | end 6 | 7 | minetest.register_on_leaveplayer(function(player) 8 | ctf.hud.players[player:get_player_name()] = nil 9 | end) 10 | 11 | ctf.register_on_join_team(function(name, tname) 12 | if ctf.setting("hud") then 13 | ctf.hud.update(minetest.get_player_by_name(name)) 14 | end 15 | end) 16 | 17 | function ctf.hud.update(player) 18 | if not player then 19 | return 20 | end 21 | 22 | local name = player:get_player_name() 23 | local tplayer = ctf.player(name) 24 | 25 | if not tplayer or not tplayer.team or not ctf.team(tplayer.team) then 26 | return 27 | end 28 | 29 | -- Team Identifier 30 | for i = 1, #ctf.hud.parts do 31 | ctf.hud.parts[i](player, name, tplayer) 32 | end 33 | end 34 | 35 | function ctf.hud.updateAll() 36 | if not ctf.setting("hud") then 37 | return 38 | end 39 | 40 | local players = minetest.get_connected_players() 41 | for i = 1, #players do 42 | ctf.hud.update(players[i]) 43 | end 44 | end 45 | 46 | local function tick() 47 | ctf.hud.updateAll() 48 | minetest.after(10, tick) 49 | end 50 | minetest.after(1, tick) 51 | -------------------------------------------------------------------------------- /ctf/init.lua: -------------------------------------------------------------------------------- 1 | -- CAPTURE THE FLAG 2 | -- by Andrew "rubenwardy" Ward 3 | ----------------------------------------- 4 | 5 | ctf = {} 6 | 7 | -- Fix for https://github.com/minetest/minetest/issues/2383 8 | local csa = minetest.chat_send_all 9 | function minetest.chat_send_all(msg) 10 | minetest.after(0, function() 11 | csa(msg) 12 | end) 13 | end 14 | 15 | -- Privs 16 | minetest.register_privilege("ctf_team_mgr", { 17 | description = "Team manager", 18 | }) 19 | 20 | minetest.register_privilege("ctf_admin", { 21 | description = "Can create teams, manage players, assign team owners.", 22 | }) 23 | 24 | -- Modules 25 | dofile(minetest.get_modpath("ctf") .. "/core.lua") 26 | dofile(minetest.get_modpath("ctf") .. "/teams.lua") 27 | dofile(minetest.get_modpath("ctf") .. "/diplomacy.lua") 28 | dofile(minetest.get_modpath("ctf") .. "/gui.lua") 29 | dofile(minetest.get_modpath("ctf") .. "/hud.lua") 30 | 31 | -- Init 32 | ctf.init() 33 | ctf.clean_player_lists() 34 | -------------------------------------------------------------------------------- /ctf/mod.conf: -------------------------------------------------------------------------------- 1 | name = ctf 2 | depends = hudkit 3 | optional_depends = chatplus 4 | -------------------------------------------------------------------------------- /ctf/teams.lua: -------------------------------------------------------------------------------- 1 | -- Get or add a team 2 | function ctf.team(name) 3 | if name == nil then 4 | return nil 5 | end 6 | if type(name) == "table" then 7 | if not name.add_team then 8 | ctf.error("team", "Invalid table given to ctf.team") 9 | return 10 | end 11 | 12 | return ctf.create_team(name.name, name) 13 | else 14 | local team = ctf.teams[name] 15 | if team then 16 | if not team.data or not team.players then 17 | ctf.warning("team", "Assertion failed, data{} or players{} not " .. 18 | "found in team{}") 19 | end 20 | return team 21 | else 22 | if name and name:trim() ~= "" then 23 | ctf.warning("team", dump(name) .. " does not exist!") 24 | end 25 | return nil 26 | end 27 | end 28 | end 29 | 30 | function ctf.create_team(name, data) 31 | ctf.log("team", "Creating team " .. name) 32 | 33 | ctf.teams[name] = { 34 | data = data, 35 | spawn = nil, 36 | players = {} 37 | } 38 | 39 | for i = 1, #ctf.registered_on_new_team do 40 | ctf.registered_on_new_team[i](ctf.teams[name]) 41 | end 42 | 43 | ctf.needs_save = true 44 | 45 | return ctf.teams[name] 46 | end 47 | 48 | function ctf.remove_team(name) 49 | local team = ctf.team(name) 50 | if team then 51 | for username, player in pairs(team.players) do 52 | player.team = nil 53 | end 54 | for i = 1, #team.flags do 55 | team.flags[i].team = nil 56 | end 57 | ctf.teams[name] = nil 58 | ctf.needs_save = true 59 | return true 60 | else 61 | return false 62 | end 63 | end 64 | 65 | function ctf.list_teams(name) 66 | minetest.chat_send_player(name, "Teams:") 67 | for tname, team in pairs(ctf.teams) do 68 | if team and team.players then 69 | local details = "" 70 | 71 | local numPlayers = ctf.count_players_in_team(tname) 72 | details = numPlayers .. " members" 73 | 74 | if team.flags then 75 | local numFlags = 0 76 | for flagid, flag in pairs(team.flags) do 77 | numFlags = numFlags + 1 78 | end 79 | details = details .. ", " .. numFlags .. " flags" 80 | end 81 | 82 | minetest.chat_send_player(name, ">> " .. tname .. 83 | " (" .. details .. ")") 84 | end 85 | end 86 | end 87 | 88 | -- Count number of players in a team 89 | function ctf.count_players_in_team(team) 90 | local count = 0 91 | for name, player in pairs(ctf.team(team).players) do 92 | count = count + 1 93 | end 94 | return count 95 | end 96 | 97 | function ctf.new_player(name) 98 | if name then 99 | ctf.players[name] = { 100 | name = name 101 | } 102 | ctf.needs_save = true 103 | else 104 | ctf.error("team", "Can't create a blank player") 105 | ctf.log("team", debug.traceback()) 106 | end 107 | end 108 | 109 | -- get a player 110 | function ctf.player(name) 111 | if not ctf.players[name] then 112 | ctf.new_player(name) 113 | end 114 | return ctf.players[name] 115 | end 116 | 117 | function ctf.player_or_nil(name) 118 | return ctf.players[name] 119 | end 120 | 121 | function ctf.chat_send_team(team, msg) 122 | if type(team) == "string" then 123 | team = ctf.team(team) 124 | end 125 | 126 | for pname, _ in pairs(team.players) do 127 | minetest.chat_send_player(pname, msg) 128 | end 129 | end 130 | 131 | function ctf.remove_player(name) 132 | ctf.log("team", "Removing player ".. dump(name)) 133 | local player = ctf.players[name] 134 | if player then 135 | local team = ctf.team(player.team) 136 | if team then 137 | team.players[name] = nil 138 | end 139 | ctf.players[name] = nil 140 | ctf.needs_save = true 141 | return true 142 | else 143 | return false 144 | end 145 | end 146 | 147 | ctf.registered_on_join_team = {} 148 | function ctf.register_on_join_team(func) 149 | if ctf._mt_loaded then 150 | error("You can't register callbacks at game time!") 151 | end 152 | table.insert(ctf.registered_on_join_team, func) 153 | end 154 | 155 | ctf.player_last_team = {} 156 | 157 | -- Player joins team 158 | -- Called by /join, /team join or auto allocate. 159 | function ctf.join(name, team, force, by) 160 | if not name or name == "" or not team or team == "" then 161 | ctf.log("team", "Missing parameters to ctf.join") 162 | return false 163 | end 164 | 165 | local player = ctf.player(name) 166 | 167 | if not force and not ctf.setting("players_can_change_team") 168 | and player.team and ctf.team(player.team) then 169 | if by then 170 | if by == name then 171 | ctf.action("teams", name .. " attempted to change to " .. team) 172 | minetest.chat_send_player(by, "You are not allowed to switch teams, traitor!") 173 | else 174 | ctf.action("teams", by .. " attempted to change " .. name .. " to " .. team) 175 | minetest.chat_send_player(by, "Failed to add " .. name .. " to " .. team .. 176 | " as players_can_change_team = false") 177 | end 178 | else 179 | ctf.log("teams", "failed to add " .. name .. " to " .. team .. 180 | " as players_can_change_team = false") 181 | end 182 | return false 183 | end 184 | 185 | local team_data = ctf.team(team) 186 | if not team_data then 187 | if by then 188 | minetest.chat_send_player(by, "No such team.") 189 | ctf.list_teams(by) 190 | if by == name then 191 | minetest.log("action", by .. " tried to move " .. name .. " to " .. team .. ", which doesn't exist") 192 | else 193 | minetest.log("action", name .. " attempted to join " .. team .. ", which doesn't exist") 194 | end 195 | else 196 | ctf.log("teams", "failed to add " .. name .. " to " .. team .. 197 | " as team does not exist") 198 | end 199 | return false 200 | end 201 | 202 | if player.team then 203 | local oldteam = ctf.team(player.team) 204 | if oldteam then 205 | oldteam.players[player.name] = nil 206 | end 207 | end 208 | 209 | player.team = team 210 | team_data.players[player.name] = player 211 | ctf.player_last_team[name] = team 212 | 213 | ctf.needs_save = true 214 | 215 | minetest.log("action", name .. " joined team " .. team) 216 | minetest.chat_send_all(name.." has joined team "..team) 217 | 218 | for i = 1, #ctf.registered_on_join_team do 219 | ctf.registered_on_join_team[i](name, team) 220 | end 221 | return true 222 | end 223 | 224 | -- Cleans up the player lists 225 | function ctf.clean_player_lists() 226 | ctf.log("utils", "Cleaning player lists") 227 | for _, str in pairs(ctf.players) do 228 | if str and str.team and ctf.teams[str.team] then 229 | ctf.log("utils", " - Adding player "..str.name.." to team "..str.team) 230 | ctf.teams[str.team].players[str.name] = str 231 | else 232 | ctf.log("utils", " - Skipping player "..str.name) 233 | end 234 | end 235 | 236 | ctf.needs_save = true 237 | end 238 | 239 | -- Sees if the player can change stuff in a team 240 | function ctf.can_mod(player,team) 241 | local privs = minetest.get_player_privs(player) 242 | 243 | if privs then 244 | if privs.ctf_admin == true then 245 | return true 246 | end 247 | end 248 | 249 | if player and ctf.teams[team] and ctf.teams[team].players and ctf.teams[team].players[player] then 250 | if ctf.teams[team].players[player].auth == true then 251 | return true 252 | end 253 | end 254 | return false 255 | end 256 | 257 | -- post a message to a team board 258 | function ctf.post(team, msg) 259 | if not ctf.team(team) then 260 | return false 261 | end 262 | 263 | if not ctf.team(team).log then 264 | ctf.team(team).log = {} 265 | end 266 | 267 | 268 | ctf.log("team", "message posted to team board") 269 | 270 | table.insert(ctf.team(team).log, 1, msg) 271 | ctf.needs_save = true 272 | 273 | return true 274 | end 275 | 276 | -- Automatic Allocation 277 | function ctf.autoalloc(name, alloc_mode) 278 | alloc_mode = alloc_mode or ctf.setting("allocate_mode") 279 | if alloc_mode == 0 then 280 | return 281 | end 282 | local last_team = ctf.player_last_team[name] 283 | if last_team then 284 | return last_team 285 | end 286 | 287 | local max_players = ctf.setting("maximum_in_team") 288 | 289 | local mtot = false -- more than one team 290 | for key, team in pairs(ctf.teams) do 291 | mtot = true 292 | break 293 | end 294 | if not mtot then 295 | ctf.error("autoalloc", "No teams to allocate " .. name .. " to!") 296 | return 297 | end 298 | 299 | if alloc_mode == 1 then 300 | local index = {} 301 | 302 | for key, team in pairs(ctf.teams) do 303 | if team.data.allow_joins ~= false and (max_players == -1 or 304 | ctf.count_players_in_team(key) < max_players) then 305 | table.insert(index, key) 306 | end 307 | end 308 | 309 | if #index == 0 then 310 | ctf.error("autoalloc", "No teams to join!") 311 | else 312 | return index[math.random(1, #index)] 313 | end 314 | elseif alloc_mode == 2 then 315 | local one = nil 316 | local one_count = -1 317 | local two = nil 318 | local two_count = -1 319 | for key, team in pairs(ctf.teams) do 320 | local count = ctf.count_players_in_team(key) 321 | if team.data.allow_joins ~= false and 322 | (max_players == -1 or count < max_players) then 323 | if count > one_count then 324 | two = one 325 | two_count = one_count 326 | one = key 327 | one_count = count 328 | end 329 | 330 | if count > two_count then 331 | two = key 332 | two_count = count 333 | end 334 | end 335 | end 336 | 337 | if not one and not two then 338 | ctf.error("autoalloc", "No teams to join!") 339 | elseif one and two then 340 | if math.random() > 0.5 then 341 | return one 342 | else 343 | return two 344 | end 345 | else 346 | if one then 347 | return one 348 | else 349 | return two 350 | end 351 | end 352 | elseif alloc_mode == 3 then 353 | local smallest = nil 354 | local smallest_count = 1000 355 | for key, team in pairs(ctf.teams) do 356 | local count = ctf.count_players_in_team(key) 357 | if team.data.allow_joins ~= false and 358 | (not smallest or count < smallest_count) then 359 | smallest = key 360 | smallest_count = count 361 | end 362 | end 363 | 364 | if not smallest then 365 | ctf.error("autoalloc", "No teams to join!") 366 | else 367 | return smallest 368 | end 369 | elseif alloc_mode == 4 then 370 | return ctf.custom_alloc(name) 371 | else 372 | ctf.error("autoalloc", 373 | "Unknown allocation mode: " .. alloc_mode) 374 | end 375 | end 376 | 377 | -- Custom team allocation function. Throws error 378 | -- if unimplemented, and autoalloc mode 4 is selected 379 | function ctf.custom_alloc() 380 | error("Allocation mode set to custom while " .. 381 | "ctf.custom_alloc hasn't been overridden!") 382 | end 383 | 384 | -- updates the spawn position for a team 385 | function ctf.get_spawn(team) 386 | if ctf.team(team) then 387 | local spawn = ctf.team(team).spawn 388 | if not spawn then 389 | return nil 390 | end 391 | return vector.add(spawn, minetest.string_to_pos(ctf.setting("spawn_offset"))) 392 | else 393 | return nil 394 | end 395 | end 396 | 397 | function ctf.move_to_spawn(name) 398 | local player = minetest.get_player_by_name(name) 399 | local tplayer = ctf.player(name) 400 | if ctf.team(tplayer.team) then 401 | local spawn = ctf.get_spawn(tplayer.team) 402 | if spawn then 403 | player:move_to(spawn, false) 404 | return true 405 | end 406 | end 407 | return false 408 | end 409 | 410 | minetest.register_on_respawnplayer(function(player) 411 | if not player then 412 | return false 413 | end 414 | 415 | return ctf.move_to_spawn(player:get_player_name()) 416 | end) 417 | 418 | function ctf.get_territory_owner(pos) 419 | local largest = nil 420 | local largest_weight = 0 421 | for i = 1, #ctf.registered_on_territory_query do 422 | local team, weight = ctf.registered_on_territory_query[i](pos) 423 | if team and weight then 424 | if weight == -1 then 425 | return team 426 | end 427 | if weight > largest_weight then 428 | largest = team 429 | largest_weight = weight 430 | end 431 | end 432 | end 433 | return largest 434 | end 435 | 436 | minetest.register_on_newplayer(function(player) 437 | local name = player:get_player_name() 438 | local team = ctf.autoalloc(name) 439 | if team then 440 | ctf.log("autoalloc", name .. " was allocated to " .. team) 441 | ctf.join(name, team) 442 | ctf.move_to_spawn(player:get_player_name()) 443 | end 444 | end) 445 | 446 | 447 | minetest.register_on_joinplayer(function(player) 448 | if not ctf.setting("autoalloc_on_joinplayer") then 449 | return 450 | end 451 | 452 | local name = player:get_player_name() 453 | if ctf.team(ctf.player(name).team) then 454 | return 455 | end 456 | 457 | local team = ctf.autoalloc(name) 458 | if team then 459 | ctf.log("autoalloc", name .. " was allocated to " .. team) 460 | ctf.join(name, team) 461 | ctf.move_to_spawn(player:get_player_name()) 462 | end 463 | end) 464 | 465 | -- Disable friendly fire. 466 | ctf.registered_on_killedplayer = {} 467 | function ctf.register_on_killedplayer(func) 468 | if ctf._mt_loaded then 469 | error("You can't register callbacks at game time!") 470 | end 471 | table.insert(ctf.registered_on_killedplayer, func) 472 | end 473 | local dead_players = {} 474 | minetest.register_on_respawnplayer(function(player) 475 | dead_players[player:get_player_name()] = nil 476 | end) 477 | minetest.register_on_joinplayer(function(player) 478 | dead_players[player:get_player_name()] = nil 479 | end) 480 | minetest.register_on_punchplayer(function(player, hitter, 481 | time_from_last_punch, tool_capabilities, dir, damage) 482 | if player and hitter then 483 | local pname = player:get_player_name() 484 | local hname = hitter:get_player_name() 485 | 486 | local to = ctf.player(pname) 487 | local from = ctf.player(hname) 488 | 489 | if dead_players[pname] then 490 | return 491 | end 492 | 493 | if to.team == from.team and to.team ~= "" and 494 | to.team ~= nil and to.name ~= from.name then 495 | minetest.chat_send_player(hname, pname .. " is on your team!") 496 | if not ctf.setting("friendly_fire") then 497 | return true 498 | end 499 | end 500 | 501 | local hp = player:get_hp() 502 | if hp == 0 then 503 | return false 504 | end 505 | 506 | if hp - damage <= 0 then 507 | dead_players[pname] = true 508 | local wielded = hitter:get_wielded_item() 509 | for i = 1, #ctf.registered_on_killedplayer do 510 | ctf.registered_on_killedplayer[i](pname, hname, 511 | wielded, tool_capabilities) 512 | end 513 | return false 514 | end 515 | end 516 | end) 517 | -------------------------------------------------------------------------------- /ctf/textures/diplo_alliance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MT-CTF/ctf_pvp_engine/d8ec80361b1261dd39cb6b6e325c7e9321f0b783/ctf/textures/diplo_alliance.png -------------------------------------------------------------------------------- /ctf/textures/diplo_peace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MT-CTF/ctf_pvp_engine/d8ec80361b1261dd39cb6b6e325c7e9321f0b783/ctf/textures/diplo_peace.png -------------------------------------------------------------------------------- /ctf/textures/diplo_war.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MT-CTF/ctf_pvp_engine/d8ec80361b1261dd39cb6b6e325c7e9321f0b783/ctf/textures/diplo_war.png -------------------------------------------------------------------------------- /ctf_chat/init.lua: -------------------------------------------------------------------------------- 1 | ctf.register_on_init(function() 2 | ctf.log("chat", "Initialising...") 3 | 4 | -- Settings: Chat 5 | ctf._set("chat.team_channel", true) 6 | ctf._set("chat.global_channel", true) 7 | ctf._set("chat.default", "global") 8 | end) 9 | 10 | function minetest.is_player_name_valid(name) 11 | return name:match("^[%a%d_-]+$") 12 | end 13 | 14 | local function team_console_help(name) 15 | minetest.chat_send_player(name, "Try:") 16 | minetest.chat_send_player(name, "/team - show team panel") 17 | minetest.chat_send_player(name, "/team all - list all teams") 18 | minetest.chat_send_player(name, "/team - show details about team 'name'") 19 | minetest.chat_send_player(name, "/team - get which team 'player' is in") 20 | minetest.chat_send_player(name, "/team player - get which team 'player' is in") 21 | 22 | local privs = minetest.get_player_privs(name) 23 | if privs and privs.ctf_admin == true then 24 | minetest.chat_send_player(name, "/team add - add a team called name (ctf_admin only)") 25 | minetest.chat_send_player(name, "/team remove - add a team called name (ctf_admin only)") 26 | end 27 | if privs and privs.ctf_team_mgr == true then 28 | minetest.chat_send_player(name, "/team lock - closes a team to new players (ctf_team_mgr only)") 29 | minetest.chat_send_player(name, "/team unlock - opens a team to new players (ctf_team_mgr only)") 30 | minetest.chat_send_player(name, "/team bjoin - Command is * for all players, playername for one, !playername to remove (ctf_team_mgr only)") 31 | minetest.chat_send_player(name, "/team join - add 'player' to team 'team' (ctf_team_mgr only)") 32 | minetest.chat_send_player(name, "/team removeply - add 'player' to team 'team' (ctf_team_mgr only)") 33 | end 34 | end 35 | 36 | minetest.register_chatcommand("team", { 37 | description = "Open the team console, or run team command (see /team help)", 38 | func = function(name, param) 39 | local test = string.match(param, "^player ([%a%d_-]+)") 40 | local create = string.match(param, "^add ([%a%d_-]+)") 41 | local remove = string.match(param, "^remove ([%a%d_-]+)") 42 | local lock = string.match(param, "^lock ([%a%d_-]+)") 43 | local unlock = string.match(param, "^unlock ([%a%d_-]+)") 44 | local j_name, j_tname = string.match(param, "^join ([%a%d_-]+) ([%a%d_]+)") 45 | local b_tname, b_pattern = string.match(param, "^bjoin ([%a%d_-]+) ([%a%d_-%*%! ]+)") 46 | local l_name = string.match(param, "^removeplr ([%a%d_-]+)") 47 | if create then 48 | local privs = minetest.get_player_privs(name) 49 | if privs and privs.ctf_admin then 50 | if ( 51 | string.match(create, "([%a%b_]-)") 52 | and create ~= "" 53 | and create ~= nil 54 | and ctf.team({name=create, add_team=true}) 55 | ) then 56 | return true, "Added team '"..create.."'" 57 | else 58 | return false, "Error adding team '"..create.."'" 59 | end 60 | else 61 | return false, "You are not a ctf_admin!" 62 | end 63 | elseif remove then 64 | local privs = minetest.get_player_privs(name) 65 | if privs and privs.ctf_admin then 66 | if ctf.remove_team(remove) then 67 | return true, "Removed team '" .. remove .. "'" 68 | else 69 | return false, "Error removing team '" .. remove .. "'" 70 | end 71 | else 72 | return false, "You are not a ctf_admin!" 73 | end 74 | elseif lock then 75 | local privs = minetest.get_player_privs(name) 76 | if privs and privs.ctf_team_mgr then 77 | local team = ctf.team(lock) 78 | if team then 79 | team.data.allow_joins = false 80 | return true, "Locked team to new members" 81 | else 82 | return false, "Unable to find that team!" 83 | end 84 | else 85 | return false, "You are not a ctf_team_mgr!" 86 | end 87 | elseif unlock then 88 | local privs = minetest.get_player_privs(name) 89 | if privs and privs.ctf_team_mgr then 90 | local team = ctf.team(unlock) 91 | if team then 92 | team.data.allow_joins = true 93 | return true, "Unlocked team to new members" 94 | else 95 | return false, "Unable to find that team!" 96 | end 97 | else 98 | return false, "You are not a ctf_team_mgr!" 99 | end 100 | elseif param == "all" then 101 | ctf.list_teams(name) 102 | elseif ctf.team(param) then 103 | minetest.chat_send_player(name, "Team "..param..":") 104 | local count = 0 105 | for _, value in pairs(ctf.team(param).players) do 106 | count = count + 1 107 | if value.auth then 108 | minetest.chat_send_player(name, count .. ">> " .. value.name 109 | .. " (team owner)") 110 | else 111 | minetest.chat_send_player(name, count .. ">> " .. value.name) 112 | end 113 | end 114 | elseif ctf.player_or_nil(param) or test then 115 | if not test then 116 | test = param 117 | end 118 | if ctf.player(test).team then 119 | if ctf.player(test).auth then 120 | return true, test .. 121 | " is in team " .. ctf.player(test).team.." (team owner)" 122 | else 123 | return true, test .. 124 | " is in team " .. ctf.player(test).team 125 | end 126 | else 127 | return true, test.." is not in a team" 128 | end 129 | elseif j_name and j_tname then 130 | local privs = minetest.get_player_privs(name) 131 | if privs and privs.ctf_team_mgr then 132 | if ctf.join(j_name, j_tname, true, name) then 133 | return true, "Successfully added " .. j_name .. " to " .. j_tname 134 | else 135 | return false, "Failed to add " .. j_name .. " to " .. j_tname 136 | end 137 | else 138 | return true, "You are not a ctf_team_mgr!" 139 | end 140 | elseif b_pattern and b_tname then 141 | local privs = minetest.get_player_privs(name) 142 | if privs and privs.ctf_team_mgr then 143 | local tokens = string.split(b_pattern, " ") 144 | local players = {} 145 | 146 | for _, token in pairs(tokens) do 147 | print(token) 148 | if token == "*" then 149 | for _, player in pairs(minetest.get_connected_players()) do 150 | players[player:get_player_name()] = true 151 | end 152 | elseif token:sub(1, 1) == "!" then 153 | players[token:sub(2, #token)] = nil 154 | elseif minetest.is_player_name_valid(token) then 155 | players[token] = true 156 | else 157 | return false, "Invalid token: " .. token .. "\nExpecting *, playername, or !playername." 158 | end 159 | end 160 | 161 | for pname, _ in pairs(players) do 162 | ctf.join(pname, b_tname, true, name) 163 | end 164 | return true, "Success!" 165 | else 166 | return false, "You are not a ctf_team_mgr!" 167 | end 168 | elseif l_name then 169 | local privs = minetest.get_player_privs(name) 170 | if privs and privs.ctf_team_mgr then 171 | if ctf.remove_player(l_name) then 172 | return true, "Removed player " .. l_name 173 | else 174 | return false, "Failed to remove player." 175 | end 176 | else 177 | return false, "You are not a ctf_team_mgr!" 178 | end 179 | elseif param=="help" then 180 | team_console_help(name) 181 | else 182 | if param ~= "" and param ~= nil then 183 | minetest.chat_send_player(name, "'"..param.."' is an invalid parameter to /team") 184 | team_console_help(name) 185 | end 186 | if ctf.setting("gui") then 187 | if (ctf and 188 | ctf.players and 189 | ctf.players[name] and 190 | ctf.players[name].team) then 191 | print("showing") 192 | ctf.gui.show(name) 193 | return true, "Showing the team window" 194 | else 195 | return false, "You're not part of a team!" 196 | end 197 | else 198 | return false, "GUI is disabled!" 199 | end 200 | end 201 | return false, "Nothing could be done" 202 | end 203 | }) 204 | 205 | minetest.register_chatcommand("join", { 206 | params = "team name", 207 | description = "Add to team", 208 | func = function(name, param) 209 | if ctf.join(name, param, false, name) then 210 | return true, "Joined team " .. param .. "!" 211 | else 212 | return false, "Failed to join team!" 213 | end 214 | end 215 | }) 216 | 217 | minetest.register_chatcommand("ctf_clean", { 218 | description = "Do admin cleaning stuff", 219 | privs = {ctf_admin=true}, 220 | func = function(name, param) 221 | ctf.log("chat", "Cleaning CTF...") 222 | ctf.clean_player_lists() 223 | if ctf_flag and ctf_flag.assert_flags then 224 | ctf_flag.assert_flags() 225 | end 226 | return true, "CTF cleaned!" 227 | end 228 | }) 229 | 230 | minetest.register_chatcommand("ctf_reset", { 231 | description = "Delete all CTF saved states and start again.", 232 | privs = {ctf_admin=true}, 233 | func = function(name, param) 234 | minetest.chat_send_all("The CTF core was reset by the admin. All team memberships," .. 235 | "flags, land ownerships etc have been deleted.") 236 | ctf.reset() 237 | return true, "Reset CTF core." 238 | end, 239 | }) 240 | 241 | minetest.register_chatcommand("ctf_reload", { 242 | description = "reload the ctf main frame and get settings", 243 | privs = {ctf_admin=true}, 244 | func = function(name, param) 245 | ctf.needs_save = true 246 | ctf.init() 247 | return true, "CTF core reloaded!" 248 | end 249 | }) 250 | 251 | minetest.register_chatcommand("ctf_ls", { 252 | description = "ctf: list settings", 253 | privs = {ctf_admin=true}, 254 | func = function(name, param) 255 | minetest.chat_send_player(name, "Settings:") 256 | for set, def in orderedPairs(ctf._defsettings) do 257 | minetest.chat_send_player(name, " - " .. set .. ": " .. dump(ctf.setting(set))) 258 | print("\"" .. set .. "\" " .. dump(ctf.setting(set))) 259 | end 260 | return true 261 | end 262 | }) 263 | 264 | minetest.register_chatcommand("team_owner", { 265 | params = "player name", 266 | description = "Make player team owner", 267 | privs = {ctf_admin=true}, 268 | func = function(name, param) 269 | if ctf and ctf.players and ctf.player(param) and ctf.player(param).team and ctf.team(ctf.player(param).team) then 270 | if ctf.player(param).auth == true then 271 | ctf.player(param).auth = false 272 | return true, param.." was downgraded from team admin status" 273 | else 274 | ctf.player(param).auth = true 275 | return true, param.." was upgraded to an admin of "..ctf.player(name).team 276 | end 277 | ctf.needs_save = true 278 | else 279 | return false, "Unable to do that :/ "..param.." does not exist, or is not part of a valid team." 280 | end 281 | end 282 | }) 283 | 284 | minetest.register_chatcommand("post", { 285 | params = "message", 286 | description = "Post a message on your team's message board", 287 | func = function(name, param) 288 | 289 | if ctf and ctf.players and ctf.players[name] and ctf.players[name].team and ctf.teams[ctf.players[name].team] then 290 | if not ctf.player(name).auth then 291 | minetest.chat_send_player(name, "You do not own that team") 292 | end 293 | 294 | if not ctf.teams[ctf.players[name].team].log then 295 | ctf.teams[ctf.players[name].team].log = {} 296 | end 297 | 298 | table.insert(ctf.teams[ctf.players[name].team].log,{msg=param}) 299 | 300 | minetest.chat_send_player(name, "Posted: "..param) 301 | else 302 | minetest.chat_send_player(name, "Could not post message") 303 | end 304 | end, 305 | }) 306 | 307 | minetest.register_chatcommand("all", { 308 | params = "msg", 309 | description = "Send a message on the global channel", 310 | func = function(name, param) 311 | if not ctf.setting("chat.global_channel") then 312 | minetest.chat_send_player(name, "The global channel is disabled") 313 | return 314 | end 315 | 316 | if ctf.player(name).team then 317 | local tosend = ctf.player(name).team .. 318 | " <" .. name .. "> " .. param 319 | minetest.chat_send_all(tosend) 320 | if minetest.global_exists("chatplus") then 321 | chatplus.log(tosend) 322 | end 323 | else 324 | minetest.chat_send_all("<"..name.."> "..param) 325 | end 326 | end 327 | }) 328 | 329 | minetest.register_chatcommand("t", { 330 | params = "msg", 331 | description = "Send a message on the team channel", 332 | func = function(name, param) 333 | if not ctf.setting("chat.team_channel") then 334 | minetest.chat_send_player(name, "The team channel is disabled.") 335 | return 336 | end 337 | 338 | local tname = ctf.player(name).team 339 | local team = ctf.team(tname) 340 | if team then 341 | minetest.log("action", tname .. "<" .. name .. "> ** ".. param .. " **") 342 | if minetest.global_exists("chatplus") then 343 | chatplus.log("<" .. name .. "> ** ".. param .. " **") 344 | end 345 | 346 | tcolor = ctf_colors.get_color(ctf.player(name)) 347 | for username, to in pairs(team.players) do 348 | minetest.chat_send_player(username, 349 | minetest.colorize(tcolor.css, "<" .. name .. "> ** " .. param .. " **")) 350 | end 351 | if minetest.global_exists("irc") and irc.feature_mod_channel then 352 | irc:say(irc.config.channel, tname .. "<" .. name .. "> ** " .. param .. " **", true) 353 | end 354 | else 355 | minetest.chat_send_player(name, 356 | "You're not in a team, so you have no team to talk to.") 357 | end 358 | end 359 | }) 360 | 361 | if minetest.global_exists("irc") then 362 | function irc.playerMessage(name, message) 363 | local tname = ctf.player(name).team 364 | local color = ctf_colors.get_irc_color(ctf.player(name)) 365 | local clear = "\x0F" 366 | if color then 367 | color = "\x03" .. color 368 | else 369 | color = "" 370 | clear = "" 371 | end 372 | local abrace = color .. "<" .. clear 373 | local bbrace = color .. ">" .. clear 374 | return ("%s%s%s %s"):format(abrace, name, bbrace, message) 375 | end 376 | end 377 | 378 | local handler 379 | handler = function(name, message) 380 | if ctf.player(name).team then 381 | for i = 1, #minetest.registered_on_chat_messages do 382 | local func = minetest.registered_on_chat_messages[i] 383 | if func ~= handler and func(name, message) then 384 | return true 385 | end 386 | end 387 | 388 | if not minetest.check_player_privs(name, {shout = true}) then 389 | minetest.chat_send_player(name, "-!- You don't have permission to shout.") 390 | return true 391 | end 392 | local tcolor = ctf_colors.get_color(ctf.player(name)) 393 | minetest.chat_send_all(minetest.colorize(tcolor.css, 394 | "<" .. name .. "> ") .. message) 395 | return true 396 | else 397 | return nil 398 | end 399 | end 400 | table.insert(minetest.registered_on_chat_messages, 1, handler) 401 | 402 | minetest.registered_chatcommands["me"].func = function(name, param) 403 | if ctf.player(name).team then 404 | local tcolor = ctf_colors.get_color(ctf.player(name)) 405 | name = minetest.colorize(tcolor.css, "* " .. name) 406 | else 407 | name = "* ".. name 408 | end 409 | 410 | minetest.chat_send_all(name .. " " .. param) 411 | end 412 | -------------------------------------------------------------------------------- /ctf_chat/mod.conf: -------------------------------------------------------------------------------- 1 | name = ctf_chat 2 | depends = ctf, ctf_colors 3 | optional_depends = chatplus, irc 4 | -------------------------------------------------------------------------------- /ctf_colors/gui.lua: -------------------------------------------------------------------------------- 1 | 2 | ctf.gui.register_tab("settings", "Settings", function(name, team) 3 | local color = "" 4 | if ctf.team(team).data.color then 5 | color = ctf.team(team).data.color 6 | end 7 | 8 | local result = "field[3,2;4,1;color;Team Color;" .. color .. "]" .. 9 | "button[4,6;2,1;save;Save]" 10 | 11 | 12 | if not ctf.can_mod(name,team) then 13 | result = "label[0.5,1;You do not own this team!" 14 | end 15 | 16 | minetest.show_formspec(name, "ctf:settings", 17 | "size[10,7]" .. 18 | ctf.gui.get_tabs(name, team) .. 19 | result 20 | ) 21 | end) 22 | 23 | minetest.register_on_player_receive_fields(function(player, formname, fields) 24 | if formname ~= "ctf:settings" then 25 | return false 26 | end 27 | 28 | -- Settings page 29 | if fields.save then 30 | local name = player:get_player_name() 31 | local pdata = ctf.player(name) 32 | local team = ctf.team(pdata.team) 33 | 34 | ctf.gui.show(name, "settings") 35 | if team and ctf.can_mod(name, pdata.team) then 36 | if ctf.flag_colors[fields.color] then 37 | team.data.color = fields.color 38 | ctf.needs_save = true 39 | 40 | minetest.chat_send_player(name, "Team color set to " .. fields.color) 41 | else 42 | local colors = "" 43 | for color, code in pairs(ctf.flag_colors) do 44 | if colors ~= "" then 45 | colors = colors .. ", " 46 | end 47 | colors = colors .. color 48 | end 49 | minetest.chat_send_player(name, "Color " .. fields.color .. 50 | " does not exist! Available: " .. colors) 51 | end 52 | elseif team then 53 | minetest.chat_send_player(name, "You don't have the rights to change settings.") 54 | else 55 | minetest.chat_send_player(name, "You don't appear to be in a team") 56 | end 57 | 58 | return true 59 | end 60 | end) 61 | -------------------------------------------------------------------------------- /ctf_colors/hud.lua: -------------------------------------------------------------------------------- 1 | function ctf_colors.get_color(tplayer) 2 | local team = ctf.team(tplayer.team) 3 | local tcolor_text = nil 4 | if team then 5 | tcolor_text = team.data.color 6 | end 7 | local tcolor_hex = ctf.flag_colors[tcolor_text] 8 | if not tcolor_hex then 9 | tcolor_hex = "0x000000" 10 | end 11 | 12 | local tcolor_css = "#" .. tcolor_hex:sub(3, 8) 13 | 14 | return { 15 | text = tcolor_text, 16 | hex = tcolor_hex, 17 | css = tcolor_css 18 | } 19 | end 20 | 21 | function ctf_colors.get_irc_color(tplayer) 22 | local team = ctf.team(tplayer.team) 23 | local tcolor_text = nil 24 | if team then 25 | tcolor_text = team.data.color 26 | end 27 | return ctf_colors.irc_colors[tcolor_text] 28 | end 29 | 30 | function ctf_colors.get_nametag_color(name, tplayer, tcolor_text, tcolor_hex) 31 | if ctf.setting("colors.nametag.tcolor") then 32 | return "0xFF" .. string.sub(tcolor_hex, 3) 33 | else 34 | return "0xFFFFFFFF" 35 | end 36 | end 37 | 38 | function ctf_colors.set_skin(player, color) 39 | if minetest.global_exists("armor") then 40 | -- TODO: how should support for skin mods be done? 41 | armor.textures[name].skin = "ctf_colors_skin_" .. color .. ".png" 42 | armor:update_player_visuals(player) 43 | else 44 | player:set_properties({ 45 | textures = {"ctf_colors_skin_" .. color .. ".png"} 46 | }) 47 | end 48 | end 49 | 50 | function ctf_colors.update(player, name, tplayer) 51 | if not player then 52 | player = minetest.get_player_by_name(name) 53 | end 54 | 55 | local tcolor = ctf_colors.get_color(tplayer) 56 | 57 | if ctf.setting("colors.hudtint") then 58 | if tcolor.text == "red" or tcolor.text == "blue" then 59 | player:hud_set_hotbar_image("ctf_colors_hotbar_" .. tcolor.text .. ".png") 60 | player:hud_set_hotbar_selected_image("ctf_colors_hotbar_selected_" .. tcolor.text .. ".png") 61 | else 62 | ctf.error("ctf_colors", "Hint color not supported for " .. tcolor.text) 63 | end 64 | end 65 | 66 | if ctf.setting("colors.skins") and tcolor.text then 67 | ctf_colors.set_skin(player, tcolor.text) 68 | end 69 | 70 | if ctf.setting("hud.teamname") then 71 | if not ctf.hud:exists(player, "ctf:hud_team") then 72 | ctf.hud:add(player, "ctf:hud_team", { 73 | hud_elem_type = "text", 74 | position = {x = 1, y = 0}, 75 | scale = {x = 100, y = 100}, 76 | text = "Team " .. tplayer.team, 77 | number = tcolor.hex, 78 | offset = {x = -20, y = 20}, 79 | alignment = {x = -1, y = 0} 80 | }) 81 | else 82 | ctf.hud:change(player, "ctf:hud_team", "text", "Team " .. tplayer.team) 83 | ctf.hud:change(player, "ctf:hud_team", "number", tcolor.hex) 84 | end 85 | end 86 | end 87 | 88 | ctf.hud.register_part(ctf_colors.update) 89 | -------------------------------------------------------------------------------- /ctf_colors/init.lua: -------------------------------------------------------------------------------- 1 | -- Supported colors 2 | ctf_colors = {} 3 | ctf_colors.colors = { 4 | red = "0xFF4444", 5 | cyan = "0x00FFFF", 6 | blue = "0x4466FF", 7 | purple = "0x800080", 8 | yellow = "0xFFFF00", 9 | green = "0x00FF00", 10 | pink = "0xFF00FF", 11 | silver = "0xC0C0C0", 12 | gray = "0x808080", 13 | black = "0x000000", 14 | orange = "0xFFA500", 15 | gold = "0x808000" 16 | } 17 | ctf_colors.irc_colors = { 18 | red = "4", 19 | blue = "2", 20 | } 21 | ctf.flag_colors = ctf_colors.colors 22 | 23 | ctf.register_on_init(function() 24 | ctf.log("colors", "Initialising...") 25 | ctf._set("colors.skins", false) 26 | ctf._set("colors.hudtint", true) 27 | ctf._set("hud.teamname", false) 28 | end) 29 | 30 | dofile(minetest.get_modpath("ctf_colors") .. "/hud.lua") 31 | dofile(minetest.get_modpath("ctf_colors") .. "/gui.lua") 32 | -------------------------------------------------------------------------------- /ctf_colors/mod.conf: -------------------------------------------------------------------------------- 1 | name = ctf_colors 2 | depends = ctf 3 | optional_depends = 3d_armor 4 | -------------------------------------------------------------------------------- /ctf_colors/textures/ctf_colors_hotbar_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MT-CTF/ctf_pvp_engine/d8ec80361b1261dd39cb6b6e325c7e9321f0b783/ctf_colors/textures/ctf_colors_hotbar_blue.png -------------------------------------------------------------------------------- /ctf_colors/textures/ctf_colors_hotbar_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MT-CTF/ctf_pvp_engine/d8ec80361b1261dd39cb6b6e325c7e9321f0b783/ctf_colors/textures/ctf_colors_hotbar_red.png -------------------------------------------------------------------------------- /ctf_colors/textures/ctf_colors_hotbar_selected_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MT-CTF/ctf_pvp_engine/d8ec80361b1261dd39cb6b6e325c7e9321f0b783/ctf_colors/textures/ctf_colors_hotbar_selected_blue.png -------------------------------------------------------------------------------- /ctf_colors/textures/ctf_colors_hotbar_selected_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MT-CTF/ctf_pvp_engine/d8ec80361b1261dd39cb6b6e325c7e9321f0b783/ctf_colors/textures/ctf_colors_hotbar_selected_red.png -------------------------------------------------------------------------------- /ctf_colors/textures/ctf_colors_skin_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MT-CTF/ctf_pvp_engine/d8ec80361b1261dd39cb6b6e325c7e9321f0b783/ctf_colors/textures/ctf_colors_skin_black.png -------------------------------------------------------------------------------- /ctf_colors/textures/ctf_colors_skin_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MT-CTF/ctf_pvp_engine/d8ec80361b1261dd39cb6b6e325c7e9321f0b783/ctf_colors/textures/ctf_colors_skin_blue.png -------------------------------------------------------------------------------- /ctf_colors/textures/ctf_colors_skin_cyan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MT-CTF/ctf_pvp_engine/d8ec80361b1261dd39cb6b6e325c7e9321f0b783/ctf_colors/textures/ctf_colors_skin_cyan.png -------------------------------------------------------------------------------- /ctf_colors/textures/ctf_colors_skin_gold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MT-CTF/ctf_pvp_engine/d8ec80361b1261dd39cb6b6e325c7e9321f0b783/ctf_colors/textures/ctf_colors_skin_gold.png -------------------------------------------------------------------------------- /ctf_colors/textures/ctf_colors_skin_gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MT-CTF/ctf_pvp_engine/d8ec80361b1261dd39cb6b6e325c7e9321f0b783/ctf_colors/textures/ctf_colors_skin_gray.png -------------------------------------------------------------------------------- /ctf_colors/textures/ctf_colors_skin_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MT-CTF/ctf_pvp_engine/d8ec80361b1261dd39cb6b6e325c7e9321f0b783/ctf_colors/textures/ctf_colors_skin_green.png -------------------------------------------------------------------------------- /ctf_colors/textures/ctf_colors_skin_orange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MT-CTF/ctf_pvp_engine/d8ec80361b1261dd39cb6b6e325c7e9321f0b783/ctf_colors/textures/ctf_colors_skin_orange.png -------------------------------------------------------------------------------- /ctf_colors/textures/ctf_colors_skin_pink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MT-CTF/ctf_pvp_engine/d8ec80361b1261dd39cb6b6e325c7e9321f0b783/ctf_colors/textures/ctf_colors_skin_pink.png -------------------------------------------------------------------------------- /ctf_colors/textures/ctf_colors_skin_purple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MT-CTF/ctf_pvp_engine/d8ec80361b1261dd39cb6b6e325c7e9321f0b783/ctf_colors/textures/ctf_colors_skin_purple.png -------------------------------------------------------------------------------- /ctf_colors/textures/ctf_colors_skin_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MT-CTF/ctf_pvp_engine/d8ec80361b1261dd39cb6b6e325c7e9321f0b783/ctf_colors/textures/ctf_colors_skin_red.png -------------------------------------------------------------------------------- /ctf_colors/textures/ctf_colors_skin_silver.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MT-CTF/ctf_pvp_engine/d8ec80361b1261dd39cb6b6e325c7e9321f0b783/ctf_colors/textures/ctf_colors_skin_silver.png -------------------------------------------------------------------------------- /ctf_colors/textures/ctf_colors_skin_yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MT-CTF/ctf_pvp_engine/d8ec80361b1261dd39cb6b6e325c7e9321f0b783/ctf_colors/textures/ctf_colors_skin_yellow.png -------------------------------------------------------------------------------- /ctf_flag/api.lua: -------------------------------------------------------------------------------- 1 | ctf_flag.registered_on_capture = {} 2 | function ctf_flag.register_on_capture(func) 3 | if ctf._mt_loaded then 4 | error("You can't register callbacks at game time!") 5 | end 6 | table.insert(ctf_flag.registered_on_capture, func) 7 | end 8 | 9 | ctf_flag.registered_on_pick_up = {} 10 | function ctf_flag.register_on_pick_up(func) 11 | if ctf._mt_loaded then 12 | error("You can't register callbacks at game time!") 13 | end 14 | table.insert(ctf_flag.registered_on_pick_up, func) 15 | end 16 | 17 | ctf_flag.registered_on_drop = {} 18 | function ctf_flag.register_on_drop(func) 19 | if ctf._mt_loaded then 20 | error("You can't register callbacks at game time!") 21 | end 22 | table.insert(ctf_flag.registered_on_drop, func) 23 | end 24 | 25 | ctf_flag.registered_on_precapture = {} 26 | function ctf_flag.register_on_precapture(func) 27 | if ctf._mt_loaded then 28 | error("You can't register callbacks at game time!") 29 | end 30 | table.insert(ctf_flag.registered_on_precapture, func) 31 | end 32 | 33 | ctf_flag.registered_on_prepick_up = {} 34 | function ctf_flag.register_on_prepick_up(func) 35 | if ctf._mt_loaded then 36 | error("You can't register callbacks at game time!") 37 | end 38 | table.insert(ctf_flag.registered_on_prepick_up, func) 39 | end 40 | 41 | function ctf_flag.collect_claimed() 42 | local claimed = {} 43 | for _, team in pairs(ctf.teams) do 44 | for i = 1, #team.flags do 45 | if team.flags[i].claimed then 46 | table.insert(claimed, team.flags[i]) 47 | end 48 | end 49 | end 50 | return claimed 51 | end 52 | 53 | function ctf_flag.get_claimed_by_player(name) 54 | local claimed = ctf_flag.collect_claimed() 55 | for _, flag in pairs(claimed) do 56 | if flag.claimed.player == name then 57 | return name 58 | end 59 | end 60 | end 61 | 62 | function ctf_flag.player_drop_flag(name) 63 | if not name then 64 | return 65 | end 66 | 67 | local claimed = ctf_flag.collect_claimed() 68 | for i = 1, #claimed do 69 | local flag = claimed[i] 70 | if flag.claimed.player == name then 71 | flag.claimed = nil 72 | 73 | local flag_name = "" 74 | if flag.name then 75 | flag_name = flag.name .. " " 76 | end 77 | flag_name = flag.team .. "'s " .. flag_name .. "flag" 78 | 79 | ctf.hud.updateAll() 80 | 81 | ctf.action("flag", name .. " dropped " .. flag_name) 82 | minetest.chat_send_all(flag_name.." has returned.") 83 | 84 | for i = 1, #ctf_flag.registered_on_drop do 85 | ctf_flag.registered_on_drop[i](name, flag) 86 | end 87 | end 88 | end 89 | end 90 | 91 | -- add a flag to a team 92 | function ctf_flag.add(team, pos) 93 | if not team or team == "" then 94 | return 95 | end 96 | 97 | ctf.log("flag", "Adding flag to " .. team .. " at (" .. pos.x .. 98 | ", " .. pos.y .. ", " .. pos.z .. ")") 99 | 100 | if not ctf.team(team).flags then 101 | ctf.team(team).flags = {} 102 | end 103 | 104 | pos.team = team 105 | table.insert(ctf.team(team).flags,pos) 106 | ctf.needs_save = true 107 | end 108 | 109 | function ctf_flag.update(pos) 110 | if minetest.get_node(pos).name ~= "ctf_flag:flag" then 111 | return 112 | end 113 | 114 | local top = {x=pos.x,y=pos.y+1,z=pos.z} 115 | local flagmeta = minetest.get_meta(pos) 116 | 117 | if not flagmeta then 118 | return 119 | end 120 | 121 | local flag_team_data = ctf_flag.get(pos) 122 | if not flag_team_data or not ctf.team(flag_team_data.team)then 123 | ctf.log("flag", "Flag does not exist! Deleting nodes. "..dump(pos)) 124 | minetest.set_node(pos,{name="air"}) 125 | minetest.set_node(top,{name="air"}) 126 | return 127 | end 128 | local topmeta = minetest.get_meta(top) 129 | local flag_name = flag_team_data.name 130 | if flag_name and flag_name ~= "" then 131 | flagmeta:set_string("infotext", flag_name.." - "..flag_team_data.team) 132 | else 133 | flagmeta:set_string("infotext", flag_team_data.team.."'s flag") 134 | end 135 | 136 | if not ctf.team(flag_team_data.team).data.color then 137 | ctf.team(flag_team_data.team).data.color = "red" 138 | ctf.needs_save = true 139 | end 140 | 141 | if flag_team_data.claimed then 142 | minetest.set_node(top,{name="ctf_flag:flag_captured_top"}) 143 | else 144 | minetest.set_node(top,{name="ctf_flag:flag_top_"..ctf.team(flag_team_data.team).data.color}) 145 | end 146 | 147 | topmeta = minetest.get_meta(top) 148 | if flag_name and flag_name ~= "" then 149 | topmeta:set_string("infotext", flag_name.." - "..flag_team_data.team) 150 | else 151 | topmeta:set_string("infotext", flag_team_data.team.."'s flag") 152 | end 153 | end 154 | 155 | function ctf_flag.flag_tick(pos) 156 | ctf_flag.update(pos) 157 | minetest.get_node_timer(pos):start(5) 158 | end 159 | 160 | -- get a flag from a team 161 | function ctf_flag.get(pos) 162 | if not pos then 163 | return 164 | end 165 | 166 | local result = nil 167 | for _, team in pairs(ctf.teams) do 168 | for i = 1, #team.flags do 169 | if ( 170 | team.flags[i].x == pos.x and 171 | team.flags[i].y == pos.y and 172 | team.flags[i].z == pos.z 173 | ) then 174 | if result then 175 | minetest.chat_send_all("[CTF ERROR] Multiple teams have same flag. Please report this to the server operator / admin") 176 | print("CTF ERROR DATA") 177 | print("Multiple teams have same flag.") 178 | print("This is a sign of ctf.txt corruption.") 179 | print("----------------") 180 | print(dump(result)) 181 | print(dump(team.flags[i])) 182 | print("----------------") 183 | else 184 | result = team.flags[i] 185 | end 186 | end 187 | end 188 | end 189 | return result 190 | end 191 | 192 | -- delete a flag from a team 193 | function ctf_flag.delete(team, pos) 194 | if not team or team == "" then 195 | return 196 | end 197 | 198 | ctf.log("flag", "Deleting flag from " .. team .. " at (" .. pos.x .. 199 | ", " .. pos.y .. ", " .. pos.z .. ")") 200 | 201 | for i = 1, #ctf.team(team).flags do 202 | if ( 203 | ctf.team(team).flags[i].x == pos.x and 204 | ctf.team(team).flags[i].y == pos.y and 205 | ctf.team(team).flags[i].z == pos.z 206 | ) then 207 | table.remove(ctf.team(team).flags,i) 208 | return 209 | end 210 | end 211 | end 212 | 213 | function ctf_flag.assert_flag(flag) 214 | minetest.get_voxel_manip(flag, { x = flag.x + 1, y = flag.y + 1, z = flag.z + 1}) 215 | local nodename = minetest.get_node(flag).name 216 | if nodename ~= "ctf_flag:flag" then 217 | ctf.log("flag", flag.team .. " has wrong node at flag position, " .. nodename .. ", correcting...") 218 | minetest.set_node(flag, { name = "ctf_flag:flag"}) 219 | ctf_flag.update(flag) 220 | end 221 | end 222 | 223 | function ctf_flag.assert_flags() 224 | for tname, team in pairs(ctf.teams) do 225 | ctf_flag.assert_flags_team(tname) 226 | end 227 | end 228 | 229 | function ctf_flag.assert_flags_team(tname) 230 | local team = ctf.team(tname) 231 | if not tname or not team then 232 | return false 233 | end 234 | 235 | if not team.flags then 236 | team.flags = {} 237 | end 238 | 239 | for i=1, #team.flags do 240 | ctf_flag.assert_flag(team.flags[i]) 241 | end 242 | end 243 | -------------------------------------------------------------------------------- /ctf_flag/flag_func.lua: -------------------------------------------------------------------------------- 1 | local function do_capture(attname, flag, returned) 2 | local team = flag.team 3 | local attacker = ctf.player(attname) 4 | 5 | local flag_name = "" 6 | if flag.name then 7 | flag_name = flag.name .. " " 8 | end 9 | flag_name = team .. "'s " .. flag_name .. "flag" 10 | 11 | 12 | if ctf.setting("flag.capture_take") and not returned then 13 | for i = 1, #ctf_flag.registered_on_prepick_up do 14 | if not ctf_flag.registered_on_prepick_up[i](attname, flag) then 15 | return 16 | end 17 | end 18 | 19 | minetest.chat_send_all(flag_name.." has been picked up by ".. 20 | attname.." (team "..attacker.team..")") 21 | 22 | ctf.action("flag", attname .. " picked up " .. flag_name) 23 | 24 | -- Post to flag owner's board 25 | ctf.post(team, { 26 | msg = flag_name .. " has been taken by " .. attname .. " of ".. attacker.team, 27 | icon="flag_red" }) 28 | 29 | -- Post to attacker's board 30 | ctf.post(attacker.team, { 31 | msg = attname .. " snatched '" .. flag_name .. "' from " .. team, 32 | icon="flag_green"}) 33 | 34 | -- Add to claimed list 35 | flag.claimed = { 36 | team = attacker.team, 37 | player = attname 38 | } 39 | 40 | ctf.hud.updateAll() 41 | 42 | ctf_flag.update(flag) 43 | 44 | for i = 1, #ctf_flag.registered_on_pick_up do 45 | ctf_flag.registered_on_pick_up[i](attname, flag) 46 | end 47 | else 48 | for i = 1, #ctf_flag.registered_on_precapture do 49 | if not ctf_flag.registered_on_precapture[i](attname, flag) then 50 | return 51 | end 52 | end 53 | 54 | minetest.chat_send_all(flag_name.." has been captured ".. 55 | " by "..attname.." (team "..attacker.team..")") 56 | 57 | ctf.action("flag", attname .. " captured " .. flag_name) 58 | 59 | -- Post to flag owner's board 60 | ctf.post(team, { 61 | msg = flag_name .. " has been captured by " .. attacker.team, 62 | icon="flag_red"}) 63 | 64 | -- Post to attacker's board 65 | ctf.post(attacker.team, { 66 | msg = attname .. " captured '" .. flag_name .. "' from " .. team, 67 | icon="flag_green"}) 68 | 69 | -- Take flag 70 | if ctf.setting("flag.allow_multiple") then 71 | ctf_flag.delete(team, vector.new(flag)) 72 | ctf_flag.add(attacker.team, vector.new(flag)) 73 | else 74 | minetest.set_node(pos,{name="air"}) 75 | ctf_flag.delete(team,pos) 76 | end 77 | 78 | for i = 1, #ctf_flag.registered_on_capture do 79 | ctf_flag.registered_on_capture[i](attname, flag) 80 | end 81 | end 82 | 83 | ctf.needs_save = true 84 | end 85 | 86 | local function player_drop_flag(player) 87 | return ctf_flag.player_drop_flag(player:get_player_name()) 88 | end 89 | minetest.register_on_dieplayer(player_drop_flag) 90 | minetest.register_on_leaveplayer(player_drop_flag) 91 | 92 | 93 | ctf_flag = { 94 | on_punch_top = function(pos, node, puncher) 95 | pos.y = pos.y - 1 96 | ctf_flag.on_punch(pos, node, puncher) 97 | end, 98 | on_rightclick_top = function(pos, node, clicker) 99 | pos.y = pos.y - 1 100 | ctf_flag.on_rightclick(pos, node, clicker) 101 | end, 102 | on_rightclick = function(pos, node, clicker) 103 | local name = clicker:get_player_name() 104 | local flag = ctf_flag.get(pos) 105 | if not flag then 106 | return 107 | end 108 | 109 | if flag.claimed then 110 | if ctf.setting("flag.capture_take") then 111 | minetest.chat_send_player(name, "This flag has been taken by "..flag.claimed.player) 112 | minetest.chat_send_player(name, "who is a member of team "..flag.claimed.team) 113 | return 114 | else 115 | minetest.chat_send_player(name, "Oops! This flag should not be captured. Reverting...") 116 | flag.claimed = nil 117 | end 118 | end 119 | ctf.gui.flag_board(name, pos) 120 | end, 121 | on_punch = function(pos, node, puncher) 122 | local name = puncher:get_player_name() 123 | if not puncher or not name then 124 | return 125 | end 126 | 127 | local flag = ctf_flag.get(pos) 128 | if not flag then 129 | return 130 | end 131 | 132 | if flag.claimed then 133 | if ctf.setting("flag.capture_take") then 134 | minetest.chat_send_player(name, "This flag has been taken by " .. flag.claimed.player) 135 | minetest.chat_send_player(name, "who is a member of team " .. flag.claimed.team) 136 | return 137 | else 138 | minetest.chat_send_player(name, "Oops! This flag should not be captured. Reverting.") 139 | flag.claimed = nil 140 | end 141 | end 142 | 143 | local team = flag.team 144 | if not team then 145 | return 146 | end 147 | 148 | if ctf.team(team) and ctf.player(name).team then 149 | if ctf.player(name).team == team then 150 | -- Clicking on their team's flag 151 | if ctf.setting("flag.capture_take") then 152 | ctf_flag._flagret(name) 153 | end 154 | else 155 | -- Clicked on another team's flag 156 | local diplo = ctf.diplo.get(team, ctf.player(name).team) or 157 | ctf.setting("default_diplo_state") 158 | 159 | if diplo ~= "war" then 160 | minetest.chat_send_player(name, "You are at peace with this team!") 161 | return 162 | end 163 | 164 | do_capture(name, flag) 165 | end 166 | else 167 | minetest.chat_send_player(name, "You are not part of a team!") 168 | end 169 | end, 170 | _flagret = function(name) 171 | local claimed = ctf_flag.collect_claimed() 172 | for i = 1, #claimed do 173 | local flag = claimed[i] 174 | if flag.claimed.player == name then 175 | do_capture(name, flag, true) 176 | end 177 | end 178 | end, 179 | on_construct = function(pos) 180 | local meta = minetest.get_meta(pos) 181 | meta:set_string("infotext", "Unowned flag") 182 | minetest.get_node_timer(pos):start(5) 183 | end, 184 | after_place_node = function(pos, placer) 185 | local name = placer:get_player_name() 186 | if not pos or not name then 187 | minetest.set_node(pos, {name="air"}) 188 | return 189 | end 190 | 191 | local meta = minetest.get_meta(pos) 192 | if not meta then 193 | minetest.set_node(pos, {name="air"}) 194 | return 195 | end 196 | 197 | local tplayer = ctf.player_or_nil(name) 198 | if tplayer and ctf.team(tplayer.team) then 199 | if not minetest.check_player_privs(name, {ctf_place_flag=true}) then 200 | minetest.chat_send_player(name, "You're not allowed to place flags! Reported to admin for investigation.") 201 | minetest.set_node(pos, {name="air"}) 202 | if minetest.global_exists("chatplus") then 203 | chatplus.send_mail("*SERVER*", minetest.settings:get("name"), 204 | "player " .. name .. " attempted to place flag!") 205 | end 206 | return 207 | end 208 | 209 | local tname = tplayer.team 210 | local team = ctf.team(tplayer.team) 211 | meta:set_string("infotext", tname.."'s flag") 212 | 213 | -- add flag 214 | ctf_flag.add(tname, pos) 215 | 216 | -- TODO: fix this hackiness 217 | if team.spawn and not ctf.setting("flag.allow_multiple") and 218 | minetest.get_node(team.spawn).name == "ctf_flag:flag" then 219 | -- send message 220 | minetest.chat_send_all(tname .. "'s flag has been moved") 221 | minetest.set_node(team.spawn, {name="air"}) 222 | minetest.set_node({ 223 | x = team.spawn.x, 224 | y = team.spawn.y+1, 225 | z = team.spawn.z 226 | }, {name="air"}) 227 | team.spawn = pos 228 | end 229 | 230 | ctf.needs_save = true 231 | 232 | local pos2 = { 233 | x = pos.x, 234 | y = pos.y + 1, 235 | z = pos.z 236 | } 237 | 238 | if not team.data.color then 239 | team.data.color = "red" 240 | ctf.needs_save = true 241 | end 242 | 243 | minetest.set_node(pos2, {name="ctf_flag:flag_top_"..team.data.color}) 244 | 245 | local meta2 = minetest.get_meta(pos2) 246 | 247 | meta2:set_string("infotext", tname.."'s flag") 248 | else 249 | minetest.chat_send_player(name, "You are not part of a team!") 250 | minetest.set_node(pos, {name="air"}) 251 | end 252 | end 253 | } 254 | -------------------------------------------------------------------------------- /ctf_flag/flags.lua: -------------------------------------------------------------------------------- 1 | -- The flag 2 | minetest.register_node("ctf_flag:flag", { 3 | description = "Flag", 4 | drawtype="nodebox", 5 | paramtype = "light", 6 | walkable = false, 7 | inventory_image = "flag_silver2.png", 8 | tiles = { 9 | "default_wood.png", 10 | "default_wood.png", 11 | "default_wood.png", 12 | "default_wood.png", 13 | "default_wood.png", 14 | "default_wood.png" 15 | }, 16 | node_box = { 17 | type = "fixed", 18 | fixed = { 19 | {0.250000,-0.500000,0.000000,0.312500,0.500000,0.062500} 20 | } 21 | }, 22 | groups = {immortal=1,is_flag=1,flag_bottom=1}, 23 | on_punch = ctf_flag.on_punch, 24 | on_rightclick = ctf_flag.on_rightclick, 25 | on_construct = ctf_flag.on_construct, 26 | after_place_node = ctf_flag.after_place_node, 27 | on_timer = ctf_flag.flag_tick 28 | }) 29 | 30 | for color, _ in pairs(ctf.flag_colors) do 31 | minetest.register_node("ctf_flag:flag_top_"..color,{ 32 | description = "You are not meant to have this! - flag top", 33 | drawtype="nodebox", 34 | paramtype = "light", 35 | walkable = false, 36 | tiles = { 37 | "default_wood.png", 38 | "default_wood.png", 39 | "default_wood.png", 40 | "default_wood.png", 41 | "flag_"..color.."2.png", 42 | "flag_"..color..".png" 43 | }, 44 | node_box = { 45 | type = "fixed", 46 | fixed = { 47 | {0.250000,-0.500000,0.000000,0.312500,0.500000,0.062500}, 48 | {-0.5,0,0.000000,0.250000,0.500000,0.062500} 49 | } 50 | }, 51 | groups = {immortal=1,is_flag=1,flag_top=1,not_in_creative_inventory=1}, 52 | on_punch = ctf_flag.on_punch_top, 53 | on_rightclick = ctf_flag.on_rightclick_top 54 | }) 55 | end 56 | 57 | minetest.register_node("ctf_flag:flag_captured_top",{ 58 | description = "You are not meant to have this! - flag captured", 59 | drawtype = "nodebox", 60 | paramtype = "light", 61 | walkable = false, 62 | tiles = { 63 | "default_wood.png", 64 | "default_wood.png", 65 | "default_wood.png", 66 | "default_wood.png", 67 | "default_wood.png", 68 | "default_wood.png" 69 | }, 70 | node_box = { 71 | type = "fixed", 72 | fixed = { 73 | {0.250000,-0.500000,0.000000,0.312500,0.500000,0.062500} 74 | } 75 | }, 76 | groups = {immortal=1,is_flag=1,flag_top=1,not_in_creative_inventory=1}, 77 | on_punch = ctf_flag.on_punch_top, 78 | on_rightclick = ctf_flag.on_rightclick_top 79 | }) 80 | 81 | if ctf.setting("flag.crafting") then 82 | minetest.register_craft({ 83 | output = "ctf_flag:flag", 84 | recipe = { 85 | {"default:stick", "group:wool"}, 86 | {"default:stick", "",}, 87 | {"default:stick", ""} 88 | } 89 | }) 90 | end 91 | -------------------------------------------------------------------------------- /ctf_flag/gui.lua: -------------------------------------------------------------------------------- 1 | -- Team interface 2 | ctf.gui.register_tab("flags", "Flags", function(name, team) 3 | local result = "" 4 | local t = ctf.team(team) 5 | 6 | if not t then 7 | return 8 | end 9 | 10 | local x = 1 11 | local y = 2 12 | result = result .. "label[1,1;Click a flag button to go there]" 13 | 14 | if ctf.setting("gui.team.teleport_to_spawn") and minetest.get_setting("static_spawnpoint") then 15 | local x,y,z = string.match(minetest.get_setting("static_spawnpoint"), "(%d+),(%d+),(%d+)") 16 | 17 | result = result .. 18 | "button[" .. x .. "," .. y .. ";2,1;goto_" 19 | ..f.x.."_"..f.y.."_"..f.z..";" 20 | 21 | result = result .. "Spawn]" 22 | x = x + 2 23 | end 24 | 25 | for i=1, #t.flags do 26 | local f = t.flags[i] 27 | 28 | if x > 8 then 29 | x = 1 30 | y = y + 1 31 | end 32 | 33 | if y > 6 then 34 | break 35 | end 36 | 37 | result = result .. 38 | "button[" .. x .. "," .. y .. ";2,1;goto_" 39 | ..f.x.."_"..f.y.."_"..f.z..";" 40 | 41 | if f.name then 42 | result = result .. f.name .. "]" 43 | else 44 | result = result .. "("..f.x..","..f.y..","..f.z..")]" 45 | end 46 | 47 | x = x + 2 48 | end 49 | 50 | minetest.show_formspec(name, "ctf:flags", 51 | "size[10,7]".. 52 | ctf.gui.get_tabs(name,team).. 53 | result) 54 | end) 55 | 56 | minetest.register_on_player_receive_fields(function(player, formname, fields) 57 | -- Todo: fix security issue here 58 | -- local name = player:get_player_name() 59 | -- if formname == "ctf:flags" then 60 | -- for key, field in pairs(fields) do 61 | -- local x,y,z = string.match(key, "goto_([%d-]+)_([%d-]+)_([%d-]+)") 62 | -- if x and y and z then 63 | -- player:setpos({ x=tonumber(x), y=tonumber(y), z=tonumber(z) }) 64 | -- return true 65 | -- end 66 | -- end 67 | -- end 68 | end) 69 | 70 | -- Flag interface 71 | function ctf.gui.flag_board(name, pos) 72 | local flag = ctf_flag.get(pos) 73 | if not flag then 74 | return 75 | end 76 | 77 | local team = flag.team 78 | if not team then 79 | return 80 | end 81 | 82 | if not ctf.can_mod(name, team) then 83 | if ctf.player(name).team and ctf.player(name).team == team then 84 | ctf.gui.show(name) 85 | end 86 | return 87 | end 88 | 89 | ctf.log("gui", name .. " views flag board") 90 | 91 | local flag_name = flag.name 92 | 93 | if not ctf.setting("flag.names") then 94 | flag.name = nil 95 | return 96 | end 97 | 98 | if not ctf.setting("gui") then 99 | return 100 | end 101 | 102 | if not flag_name then 103 | flag_name = "" 104 | end 105 | 106 | if not ctf.gui.flag_data then 107 | ctf.gui.flag_data = {} 108 | end 109 | 110 | ctf.gui.flag_data[name] = {pos=pos} 111 | 112 | minetest.show_formspec(name, "ctf:flag_board", 113 | "size[6,3]".. 114 | "field[1,1;4,1;flag_name;Flag Name;"..flag_name.."]".. 115 | "button_exit[1,2;2,1;save;Save]".. 116 | "button_exit[3,2;2,1;delete;Delete]" 117 | ) 118 | end 119 | minetest.register_on_player_receive_fields(function(player, formname, fields) 120 | local name = player:get_player_name() 121 | 122 | if not formname=="ctf:flag_board" then 123 | return false 124 | end 125 | 126 | if fields.save and fields.flag_name then 127 | local flag = ctf_flag.get(ctf.gui.flag_data[name].pos) 128 | if not flag then 129 | return false 130 | end 131 | 132 | local team = flag.team 133 | if not team then 134 | return false 135 | end 136 | 137 | if ctf.can_mod(name,team) == false then 138 | return false 139 | end 140 | 141 | local flag_name = flag.name 142 | if not flag_name then 143 | flag_name = "" 144 | end 145 | 146 | flag.name = fields.flag_name 147 | 148 | local msg = flag_name.." was renamed to "..fields.flag_name 149 | 150 | if flag_name=="" then 151 | msg = "A flag was named "..fields.flag_name.." at ("..ctf.gui.flag_data[name].pos.x..","..ctf.gui.flag_data[name].pos.z..")" 152 | end 153 | 154 | ctf.post(team,{msg=msg,icon="flag_info"}) 155 | 156 | return true 157 | elseif fields.delete then 158 | local pos = ctf.gui.flag_data[name].pos 159 | 160 | local flag = ctf_flag.get(ctf.gui.flag_data[name].pos) 161 | 162 | if not flag then 163 | return 164 | end 165 | 166 | local team = flag.team 167 | if not team then 168 | return 169 | end 170 | 171 | if ctf.can_mod(name,team) == false then 172 | return false 173 | end 174 | 175 | ctf_flag.delete(team,pos) 176 | 177 | minetest.set_node(pos,{name="air"}) 178 | pos.y=pos.y+1 179 | minetest.set_node(pos,{name="air"}) 180 | player:get_inventory():add_item("main", "ctf_flag:flag") 181 | 182 | return true 183 | end 184 | end) 185 | -------------------------------------------------------------------------------- /ctf_flag/hud.lua: -------------------------------------------------------------------------------- 1 | -- TODO: delete flags if they are removed (ctf.next, or captured) 2 | ctf.hud.register_part(function(player, name, tplayer) 3 | if ctf.setting("flag.waypoints") then 4 | for tname, team in pairs(ctf.teams) do 5 | for _, flag in pairs(team.flags) do 6 | local hud = "ctf:hud_" .. tname 7 | local flag_name = flag.name or tname .. "'s base" 8 | local color = ctf.flag_colors[team.data.color] 9 | if not color then 10 | color = "0x000000" 11 | end 12 | 13 | if ctf.hud:exists(player, hud) then 14 | ctf.hud:change(player, hud, "world_pos", { 15 | x = flag.x, 16 | y = flag.y, 17 | z = flag.z 18 | }) 19 | else 20 | ctf.hud:add(player, hud, { 21 | hud_elem_type = "waypoint", 22 | name = flag_name, 23 | number = color, 24 | world_pos = { 25 | x = flag.x, 26 | y = flag.y, 27 | z = flag.z 28 | } 29 | }) 30 | end 31 | end 32 | end 33 | end 34 | end) 35 | 36 | ctf.hud.register_part(function(player, name, tplayer) 37 | -- Check all flags 38 | local alert = nil 39 | local color = "0xFFFFFF" 40 | if ctf.setting("flag.alerts") then 41 | if ctf.setting("flag.alerts.neutral_alert") then 42 | alert = "Punch the enemy flag! Protect your flag!" 43 | end 44 | local claimed = ctf_flag.collect_claimed() 45 | local enemyHolder = nil 46 | local teamHolder = nil 47 | for _, flag in pairs(claimed) do 48 | if flag.team == tplayer.team then 49 | enemyHolder = flag.claimed.player 50 | else 51 | teamHolder = flag.claimed.player 52 | end 53 | end 54 | 55 | if teamHolder == name then 56 | if enemyHolder then 57 | alert = "You can't capture the flag until " .. enemyHolder .. " is killed!" 58 | color = "0xFF0000" 59 | else 60 | alert = "You've got the flag! Run back and punch your flag!" 61 | color = "0xFF0000" 62 | end 63 | elseif teamHolder then 64 | if enemyHolder then 65 | alert = "Kill " .. enemyHolder .. " to allow " .. teamHolder .. " to capture the flag!" 66 | color = "0xFF0000" 67 | else 68 | alert = "Protect " .. teamHolder .. ", they've got the enemy flag!" 69 | color = "0xFF0000" 70 | end 71 | elseif enemyHolder then 72 | alert = "Kill " .. enemyHolder .. ", they've got your flag!" 73 | color = "0xFF0000" 74 | end 75 | end 76 | 77 | -- Display alert 78 | if alert then 79 | if ctf.hud:exists(player, "ctf:hud_team_alert") then 80 | ctf.hud:change(player, "ctf:hud_team_alert", "text", alert) 81 | ctf.hud:change(player, "ctf:hud_team_alert", "number", color) 82 | else 83 | local y 84 | if ctf.setting("hud.teamname") then 85 | y = 50 86 | else 87 | y = 20 88 | end 89 | ctf.hud:add(player, "ctf:hud_team_alert", { 90 | hud_elem_type = "text", 91 | position = {x = 1, y = 0}, 92 | scale = {x = 100, y = 100}, 93 | text = alert, 94 | number = color, 95 | offset = {x = -10, y = y}, 96 | alignment = {x = -1, y = 0} 97 | }) 98 | end 99 | else 100 | ctf.hud:remove(player, "ctf:hud_team_alert") 101 | end 102 | end) 103 | -------------------------------------------------------------------------------- /ctf_flag/init.lua: -------------------------------------------------------------------------------- 1 | -- Initialise 2 | ctf.register_on_init(function() 3 | ctf.log("flag", "Initialising...") 4 | ctf._set("flag.allow_multiple", true) 5 | ctf._set("flag.capture_take", false) 6 | ctf._set("flag.names", true) 7 | ctf._set("flag.waypoints", true) 8 | ctf._set("flag.protect_distance", 25) 9 | ctf._set("flag.nobuild_radius", 3) 10 | ctf._set("flag.drop_time", 7*60) 11 | ctf._set("flag.drop_warn_time", 60) 12 | ctf._set("flag.crafting", false) 13 | ctf._set("flag.alerts", true) 14 | ctf._set("flag.alerts.neutral_alert", true) 15 | ctf._set("gui.team.teleport_to_flag", true) 16 | ctf._set("gui.team.teleport_to_spawn", false) 17 | end) 18 | 19 | minetest.register_privilege("ctf_place_flag", { 20 | description = "can place flag" 21 | }) 22 | 23 | dofile(minetest.get_modpath("ctf_flag") .. "/hud.lua") 24 | dofile(minetest.get_modpath("ctf_flag") .. "/gui.lua") 25 | dofile(minetest.get_modpath("ctf_flag") .. "/flag_func.lua") 26 | dofile(minetest.get_modpath("ctf_flag") .. "/api.lua") 27 | dofile(minetest.get_modpath("ctf_flag") .. "/flags.lua") 28 | 29 | ctf.register_on_new_team(function(team) 30 | team.flags = {} 31 | end) 32 | 33 | function ctf_flag.get_nearest(pos) 34 | local closest = nil 35 | local closest_distSQ = 1000000 36 | local pd = ctf.setting("flag.protect_distance") 37 | local pdSQ = pd * pd 38 | 39 | for tname, team in pairs(ctf.teams) do 40 | for i = 1, #team.flags do 41 | local distSQ = vector.distanceSQ(pos, team.flags[i]) 42 | if distSQ < pdSQ and distSQ < closest_distSQ then 43 | closest = team.flags[i] 44 | closest_distSQ = distSQ 45 | end 46 | end 47 | end 48 | 49 | return closest, closest_distSQ 50 | end 51 | 52 | function ctf_flag.get_nearest_team_dist(pos) 53 | local flag, distSQ = ctf_flag.get_nearest(pos) 54 | if flag then 55 | return flag.team, distSQ 56 | end 57 | end 58 | 59 | ctf.register_on_territory_query(ctf_flag.get_nearest_team_dist) 60 | 61 | function ctf.get_spawn(team) 62 | if not ctf.team(team) then 63 | return nil 64 | end 65 | 66 | if ctf.team(team).spawn then 67 | return ctf.team(team).spawn 68 | end 69 | 70 | -- Get spawn from first flag 71 | ctf_flag.assert_flags(team) 72 | if #ctf.team(team).flags > 0 then 73 | return ctf.team(team).flags[1] 74 | else 75 | return nil 76 | end 77 | end 78 | 79 | -- Add minimum build range 80 | local old_is_protected = minetest.is_protected 81 | local r = ctf.setting("flag.nobuild_radius") 82 | local rs = r * r 83 | function minetest.is_protected(pos, name) 84 | if r <= 0 or rs == 0 then 85 | return old_is_protected(pos, name) 86 | end 87 | 88 | local flag, distSQ = ctf_flag.get_nearest(pos) 89 | if flag and pos.y >= flag.y - 1 and distSQ < rs then 90 | minetest.chat_send_player(name, 91 | "Too close to the flag to build! Leave at least " .. r .. " blocks around the flag.") 92 | return true 93 | else 94 | return old_is_protected(pos, name) 95 | end 96 | end 97 | 98 | -- Play sound 99 | ctf_flag.register_on_pick_up(function(attname, flag) 100 | local vteam = ctf.team(flag.team) 101 | for name, player in pairs(vteam.players) do 102 | minetest.sound_play({name="trumpet_lose"}, { 103 | to_player = name, 104 | gain = 1.0, -- default 105 | }) 106 | end 107 | 108 | local ateam = ctf.team(ctf.player(attname).team) 109 | for name, player in pairs(ateam.players) do 110 | minetest.sound_play({name="trumpet_win"}, { 111 | to_player = name, 112 | gain = 1.0, -- default 113 | }) 114 | end 115 | end) 116 | 117 | -- Drop after time 118 | local pickup_times = {} 119 | ctf_flag.register_on_pick_up(function(attname, flag) 120 | pickup_times[attname] = minetest.get_gametime() 121 | end) 122 | ctf_flag.register_on_drop(function(attname, flag) 123 | pickup_times[attname] = nil 124 | end) 125 | ctf_flag.register_on_capture(function(attname, flag) 126 | pickup_times[attname] = nil 127 | end) 128 | ctf.register_on_new_game(function() 129 | pickup_times = {} 130 | end) 131 | local function update_flag_drops() 132 | local time = minetest.get_gametime() 133 | local drop_time = ctf.setting("flag.drop_time") 134 | for name, start in pairs(pickup_times) do 135 | local remaining = drop_time - time + start 136 | if remaining < 0 then 137 | ctf_flag.player_drop_flag(name) 138 | minetest.chat_send_player(name, "You took too long to capture the flag, so it returned!") 139 | elseif remaining < ctf.setting("flag.drop_warn_time") then 140 | minetest.chat_send_player(name, "You have " .. remaining .. 141 | " seconds to capture the flag before it returns.") 142 | end 143 | end 144 | minetest.after(5, update_flag_drops) 145 | end 146 | minetest.after(5, update_flag_drops) 147 | -------------------------------------------------------------------------------- /ctf_flag/mod.conf: -------------------------------------------------------------------------------- 1 | name = ctf_flag 2 | depends = ctf, ctf_colors 3 | optional_depends = chatplus 4 | -------------------------------------------------------------------------------- /ctf_flag/sounds/trumpet_lose.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MT-CTF/ctf_pvp_engine/d8ec80361b1261dd39cb6b6e325c7e9321f0b783/ctf_flag/sounds/trumpet_lose.ogg -------------------------------------------------------------------------------- /ctf_flag/sounds/trumpet_win.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MT-CTF/ctf_pvp_engine/d8ec80361b1261dd39cb6b6e325c7e9321f0b783/ctf_flag/sounds/trumpet_win.ogg -------------------------------------------------------------------------------- /ctf_flag/textures/flag_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MT-CTF/ctf_pvp_engine/d8ec80361b1261dd39cb6b6e325c7e9321f0b783/ctf_flag/textures/flag_black.png -------------------------------------------------------------------------------- /ctf_flag/textures/flag_black2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MT-CTF/ctf_pvp_engine/d8ec80361b1261dd39cb6b6e325c7e9321f0b783/ctf_flag/textures/flag_black2.png -------------------------------------------------------------------------------- /ctf_flag/textures/flag_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MT-CTF/ctf_pvp_engine/d8ec80361b1261dd39cb6b6e325c7e9321f0b783/ctf_flag/textures/flag_blue.png -------------------------------------------------------------------------------- /ctf_flag/textures/flag_blue2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MT-CTF/ctf_pvp_engine/d8ec80361b1261dd39cb6b6e325c7e9321f0b783/ctf_flag/textures/flag_blue2.png -------------------------------------------------------------------------------- /ctf_flag/textures/flag_cyan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MT-CTF/ctf_pvp_engine/d8ec80361b1261dd39cb6b6e325c7e9321f0b783/ctf_flag/textures/flag_cyan.png -------------------------------------------------------------------------------- /ctf_flag/textures/flag_cyan2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MT-CTF/ctf_pvp_engine/d8ec80361b1261dd39cb6b6e325c7e9321f0b783/ctf_flag/textures/flag_cyan2.png -------------------------------------------------------------------------------- /ctf_flag/textures/flag_gold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MT-CTF/ctf_pvp_engine/d8ec80361b1261dd39cb6b6e325c7e9321f0b783/ctf_flag/textures/flag_gold.png -------------------------------------------------------------------------------- /ctf_flag/textures/flag_gold2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MT-CTF/ctf_pvp_engine/d8ec80361b1261dd39cb6b6e325c7e9321f0b783/ctf_flag/textures/flag_gold2.png -------------------------------------------------------------------------------- /ctf_flag/textures/flag_gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MT-CTF/ctf_pvp_engine/d8ec80361b1261dd39cb6b6e325c7e9321f0b783/ctf_flag/textures/flag_gray.png -------------------------------------------------------------------------------- /ctf_flag/textures/flag_gray2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MT-CTF/ctf_pvp_engine/d8ec80361b1261dd39cb6b6e325c7e9321f0b783/ctf_flag/textures/flag_gray2.png -------------------------------------------------------------------------------- /ctf_flag/textures/flag_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MT-CTF/ctf_pvp_engine/d8ec80361b1261dd39cb6b6e325c7e9321f0b783/ctf_flag/textures/flag_green.png -------------------------------------------------------------------------------- /ctf_flag/textures/flag_green2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MT-CTF/ctf_pvp_engine/d8ec80361b1261dd39cb6b6e325c7e9321f0b783/ctf_flag/textures/flag_green2.png -------------------------------------------------------------------------------- /ctf_flag/textures/flag_orange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MT-CTF/ctf_pvp_engine/d8ec80361b1261dd39cb6b6e325c7e9321f0b783/ctf_flag/textures/flag_orange.png -------------------------------------------------------------------------------- /ctf_flag/textures/flag_orange2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MT-CTF/ctf_pvp_engine/d8ec80361b1261dd39cb6b6e325c7e9321f0b783/ctf_flag/textures/flag_orange2.png -------------------------------------------------------------------------------- /ctf_flag/textures/flag_pink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MT-CTF/ctf_pvp_engine/d8ec80361b1261dd39cb6b6e325c7e9321f0b783/ctf_flag/textures/flag_pink.png -------------------------------------------------------------------------------- /ctf_flag/textures/flag_pink2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MT-CTF/ctf_pvp_engine/d8ec80361b1261dd39cb6b6e325c7e9321f0b783/ctf_flag/textures/flag_pink2.png -------------------------------------------------------------------------------- /ctf_flag/textures/flag_purple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MT-CTF/ctf_pvp_engine/d8ec80361b1261dd39cb6b6e325c7e9321f0b783/ctf_flag/textures/flag_purple.png -------------------------------------------------------------------------------- /ctf_flag/textures/flag_purple2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MT-CTF/ctf_pvp_engine/d8ec80361b1261dd39cb6b6e325c7e9321f0b783/ctf_flag/textures/flag_purple2.png -------------------------------------------------------------------------------- /ctf_flag/textures/flag_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MT-CTF/ctf_pvp_engine/d8ec80361b1261dd39cb6b6e325c7e9321f0b783/ctf_flag/textures/flag_red.png -------------------------------------------------------------------------------- /ctf_flag/textures/flag_red2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MT-CTF/ctf_pvp_engine/d8ec80361b1261dd39cb6b6e325c7e9321f0b783/ctf_flag/textures/flag_red2.png -------------------------------------------------------------------------------- /ctf_flag/textures/flag_silver.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MT-CTF/ctf_pvp_engine/d8ec80361b1261dd39cb6b6e325c7e9321f0b783/ctf_flag/textures/flag_silver.png -------------------------------------------------------------------------------- /ctf_flag/textures/flag_silver2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MT-CTF/ctf_pvp_engine/d8ec80361b1261dd39cb6b6e325c7e9321f0b783/ctf_flag/textures/flag_silver2.png -------------------------------------------------------------------------------- /ctf_flag/textures/flag_yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MT-CTF/ctf_pvp_engine/d8ec80361b1261dd39cb6b6e325c7e9321f0b783/ctf_flag/textures/flag_yellow.png -------------------------------------------------------------------------------- /ctf_flag/textures/flag_yellow2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MT-CTF/ctf_pvp_engine/d8ec80361b1261dd39cb6b6e325c7e9321f0b783/ctf_flag/textures/flag_yellow2.png -------------------------------------------------------------------------------- /ctf_protect/init.lua: -------------------------------------------------------------------------------- 1 | -- This mod is used to protect nodes in the capture the flag game 2 | ctf.register_on_init(function() 3 | ctf.log("chat", "Initialising...") 4 | 5 | -- Settings: Chat 6 | ctf._set("node_ownership", true) 7 | end) 8 | 9 | local old_is_protected = minetest.is_protected 10 | 11 | function minetest.is_protected(pos, name) 12 | if not ctf.setting("node_ownership") then 13 | return old_is_protected(pos, name) 14 | end 15 | 16 | local team = ctf.get_territory_owner(pos) 17 | 18 | if not team or not ctf.team(team) then 19 | return old_is_protected(pos, name) 20 | end 21 | 22 | if ctf.player(name).team == team then 23 | return old_is_protected(pos, name) 24 | else 25 | minetest.chat_send_player(name, "You cannot dig on team "..team.."'s land") 26 | return true 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /ctf_protect/mod.conf: -------------------------------------------------------------------------------- 1 | name = ctf_protect 2 | depends = ctf 3 | -------------------------------------------------------------------------------- /doc_data.md: -------------------------------------------------------------------------------- 1 | # Data Formats 2 | 3 | This file documents the contents of ctf.txt 4 | Values are added to the file using ctf.register_on_save and ctf.register_on_load. 5 | Here are the default values: 6 | 7 | ```lua 8 | { 9 | players = ctf.players, 10 | teams = ctf.teams, 11 | diplo = ctf.diplo.diplo 12 | } 13 | ``` 14 | 15 | ## Players 16 | 17 | Commonly called tplayer (may be called data or player in old code). 18 | Player name is commonly called name (but may be called other things in older code). 19 | 20 | ```lua 21 | ctf.players = { 22 | username = (player_table) 23 | } 24 | 25 | (player_table) = { 26 | name = "username", 27 | team = "teamname", 28 | auth = false 29 | -- true if the player is a team admin. Team admins can change team settings. 30 | -- See ctf.can_mod() 31 | -- Note that priv:ctf_admin can also change team settings 32 | } 33 | ``` 34 | 35 | ## Teams 36 | 37 | Commonly called team. 38 | Team name is commonly called tname (but may be called team in old code). 39 | 40 | ```lua 41 | ctf.teams = { 42 | teamname = (team_table) 43 | } 44 | 45 | (team_table) = { 46 | data = { 47 | name = "teamname", 48 | color = "teamcolor" -- see ctf_colors 49 | }, 50 | flags = { 51 | (flag_table), (flag_table) 52 | }, 53 | players = { 54 | username1 = (player_table), 55 | username2 = (player_table) 56 | }, 57 | spawn = { x=0, y=0, z=0 } 58 | -- fallback team spawn. Read by ctf.get_spawn() and overriding functions 59 | -- Don't use directly, instead call ctf.get_spawn("teamname") 60 | } 61 | 62 | (flag_table) = { 63 | x=0, y=0, z=0, 64 | flag_name = "Capital" -- human readable name 65 | } 66 | ``` 67 | 68 | ## Diplomacy 69 | 70 | ```lua 71 | ctf.diplo.diplo = { 72 | (diplo_table), (diplo_table) 73 | } 74 | 75 | (diplo_table) = { 76 | one = "teamname1", 77 | two = "teamname2", 78 | state = "war" / "peace" / "alliance" 79 | } 80 | ``` 81 | -------------------------------------------------------------------------------- /doc_project_overview.md: -------------------------------------------------------------------------------- 1 | # Welcome 2 | 3 | The aim of CTF_PvP_Engine is to provide a base to any subgame which uses the 4 | concepts of teams. Flags are a plugin mod, so it isn't CTF as such. 5 | 6 | # Modules in CTF_PvP_Engine 7 | 8 | ## hudkit 9 | 10 | A support library to make the HUD API nicer. 11 | WTFPL. 12 | 13 | ## ctf 14 | 15 | Requires hudkit. Support for chatplus. 16 | Core framework, players, teams, diplomacy, hud and gui. 17 | 18 | * core - adds saving, loading and settings. All modules depend on this. 19 | * teams - add the concepts of teams and players. All modules except core depend on this. 20 | * diplomacy - adds inter team states of war, peace and alliances. 21 | * gui - adds the team gui on /team. Allows tabs to be registered. 22 | * hud - adds the name of the team in the TR of the screen, and sets the color 23 | 24 | ## ctf_chat 25 | 26 | Requires ctf. Support for chatplus. 27 | Chat commands and chat channels. 28 | 29 | ## ctf_colors 30 | 31 | Requires ctf. Support for 3d_armor. 32 | Adds player colors. 33 | 34 | * gui - settings form 35 | * hud - team name color, player skin color, nametag color 36 | * init - table of colors 37 | 38 | ## ctf_flag 39 | 40 | Requires ctf and ctf_colors. Support for chatplus. 41 | Adds flags and flag taking. 42 | 43 | * api - flag callbacks, flag management (adding, capturing, updating), flag checking (asserts) 44 | * flag_func - functions for flag node definitions. 45 | * flags - flag node definitions. 46 | * gui - flag naming GUI, flag teleport GUI. 47 | * hud - waypoints, alerts ("Punch the enemy flag!" etc in top right) 48 | * init - get nearest flag, overrides ctf.get_spawn(), minimum build range, pick up sound, flag capture timeout. 49 | 50 | ## ctf_protect 51 | 52 | Adds node ownership / protection to teams. 53 | Requires ctf_flag. 54 | 55 | # Past/Other Mods 56 | 57 | Please look 58 | 59 | ## ctf_turret 60 | 61 | Adds auto-firing turrets that fire on enemies. 62 | See git history. 63 | 64 | ## Capture the flag 65 | 66 | more mods available in [capture the flag](http://github.com/rubenwardy/capturetheflag/). 67 | 68 | * ctf_match - adds the concept of winning, match build time, 69 | and reseting the map / setting up a new game. 70 | Requires ctf_flag 71 | -------------------------------------------------------------------------------- /doc_settings.md: -------------------------------------------------------------------------------- 1 | # ctf 2 | 3 | | name | default value | description | 4 | | -------------------------- | ------------- | ---------------------------------------------------------------- | 5 | | allocate_mode | 0 | 0=off, 1=firstnonfullteam, 2=RandomOfSmallestTwo, 3=SmallestTeam | 6 | | autoalloc_on_joinplayer | true | Trigger auto alloc on join player | 7 | | default_diplo_state | "war" | "war", "alliance" or "peace" | 8 | | diplomacy | true | Is diplomacy enabled | 9 | | friendly_fire | true | True if players can't hit other players on their team | 10 | | maximum_in_team | -1 | Player cap | 11 | | players_can_change_team | true | | 12 | | hud | true | Enable HUD | 13 | | gui | true | Enable GUI | 14 | | gui.team | true | Whether to show team gui (/team) | 15 | | gui.team.initial | "news" | Initial tab | 16 | | gui.tab.diplo | true | Show diplo tab | 17 | | gui.tab.news | true | Show news tab | 18 | | gui.tab.settings | true | Show settings tab | 19 | | spawn_offset | {x=0, y=0, z=0} | Offset of static spawn-point from team-base | 20 | 21 | # ctf_chat 22 | 23 | | name | default value | description | 24 | | -------------------------- | ------------- | ---------------------------------------------------------------- | 25 | | chat.default | "global" | "global" or "team" | 26 | | chat.global_channel | true | | 27 | | chat.team_channel | true | | 28 | 29 | # ctf_colors 30 | 31 | | name | default value | description | 32 | | -------------------------- | ------------- | ---------------------------------------------------------------- | 33 | | colors.nametag | true | Whether to colour the name tagColour nametag | 34 | | colors.nametag.tcolor | false | Base nametag colour on team colour | 35 | | colors.skins | false | Team skins are coloured | 36 | 37 | # ctf_flag 38 | 39 | | name | default value | description | 40 | | -------------------------- | ------------- | ---------------------------------------------------------------- | 41 | | flag.alerts | true | Prompts like "X has captured your flag" | 42 | | flag.alerts.neutral_alert | true | Show prompt in neutral state, ie: "attack and defend!" | 43 | | flag.allow_multiple | true | Teams can have multiple flags | 44 | | flag.capture_take | false | Whether a player needs to return flag to base to capture | 45 | | flag.drop_time | 420 | Time in seconds before a player drops the flag they're holding | 46 | | flag.drop_warn_time | 60 | Warning time before drop | 47 | | flag.nobuild_radius | 3 | Area around flag where you can't build | 48 | | flag.names | true | Enable naming flags | 49 | | flag.protect_distance | 25 | Area protection distance | 50 | | flag.waypoints | true | Enable waypoints to flags | 51 | | flag.crafting | false | Enable the crafting of flags | 52 | | gui.tab.flags | true | Show flags tab | 53 | | gui.team.teleport_to_flag | true | Enable teleport to flag button in flags tab | 54 | | gui.team.teleport_to_spawn | false | Enable teleport to spawn button in flags tab | 55 | 56 | # ctf_protect 57 | 58 | | name | default value | description | 59 | | -------------------------- | ------------- | ---------------------------------------------------------------- | 60 | | node_ownership | true | Whether node protection per team is enabled | 61 | -------------------------------------------------------------------------------- /hudkit/init.lua: -------------------------------------------------------------------------------- 1 | function hudkit() 2 | return { 3 | players = {}, 4 | 5 | add = function(self, player, id, def) 6 | local name = player:get_player_name() 7 | local elements = self.players[name] 8 | 9 | if not elements then 10 | self.players[name] = {} 11 | elements = self.players[name] 12 | end 13 | 14 | elements[id] = { 15 | id = player:hud_add(def), 16 | def = def 17 | } 18 | return true 19 | end, 20 | 21 | exists = function(self, player, id) 22 | if not player then 23 | return false 24 | end 25 | 26 | local name = player:get_player_name() 27 | local elements = self.players[name] 28 | 29 | if not elements or not elements[id] then 30 | return false 31 | end 32 | return true 33 | end, 34 | 35 | change = function(self, player, id, stat, value) 36 | if not player then 37 | return false 38 | end 39 | 40 | local name = player:get_player_name() 41 | local elements = self.players[name] 42 | 43 | if not elements or not elements[id] or not elements[id].id then 44 | return false 45 | end 46 | 47 | if elements[id].def[stat] ~= value then 48 | elements[id].def[stat] = value 49 | player:hud_change(elements[id].id, stat, value) 50 | end 51 | return true 52 | end, 53 | 54 | remove = function(self, player, id) 55 | local name = player:get_player_name() 56 | local elements = self.players[name] 57 | 58 | if not elements or not elements[id] or not elements[id].id then 59 | return false 60 | end 61 | 62 | player:hud_remove(elements[id].id) 63 | elements[id] = nil 64 | return true 65 | end 66 | } 67 | end 68 | -------------------------------------------------------------------------------- /hudkit/mod.conf: -------------------------------------------------------------------------------- 1 | name = hudkit 2 | -------------------------------------------------------------------------------- /lib_chatcmdbuilder/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/lin,linux,windows,lua 3 | 4 | #!! ERROR: lin is undefined. Use list command to see defined gitignore types !!# 5 | 6 | ### Linux ### 7 | *~ 8 | 9 | # temporary files which can be created if a process still has a handle open of a deleted file 10 | .fuse_hidden* 11 | 12 | # KDE directory preferences 13 | .directory 14 | 15 | # Linux trash folder which might appear on any partition or disk 16 | .Trash-* 17 | 18 | 19 | ### Windows ### 20 | # Windows image file caches 21 | Thumbs.db 22 | ehthumbs.db 23 | 24 | # Folder config file 25 | Desktop.ini 26 | 27 | # Recycle Bin used on file shares 28 | $RECYCLE.BIN/ 29 | 30 | # Windows Installer files 31 | *.cab 32 | *.msi 33 | *.msm 34 | *.msp 35 | 36 | # Windows shortcuts 37 | *.lnk 38 | 39 | 40 | ### Lua ### 41 | # Compiled Lua sources 42 | luac.out 43 | 44 | # luarocks build files 45 | *.src.rock 46 | *.zip 47 | *.tar.gz 48 | 49 | # Object files 50 | *.o 51 | *.os 52 | *.ko 53 | *.obj 54 | *.elf 55 | 56 | # Precompiled Headers 57 | *.gch 58 | *.pch 59 | 60 | # Libraries 61 | *.lib 62 | *.a 63 | *.la 64 | *.lo 65 | *.def 66 | *.exp 67 | 68 | # Shared objects (inc. Windows DLLs) 69 | *.dll 70 | *.so 71 | *.so.* 72 | *.dylib 73 | 74 | # Executables 75 | *.exe 76 | *.out 77 | *.app 78 | *.i*86 79 | *.x86_64 80 | *.hex 81 | -------------------------------------------------------------------------------- /lib_chatcmdbuilder/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016-17 rubenwardy 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /lib_chatcmdbuilder/README.md: -------------------------------------------------------------------------------- 1 | # ChatCmdBuilder 2 | 3 | Easily create complex chat commands with no regex. 4 | Created by rubenwardy 5 | License: MIT 6 | 7 | # Usage 8 | 9 | ## Registering Chat Commands 10 | 11 | `ChatCmdBuilder.new(name, setup)` registers a new chat command called `name`. 12 | Setup is called immediately after calling `new` to initialise subcommands. 13 | 14 | You can set values in the chat command definition by using def: 15 | `ChatCmdBuilder.new(name, setup, def)`. 16 | 17 | Here is an example: 18 | 19 | ```Lua 20 | ChatCmdBuilder.new("admin", function(cmd) 21 | cmd:sub("kill :target", function(name, target) 22 | local player = minetest.get_player_by_name(target) 23 | if player then 24 | player:set_hp(0) 25 | return true, "Killed " .. target 26 | else 27 | return false, "Unable to find " .. target 28 | end 29 | end) 30 | 31 | cmd:sub("move :target to :pos:pos", function(name, target, pos) 32 | local player = minetest.get_player_by_name(target) 33 | if player then 34 | player:setpos(pos) 35 | return true, "Moved " .. target .. " to " .. minetest.pos_to_string(pos) 36 | else 37 | return false, "Unable to find " .. target 38 | end 39 | end) 40 | end, { 41 | description = "Admin tools", 42 | privs = { 43 | kick = true, 44 | ban = true 45 | } 46 | }) 47 | ``` 48 | 49 | A player could then do `/admin kill player1` to kill player1, 50 | or `/admin move player1 to 0,0,0` to teleport a user. 51 | 52 | ## Introduction to Routing 53 | 54 | A route is a string. Let's look at `move :target to :pos:pos`: 55 | 56 | * `move` and `to` are constants. They need to be there in order to match. 57 | * `:target` and `:pos:pos` are parameters. They're passed to the function. 58 | * The second `pos` in `:pos:pos` after `:` is the param type. `:target` has an implicit 59 | type of `word`. 60 | 61 | ## Param Types 62 | 63 | * `word` - default. Any string without spaces. 64 | * `number` - Any number, including decimals 65 | * `int` - Any integer, no decimals 66 | * `text` - Any string 67 | * `pos` - 1,2,3 or 1.1,2,3.4567 or (1,2,3) or 1.2, 2 ,3.2 68 | 69 | ## Build chat command function 70 | 71 | If you don't want to register the chatcommand at this point, you can just generate 72 | a function using `ChatCmdBuilder.build`. 73 | 74 | For example, this is the full definition of ChatCmdBuilder.new: 75 | 76 | ```Lua 77 | function ChatCmdBuilder.new(name, func, def) 78 | def = def or {} 79 | def.func = ChatCmdBuilder.build(name, func) 80 | minetest.register_chatcommand(name, def) 81 | end 82 | ``` 83 | 84 | ## Run tests 85 | 86 | ```Bash 87 | sudo apt-get install luajit 88 | luajit init.lua 89 | ``` 90 | -------------------------------------------------------------------------------- /lib_chatcmdbuilder/description.txt: -------------------------------------------------------------------------------- 1 | A library to make registering chat commands easier 2 | -------------------------------------------------------------------------------- /lib_chatcmdbuilder/init.lua: -------------------------------------------------------------------------------- 1 | ChatCmdBuilder = {} 2 | 3 | function ChatCmdBuilder.new(name, func, def) 4 | def = def or {} 5 | local cmd = ChatCmdBuilder.build(func) 6 | cmd.def = def 7 | def.func = cmd.run 8 | minetest.register_chatcommand(name, def) 9 | return cmd 10 | end 11 | 12 | local STATE_READY = 1 13 | local STATE_PARAM = 2 14 | local STATE_PARAM_TYPE = 3 15 | local bad_chars = {} 16 | bad_chars["("] = true 17 | bad_chars[")"] = true 18 | bad_chars["."] = true 19 | bad_chars["%"] = true 20 | bad_chars["+"] = true 21 | bad_chars["-"] = true 22 | bad_chars["*"] = true 23 | bad_chars["?"] = true 24 | bad_chars["["] = true 25 | bad_chars["^"] = true 26 | bad_chars["$"] = true 27 | local function escape(char) 28 | if bad_chars[char] then 29 | return "%" .. char 30 | else 31 | return char 32 | end 33 | end 34 | 35 | function ChatCmdBuilder.build(func) 36 | local cmd = { 37 | _subs = {} 38 | } 39 | function cmd:sub(route, func, def) 40 | print("Parsing " .. route) 41 | 42 | def = def or {} 43 | if string.trim then 44 | route = string.trim(route) 45 | end 46 | 47 | local sub = { 48 | pattern = "^", 49 | params = {}, 50 | func = func 51 | } 52 | 53 | -- End of param reached: add it to the pattern 54 | local param = "" 55 | local param_type = "" 56 | local should_be_eos = false 57 | local function finishParam() 58 | if param ~= "" and param_type ~= "" then 59 | print(" - Found param " .. param .. " type " .. param_type) 60 | 61 | if param_type == "pos" then 62 | sub.pattern = sub.pattern .. "%(? *(%-?[%d.]+) *, *(%-?[%d.]+) *, *(%-?[%d.]+) *%)?" 63 | elseif param_type == "text" then 64 | sub.pattern = sub.pattern .. "(*+)" 65 | should_be_eos = true 66 | elseif param_type == "number" then 67 | sub.pattern = sub.pattern .. "([%d.]+)" 68 | elseif param_type == "int" then 69 | sub.pattern = sub.pattern .. "([%d]+)" 70 | else 71 | if param_type ~= "word" then 72 | print("Unrecognised param_type=" .. param_type .. ", using 'word' type instead") 73 | param_type = "word" 74 | end 75 | sub.pattern = sub.pattern .. "([^ ]+)" 76 | end 77 | 78 | table.insert(sub.params, param_type) 79 | 80 | param = "" 81 | param_type = "" 82 | end 83 | end 84 | 85 | -- Iterate through the route to find params 86 | local state = STATE_READY 87 | for i = 1, #route do 88 | local c = route:sub(i, i) 89 | if should_be_eos then 90 | error("Should be end of string. Nothing is allowed after a param of type text.") 91 | end 92 | 93 | if state == STATE_READY then 94 | if c == ":" then 95 | print(" - Found :, entering param") 96 | state = STATE_PARAM 97 | param_type = "word" 98 | else 99 | sub.pattern = sub.pattern .. escape(c) 100 | end 101 | elseif state == STATE_PARAM then 102 | if c == ":" then 103 | print(" - Found :, entering param type") 104 | state = STATE_PARAM_TYPE 105 | param_type = "" 106 | elseif c:match("%W") then 107 | print(" - Found nonalphanum, leaving param") 108 | state = STATE_READY 109 | finishParam() 110 | sub.pattern = sub.pattern .. escape(c) 111 | else 112 | param = param .. c 113 | end 114 | elseif state == STATE_PARAM_TYPE then 115 | if c:match("%W") then 116 | print(" - Found nonalphanum, leaving param type") 117 | state = STATE_READY 118 | finishParam() 119 | sub.pattern = sub.pattern .. escape(c) 120 | else 121 | param_type = param_type .. c 122 | end 123 | end 124 | end 125 | print(" - End of route") 126 | finishParam() 127 | sub.pattern = sub.pattern .. "$" 128 | print("Pattern: " .. sub.pattern) 129 | 130 | table.insert(self._subs, sub) 131 | end 132 | 133 | if func then 134 | func(cmd) 135 | end 136 | 137 | cmd.run = function(name, param) 138 | for i = 1, #cmd._subs do 139 | local sub = cmd._subs[i] 140 | local res = { string.match(param, sub.pattern) } 141 | if #res > 0 then 142 | local pointer = 1 143 | local params = { name } 144 | for j = 1, #sub.params do 145 | local param = sub.params[j] 146 | if param == "pos" then 147 | local pos = { 148 | x = tonumber(res[pointer]), 149 | y = tonumber(res[pointer + 1]), 150 | z = tonumber(res[pointer + 2]) 151 | } 152 | table.insert(params, pos) 153 | pointer = pointer + 3 154 | elseif param == "number" or param == "int" then 155 | table.insert(params, tonumber(res[pointer])) 156 | pointer = pointer + 1 157 | else 158 | table.insert(params, res[pointer]) 159 | pointer = pointer + 1 160 | end 161 | end 162 | return sub.func(unpack(params)) 163 | end 164 | end 165 | print("No matches") 166 | end 167 | 168 | return cmd 169 | end 170 | 171 | local function run_tests() 172 | if not (ChatCmdBuilder.build(function(cmd) 173 | cmd:sub("bar :one and :two:word", function(name, one, two) 174 | if name == "singleplayer" and one == "abc" and two == "def" then 175 | return true 176 | end 177 | end) 178 | end))("singleplayer", "bar abc and def") then 179 | error("Test 1 failed") 180 | end 181 | 182 | local move = ChatCmdBuilder.build(function(cmd) 183 | cmd:sub("move :target to :pos:pos", function(name, target, pos) 184 | if name == "singleplayer" and target == "player1" and 185 | pos.x == 0 and pos.y == 1 and pos.z == 2 then 186 | return true 187 | end 188 | end) 189 | end) 190 | if not move("singleplayer", "move player1 to 0,1,2") then 191 | error("Test 2 failed") 192 | end 193 | if not move("singleplayer", "move player1 to (0,1,2)") then 194 | error("Test 3 failed") 195 | end 196 | if not move("singleplayer", "move player1 to 0, 1,2") then 197 | error("Test 4 failed") 198 | end 199 | if not move("singleplayer", "move player1 to 0 ,1, 2") then 200 | error("Test 5 failed") 201 | end 202 | if not move("singleplayer", "move player1 to 0, 1, 2") then 203 | error("Test 6 failed") 204 | end 205 | if not move("singleplayer", "move player1 to 0 ,1 ,2") then 206 | error("Test 7 failed") 207 | end 208 | if not move("singleplayer", "move player1 to ( 0 ,1 ,2)") then 209 | error("Test 7 failed") 210 | end 211 | if move("singleplayer", "move player1 to abc,def,sdosd") then 212 | error("Test 8 failed") 213 | end 214 | if move("singleplayer", "move player1 to abc def sdosd") then 215 | error("Test 8 failed") 216 | end 217 | 218 | if not (ChatCmdBuilder.build(function(cmd) 219 | cmd:sub("does :one:int plus :two:int equal :three:int", function(name, one, two, three) 220 | if name == "singleplayer" and one + two == three then 221 | return true 222 | end 223 | end) 224 | end))("singleplayer", "does 1 plus 2 equal 3") then 225 | error("Test 9 failed") 226 | end 227 | end 228 | if not minetest then 229 | run_tests() 230 | end 231 | -------------------------------------------------------------------------------- /lib_chatcmdbuilder/mod.conf: -------------------------------------------------------------------------------- 1 | name = lib_chatcmdbuilder 2 | title = Chat Command Builder 3 | author = rubenwardy 4 | description = A library to make registering chat commands easier 5 | license = MIT 6 | forum = https://forum.minetest.net/viewtopic.php?t=14899 7 | version = 0.1.0 8 | -------------------------------------------------------------------------------- /modpack.conf: -------------------------------------------------------------------------------- 1 | name = ctf_pvp_engine 2 | --------------------------------------------------------------------------------