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", "ctf_match", "ctf_stats", "ctf_treasure", "ctf_playertag", "chatplus", "irc",
"armor", "vote", "give_initial_stuff", "hud_score", "physics", "tsm_chests", "armor", "vote", "give_initial_stuff", "hud_score", "physics", "tsm_chests",
"armor", "shooter", "grenades", "ctf_classes", "ctf_bandages", "ctf_respawn_immunity", "armor", "shooter", "grenades", "ctf_classes", "ctf_bandages", "ctf_respawn_immunity",
"ctf_marker", "ctf_marker", "kill_assist"
} }
read_globals = { read_globals = {

View file

@ -1,3 +1,4 @@
name = Capture the Flag name = Capture the Flag
description = Use swords, guns, and grenades to combat the enemy and capture their flag before they capture yours. 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 min_minetest_version = 5.3

View file

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

View file

@ -445,6 +445,19 @@ function ctf.register_on_killedplayer(func)
end end
table.insert(ctf.registered_on_killedplayer, func) table.insert(ctf.registered_on_killedplayer, func)
end 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 = {} local dead_players = {}
minetest.register_on_respawnplayer(function(player) minetest.register_on_respawnplayer(function(player)
dead_players[player:get_player_name()] = nil dead_players[player:get_player_name()] = nil
@ -453,7 +466,7 @@ minetest.register_on_joinplayer(function(player)
dead_players[player:get_player_name()] = nil dead_players[player:get_player_name()] = nil
end) end)
minetest.register_on_punchplayer(function(player, hitter, 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 if player and hitter then
local pname = player:get_player_name() local pname = player:get_player_name()
local hname = hitter:get_player_name() local hname = hitter:get_player_name()
@ -473,6 +486,12 @@ minetest.register_on_punchplayer(function(player, hitter,
end end
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() local hp = player:get_hp()
if hp == 0 then if hp == 0 then
return false return false
@ -487,5 +506,12 @@ minetest.register_on_punchplayer(function(player, hitter,
end end
return false return false
end 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
end) end)

View file

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

View file

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

View file

@ -73,16 +73,18 @@ function ctf_classes.set(player, new_name)
local meta = player:get_meta() local meta = player:get_meta()
local old_name = meta:get("ctf_classes:class") local old_name = meta:get("ctf_classes:class")
if old_name == new_name then
return
end
meta:set_string("ctf_classes:class", new_name) meta:set_string("ctf_classes:class", new_name)
ctf_classes.update(player) ctf_classes.update(player)
ctf_classes.set_cooldown(player:get_player_name()) 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]
local old = old_name and ctf_classes.__classes[old_name] for i=1, #registered_on_changed do
for i=1, #registered_on_changed do registered_on_changed[i](player, old, new)
registered_on_changed[i](player, old, new)
end
end end
end end

View file

@ -99,6 +99,7 @@ minetest.override_item("ctf_bandages:bandage", {
if ctf.player(pname).team == ctf.player(name).team then if ctf.player(pname).team == ctf.player(name).team then
local nodename = minetest.get_node(object:get_pos()).name 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 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 return -- Can't heal players in lava/water/spikes
end end
@ -132,7 +133,7 @@ minetest.override_item("ctf_bandages:bandage", {
}) })
local diggers = {} local diggers = {}
local DIG_COOLDOWN = 45 local DIG_COOLDOWN = 30
local DIG_DIST_LIMIT = 30 local DIG_DIST_LIMIT = 30
local DIG_SPEED = 0.1 local DIG_SPEED = 0.1
@ -147,17 +148,21 @@ local function isdiggable(name)
) )
end 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 function remove_pillar(pos, pname)
local name = minetest.get_node(pos).name local name = minetest.get_node(pos).name
if name:find("default") and isdiggable(name) then if name:find("default") and isdiggable(name) then
local player = minetest.get_player_by_name(pname) 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) minetest.dig_node(pos)
if player and diggers[pname] and type(diggers[pname]) ~= "table" then 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 pos.y = pos.y + 1
minetest.after(DIG_SPEED, remove_pillar, pos, pname) minetest.after(DIG_SPEED, remove_pillar, pos, pname)
else else
minetest.chat_send_player(pname, "Pillar digging stopped, too far away from digging pos. Can activate again in "..DIG_COOLDOWN.." seconds") paxel_stop(pname, "at too far away node")
diggers[pname] = minetest.after(DIG_COOLDOWN, function() diggers[pname] = nil end)
end end
end end
else else
minetest.chat_send_player(pname, "Pillar digging stopped at undiggable node. Can activate again in "..DIG_COOLDOWN.." seconds") paxel_stop(pname, "at undiggable node")
diggers[pname] = minetest.after(DIG_COOLDOWN, function() diggers[pname] = nil end)
end end
end end
@ -195,20 +198,39 @@ minetest.register_tool("ctf_classes:paxel_bronze", {
if pointed_thing.type == "node" then if pointed_thing.type == "node" then
local pname = placer:get_player_name() local pname = placer:get_player_name()
if not isdiggable(minetest.get_node(pointed_thing.under).name) or ctf_match.is_in_build_time() then if not isdiggable(minetest.get_node(pointed_thing.under).name) then
minetest.chat_send_player(pname, "Can't dig node or build time active") 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) return minetest.item_place(itemstack, placer, pointed_thing)
end end
if not diggers[pname] then 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 diggers[pname] = true
remove_pillar(pointed_thing.under, pname) remove_pillar(pointed_thing.under, pname)
elseif type(diggers[pname]) ~= "table" then elseif type(diggers[pname]) ~= "table" then
minetest.chat_send_player(pname, "Pillar digging stopped. Can activate again in "..DIG_COOLDOWN.." seconds") paxel_stop(pname)
diggers[pname] = minetest.after(DIG_COOLDOWN, function() diggers[pname] = nil end)
else 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 end
end, end,
@ -220,8 +242,7 @@ minetest.register_tool("ctf_classes:paxel_bronze", {
minetest.after(2, function() minetest.after(2, function()
if user and user:get_player_control().RMB then if user and user:get_player_control().RMB then
if diggers[pname] and type(diggers[pname]) ~= "table" 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") paxel_stop(pname)
diggers[pname] = minetest.after(DIG_COOLDOWN, function() diggers[pname] = nil end)
end end
end end
end) end)

View file

@ -29,12 +29,12 @@ end, true)
local sword_special_timer = {} local sword_special_timer = {}
local SWORD_SPECIAL_COOLDOWN = 40 local SWORD_SPECIAL_COOLDOWN = 20
local function sword_special_timer_func(pname, timeleft) local function sword_special_timer_func(pname, timeleft)
sword_special_timer[pname] = timeleft sword_special_timer[pname] = timeleft
if timeleft - 10 >= 0 then if timeleft - 2 >= 0 then
minetest.after(10, sword_special_timer_func, pname, timeleft - 10) minetest.after(2, sword_special_timer_func, pname, timeleft - 2)
else else
sword_special_timer[pname] = nil sword_special_timer[pname] = nil
end end
@ -57,8 +57,8 @@ minetest.register_tool("ctf_classes:sword_bronze", {
local pname = placer:get_player_name() local pname = placer:get_player_name()
if not pointed_thing then return end if not pointed_thing then return end
if sword_special_timer[pname] then if sword_special_timer[pname] and placer:get_player_control().sneak then
minetest.chat_send_player(pname, "You can't place a marker yet (>"..sword_special_timer[pname].."s left)") minetest.chat_send_player(pname, "You have to wait "..sword_special_timer[pname].."s to place marker again")
if pointed_thing.type == "node" then if pointed_thing.type == "node" then
return minetest.item_place(itemstack, placer, pointed_thing) return minetest.item_place(itemstack, placer, pointed_thing)
@ -89,7 +89,7 @@ minetest.register_tool("ctf_classes:sword_bronze", {
if #enemies > 0 then if #enemies > 0 then
ctf_marker.remove_marker(pteam) 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 end
return return
@ -102,10 +102,10 @@ minetest.register_tool("ctf_classes:sword_bronze", {
-- Check if player is sneaking before placing marker -- Check if player is sneaking before placing marker
if not placer:get_player_control().sneak then return end if not placer:get_player_control().sneak then return end
sword_special_timer[pname] = 20 sword_special_timer[pname] = 4
sword_special_timer_func(pname, 20) 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, end,
on_secondary_use = function(itemstack, user, pointed_thing) on_secondary_use = function(itemstack, user, pointed_thing)
if pointed_thing then 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 if table.indexof(class.properties.allowed_guns or {}, weapon_name) == -1 then
minetest.chat_send_player(user:get_player_name(), 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 return nil
end end
@ -49,7 +49,7 @@ local function check_grapple(itemname)
on_use = function(itemstack, user, ...) on_use = function(itemstack, user, ...)
if not ctf_classes.get(user).properties.allow_grapples then if not ctf_classes.get(user).properties.allow_grapples then
minetest.chat_send_player(user:get_player_name(), 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 return itemstack
end end
@ -73,6 +73,12 @@ check_grapple("shooter_hook:grapple_hook")
-- Override grappling hook entity to check if player has flag before teleporting -- Override grappling hook entity to check if player has flag before teleporting
local old_grapple_step = minetest.registered_entities["shooter_hook:hook"].on_step local old_grapple_step = minetest.registered_entities["shooter_hook:hook"].on_step
minetest.registered_entities["shooter_hook:hook"].on_step = function(self, dtime, ...) 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 -- Remove entity if player has flag
-- This is to prevent players from firing the hook, and then punching the flag -- This is to prevent players from firing the hook, and then punching the flag
if ctf_flag.has_flag(self.user) then if ctf_flag.has_flag(self.user) then

View file

@ -52,15 +52,6 @@ for ore, ore_item in pairs(full_ores) do
}) })
end 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 -- Mese crystal x9 <== Mese block
crafting.register_recipe({ crafting.register_recipe({
type = "inv", type = "inv",
@ -110,7 +101,7 @@ crafting.register_recipe({
crafting.register_recipe({ crafting.register_recipe({
type = "inv", type = "inv",
output = "default:stick 4", output = "default:stick 4",
items = { "default:wood" }, items = { "group:wood" },
always_known = true, always_known = true,
level = 1, level = 1,
}) })
@ -142,20 +133,11 @@ crafting.register_recipe({
level = 1, 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) -- 7.62mm sniper rifle (unloaded)
crafting.register_recipe({ crafting.register_recipe({
type = "inv", type = "inv",
output = "sniper_rifles:rifle_762", 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, always_known = false,
level = 1 level = 1
}) })
@ -164,7 +146,7 @@ crafting.register_recipe({
crafting.register_recipe({ crafting.register_recipe({
type = "inv", type = "inv",
output = "sniper_rifles:rifle_magnum", 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, always_known = false,
level = 1, level = 1,
}) })
@ -198,7 +180,7 @@ crafting.register_recipe({
crafting.register_recipe({ crafting.register_recipe({
type = "inv", type = "inv",
output = "ctf_traps:dirt 1", output = "ctf_traps:dirt 5",
items = { "default:dirt 5", "default:coal_lump" }, items = { "default:dirt 5", "default:coal_lump" },
always_known = true, always_known = true,
level = 1, level = 1,
@ -206,7 +188,7 @@ crafting.register_recipe({
crafting.register_recipe({ crafting.register_recipe({
type = "inv", type = "inv",
output = "ctf_traps:cobble 1", output = "ctf_traps:cobble 4",
items = { "default:cobble 4", "default:coal_lump" }, items = { "default:cobble 4", "default:coal_lump" },
always_known = true, always_known = true,
level = 1, level = 1,
@ -267,7 +249,7 @@ end
crafting.register_recipe({ crafting.register_recipe({
type = "inv", type = "inv",
output = "grenades:frag 1", 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, always_known = true,
level = 1, level = 1,
}) })
@ -283,7 +265,7 @@ crafting.register_recipe({
crafting.register_recipe({ crafting.register_recipe({
type = "inv", type = "inv",
output = "grenades:smoke 1", 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, always_known = true,
level = 1, level = 1,
}) })

View file

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

View file

@ -1,4 +1,4 @@
local color = "#66A0FF" color = "#66A0FF"
local items = { local items = {
"", "",
color .. "Game Play", color .. "Game Play",
@ -10,8 +10,8 @@ local items = {
"* Use medkits to replenish health gradually.", "* Use medkits to replenish health gradually.",
"* Gain more score by killing more than you die, or by capturing the flag.", "* Gain more score by killing more than you die, or by capturing the flag.",
"* Players are immune for 5 seconds after they respawn.", "* Players are immune for 5 seconds after they respawn.",
"* Access the pro section of the chest by achieving a 2k+ score and", "* Access the pro section of the chest by achieving 2k+ score,",
" killing 2 people for every death.", " killing 3 people for every 2 deaths, and capturing the flag at least 10 times",
"", "",
color .. "Team Co-op", color .. "Team Co-op",
@ -30,7 +30,12 @@ local items = {
color .. "Contact Moderators", 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 for i = 1, #items do
items[i] = minetest.formspec_escape(items[i]) 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) minetest.set_node(pos3, chest)
local inv = minetest.get_meta(pos3):get_inventory() 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("default:cobble 99")) inv:add_item("main", ItemStack("ctf_traps:damage_cobble 40"))
inv:add_item("main", ItemStack("default:wood 99")) inv:add_item("main", ItemStack("default:wood 99"))
inv:add_item("main", ItemStack("default:stick 30")) inv:add_item("main", ItemStack("default:stick 30"))
inv:add_item("main", ItemStack("default:glass 5")) inv:add_item("main", ItemStack("default:glass 5"))
inv:add_item("main", ItemStack("default:torch 10")) inv:add_item("main", ItemStack("default:torch 10"))
inv:add_item("main", ItemStack("doors:door_steel 2"))
end end
-- Override ctf.get_spawn to implement random spawns -- Override ctf.get_spawn to implement random spawns

View file

@ -102,7 +102,7 @@ for _, chest_color in pairs(colors) do
else else
formspec = formspec .. "label[4.75,3;" .. formspec = formspec .. "label[4.75,3;" ..
minetest.formspec_escape("You need at least 10000" .. 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 end
formspec = formspec .. formspec = formspec ..

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

View file

@ -424,6 +424,25 @@ do
sounds = default.node_sound_leaves_defaults() 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", { minetest.register_node(":ctf_map:jungletree", {
description = "Indestructible Jungle Tree", 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 ### 4. Place barriers
* Set the middle barrier direction. The barrier is a plane defined by a co-ordinate = 0. * The barrier is a plane defined by co-ordinate (=0).
If the barrier is X=0, then it will placed with every node of the barrier having X=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 the barrier is Z=0, then it will placed with every node of the barrier having Z=0. * 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. * 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` * 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 ### 6. Export
* Click export, and wait until completion. * 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! * Profit!
@ -56,7 +56,7 @@ There are multiple ways do this, this is the simplest in most cases.
### Map meta ### 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. * `name`: Name of map.
* `author`: Author of the 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. * `license`: Name of the license of the map.
* `other`: [Optional] Misc. information about the map. This is displayed in the maps catalog. * `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. * `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 * `initial_stuff`: [Optional] Comma-separated list of itemstacks to be given to the player
on join and on respawn. on join and on respawn.
* `treasures`: [Optional] List of treasures to be registered for the map, in a serialized * `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` #### `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> 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 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: If you want to tell more infomation, you can use:
```lua ```properties
others = <description> 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: 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 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) (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` ### `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). 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_5.png` - south
* `skybox_6.png` - north * `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 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 [https://opengameart.org](https://opengameart.org/art-search-advanced?field_art_tags_tid=skybox).
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 ## Editing exported map
The easiest way to edit exported maps is the following: The easiest way to edit exported maps is the following:
* Create a world using `singlenode` mapgen. Enable `WorldEdit` and `ctf_map` mod, * 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), * 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 `//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: When you finish:

View file

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

View file

@ -47,7 +47,7 @@ function ctf_marker.add_marker(name, tname, pos, str)
if tplayer then if tplayer then
teams[tname].players[pname] = tplayer:hud_add({ teams[tname].players[pname] = tplayer:hud_add({
hud_elem_type = "waypoint", hud_elem_type = "waypoint",
name = str, name = "[" .. name .. "'s marker" .. str,
number = tonumber(ctf.flag_colors[team.data.color]), number = tonumber(ctf.flag_colors[team.data.color]),
world_pos = pos world_pos = pos
}) })
@ -99,7 +99,7 @@ minetest.register_chatcommand("m", {
local tname = ctf.player(name).team local tname = ctf.player(name).team
-- Handle waypoint string -- 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 if pointed.type == "object" then
local concat local concat
local obj = pointed.ref local obj = pointed.ref
@ -126,7 +126,7 @@ minetest.register_chatcommand("m", {
end end
str = concat and str .. " <" .. concat .. ">" str = concat and str .. " <" .. concat .. ">"
end end
str = "[" .. str .. "]" str = str .. "]"
-- Remove existing marker if it exists -- Remove existing marker if it exists
ctf_marker.remove_marker(tname) 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) ctf_marker.add_marker(name, tname, minetest.get_pointed_thing_position(pointed), str)
end 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
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 ctf_match.is_in_build_time() then
if hitter:is_player() 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 end
return true return false
end end
end)
return old_can_attack(player, hitter, ...)
end
ctf_match.register_on_build_time_start(function() ctf_match.register_on_build_time_start(function()
minetest.chat_send_all(minetest.colorize("#fcca05", ("Prepare your base! Match starts in " .. 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 = false
local restart_on_next_match_by = nil local restart_on_next_match_by = nil
minetest.register_chatcommand("ctf_queue_restart", { minetest.register_chatcommand("restart", {
description = "Queue server restart", description = "Queue server restart",
privs = { privs = {
server = true server = true
@ -65,7 +65,7 @@ minetest.register_chatcommand("ctf_queue_restart", {
end end
}) })
minetest.register_chatcommand("ctf_unqueue_restart", { minetest.register_chatcommand("unqueue_restart", {
description = "Unqueue server restart", description = "Unqueue server restart",
privs = { privs = {
server = true 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) table.insert(ctf_match.registered_on_skip_map, func)
end 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) function ctf_match.vote_next(name)
local tcolor = ctf_colors.get_color(ctf.player(name)).css or "#FFFFFFFF" local tcolor = ctf_colors.get_color(ctf.player(name)).css or "#FFFFFFFF"
minetest.chat_send_all(minetest.colorize("#FFAA11", "Vote started by ") .. minetest.chat_send_all(minetest.colorize("#FFAA11", "Vote started by ") ..
@ -22,10 +32,12 @@ function ctf_match.vote_next(name)
if result == "yes" then if result == "yes" then
minetest.chat_send_all("Vote to skip match passed, " .. minetest.chat_send_all("Vote to skip match passed, " ..
#results.yes .. " to " .. #results.no) #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 end
ctf_match.next()
else else
minetest.chat_send_all("Vote to skip match failed, " .. minetest.chat_send_all("Vote to skip match failed, " ..
#results.no .. " to " .. #results.yes) #results.no .. " to " .. #results.yes)
@ -53,9 +65,8 @@ minetest.register_chatcommand("vote", {
local matchskip_time local matchskip_time
local matchskip_timer = 0 local matchskip_timer = 0
local can_skip = false
minetest.register_globalstep(function(dtime) 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 matchskip_timer = matchskip_timer + dtime
@ -68,19 +79,26 @@ minetest.register_globalstep(function(dtime)
end end
end) end)
local function prevent_autoskip() ctf.register_on_new_game(function()
can_skip = false can_vote_skip = false
end voted_skip = false
flags_hold = 0
ctf.register_on_new_game(prevent_autoskip) end)
ctf_flag.register_on_pick_up(prevent_autoskip) ctf_flag.register_on_pick_up(function()
flags_hold = flags_hold + 1
end)
ctf_flag.register_on_drop(function() 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) end)
ctf_match.register_on_build_time_end(function() ctf_match.register_on_build_time_end(function()
can_skip = true can_vote_skip = true
matchskip_timer = 0 matchskip_timer = 0
-- Set to initial vote time -- 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) end)

View file

@ -1,44 +1,77 @@
if not minetest.global_exists("prometheus") then if not minetest.create_metric then
return return
end 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) ctf.register_on_killedplayer(function(victim, killer, type)
kill_counter = kill_counter + 1 kill_counter:increment()
end) end)
local function step()
prometheus.post("minetest_kills", kill_counter)
kill_counter = 0
local sum = 0 --
local avg = 0 -- Damage
local bins = { r050=0, r200=0, r5000=0, rest=0 } --
if #minetest.get_connected_players() > 0 then local punch_counter = counter("ctf_punches", "Total punches")
for _, player in pairs(minetest.get_connected_players()) do local damage_counter = counter("ctf_damage_given", "Total damage given")
local total, _ = ctf_stats.player(player:get_player_name()) ctf.register_on_attack(function(_, _, _, _, _, damage)
sum = sum + total.score punch_counter:increment()
damage_counter:increment(damage)
end)
if total.score > 174000 then --
bins.r050 = bins.r050 + 1 -- Digs / places
elseif total.score > 10000 then --
bins.r200 = bins.r200 + 1 local dig_counter = counter("ctf_digs", "Total digs")
elseif total.score > 1000 then local place_counter = counter("ctf_places", "Total digs")
bins.r5000 = bins.r5000 + 1 minetest.register_on_dignode(function()
else dig_counter:increment()
bins.rest = bins.rest + 1 end)
end minetest.register_on_placenode(function()
end place_counter:increment()
avg = sum / #minetest.get_connected_players() end)
end
for key, value in pairs(bins) do
prometheus.post("minetest_ctf_score_bins{rank=\"" .. key .. "\"}", value)
end
prometheus.post("minetest_ctf_score_total", sum) --
prometheus.post("minetest_ctf_score_avg", avg) -- Gauges
--
minetest.after(15, step) 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 end
minetest.after(15, step)
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 class_counts = {}
for _, player in pairs(minetest.get_connected_players()) do
local total, _ = ctf_stats.player(player:get_player_name())
sum = sum + total.score
local class = ctf_classes.get(player)
class_counts[class.name] = (class_counts[class.name] or 0) + 1
end
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
end)

View file

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

View file

@ -34,32 +34,36 @@ function ctf_respawn_immunity.update_effects(player)
-- end -- end
end end
minetest.register_on_punchplayer(function(player, hitter, local old_can_attack = ctf.can_attack
time_from_last_punch, tool_capabilities, dir, damage) function ctf.can_attack(player, hitter, ...)
if not player or not hitter then if not player or not hitter then
return false return
end end
local pname = player:get_player_name() local pname = player:get_player_name()
local hname = hitter: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 ctf_respawn_immunity.is_immune(player) then
if player and ctf_respawn_immunity.is_immune(player) then hud_event.new(hname, {
minetest.chat_send_player(hname, minetest.colorize("#EE8822", pname .. name = "ctf_respawn_immunity:hit",
" just respawned or joined," .. " and is immune to attacks!")) color = 0xEE8822,
return true value = pname .. " has respawn immunity!",
end })
return false
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"))
immune_players[hname] = nil
ctf_respawn_immunity.update_effects(hitter)
end
end end
end)
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
return old_can_attack(player, hitter, ...)
end
minetest.register_on_joinplayer(ctf_respawn_immunity.set_immune) minetest.register_on_joinplayer(ctf_respawn_immunity.set_immune)
minetest.register_on_respawnplayer(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 modified = true
end end
if stats.captures < 10 then
stats.captures = 10
modified = true
end
if modified then if modified then
ctf_stats.request_save() ctf_stats.request_save()
return true, "Made " .. param .. " a pro!" return true, "Made " .. param .. " a pro!"

View file

@ -207,7 +207,7 @@ end
function ctf_stats.is_pro(name) function ctf_stats.is_pro(name)
local stats = ctf_stats.player(name) local stats = ctf_stats.player(name)
local kd = stats.kills / (stats.deaths == 0 and 1 or stats.deaths) 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 end
ctf.register_on_join_team(function(name, tname, oldteam) ctf.register_on_join_team(function(name, tname, oldteam)
@ -373,7 +373,7 @@ local function invHasGoodWeapons(inv)
return false return false
end end
local function calculateKillReward(victim, killer, toolcaps) function ctf_stats.calculateKillReward(victim, killer, toolcaps)
local vmain, victim_match = ctf_stats.player(victim) local vmain, victim_match = ctf_stats.player(victim)
if not vmain or not victim_match then return 5 end if not vmain or not victim_match then return 5 end
@ -417,33 +417,23 @@ local function calculateKillReward(victim, killer, toolcaps)
return reward return reward
end end
ctf.register_on_killedplayer(function(victim, killer, _, toolcaps) ctf.register_on_killedplayer(function(victim, killer)
-- Suicide is not encouraged here at CTF -- Suicide is not encouraged here at CTF
if victim == killer then if victim == killer then
return return
end end
local main, match = ctf_stats.player(killer) local main, match = ctf_stats.player(killer)
if main and match then if main and match then
local reward = calculateKillReward(victim, killer, toolcaps)
main.kills = main.kills + 1 main.kills = main.kills + 1
main.score = main.score + reward
match.kills = match.kills + 1 match.kills = match.kills + 1
match.score = match.score + reward
match.kills_since_death = match.kills_since_death + 1 match.kills_since_death = match.kills_since_death + 1
_needs_save = true _needs_save = true
reward = math.floor(reward * 100) / 100
hud_score.new(killer, {
name = "ctf_stats:kill_score",
color = "0x00FF00",
value = reward
})
end end
end) end)
minetest.register_on_dieplayer(function(player) minetest.register_on_dieplayer(function(player)
local main, match = ctf_stats.player(player:get_player_name()) local main, match = ctf_stats.player(player:get_player_name())
if main and match then if main and match then
main.deaths = main.deaths + 1 main.deaths = main.deaths + 1
match.deaths = match.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_snow
default:dirt_with_rainforest_litter default:dirt_with_rainforest_litter
default:dirt_with_coniferous_litter default:dirt_with_coniferous_litter
default:dry_dirt
default:dry_dirt_with_dry_grass
default:permafrost default:permafrost
default:permafrost_with_stones 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", { minetest.register_node("default:permafrost", {
description = "Permafrost", description = "Permafrost",
tiles = {"default_permafrost.png"}, 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 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) local function can_dig_door(pos, digger)
replace_old_owner_information(pos) replace_old_owner_information(pos)
if default.can_interact_with_node(digger, pos) then if default.can_interact_with_node(digger, pos) then
@ -362,15 +341,19 @@ function doors.register(name, def)
meta:set_string("owner_team", tname) meta:set_string("owner_team", tname)
end 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 if not (creative and creative.is_enabled_for and creative.is_enabled_for(pn)) then
itemstack:take_item() itemstack:take_item()
end end
minetest.sound_play(def.sounds.place, {pos = pos}) minetest.sound_play(def.sounds.place, {pos = pos})
on_place_node(pos, minetest.get_node(pos),
placer, node, itemstack, pointed_thing)
return itemstack return itemstack
end end
}) })

View file

@ -29,7 +29,7 @@ for i = 1, #dyes do
is_ground_content = false, is_ground_content = false,
groups = {snappy = 2, choppy = 2, oddly_breakable_by_hand = 3, groups = {snappy = 2, choppy = 2, oddly_breakable_by_hand = 3,
flammable = 3, wool = 1}, flammable = 3, wool = 1},
sounds = default.node_sound_defaults(), sounds = default.node_sound_dirt_defaults(),
}) })
end 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.", "You gain more score the better the opponent you defeat.",
"Find weapons in chests or mine and use furnaces to make stronger swords.", "Find weapons in chests or mine and use furnaces to make stronger swords.",
"Players are immune to attack for 5 seconds after they respawn.", "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.", "Use team doors (steel) to stop the enemy walking into your base.",
"Craft 6 cobbles and 1 steel ingot together to make reinforced cobble.", "Craft 6 cobbles and 1 steel ingot together to make reinforced cobble.",
"Sprint by pressing the fast key (E) when you have stamina.", "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.", "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.", "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>", "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 /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 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 /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.", "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.", "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.", "Medics cause troops within 10 metres to regenerate health faster.",
"Hitting your enemy does more damage than not hitting them.", "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.", "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 end

View file

@ -3,29 +3,18 @@
potential_cowards = {} potential_cowards = {}
local TIMER_UPDATE_INTERVAL = 2 local TIMER_UPDATE_INTERVAL = 2
local COMBAT_TIMEOUT_TIME = 20 local COMBAT_TIMEOUT_TIME = 20
local COMBATLOG_SCORE_PENALTY = 10
-- --
--- Make suicides and combat logs award last puncher with kill --- Make suicides and combat logs award last puncher with kill
-- --
minetest.register_on_punchplayer(function(player, hitter, ctf.register_on_attack(function(player, hitter,
time_from_last_punch, tool_capabilities, dir, damage) time_from_last_punch, tool_capabilities, dir, damage)
if player and hitter then 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 pname = player:get_player_name()
local hname = hitter: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 local hp = player:get_hp() - damage
if hp <= 0 then if hp <= 0 then
if potential_cowards[pname] then if potential_cowards[pname] then
@ -38,7 +27,7 @@ time_from_last_punch, tool_capabilities, dir, damage)
potential_cowards[hname] = nil potential_cowards[hname] = nil
end end
return false return
end end
if not potential_cowards[pname] then 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) last_attacker:hud_remove(potential_cowards[hname].hud or 0)
potential_cowards[hname] = nil potential_cowards[hname] = nil
end end
if potential_cowards[pname] then
player:hud_remove(potential_cowards[pname].hud or 0)
potential_cowards[pname] = nil
end
else else
for victim in pairs(potential_cowards) do for victim in pairs(potential_cowards) do
if potential_cowards[victim].puncher == pname then if potential_cowards[victim].puncher == pname then
@ -114,6 +98,11 @@ minetest.register_on_dieplayer(function(player, reason)
end end
end end
end end
if potential_cowards[pname] then
player:hud_remove(potential_cowards[pname].hud or 0)
potential_cowards[pname] = nil
end
end) end)
minetest.register_on_leaveplayer(function(player, timeout) minetest.register_on_leaveplayer(function(player, timeout)
@ -136,6 +125,17 @@ minetest.register_on_leaveplayer(function(player, timeout)
) )
end 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 potential_cowards[pname] = nil
end end
end) end)
@ -156,6 +156,7 @@ minetest.register_globalstep(function(dtime)
end end
potential_cowards[k] = nil potential_cowards[k] = nil
kill_assist.clear_assists(k)
end end
end end
@ -164,9 +165,23 @@ minetest.register_globalstep(function(dtime)
end) end)
ctf_match.register_on_new_match(function() 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 = {} potential_cowards = {}
end) end)
ctf.register_on_new_game(function() 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 = {} potential_cowards = {}
end) end)

View file

@ -1,2 +1,2 @@
name = anticoward 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 local newhp = oldhp + hpregen.amount
if newhp > player:get_properties().hp_max then if newhp > player:get_properties().hp_max then
newhp = player:get_properties().hp_max newhp = player:get_properties().hp_max
kill_assist.clear_assists(player:get_player_name())
end end
if oldhp ~= newhp then if oldhp ~= newhp then
kill_assist.add_heal_assist(player:get_player_name(), hpregen.amount)
player:set_hp(newhp) player:set_hp(newhp)
end end
end end

View file

@ -1 +1,2 @@
name = hpregen 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 players[name] = nil
if interrupted then if interrupted then
minetest.chat_send_player(name, minetest.colorize("#FF4444", hud_event.new(name, {
"Your healing was interrupted"..reason_handler(interrupted))) name = "medkits:interrupt",
color = 0xFF4444,
value = "Your healing was interrupted" .. reason_handler(interrupted),
})
player:set_hp(info.hp) player:set_hp(info.hp)
player:get_inventory():add_item("main", ItemStack("medkits:medkit 1")) player:get_inventory():add_item("main", ItemStack("medkits:medkit 1"))
end end
@ -117,6 +120,7 @@ minetest.register_globalstep(function(dtime)
if pstat then if pstat then
local hp = player:get_hp() local hp = player:get_hp()
if hp < pstat.regen_max then 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)) player:set_hp(math.min(hp + regen_step, pstat.regen_max))
else else
stop_healing(player) 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) local flag, distSQ = ctf_flag.get_nearest(pos)
if flag and pos.y >= flag.y - 1 and distSQ < rs then if flag and pos.y >= flag.y - 1 and distSQ < rs then
minetest.chat_send_player(name, hud_event.new(name, {
"You can't shoot blocks within "..r.." nodes of a flag!") name = "sniper_rifles:hit_base",
color = "warning",
value = "You can't shoot blocks within " .. r .. " nodes of a flag!",
})
return true return true
else else
return old_is_protected(pos, name, info, ...) return old_is_protected(pos, name, info, ...)