Merge pull request #1 from MT-CTF/master

update
This commit is contained in:
olliy 2021-04-01 15:00:47 +05:00 committed by GitHub
commit 1225504258
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
53 changed files with 862 additions and 294 deletions

View file

@ -18,7 +18,7 @@ globals = {
"ctf_match", "ctf_stats", "ctf_treasure", "ctf_playertag", "chatplus", "irc",
"armor", "vote", "give_initial_stuff", "hud_score", "physics", "tsm_chests",
"armor", "shooter", "grenades", "ctf_classes", "ctf_bandages", "ctf_respawn_immunity",
"ctf_marker",
"ctf_marker", "kill_assist"
}
read_globals = {

View file

@ -1,3 +1,4 @@
name = Capture the Flag
description = Use swords, guns, and grenades to combat the enemy and capture their flag before they capture yours.
allowed_mapgens = singlenode
min_minetest_version = 5.3

View file

@ -103,9 +103,9 @@ function ctf.error(area, msg)
end
function ctf.log(area, msg)
if area and area ~= "" then
minetest.log("[CTF | " .. area .. "] " .. msg)
minetest.log("info", "[CTF | " .. area .. "] " .. msg)
else
minetest.log("[CTF]" .. msg)
minetest.log("info", "[CTF]" .. msg)
end
end
function ctf.action(area, msg)

View file

@ -445,6 +445,19 @@ function ctf.register_on_killedplayer(func)
end
table.insert(ctf.registered_on_killedplayer, func)
end
function ctf.can_attack(player, hitter, time_from_last_punch, tool_capabilities, dir, damage, ...)
return true
end
ctf.registered_on_attack = {}
function ctf.register_on_attack(func)
if ctf._mt_loaded then
error("You can't register callbacks at game time!")
end
table.insert(ctf.registered_on_attack, func)
end
local dead_players = {}
minetest.register_on_respawnplayer(function(player)
dead_players[player:get_player_name()] = nil
@ -453,7 +466,7 @@ minetest.register_on_joinplayer(function(player)
dead_players[player:get_player_name()] = nil
end)
minetest.register_on_punchplayer(function(player, hitter,
time_from_last_punch, tool_capabilities, dir, damage)
time_from_last_punch, tool_capabilities, dir, damage, ...)
if player and hitter then
local pname = player:get_player_name()
local hname = hitter:get_player_name()
@ -473,6 +486,12 @@ minetest.register_on_punchplayer(function(player, hitter,
end
end
if ctf.can_attack(player, hitter, time_from_last_punch, tool_capabilities,
dir, damage, ...) == false
then
return true
end
local hp = player:get_hp()
if hp == 0 then
return false
@ -487,5 +506,12 @@ minetest.register_on_punchplayer(function(player, hitter,
end
return false
end
for i = 1, #ctf.registered_on_attack do
ctf.registered_on_attack[i](
player, hitter, time_from_last_punch,
tool_capabilities, dir, damage, ...
)
end
end
end)

View file

@ -1,7 +1,7 @@
--Inspired from Andrey's bandages mod
ctf_bandages = {}
ctf_bandages.heal_percent = 0.75 --Percentage of total HP to be healed
ctf_bandages.heal_percent = 0.75 -- Percentage of total HP to be healed
minetest.register_craftitem("ctf_bandages:bandage", {
description = "Bandage\n\n" ..
@ -10,32 +10,53 @@ minetest.register_craftitem("ctf_bandages:bandage", {
inventory_image = "ctf_bandages_bandage.png",
stack_max = 1,
on_use = function(itemstack, player, pointed_thing)
if pointed_thing.type ~= "object" then
return
end
if pointed_thing.type ~= "object" then return end
local object = pointed_thing.ref
if not object:is_player() then
return
end
if not object:is_player() then return end
local pname = object:get_player_name()
local name = player:get_player_name()
if ctf.player(pname).team == ctf.player(name).team then
local hp = object:get_hp()
local limit = ctf_bandages.heal_percent *
object:get_properties().hp_max
if hp > 0 and hp < limit then
hp = hp + math.random(3,4)
local limit = ctf_bandages.heal_percent * object:get_properties().hp_max
if hp <= 0 then
hud_event.new(name, {
name = "ctf_bandages:dead",
color = "warning",
value = pname .. " is dead!",
})
elseif hp >= limit then
hud_event.new(name, {
name = "ctf_bandages:limit",
color = "warning",
value = pname .. " already has " .. limit .. " HP!",
})
else
local hp_add = math.random(3,4)
kill_assist.add_heal_assist(pname, hp_add)
hp = hp + hp_add
if hp > limit then
hp = limit
end
object:set_hp(hp)
minetest.chat_send_player(pname, minetest.colorize("#C1FF44", name .. " has healed you!"))
return itemstack
else
minetest.chat_send_player(name, pname .. " has " .. hp .. " HP. You can't heal them.")
hud_event.new(pname, {
name = "ctf_bandages:heal",
color = 0xC1FF44,
value = name .. " healed you!",
})
end
else
minetest.chat_send_player(name, pname.." isn't in your team!")
hud_event.new(name, {
name = "ctf_bandages:team",
color = "warning",
value = pname .. " isn't in your team!",
})
end
end,
})

View file

@ -183,6 +183,7 @@ minetest.register_chatcommand("team", {
minetest.register_chatcommand("join", {
params = "team name",
description = "Add to team",
privs = {ctf_team_mgr = true},
func = function(name, param)
if ctf.join(name, param, false, name) then
return true, "Joined team " .. param .. "!"
@ -271,6 +272,8 @@ minetest.register_chatcommand("t", {
end
})
local function me_func() end
if minetest.global_exists("irc") then
function irc.playerMessage(name, message)
local color = ctf_colors.get_irc_color(ctf.player(name))
@ -285,6 +288,14 @@ if minetest.global_exists("irc") then
local bbrace = color .. ">" .. clear
return ("%s%s%s %s"):format(abrace, name, bbrace, message)
end
me_func = function(...)
local message = irc.playerMessage(...)
message = "*" .. message:sub(message:find(" "))
irc.say(message)
end
end
local handler
@ -312,6 +323,8 @@ end
table.insert(minetest.registered_on_chat_messages, 1, handler)
minetest.registered_chatcommands["me"].func = function(name, param)
me_func(name, param)
if ctf.player(name).team then
local tcolor = ctf_colors.get_color(ctf.player(name))
name = minetest.colorize(tcolor.css, "* " .. name)
@ -319,5 +332,7 @@ minetest.registered_chatcommands["me"].func = function(name, param)
name = "* ".. name
end
minetest.log("action", "[CHAT] "..name.." "..param)
minetest.chat_send_all(name .. " " .. param)
end

View file

@ -73,17 +73,19 @@ function ctf_classes.set(player, new_name)
local meta = player:get_meta()
local old_name = meta:get("ctf_classes:class")
if old_name == new_name then
return
end
meta:set_string("ctf_classes:class", new_name)
ctf_classes.update(player)
ctf_classes.set_cooldown(player:get_player_name())
if old_name == nil or old_name ~= new_name then
local old = old_name and ctf_classes.__classes[old_name]
for i=1, #registered_on_changed do
registered_on_changed[i](player, old, new)
end
end
end
local function set_max_hp(player, max_hp)

View file

@ -99,6 +99,7 @@ minetest.override_item("ctf_bandages:bandage", {
if ctf.player(pname).team == ctf.player(name).team then
local nodename = minetest.get_node(object:get_pos()).name
if ctf_classes.dont_heal[pname] or nodename:find("lava") or nodename:find("water") or nodename:find("trap") then
minetest.chat_send_player(name, "You can't heal player in lava, water or spikes!")
return -- Can't heal players in lava/water/spikes
end
@ -132,7 +133,7 @@ minetest.override_item("ctf_bandages:bandage", {
})
local diggers = {}
local DIG_COOLDOWN = 45
local DIG_COOLDOWN = 30
local DIG_DIST_LIMIT = 30
local DIG_SPEED = 0.1
@ -147,17 +148,21 @@ local function isdiggable(name)
)
end
local function paxel_stop(pname, reason)
hud_event.new(pname, {
name = "ctf_classes:paxel_stop",
color = "success",
value = table.concat({"Pillar digging stopped", reason, "- wait " .. DIG_COOLDOWN .. "s"}, " "),
})
diggers[pname] = minetest.after(DIG_COOLDOWN, function() diggers[pname] = nil end)
end
local function remove_pillar(pos, pname)
local name = minetest.get_node(pos).name
if name:find("default") and isdiggable(name) then
local player = minetest.get_player_by_name(pname)
if minetest.get_modpath("antisabotage") then
-- Fix paxel being capable of mining blocks under teammates
if antisabotage.is_sabotage(pos, minetest.get_node(pos), player) then return end
end
minetest.dig_node(pos)
if player and diggers[pname] and type(diggers[pname]) ~= "table" then
@ -165,13 +170,11 @@ local function remove_pillar(pos, pname)
pos.y = pos.y + 1
minetest.after(DIG_SPEED, remove_pillar, pos, pname)
else
minetest.chat_send_player(pname, "Pillar digging stopped, too far away from digging pos. Can activate again in "..DIG_COOLDOWN.." seconds")
diggers[pname] = minetest.after(DIG_COOLDOWN, function() diggers[pname] = nil end)
paxel_stop(pname, "at too far away node")
end
end
else
minetest.chat_send_player(pname, "Pillar digging stopped at undiggable node. Can activate again in "..DIG_COOLDOWN.." seconds")
diggers[pname] = minetest.after(DIG_COOLDOWN, function() diggers[pname] = nil end)
paxel_stop(pname, "at undiggable node")
end
end
@ -195,20 +198,39 @@ minetest.register_tool("ctf_classes:paxel_bronze", {
if pointed_thing.type == "node" then
local pname = placer:get_player_name()
if not isdiggable(minetest.get_node(pointed_thing.under).name) or ctf_match.is_in_build_time() then
minetest.chat_send_player(pname, "Can't dig node or build time active")
if not isdiggable(minetest.get_node(pointed_thing.under).name) then
hud_event.new(pname, {
name = "ctf_classes:paxel_undiggable",
color = "warning",
value = "Can't paxel node!",
})
return minetest.item_place(itemstack, placer, pointed_thing)
end
if ctf_match.is_in_build_time() then
hud_event.new(pname, {
name = "ctf_classes:paxel_build_time",
color = "warning",
value = "Build time active!",
})
return minetest.item_place(itemstack, placer, pointed_thing)
end
if not diggers[pname] then
minetest.chat_send_player(pname, "Pillar digging started")
hud_event.new(pname, {
name = "ctf_classes:paxel_start",
color = "primary",
value = "Pillar digging started",
})
diggers[pname] = true
remove_pillar(pointed_thing.under, pname)
elseif type(diggers[pname]) ~= "table" then
minetest.chat_send_player(pname, "Pillar digging stopped. Can activate again in "..DIG_COOLDOWN.." seconds")
diggers[pname] = minetest.after(DIG_COOLDOWN, function() diggers[pname] = nil end)
paxel_stop(pname)
else
minetest.chat_send_player(pname, "You can't activate yet")
hud_event.new(pname, {
name = "ctf_classes:paxel_timer",
color = "warning",
value = "You can't activate yet!",
})
end
end
end,
@ -220,8 +242,7 @@ minetest.register_tool("ctf_classes:paxel_bronze", {
minetest.after(2, function()
if user and user:get_player_control().RMB then
if diggers[pname] and type(diggers[pname]) ~= "table" then
minetest.chat_send_player(pname, "Pillar digging stopped. Can activate again in "..DIG_COOLDOWN.." seconds")
diggers[pname] = minetest.after(DIG_COOLDOWN, function() diggers[pname] = nil end)
paxel_stop(pname)
end
end
end)

View file

@ -29,12 +29,12 @@ end, true)
local sword_special_timer = {}
local SWORD_SPECIAL_COOLDOWN = 40
local SWORD_SPECIAL_COOLDOWN = 20
local function sword_special_timer_func(pname, timeleft)
sword_special_timer[pname] = timeleft
if timeleft - 10 >= 0 then
minetest.after(10, sword_special_timer_func, pname, timeleft - 10)
if timeleft - 2 >= 0 then
minetest.after(2, sword_special_timer_func, pname, timeleft - 2)
else
sword_special_timer[pname] = nil
end
@ -57,8 +57,8 @@ minetest.register_tool("ctf_classes:sword_bronze", {
local pname = placer:get_player_name()
if not pointed_thing then return end
if sword_special_timer[pname] then
minetest.chat_send_player(pname, "You can't place a marker yet (>"..sword_special_timer[pname].."s left)")
if sword_special_timer[pname] and placer:get_player_control().sneak then
minetest.chat_send_player(pname, "You have to wait "..sword_special_timer[pname].."s to place marker again")
if pointed_thing.type == "node" then
return minetest.item_place(itemstack, placer, pointed_thing)
@ -89,7 +89,7 @@ minetest.register_tool("ctf_classes:sword_bronze", {
if #enemies > 0 then
ctf_marker.remove_marker(pteam)
ctf_marker.add_marker(pname, pteam, pos, ("[Enemies Found!: <%s>]"):format(table.concat(enemies, ", ")))
ctf_marker.add_marker(pname, pteam, pos, (" found enemies: <%s>]"):format(table.concat(enemies, ", ")))
end
return
@ -102,10 +102,10 @@ minetest.register_tool("ctf_classes:sword_bronze", {
-- Check if player is sneaking before placing marker
if not placer:get_player_control().sneak then return end
sword_special_timer[pname] = 20
sword_special_timer_func(pname, 20)
sword_special_timer[pname] = 4
sword_special_timer_func(pname, 4)
minetest.registered_chatcommands["m"].func(pname, "Marked with "..pname.."'s sword")
minetest.registered_chatcommands["m"].func(pname, "placed with sword")
end,
on_secondary_use = function(itemstack, user, pointed_thing)
if pointed_thing then

View file

@ -29,7 +29,7 @@ shooter.get_weapon_spec = function(user, weapon_name)
if table.indexof(class.properties.allowed_guns or {}, weapon_name) == -1 then
minetest.chat_send_player(user:get_player_name(),
"Your class can't use that weapon! Change your class at spawn")
"Your class can't use that weapon! Change your class at base")
return nil
end
@ -49,7 +49,7 @@ local function check_grapple(itemname)
on_use = function(itemstack, user, ...)
if not ctf_classes.get(user).properties.allow_grapples then
minetest.chat_send_player(user:get_player_name(),
"Your class can't use that weapon! Change your class at spawn")
"Your class can't use that weapon! Change your class at base")
return itemstack
end
@ -73,6 +73,12 @@ check_grapple("shooter_hook:grapple_hook")
-- Override grappling hook entity to check if player has flag before teleporting
local old_grapple_step = minetest.registered_entities["shooter_hook:hook"].on_step
minetest.registered_entities["shooter_hook:hook"].on_step = function(self, dtime, ...)
-- User left the game. Life is no longer worth living for this poor hook
if not self.user or not minetest.get_player_by_name(self.user) then
self.object:remove()
return
end
-- Remove entity if player has flag
-- This is to prevent players from firing the hook, and then punching the flag
if ctf_flag.has_flag(self.user) then

View file

@ -52,15 +52,6 @@ for ore, ore_item in pairs(full_ores) do
})
end
-- Bronze ingot <== Steel + Coal + wood
crafting.register_recipe({
type = "inv",
output = "default:bronze_ingot",
items = { "default:steel_ingot", "default:coal_lump", "group:wood"},
always_known = true,
level = 1,
})
-- Mese crystal x9 <== Mese block
crafting.register_recipe({
type = "inv",
@ -110,7 +101,7 @@ crafting.register_recipe({
crafting.register_recipe({
type = "inv",
output = "default:stick 4",
items = { "default:wood" },
items = { "group:wood" },
always_known = true,
level = 1,
})
@ -142,20 +133,11 @@ crafting.register_recipe({
level = 1,
})
-- Arrow x5
crafting.register_recipe({
type = "inv",
output = "shooter:arrow_white 5",
items = { "default:stick 5", "default:cobble" },
always_known = true,
level = 1,
})
-- 7.62mm sniper rifle (unloaded)
crafting.register_recipe({
type = "inv",
output = "sniper_rifles:rifle_762",
items = { "default:steel_ingot 10", "default:mese_crystal", "default:wood 2" },
items = { "default:steel_ingot 10", "default:mese_crystal", "group:wood 2" },
always_known = false,
level = 1
})
@ -164,7 +146,7 @@ crafting.register_recipe({
crafting.register_recipe({
type = "inv",
output = "sniper_rifles:rifle_magnum",
items = { "default:steel_ingot 5", "default:bronze_ingot 5", "default:diamond", "default:wood 3" },
items = { "default:steel_ingot 10", "default:coal_lump 5", "default:diamond", "group:wood 2" },
always_known = false,
level = 1,
})
@ -198,7 +180,7 @@ crafting.register_recipe({
crafting.register_recipe({
type = "inv",
output = "ctf_traps:dirt 1",
output = "ctf_traps:dirt 5",
items = { "default:dirt 5", "default:coal_lump" },
always_known = true,
level = 1,
@ -206,7 +188,7 @@ crafting.register_recipe({
crafting.register_recipe({
type = "inv",
output = "ctf_traps:cobble 1",
output = "ctf_traps:cobble 4",
items = { "default:cobble 4", "default:coal_lump" },
always_known = true,
level = 1,
@ -267,7 +249,7 @@ end
crafting.register_recipe({
type = "inv",
output = "grenades:frag 1",
items = { "default:steel_ingot 5", "default:iron_lump" },
items = { "default:steel_ingot 5", "default:iron_lump", "group:wood", "default:coal_lump",},
always_known = true,
level = 1,
})
@ -283,7 +265,7 @@ crafting.register_recipe({
crafting.register_recipe({
type = "inv",
output = "grenades:smoke 1",
items = { "default:steel_ingot 5", "default:coal_lump 4" },
items = { "default:steel_ingot 5", "default:coal_lump 5", "group:wood" },
always_known = true,
level = 1,
})

View file

@ -84,8 +84,11 @@ function minetest.is_protected(pos, name, ...)
local flag, distSQ = ctf_flag.get_nearest(pos)
if flag and pos.y >= flag.y - 1 and distSQ < rs then
minetest.chat_send_player(name,
"Too close to the flag to build! Leave at least " .. r .. " blocks around the flag.")
hud_event.new(name, {
name = "ctf_bandages:team",
color = "warning",
value = "Too close to the flag to build (< " .. r .. " nodes) !",
})
return true
else
return old_is_protected(pos, name, ...)

View file

@ -1,4 +1,4 @@
local color = "#66A0FF"
color = "#66A0FF"
local items = {
"",
color .. "Game Play",
@ -10,8 +10,8 @@ local items = {
"* Use medkits to replenish health gradually.",
"* Gain more score by killing more than you die, or by capturing the flag.",
"* Players are immune for 5 seconds after they respawn.",
"* Access the pro section of the chest by achieving a 2k+ score and",
" killing 2 people for every death.",
"* Access the pro section of the chest by achieving 2k+ score,",
" killing 3 people for every 2 deaths, and capturing the flag at least 10 times",
"",
color .. "Team Co-op",
@ -30,7 +30,12 @@ local items = {
color .. "Contact Moderators",
"",
"* Report people who sabotage using /report."
"* Report people using /report or the #reports channel in Discord",
"",
color .. "Other",
"",
"* Capture The Flag Discord: https://discord.gg/vcZTRPX",
}
for i = 1, #items do
items[i] = minetest.formspec_escape(items[i])

View file

@ -34,12 +34,13 @@ function ctf_map.place_base(color, pos)
minetest.set_node(pos3, chest)
local inv = minetest.get_meta(pos3):get_inventory()
inv:add_item("main", ItemStack("default:cobble 99"))
inv:add_item("main", ItemStack("default:cobble 99"))
inv:add_item("main", ItemStack("default:cobble 99"))
inv:add_item("main", ItemStack("ctf_map:reinforced_cobble 50"))
inv:add_item("main", ItemStack("ctf_traps:damage_cobble 40"))
inv:add_item("main", ItemStack("default:wood 99"))
inv:add_item("main", ItemStack("default:stick 30"))
inv:add_item("main", ItemStack("default:glass 5"))
inv:add_item("main", ItemStack("default:torch 10"))
inv:add_item("main", ItemStack("doors:door_steel 2"))
end
-- Override ctf.get_spawn to implement random spawns

View file

@ -102,7 +102,7 @@ for _, chest_color in pairs(colors) do
else
formspec = formspec .. "label[4.75,3;" ..
minetest.formspec_escape("You need at least 10000" ..
"\nscore and 1.5+ KD to\naccess the pro section") .. "]"
"\nscore, 1.5+ KD, and 10+\ncaptures to access the\npro section") .. "]"
end
formspec = formspec ..

@ -1 +1 @@
Subproject commit 9dd765d328e44d5a1ba2b2a444df4ce7e90b35be
Subproject commit bbf60077855ee90b95ca68f1b9bc853f7d53ecf2

View file

@ -424,6 +424,25 @@ do
sounds = default.node_sound_leaves_defaults()
})
minetest.register_node(":ctf_map:papyrus", {
description = "Indestructible Papyrus",
drawtype = "plantlike",
tiles = {"default_papyrus.png"},
inventory_image = "default_papyrus.png",
wield_image = "default_papyrus.png",
paramtype = "light",
sunlight_propagates = true,
walkable = false,
selection_box = {
type = "fixed",
fixed = {-6 / 16, -0.5, -6 / 16, 6 / 16, 0.5, 6 / 16},
},
groups = {immortal = 1},
sounds = default.node_sound_leaves_defaults(),
after_dig_node = function(pos, node, metadata, digger)
default.dig_up(pos, node, digger)
end,
})
minetest.register_node(":ctf_map:jungletree", {
description = "Indestructible Jungle Tree",

View file

@ -35,9 +35,9 @@ There are multiple ways do this, this is the simplest in most cases.
### 4. Place barriers
* Set the middle barrier direction. The barrier is a plane defined by a co-ordinate = 0.
If the barrier is X=0, then it will placed with every node of the barrier having X=0.
If the barrier is Z=0, then it will placed with every node of the barrier having Z=0.
* The barrier is a plane defined by co-ordinate (=0).
* If you choose `X=0` the barrier will be placed having the X co-ordinate as 0. But from a few months, the `X=0` co-ordinate creates bugs and errors. It's better if you choose `Z=0` for creating your map.
* If you choose `Z=0` The barrier will be placed having the Z co-ordinate as 0.
* Click "place barrier". Note that this command does not have an undo.
* After placing barriers you should place 2 flags where you want bases to be. You get flags in `/gui` --> `Giveme flags`
@ -48,7 +48,7 @@ There are multiple ways do this, this is the simplest in most cases.
### 6. Export
* Click export, and wait until completion.
* Copy the resultant folder from `worlddir/schems/` into `ctf_map/ctf_map_core/maps/`.
* Copy the resultant folder from `worlddir/schems/` into `games/capturetheflag/mods/ctf/ctf_map/ctf_map_core/maps/`.
* Profit!
@ -56,7 +56,7 @@ There are multiple ways do this, this is the simplest in most cases.
### Map meta
Each map's metadata is stored in an accompanying .conf file containing the following data:
Each map's metadata is stored in an accompanying `map.conf` file containing the following data:
* `name`: Name of map.
* `author`: Author of the map.
@ -73,7 +73,6 @@ zone `i`, relative to the center of the schem.
* `license`: Name of the license of the map.
* `other`: [Optional] Misc. information about the map. This is displayed in the maps catalog.
* `base_node`: [Optional] Technical name of node to be used for the team base.
* `schematic`: Name of the map's schematic.
* `initial_stuff`: [Optional] Comma-separated list of itemstacks to be given to the player
on join and on respawn.
* `treasures`: [Optional] List of treasures to be registered for the map, in a serialized
@ -86,16 +85,16 @@ format. Refer to the `treasures` sub-section for more details.
#### `license`
* Every map must have its own license. Once you've chosen your license, simply add the following line to the map's `.conf` file:
* Every map must have its own license. Once you've chosen your license, simply add the following line to the `map.conf` file:
```lua
```properties
license = <name>
```
* If attribution is required (for example if you modify other's map and you have to tell who is author of the original map), that has to be appended to the `license` field.
If you want to tell more infomation, you can use:
```lua
```properties
others = <description>
```
@ -110,12 +109,21 @@ If you want to tell more infomation, you can use:
An example `treasures` value that registers steel pick, shotgun, and grenade:
```lua
```properties
treasures = default:pick_steel,0.5,5,1,10;shooter:shotgun,0.04,2,1;shooter:grenade,0.08,2,1
```
(See [here](../../../other/treasurer/README.md) to understand the magic numbers)
#### `initial_stuff`
`initial_stuff` are the items given to players at their (re)spawn. The `initial_stuff` field is located in the `map.conf` file. At least a pickaxe and some torches should be given in the map's `initial_stuff`.
An example of `initial_stuff` value that registers a stone pickaxe, 30 cobblestones, 5 torches and a pistol is given below.
```properties
initial_stuff = default:pick_stone,default:cobble 30,default:torch 5,shooter:pistol
```
### `screenshot`
Every map must have its own screenshot in map's folder. It should have an aspect ratio of 3:2 (screenshot 600x400px is suggested).
@ -133,18 +141,18 @@ Six images which should be in map's folder.
* `skybox_5.png` - south
* `skybox_6.png` - north
You have to include skybox license in `license` in `.conf` file. We can only accept Free Software licenses, e.g. `CC0`, `CC BY 3.0`, `CC BY 4.0`, `CC BY-SA 3.0`, `CC BY-SA 4.0`.
You can find some good skyboxes with suitable licenses at [https://opengameart.org](https://opengameart.org/art-search-advanced?field_art_tags_tid=skybox).
You have to include skybox license in `license` in `map.conf` file. We can only accept Free Software licenses, e.g. `CC0`, `CC BY 3.0`, `CC BY 4.0`, `CC BY-SA 3.0`, `CC BY-SA 4.0`.
Before you test your skybox images in local CTF game, run the `update.sh` file in the `games/capturetheflag/` folder.
You can find some good skyboxes with suitable licenses at [opengameart.org](https://opengameart.org/art-search-advanced?field_art_tags_tid=skybox) or [www.humus.name](https://www.humus.name/index.php?page=Textures).
## Editing exported map
The easiest way to edit exported maps is the following:
* Create a world using `singlenode` mapgen. Enable `WorldEdit` and `ctf_map` mod,
* Go in the world's folder, create a folder named `schems`, and place the `.mts` file inside,
* Go in the world's folder, create a folder named `schems`, and place the `map.mts` file inside,
* Start the game, `/grantme all` and enable `fly` (there is no ground in singlenode mapgen),
* Do `//1` to set the position where you will generate the map,
* Do `//mtschemplace yourschematic` (where `yourschematic` is the name of the mts file without `.mts`).
* Do `//mtschemplace map` (where `map` is the name of the mts file without `.mts`).
When you finish:

View file

@ -5,6 +5,8 @@ function map_maker.show_gui(name)
local formspec = {
"size[9,9.5]",
"bgcolor[#080808BB;true]",
default.gui_bg,
default.gui_bg_img,
"label[0,0;1. Select Area]",
"field[0.4,1;1,1;posx;X;", context.center.x, "]",
@ -55,7 +57,8 @@ end
function map_maker.show_progress_formspec(name, text)
minetest.show_formspec(name, "ctf_map:progress",
"size[6,1]bgcolor[#080808BB;true]" ..
"label[0,0;" ..
default.gui_bg ..
default.gui_bg_img .. "label[0,0;" ..
minetest.formspec_escape(text) .. "]")
end

View file

@ -47,7 +47,7 @@ function ctf_marker.add_marker(name, tname, pos, str)
if tplayer then
teams[tname].players[pname] = tplayer:hud_add({
hud_elem_type = "waypoint",
name = str,
name = "[" .. name .. "'s marker" .. str,
number = tonumber(ctf.flag_colors[team.data.color]),
world_pos = pos
})
@ -99,7 +99,7 @@ minetest.register_chatcommand("m", {
local tname = ctf.player(name).team
-- Handle waypoint string
local str = (param and param:trim() ~= "") and param or name .. "'s marker"
local str = (param and param:trim() ~= "") and ": " .. param or ""
if pointed.type == "object" then
local concat
local obj = pointed.ref
@ -126,7 +126,7 @@ minetest.register_chatcommand("m", {
end
str = concat and str .. " <" .. concat .. ">"
end
str = "[" .. str .. "]"
str = str .. "]"
-- Remove existing marker if it exists
ctf_marker.remove_marker(tname)
@ -134,3 +134,46 @@ minetest.register_chatcommand("m", {
ctf_marker.add_marker(name, tname, minetest.get_pointed_thing_position(pointed), str)
end
})
local function mr_command(name)
local tname = ctf.player(name).team
local player = minetest.get_player_by_name(name)
local mmsg = ""
local args = ""
local function hud_check()
if teams[tname].players[name] then
mmsg = player:hud_get(teams[tname].players[name]).name
args = mmsg:split("'")
end
end
if pcall(hud_check) then
if args[1] == "[" .. name then
ctf_marker.remove_marker(tname)
local team = ctf.team(tname)
for pname, _ in pairs(team.players) do
minetest.chat_send_player(pname, msg("* " .. name .. " removed their marker!"))
end
elseif args[1] == "" or nil then
minetest.chat_send_player(name, msg("No marker to remove"))
else
minetest.chat_send_player(name, msg("Not your marker!"))
end
else
minetest.chat_send_player(name, msg("No marker to remove"))
end
end
minetest.register_chatcommand("m_remove", {
description = "Remove your own marker (/mr)",
privs = {interact = true},
func = mr_command
})
minetest.register_chatcommand("mr", {
description = "Remove your own marker",
privs = {interact = true},
func = mr_command
})

View file

@ -62,14 +62,21 @@ minetest.register_globalstep(function(delta)
end
end)
minetest.register_on_punchplayer(function(_, hitter)
local old_can_attack = ctf.can_attack
function ctf.can_attack(player, hitter, ...)
if ctf_match.is_in_build_time() then
if hitter:is_player() then
minetest.chat_send_player(hitter:get_player_name(), "Match hasn't started yet!")
hud_event.new(hitter:get_player_name(), {
name = "ctf_match:buildtime_hit",
color = "warning",
value = "Match hasn't started yet!",
})
end
return true
return false
end
end)
return old_can_attack(player, hitter, ...)
end
ctf_match.register_on_build_time_start(function()
minetest.chat_send_all(minetest.colorize("#fcca05", ("Prepare your base! Match starts in " ..

View file

@ -52,7 +52,7 @@ minetest.register_chatcommand("ctf_respawn", {
local restart_on_next_match = false
local restart_on_next_match_by = nil
minetest.register_chatcommand("ctf_queue_restart", {
minetest.register_chatcommand("restart", {
description = "Queue server restart",
privs = {
server = true
@ -65,7 +65,7 @@ minetest.register_chatcommand("ctf_queue_restart", {
end
})
minetest.register_chatcommand("ctf_unqueue_restart", {
minetest.register_chatcommand("unqueue_restart", {
description = "Unqueue server restart",
privs = {
server = true

View file

@ -6,6 +6,16 @@ function ctf_match.register_on_skip_map(func)
table.insert(ctf_match.registered_on_skip_map, func)
end
function skip()
for i = 1, #ctf_match.registered_on_skip_map do
ctf_match.registered_on_skip_map[i]()
end
ctf_match.next()
end
local can_vote_skip = false
local voted_skip = false
local flags_hold = 0
function ctf_match.vote_next(name)
local tcolor = ctf_colors.get_color(ctf.player(name)).css or "#FFFFFFFF"
minetest.chat_send_all(minetest.colorize("#FFAA11", "Vote started by ") ..
@ -22,10 +32,12 @@ function ctf_match.vote_next(name)
if result == "yes" then
minetest.chat_send_all("Vote to skip match passed, " ..
#results.yes .. " to " .. #results.no)
for i = 1, #ctf_match.registered_on_skip_map do
ctf_match.registered_on_skip_map[i]()
can_vote_skip = false
voted_skip = true
if flags_hold <= 0 then
skip()
end
ctf_match.next()
else
minetest.chat_send_all("Vote to skip match failed, " ..
#results.no .. " to " .. #results.yes)
@ -53,9 +65,8 @@ minetest.register_chatcommand("vote", {
local matchskip_time
local matchskip_timer = 0
local can_skip = false
minetest.register_globalstep(function(dtime)
if not can_skip then return end
if not can_vote_skip then return end
matchskip_timer = matchskip_timer + dtime
@ -68,19 +79,26 @@ minetest.register_globalstep(function(dtime)
end
end)
local function prevent_autoskip()
can_skip = false
end
ctf.register_on_new_game(prevent_autoskip)
ctf_flag.register_on_pick_up(prevent_autoskip)
ctf.register_on_new_game(function()
can_vote_skip = false
voted_skip = false
flags_hold = 0
end)
ctf_flag.register_on_pick_up(function()
flags_hold = flags_hold + 1
end)
ctf_flag.register_on_drop(function()
can_skip = true
flags_hold = flags_hold - 1
if voted_skip and flags_hold <= 0 then
minetest.after(5, function()
skip()
end)
end
end)
ctf_match.register_on_build_time_end(function()
can_skip = true
can_vote_skip = true
matchskip_timer = 0
-- Set to initial vote time
matchskip_time = tonumber(minetest.settings:get("ctf_match.auto_skip_delay")) or 60 * 60
matchskip_time = tonumber(minetest.settings:get("ctf_match.auto_skip_delay")) or 50 * 60
end)

View file

@ -1,44 +1,77 @@
if not minetest.global_exists("prometheus") then
if not minetest.create_metric then
return
end
local kill_counter = 0
local function counter(...)
return minetest.create_metric("counter", ...)
end
local function gauge(...)
return minetest.create_metric("gauge", ...)
end
--
-- Kills
--
local kill_counter = counter("ctf_kills", "Total kills")
ctf.register_on_killedplayer(function(victim, killer, type)
kill_counter = kill_counter + 1
kill_counter:increment()
end)
local function step()
prometheus.post("minetest_kills", kill_counter)
kill_counter = 0
--
-- Damage
--
local punch_counter = counter("ctf_punches", "Total punches")
local damage_counter = counter("ctf_damage_given", "Total damage given")
ctf.register_on_attack(function(_, _, _, _, _, damage)
punch_counter:increment()
damage_counter:increment(damage)
end)
--
-- Digs / places
--
local dig_counter = counter("ctf_digs", "Total digs")
local place_counter = counter("ctf_places", "Total digs")
minetest.register_on_dignode(function()
dig_counter:increment()
end)
minetest.register_on_placenode(function()
place_counter:increment()
end)
--
-- Gauges
--
local class_gauges = {}
for _, class in ipairs(ctf_classes.__classes_ordered) do
class_gauges[class.name] = gauge("ctf_class_players", "Player count for class", {
class = class.name
})
end
local online_score = gauge("ctf_online_score", "Total score of online players")
local match_time = gauge("ctf_match_play_time", "Current time in match")
minetest.register_globalstep(function()
local sum = 0
local avg = 0
local bins = { r050=0, r200=0, r5000=0, rest=0 }
if #minetest.get_connected_players() > 0 then
local class_counts = {}
for _, player in pairs(minetest.get_connected_players()) do
local total, _ = ctf_stats.player(player:get_player_name())
sum = sum + total.score
if total.score > 174000 then
bins.r050 = bins.r050 + 1
elseif total.score > 10000 then
bins.r200 = bins.r200 + 1
elseif total.score > 1000 then
bins.r5000 = bins.r5000 + 1
else
bins.rest = bins.rest + 1
end
end
avg = sum / #minetest.get_connected_players()
local class = ctf_classes.get(player)
class_counts[class.name] = (class_counts[class.name] or 0) + 1
end
for key, value in pairs(bins) do
prometheus.post("minetest_ctf_score_bins{rank=\"" .. key .. "\"}", value)
online_score:set(sum)
match_time:set(ctf_match.get_match_duration() or 0)
for _, class in ipairs(ctf_classes.__classes_ordered) do
class_gauges[class.name]:set(class_counts[class.name] or 0)
end
prometheus.post("minetest_ctf_score_total", sum)
prometheus.post("minetest_ctf_score_avg", avg)
minetest.after(15, step)
end
minetest.after(15, step)
end)

View file

@ -1,3 +1,2 @@
name = ctf_metrics
depends = ctf, ctf_stats
optional_depends = prometheus
depends = ctf, ctf_stats, ctf_classes

View file

@ -34,32 +34,36 @@ function ctf_respawn_immunity.update_effects(player)
-- end
end
minetest.register_on_punchplayer(function(player, hitter,
time_from_last_punch, tool_capabilities, dir, damage)
local old_can_attack = ctf.can_attack
function ctf.can_attack(player, hitter, ...)
if not player or not hitter then
return false
return
end
local pname = player:get_player_name()
local hname = hitter:get_player_name()
local pteam = ctf.player(pname).team
local hteam = ctf.player(hname).team
if pteam ~= hteam then
if player and ctf_respawn_immunity.is_immune(player) then
minetest.chat_send_player(hname, minetest.colorize("#EE8822", pname ..
" just respawned or joined," .. " and is immune to attacks!"))
return true
if ctf_respawn_immunity.is_immune(player) then
hud_event.new(hname, {
name = "ctf_respawn_immunity:hit",
color = 0xEE8822,
value = pname .. " has respawn immunity!",
})
return false
end
if hitter and ctf_respawn_immunity.is_immune(hitter) then
minetest.chat_send_player(hname, minetest.colorize("#FF8C00",
"Your immunity has ended because you attacked a player"))
if ctf_respawn_immunity.is_immune(hitter) then
hud_event.new(hname, {
name = "ctf_respawn_immunity:end",
color = 0xFF8C00,
value = "Your immunity has ended!",
})
immune_players[hname] = nil
ctf_respawn_immunity.update_effects(hitter)
end
end
end)
return old_can_attack(player, hitter, ...)
end
minetest.register_on_joinplayer(ctf_respawn_immunity.set_immune)
minetest.register_on_respawnplayer(ctf_respawn_immunity.set_immune)

View file

@ -240,6 +240,11 @@ minetest.register_chatcommand("makepro", {
modified = true
end
if stats.captures < 10 then
stats.captures = 10
modified = true
end
if modified then
ctf_stats.request_save()
return true, "Made " .. param .. " a pro!"

View file

@ -207,7 +207,7 @@ end
function ctf_stats.is_pro(name)
local stats = ctf_stats.player(name)
local kd = stats.kills / (stats.deaths == 0 and 1 or stats.deaths)
return stats.score >= 10000 and kd >= 1.5
return stats.score >= 10000 and kd >= 1.5 and stats.captures >= 10
end
ctf.register_on_join_team(function(name, tname, oldteam)
@ -373,7 +373,7 @@ local function invHasGoodWeapons(inv)
return false
end
local function calculateKillReward(victim, killer, toolcaps)
function ctf_stats.calculateKillReward(victim, killer, toolcaps)
local vmain, victim_match = ctf_stats.player(victim)
if not vmain or not victim_match then return 5 end
@ -417,33 +417,23 @@ local function calculateKillReward(victim, killer, toolcaps)
return reward
end
ctf.register_on_killedplayer(function(victim, killer, _, toolcaps)
ctf.register_on_killedplayer(function(victim, killer)
-- Suicide is not encouraged here at CTF
if victim == killer then
return
end
local main, match = ctf_stats.player(killer)
if main and match then
local reward = calculateKillReward(victim, killer, toolcaps)
main.kills = main.kills + 1
main.score = main.score + reward
match.kills = match.kills + 1
match.score = match.score + reward
match.kills_since_death = match.kills_since_death + 1
_needs_save = true
reward = math.floor(reward * 100) / 100
hud_score.new(killer, {
name = "ctf_stats:kill_score",
color = "0x00FF00",
value = reward
})
end
end)
minetest.register_on_dieplayer(function(player)
local main, match = ctf_stats.player(player:get_player_name())
if main and match then
main.deaths = main.deaths + 1
match.deaths = match.deaths + 1

View file

@ -52,6 +52,8 @@ default:dirt_with_dry_grass
default:dirt_with_snow
default:dirt_with_rainforest_litter
default:dirt_with_coniferous_litter
default:dry_dirt
default:dry_dirt_with_dry_grass
default:permafrost
default:permafrost_with_stones
@ -497,6 +499,25 @@ minetest.register_node("default:dirt_with_coniferous_litter", {
}),
})
minetest.register_node("default:dry_dirt", {
description = "Savanna Dirt",
tiles = {"default_dry_dirt.png"},
groups = {crumbly = 3, soil = 1},
sounds = default.node_sound_dirt_defaults(),
})
minetest.register_node("default:dry_dirt_with_dry_grass", {
description = "Savanna Dirt with Savanna Grass",
tiles = {"default_dry_grass.png", "default_dry_dirt.png",
{name = "default_dry_dirt.png^default_dry_grass_side.png",
tileable_vertical = false}},
groups = {crumbly = 3, soil = 1},
drop = "default:dry_dirt",
sounds = default.node_sound_dirt_defaults({
footstep = {name = "default_grass_footstep", gain = 0.4},
}),
})
minetest.register_node("default:permafrost", {
description = "Permafrost",
tiles = {"default_permafrost.png"},

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 B

View file

@ -188,27 +188,6 @@ function _doors.door_toggle(pos, node, clicker)
end
local function on_place_node(place_to, newnode,
placer, oldnode, itemstack, pointed_thing)
-- Run script hook
for _, callback in ipairs(minetest.registered_on_placenodes) do
-- Deepcopy pos, node and pointed_thing because callback can modify them
local place_to_copy = {x = place_to.x, y = place_to.y, z = place_to.z}
local newnode_copy =
{name = newnode.name, param1 = newnode.param1, param2 = newnode.param2}
local oldnode_copy =
{name = oldnode.name, param1 = oldnode.param1, param2 = oldnode.param2}
local pointed_thing_copy = {
type = pointed_thing.type,
above = vector.new(pointed_thing.above),
under = vector.new(pointed_thing.under),
ref = pointed_thing.ref,
}
callback(place_to_copy, newnode_copy, placer,
oldnode_copy, itemstack, pointed_thing_copy)
end
end
local function can_dig_door(pos, digger)
replace_old_owner_information(pos)
if default.can_interact_with_node(digger, pos) then
@ -362,15 +341,19 @@ function doors.register(name, def)
meta:set_string("owner_team", tname)
end
local copy = table.copy
local newnode = minetest.get_node(pos)
for _, on_placenode in ipairs(minetest.registered_on_placenodes) do
if on_placenode(copy(pos), copy(newnode), placer, copy(node), ItemStack(itemstack), copy(pointed_thing)) then
return itemstack
end
end
if not (creative and creative.is_enabled_for and creative.is_enabled_for(pn)) then
itemstack:take_item()
end
minetest.sound_play(def.sounds.place, {pos = pos})
on_place_node(pos, minetest.get_node(pos),
placer, node, itemstack, pointed_thing)
return itemstack
end
})

View file

@ -29,7 +29,7 @@ for i = 1, #dyes do
is_ground_content = false,
groups = {snappy = 2, choppy = 2, oddly_breakable_by_hand = 3,
flammable = 3, wool = 1},
sounds = default.node_sound_defaults(),
sounds = default.node_sound_dirt_defaults(),
})
end

View file

@ -0,0 +1,57 @@
# `hud_events`
Forked and edited from `hud_score` by ANAND (ClobberXD), licensed under the LGPLv2.1+ license.
`hud_events` provides an API to display HUD event elements which can be used to
display various hints and messages.
## Methods
- `hud_event.new(name, event_def)`: Adds a new HUD event element to player `name`.
- `name` [string]: Player name
- `event_def` [table]: HUD event element definition. See below.
## HUD event element definition
HUD event element definition table, passed to `hud_event.new`.
Example definition:
```lua
{
name = "ctf_bandages:healing", -- Can be any arbitrary string
color = "0x00FF00", -- Should be compatible with Minetest's HUD def
value = "x has healed y", -- The actual event to be displayed
-- Field `time` is automatically added by `hud_event.new`
-- to keep track of element expiry
}
```
## `players` table
This is a table of tables, indexed by player names. This table holds the HUD
data of all online players. Each sub-table is a list of HUD event elements,
which are added by `hud_event.new`.
```lua
local players = {
["name"] = {
[1] = <hud_event_element>,
[2] = <hud_event_element>,
[3] = <hud_event_element>
...
},
["name2"] = {
...
},
...
}
```
## Changes
Changes that have been made compared to the original `hud_score` mod. Lines mentioned underneath refer to the lines in the `hud_events`' init.lua file.
- replaced all occurences of `score` with `event` (10th March 2021)
- changed variables and arguments in the lines 5, 6 and 36 (10th march 2021)
- edited and added arguments in line 39 and 40 (10th march 2021)
- deleted an `if` statement after line 28 (10th march 2021)

View file

@ -0,0 +1,139 @@
hud_event = {}
local hud = hudkit()
local players = {}
local duration = 7
local max = 3
local next_check = 10000000
-- Bootstrap 5 palette
local colors = {
primary = 0x0D6EFD,
secondary = 0x6C757D,
success = 0x198754,
info = 0x0DCAF0,
warning = 0xFFC107,
danger = 0xDC3545,
light = 0xF8F9FA,
dark = 0x212529,
}
hud_event.colors = colors
local function update(name)
local player = minetest.get_player_by_name(name)
if not player then
return
end
-- Handle all elements marked for deletion
-- and rebuild table
local temp = {}
for _, def in ipairs(players[name]) do
if def.delete then
if hud:exists(player, def.name) then
hud:remove(player, def.name)
end
else
table.insert(temp, def)
end
end
for i, def in ipairs(temp) do
local text = tostring(def.value)
if hud:exists(player, def.name) then
hud:change(player, def.name, "text", text)
hud:change(player, def.name, "offset", {x = 0, y = i * 20})
else
hud:add(player, def.name, {
hud_elem_type = "text",
alignment = {x = 0, y = 0},
position = {x = 0.5, y = 0.7},
offset = {x = 0, y = i * 20},
number = tonumber(def.color),
text = text,
z_index = -200
})
end
end
players[name] = temp
end
function hud_event.new(name, def)
-- Verify HUD event element def
if not name or not def or type(def) ~= "table" or
not def.name or not def.value or not def.color then
error("hud_event: Invalid HUD event element definition", 2)
end
def.color = colors[def.color] or def.color
local player = minetest.get_player_by_name(name)
if not player then
return
end
-- Store element expiration time in def.time
-- and append event element def to players[name]
def.time = os.time() + duration
if next_check > duration then
next_check = duration
end
-- If a HUD event element with the same name exists already,
-- reuse it instead of creating a new element
local is_new = true
for i, hud_event_spec in ipairs(players[name]) do
if hud_event_spec.name == def.name then
is_new = false
players[name][i] = def
break
end
end
if is_new then
table.insert(players[name], def)
end
-- If more than `max` active elements, mark oldest element for deletion
if #players[name] > max then
players[name][1].delete = true
end
update(name)
end
minetest.register_globalstep(function(dtime)
next_check = next_check - dtime
if next_check > 0 then
return
end
next_check = 10000000
-- Loop through HUD score elements of all players
-- and remove them if they've expired
for name, hudset in pairs(players) do
local modified = false
for i, def in pairs(hudset) do
local rem = def.time - os.time()
if rem <= 0 then
def.delete = true
modified = true
elseif rem < next_check then
next_check = rem
end
end
-- If a player's hudset was modified, update player's HUD
if modified then
update(name)
end
end
end)
minetest.register_on_joinplayer(function(player)
players[player:get_player_name()] = {}
end)
minetest.register_on_leaveplayer(function(player)
players[player:get_player_name()] = nil
end)

View file

@ -0,0 +1,3 @@
name = hud_events
description = API for displaying events on HUD
depends = hudkit

View file

@ -56,11 +56,11 @@ function random_messages.read_messages()
"You gain more score the better the opponent you defeat.",
"Find weapons in chests or mine and use furnaces to make stronger swords.",
"Players are immune to attack for 5 seconds after they respawn.",
"Access the pro section of the chest by achieving a 10k+ score and killing 3 people for every 2 deaths.",
"Access the pro section of the chest by achieving a 10k+ score, killing 3 people for every 2 deaths and capture the flag 10 times.",
"Use team doors (steel) to stop the enemy walking into your base.",
"Craft 6 cobbles and 1 steel ingot together to make reinforced cobble.",
"Sprint by pressing the fast key (E) when you have stamina.",
"Like CTF? Give feedback using /report, and consider donating at rubenwardy.com/donate",
"Like CTF? Give feedback using /report, and consider joining the Discord",
"Want to submit your own map? Visit ctf.rubenwardy.com to get involved.",
"Using limited resources for building structures that don't strengthen your base's defences is discouraged.",
"To report misbehaving players to moderators, please use /report <name> <action>",
@ -72,13 +72,14 @@ function random_messages.read_messages()
"Use /r <number> or /rn <number> to check the rankings of the player in the given rank.",
"Use bandages on team-mates to heal them by 3-4 HP if their health is below 15 HP.",
"Use /m to add a team marker at pointed location, that's visible only to team-mates.",
"Use /summary to check scores of the current match and the previous match.",
"Use /mr to remove your marker.",
"Use /summary or /s to check scores of the current match and the previous match.",
"Use /maps to view the maps catalog. It also contains license info and attribution details.",
"Change your class in your base by right clicking the home flag or typing /class.",
"Medics cause troops within 10 metres to regenerate health faster.",
"Hitting your enemy does more damage than not hitting them.",
"Press right mouse button or double-tap the screen to activate scope while wielding a sniper rifle.",
"The 'Updates' tab in your inventory will show some of the latest updates to CTF",
"Medics can dig pillars by right clicking the base of one with their paxel.",
}
end

View file

@ -3,29 +3,18 @@
potential_cowards = {}
local TIMER_UPDATE_INTERVAL = 2
local COMBAT_TIMEOUT_TIME = 20
local COMBATLOG_SCORE_PENALTY = 10
--
--- Make suicides and combat logs award last puncher with kill
--
minetest.register_on_punchplayer(function(player, hitter,
time_from_last_punch, tool_capabilities, dir, damage)
ctf.register_on_attack(function(player, hitter,
time_from_last_punch, tool_capabilities, dir, damage)
if player and hitter then
if ctf_respawn_immunity.is_immune(player) or ctf_match.is_in_build_time() then
return
end
local pname = player:get_player_name()
local hname = hitter:get_player_name()
local to = ctf.player(pname)
local from = ctf.player(hname)
if to.team == from.team and to.team ~= "" and
to.team ~= nil and to.name ~= from.name then
return
end
local hp = player:get_hp() - damage
if hp <= 0 then
if potential_cowards[pname] then
@ -38,7 +27,7 @@ time_from_last_punch, tool_capabilities, dir, damage)
potential_cowards[hname] = nil
end
return false
return
end
if not potential_cowards[pname] then
@ -94,11 +83,6 @@ minetest.register_on_dieplayer(function(player, reason)
last_attacker:hud_remove(potential_cowards[hname].hud or 0)
potential_cowards[hname] = nil
end
if potential_cowards[pname] then
player:hud_remove(potential_cowards[pname].hud or 0)
potential_cowards[pname] = nil
end
else
for victim in pairs(potential_cowards) do
if potential_cowards[victim].puncher == pname then
@ -114,6 +98,11 @@ minetest.register_on_dieplayer(function(player, reason)
end
end
end
if potential_cowards[pname] then
player:hud_remove(potential_cowards[pname].hud or 0)
potential_cowards[pname] = nil
end
end)
minetest.register_on_leaveplayer(function(player, timeout)
@ -136,6 +125,17 @@ minetest.register_on_leaveplayer(function(player, timeout)
)
end
local main, match = ctf_stats.player(pname)
if main and match then
main.deaths = main.deaths + 1
match.deaths = match.deaths + 1
main.score = main.score - COMBATLOG_SCORE_PENALTY
match.score = match.score - COMBATLOG_SCORE_PENALTY
match.kills_since_death = 0
ctf_stats.request_save()
end
potential_cowards[pname] = nil
end
end)
@ -156,6 +156,7 @@ minetest.register_globalstep(function(dtime)
end
potential_cowards[k] = nil
kill_assist.clear_assists(k)
end
end
@ -164,9 +165,23 @@ minetest.register_globalstep(function(dtime)
end)
ctf_match.register_on_new_match(function()
for coward, info in pairs(potential_cowards) do
coward = minetest.get_player_by_name(coward)
if coward and info.hud then
coward:hud_remove(info.hud)
end
end
potential_cowards = {}
end)
ctf.register_on_new_game(function()
for coward, info in pairs(potential_cowards) do
coward = minetest.get_player_by_name(coward)
if coward and info.hud then
coward:hud_remove(info.hud)
end
end
potential_cowards = {}
end)

View file

@ -1,2 +1,2 @@
name = anticoward
depends = ctf, ctf_classes, ctf_match
depends = ctf, ctf_classes, ctf_match, kill_assist

View file

@ -1,32 +0,0 @@
-- Code by Apelta. Mutelated by Lone_Wolf. Mutelated again by Apelta.
antisabotage = {}
function antisabotage.is_sabotage(pos, oldnode, digger) -- used for paxel
local dname = digger:get_player_name()
for _, player in pairs(minetest.get_connected_players()) do
local name = player:get_player_name()
if name ~= dname and ctf.players[name].team == ctf.players[dname].team then
local player_pos = player:get_pos()
if math.floor(player_pos.y) == pos.y and vector.distance(player_pos, pos) <= 1.5 then
minetest.set_node(pos, oldnode)
-- Remove all node drops
for _, item in pairs(minetest.get_node_drops(oldnode)) do
digger:get_inventory():remove_item("main", ItemStack(item))
end
minetest.chat_send_player(dname, "You can't mine blocks under your teammates!")
return true
end
end
end
end
minetest.register_on_dignode(function(pos, oldnode, digger)
if not digger:is_player() then return end
antisabotage.is_sabotage(pos, oldnode, digger)
end)

View file

@ -1,7 +0,0 @@
Copyright 2020 Apelta
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -1,2 +0,0 @@
name = antisabotage
depends = ctf

View file

@ -1,3 +0,0 @@
# Anti Sabotage
A simple mod that prevents players from sabotaging their teammates by digging blocks out from underneath them

View file

@ -16,8 +16,10 @@ local function regen_all()
local newhp = oldhp + hpregen.amount
if newhp > player:get_properties().hp_max then
newhp = player:get_properties().hp_max
kill_assist.clear_assists(player:get_player_name())
end
if oldhp ~= newhp then
kill_assist.add_heal_assist(player:get_player_name(), hpregen.amount)
player:set_hp(newhp)
end
end

View file

@ -1 +1,2 @@
name = hpregen
depends = kill_assist

View file

@ -0,0 +1,90 @@
kill_assist = {}
local kill_assists = {}
function kill_assist.clear_assists(player)
if type(player) == "string" then
kill_assists[player] = nil
else
kill_assists = {}
end
end
function kill_assist.add_assist(victim, attacker, damage)
if victim == attacker then return end
if not kill_assists[victim] then
kill_assists[victim] = {
players = {},
hp_offset = 0
}
end
kill_assists[victim].players[attacker] = (kill_assists[victim].players[attacker] or 0) + damage
end
function kill_assist.add_heal_assist(victim, healed_hp)
if not kill_assists[victim] then return end
kill_assists[victim].hp_offset = kill_assists[victim].hp_offset + healed_hp
end
function kill_assist.reward_assists(victim, killer, reward)
local max_hp = minetest.get_player_by_name(victim):get_properties().max_hp or 20
if not kill_assists[victim] then
if victim ~= killer then
kill_assist.add_assist(victim, killer, max_hp)
else
return
end
end
for name, damage in pairs(kill_assists[victim].players) do
if minetest.get_player_by_name(name) then
local help_percent = damage / (max_hp + kill_assists[victim].hp_offset)
local main, match = ctf_stats.player(name)
local color = "0x00FFFF"
if name == killer or help_percent >= 0.33 then
reward = math.max(math.floor((reward * help_percent)*100)/100, 1)
end
match.score = match.score + reward
main.score = main.score + reward
if name == killer then
color = "0x00FF00"
end
hud_score.new(name, {
name = "kill_assist:score",
color = color,
value = reward
})
end
end
ctf_stats.request_save()
kill_assist.clear_assists(victim)
end
ctf.register_on_killedplayer(function(victim, killer, _, toolcaps)
local reward = ctf_stats.calculateKillReward(victim, killer, toolcaps)
reward = math.floor(reward * 100) / 100
kill_assist.reward_assists(victim, killer, reward)
end)
ctf.register_on_attack(function(player, hitter, _, _, _, damage)
kill_assist.add_assist(player:get_player_name(), hitter:get_player_name(), damage)
end)
ctf_match.register_on_new_match(function()
kill_assist.clear_assists()
end)
ctf.register_on_new_game(function()
kill_assist.clear_assists()
end)
minetest.register_on_leaveplayer(function(player)
kill_assist.clear_assists(player)
end)

View file

@ -0,0 +1,2 @@
name = kill_assist
depends = ctf, ctf_match, ctf_stats, hud_score

View file

@ -71,8 +71,11 @@ local function stop_healing(player, interrupted)
players[name] = nil
if interrupted then
minetest.chat_send_player(name, minetest.colorize("#FF4444",
"Your healing was interrupted"..reason_handler(interrupted)))
hud_event.new(name, {
name = "medkits:interrupt",
color = 0xFF4444,
value = "Your healing was interrupted" .. reason_handler(interrupted),
})
player:set_hp(info.hp)
player:get_inventory():add_item("main", ItemStack("medkits:medkit 1"))
end
@ -117,6 +120,7 @@ minetest.register_globalstep(function(dtime)
if pstat then
local hp = player:get_hp()
if hp < pstat.regen_max then
kill_assist.add_heal_assist(name, regen_step)
player:set_hp(math.min(hp + regen_step, pstat.regen_max))
else
stop_healing(player)

View file

@ -0,0 +1,19 @@
Copyright (c) 2021 appgurueu
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,8 @@
# `place_limit`
Fixes two things related to node placement:
* Ratelimits node placement
* Disallows building to non-pointable nodes (anticheat + race condition fix)
Licensed under the MIT license, written by appgurueu.

View file

@ -0,0 +1,49 @@
-- Licensed under the MIT license, written by appgurueu.
local players = {}
local blocks_per_second = 5
local resend_notification_seconds = 10
minetest.register_on_joinplayer(function(player)
-- player has to wait after join before they can place a node
players[player:get_player_name()] = {
last_notification_sent = -math.huge
}
end)
minetest.register_on_leaveplayer(function(player)
players[player:get_player_name()] = nil
end)
minetest.register_on_placenode(function(pos, _newnode, placer, oldnode, _itemstack, pointed_thing)
local name = placer:get_player_name()
if not ItemStack(minetest.get_node(pointed_thing.under).name):get_definition().pointable then
-- This should happen rarely
hud_event.new(name, {
name = "place_limit:unpointable",
color = "warning",
value = "Block not pointable (dug/replaced)!",
})
minetest.set_node(pos, oldnode)
return true
end
local time = minetest.get_us_time()
local placements = players[name]
for i = #placements, 1, -1 do
if time - placements[i] > 1e6 then
placements[i] = nil
end
end
if #placements >= blocks_per_second then
if (time - placements.last_notification_sent) / 1e6 >= resend_notification_seconds then
hud_event.new(name, {
name = "place_limit:speed",
color = "warning",
value = "Placing too fast!",
})
placements.last_notification_sent = time
end
minetest.set_node(pos, oldnode)
return true
end
table.insert(placements, 1, time)
end)

View file

@ -0,0 +1,3 @@
name = place_limit
description = Limits block placement
depends = hud_events

@ -1 +1 @@
Subproject commit e856150c13972825626bdadd2f80165a853408c1
Subproject commit 6939ab0b6d22387fcede92d152cc3cd5c6a0d151

View file

@ -163,8 +163,11 @@ function minetest.is_protected(pos, name, info, ...)
local flag, distSQ = ctf_flag.get_nearest(pos)
if flag and pos.y >= flag.y - 1 and distSQ < rs then
minetest.chat_send_player(name,
"You can't shoot blocks within "..r.." nodes of a flag!")
hud_event.new(name, {
name = "sniper_rifles:hit_base",
color = "warning",
value = "You can't shoot blocks within " .. r .. " nodes of a flag!",
})
return true
else
return old_is_protected(pos, name, info, ...)