commit
1225504258
53 changed files with 862 additions and 294 deletions
|
@ -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 = {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
|
|
|
@ -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, ...)
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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",
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
})
|
||||
|
|
|
@ -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 " ..
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
name = ctf_metrics
|
||||
depends = ctf, ctf_stats
|
||||
optional_depends = prometheus
|
||||
depends = ctf, ctf_stats, ctf_classes
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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!"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"},
|
||||
|
|
BIN
mods/mtg/default/textures/default_dry_dirt.png
Normal file
BIN
mods/mtg/default/textures/default_dry_dirt.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 256 B |
|
@ -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
|
||||
})
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
57
mods/other/hud_events/README.md
Normal file
57
mods/other/hud_events/README.md
Normal 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)
|
139
mods/other/hud_events/init.lua
Normal file
139
mods/other/hud_events/init.lua
Normal 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)
|
3
mods/other/hud_events/mod.conf
Normal file
3
mods/other/hud_events/mod.conf
Normal file
|
@ -0,0 +1,3 @@
|
|||
name = hud_events
|
||||
description = API for displaying events on HUD
|
||||
depends = hudkit
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
name = anticoward
|
||||
depends = ctf, ctf_classes, ctf_match
|
||||
depends = ctf, ctf_classes, ctf_match, kill_assist
|
||||
|
|
|
@ -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)
|
|
@ -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.
|
|
@ -1,2 +0,0 @@
|
|||
name = antisabotage
|
||||
depends = ctf
|
|
@ -1,3 +0,0 @@
|
|||
# Anti Sabotage
|
||||
|
||||
A simple mod that prevents players from sabotaging their teammates by digging blocks out from underneath them
|
|
@ -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
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
name = hpregen
|
||||
depends = kill_assist
|
||||
|
|
90
mods/pvp/kill_assist/init.lua
Normal file
90
mods/pvp/kill_assist/init.lua
Normal 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)
|
2
mods/pvp/kill_assist/mod.conf
Normal file
2
mods/pvp/kill_assist/mod.conf
Normal file
|
@ -0,0 +1,2 @@
|
|||
name = kill_assist
|
||||
depends = ctf, ctf_match, ctf_stats, hud_score
|
|
@ -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)
|
||||
|
|
19
mods/pvp/place_limit/License.txt
Normal file
19
mods/pvp/place_limit/License.txt
Normal 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.
|
8
mods/pvp/place_limit/Readme.md
Normal file
8
mods/pvp/place_limit/Readme.md
Normal 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.
|
49
mods/pvp/place_limit/init.lua
Normal file
49
mods/pvp/place_limit/init.lua
Normal 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)
|
3
mods/pvp/place_limit/mod.conf
Normal file
3
mods/pvp/place_limit/mod.conf
Normal file
|
@ -0,0 +1,3 @@
|
|||
name = place_limit
|
||||
description = Limits block placement
|
||||
depends = hud_events
|
|
@ -1 +1 @@
|
|||
Subproject commit e856150c13972825626bdadd2f80165a853408c1
|
||||
Subproject commit 6939ab0b6d22387fcede92d152cc3cd5c6a0d151
|
|
@ -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, ...)
|
||||
|
|
Loading…
Reference in a new issue