Classes Updates (#994)

* Classes Rework

* Add ctf_settings

* Auto-start respawn timer

* cooldowns: provide cooldown start time

* Allow squinting with zoom

* Restore old scout ability, allow sniper ADS

* Knight combat rework rework rework

* Fix luacheck warnings

* Change classes build timer to a minute

* Default to default wielditem display

* Update class desc

* Fix crash and tweak restart message

* Fix potential issue

* Undo failure of a crash fix

* Fix crash

* Fix potential issues

* Fix crash?

* Roughly restore old knight sword behaviour
This commit is contained in:
LoneWolfHT 2022-04-06 10:49:41 -07:00 committed by GitHub
parent 78ababcb17
commit c412c6058b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 1157 additions and 234 deletions

View File

@ -3,18 +3,18 @@ unused_args = false
globals = {
"PlayerObj", "PlayerName", "HumanReadable", "RunCallbacks",
"ctf_gui", "hud_events", "mhud", "physics", "rawf",
"ctf_gui", "hud_events", "mhud", "physics", "rawf", "ctf_settings",
"ctf_api", "ctf_chat", "ctf_combat_mode", "ctf_core", "ctf_cosmetics",
"ctf_healing", "ctf_kill_list", "ctf_map", "ctf_melee", "ctf_modebase",
"ctf_ranged", "ctf_rankings", "ctf_report", "ctf_teams",
"ctf_ranged", "ctf_rankings", "ctf_report", "ctf_teams", "ctf_player",
"dropondie", "grenades",
"chatcmdbuilder", "crafting", "hpbar", "playertag", "random_messages",
"skybox", "throwable_snow",
"default", "doors",
"default", "doors", "player_api", "sfinv", "binoculars",
"vector",
math = {
@ -47,6 +47,7 @@ read_globals = {
"ItemStack",
"Settings",
"unpack",
"loadstring",
table = {
fields = {

View File

@ -2,10 +2,14 @@
A tool for easily creating basic CTF-themed GUIs
**There is a new API in the works, use of this is not recommended**
# API
```lua
ctf_gui.show_formspec(player, "modname:formname", {
ctf_gui.old_init()
ctf_gui.old_show_formspec(player, "modname:formname", {
size = {x = <x size>, y = <y size>},
title = "Formspec Title",
description = "Text below the title",
@ -86,4 +90,3 @@ ctf_gui.show_formspec(player, "modname:formname", {
-- Called when this element shows up in on_player_recieve_fields
end,
}

37
mods/apis/ctf_gui/dev.lua Normal file
View File

@ -0,0 +1,37 @@
function ctf_gui.show_formspec_dev(player, formname, formspec, formcontext)
local filepath = minetest.get_worldpath().."/ctf_gui/"
local filename = filepath.."file_edit.txt"
minetest.mkdir(filepath)
local file = assert(io.open(filename, "w"))
file:write(formspec)
file:close()
local function interval()
if formspec:sub(1, 3) == "[f]" then
local result, form = pcall(loadstring(formspec:sub(4)), formcontext)
ctf_gui.show_formspec(player, formname, result and form or "")
else
ctf_gui.show_formspec(player, formname, formspec)
end
minetest.after(1, function()
local f = assert(io.open(filename, "r"))
formspec = f:read("*a")
f:close()
if formspec ~= "exit" then
interval()
else
minetest.request_shutdown("Formspec dev requested shutdown", true)
end
end)
end
interval()
end

View File

@ -5,8 +5,8 @@ ctf_gui = {
}
local context = {}
local gui_users_initialized = {}
function ctf_gui.init()
local modname = minetest.get_current_modname()
@ -14,6 +14,45 @@ function ctf_gui.init()
gui_users_initialized[modname] = true
ctf_core.register_on_formspec_input(modname..":", function(pname, formname, fields, ...)
local ctx = context[pname]
if not ctx then return end
if ctx._formname == formname and ctx._on_formspec_input then
if ctx._privs then
local playerprivs = minetest.get_player_privs(pname)
for priv, needed in pairs(ctx._privs) do
if needed and not playerprivs[priv] then
minetest.log("warning", string.format(
"Player '%q' doesn't have the privs needed to access the formspec '%s'",
pname, formname
))
return
end
end
end
if fields.quit and ctx._on_quit then
ctx._on_quit(pname, fields)
else
local action = ctx._on_formspec_input(pname, ctx, fields, ...)
if action == "refresh" then
minetest.show_formspec(pname, ctx._formname, ctx._formspec(ctx))
end
end
end
end)
end
function ctf_gui.old_init()
local modname = minetest.get_current_modname()
assert(not gui_users_initialized[modname], "Already initialized for mod: "..modname)
gui_users_initialized[modname] = true
ctf_core.register_on_formspec_input(modname..":", function(pname, formname, fields)
local ctx = context[pname]
if not ctx then return end
@ -24,8 +63,10 @@ function ctf_gui.init()
for priv, needed in pairs(ctx.privs) do
if needed and not playerprivs[priv] then
minetest.log("warning", "Player " .. dump(pname) ..
" doesn't have the privs needed to access the formspec " .. dump(formname))
minetest.log("warning", string.format(
"Player '%q' doesn't have the privs needed to access the formspec '%s'",
pname, formname
))
return
end
end
@ -44,8 +85,10 @@ function ctf_gui.init()
end
end
if bad then
minetest.log("warning", "Player " .. dump(pname) ..
" sent unallowed values for formspec " .. dump(formname) .. " : " .. dump(fields))
minetest.log("warning", string.format(
"Player %s sent unallowed values for formspec %s : %s",
pname, formname, dump(fields)
))
return
end
end
@ -64,7 +107,52 @@ function ctf_gui.init()
end)
end
function ctf_gui.show_formspec(player, formname, formdef)
function ctf_gui.show_formspec(player, formname, formspec, formcontext)
player = PlayerName(player)
context[player] = formcontext or {}
context[player]._formname = formname
context[player]._formspec = formspec
if type(formspec) == "function" then
minetest.show_formspec(player, formname, formspec(formcontext))
else
minetest.show_formspec(player, formname, formspec)
end
end
do
local remove = table.remove
local concat = table.concat
local formspec_escape = minetest.formspec_escape
local format = string.format
local unpck = unpack
function ctf_gui.list_to_element(l)
local base = remove(l, 1)
for k, format_var in ipairs(l) do
l[k] = formspec_escape(format_var)
end
return format(base, unpck(l))
end
local lte = ctf_gui.list_to_element
function ctf_gui.list_to_formspec_str(l)
for k, v in ipairs(l) do
if type(v) == "table" then
l[k] = lte(v)
end
end
return concat(l, "")
end
end
function ctf_gui.old_show_formspec(player, formname, formdef)
player = PlayerName(player)
formdef.formname = formname
@ -289,7 +377,10 @@ function ctf_gui.show_formspec(player, formname, formdef)
"]"
end
formdef._info = formdef
context[player] = formdef
minetest.show_formspec(player, formdef.formname, formspec)
end
dofile(minetest.get_modpath("ctf_gui").."/dev.lua")

View File

@ -0,0 +1,122 @@
ctf_settings = {
settings = {},
settings_list = {},
}
local FORMSIZE = {x = 8, y = 5}
local SCROLLBAR_W = 0.4
minetest.after(0, function()
table.sort(ctf_settings.settings_list, function(a, b) return a < b end)
end)
--[[
Settings should only be registered at loadtime
{
type = "bool",
label = "Setting name/label",
description = "Text in tooltip",
default = "default value",
on_change = function(player, new_value)
<...>
end
}
]]
---@param def table
function ctf_settings.register(name, def)
ctf_settings.settings[name] = def
table.insert(ctf_settings.settings_list, name)
end
function ctf_settings.set(player, setting, value)
player:get_meta():set_string("ctf_settings:"..setting, value)
end
---@return string Returns the player's chosen setting value, the default given at registration, or if both are unset: ""
function ctf_settings.get(player, setting)
local value = player:get_meta():get_string("ctf_settings:"..setting)
local info = ctf_settings.settings[setting]
return value == "" and info.default or value
end
minetest.register_on_mods_loaded(function()
sfinv.register_page("ctf_settings:settings", {
title = "Settings",
get = function(self, player, context)
local setting_list = {}
local lastypos
for k, setting in ipairs(ctf_settings.settings_list) do
local settingdef = ctf_settings.settings[setting]
if settingdef.type == "bool" then
lastypos = (k / 2) - 1
setting_list[k] = {
"checkbox[0,%f;%s;%s;%s]tooltip[%s;%s]",
lastypos,
setting,
settingdef.label or setting,
ctf_settings.get(player, setting),
setting,
settingdef.description or HumanReadable(setting)
}
end
end
local form = {
{"box[-0.1,-0.1;%f,%f;#00000055]", FORMSIZE.x - SCROLLBAR_W, FORMSIZE.y},
{"scroll_container[-0.1,0.3;%f,%f;settings_scrollbar;vertical;0.1]",
FORMSIZE.x - SCROLLBAR_W,
FORMSIZE.y + 0.7
},
ctf_gui.list_to_formspec_str(setting_list),
"scroll_container_end[]",
{"scrollbaroptions[max=%d]", math.ceil((lastypos - 3.833) * 11.538)},
{"scrollbar[%f,-0.1;%f,%f;vertical;settings_scrollbar;%f]",
FORMSIZE.x - SCROLLBAR_W,
SCROLLBAR_W,
FORMSIZE.y,
context and context.settings_scrollbar or 0
},
}
return sfinv.make_formspec(player, context, ctf_gui.list_to_formspec_str(form), true)
end,
on_player_receive_fields = function(self, player, context, fields)
local refresh = false
for field, value in pairs(fields) do
local setting = ctf_settings.settings[field]
if setting then
if setting.type == "bool" then
local newvalue = value == "true" and "true" or "false"
ctf_settings.set(player, field, newvalue)
if setting.on_change then
setting.on_change(player, newvalue)
end
end
refresh = true
end
end
if not refresh then return end
if fields.settings_scrollbar then
local scrollevent = minetest.explode_scrollbar_event(fields.settings_scrollbar)
if scrollevent.value then
context.settings_scrollbar = scrollevent.value
end
end
sfinv.set_page(player, sfinv.get_page(player))
end,
})
end)

View File

@ -0,0 +1,2 @@
name = ctf_settings
depends = sfinv, ctf_gui

View File

@ -38,4 +38,4 @@ end
--- * flag_team
function ctf_api.register_on_flag_take(func)
table.insert(ctf_api.registered_on_flag_take, func)
end
end

View File

@ -29,6 +29,8 @@ local sword_mats = {
}
}
local attack_cooldown = ctf_core.init_cooldowns()
function ctf_melee.simple_register_sword(name, def)
local base_def = {
description = def.description,
@ -74,6 +76,253 @@ function ctf_melee.simple_register_sword(name, def)
ctf_melee.registered_swords[name] = base_def
end
local slash_stab_anim_length = ctf_player.animation_time.stab_slash
local EXTRA_ANIM_LENGTH = {
slash = slash_stab_anim_length * 0.5,
stab = 0.1,
}
local SWOOSH_SOUND_DISTANCE = 8
local COMBAT_SOUND_DISTANCE = 16
local KNOCKBACK = {slash = 6, stab = 0}
local HIT_BOOST = 9
local function dopunch(target, attacker, ignores, attack_capabilities, dir, attack_interval)
if target.ref:is_player() and not ignores[target.ref] and
ctf_modebase:get_current_mode().can_punchplayer(attacker, target.ref) then
ignores[target.ref] = true -- add to the table we were passed
target.ref:punch(attacker, attack_interval, attack_capabilities, dir)
minetest.sound_play("player_damage", {
object = attacker,
exclude_player = target.ref:get_player_name(),
pitch = 0.8,
gain = 0.4,
max_hear_distance = COMBAT_SOUND_DISTANCE,
}, true)
return true
end
return false
end
local function slash_stab_sword_func(keypress, itemstack, user, pointed)
local uname = user:get_player_name()
local cooldown = attack_cooldown:get(uname)
local anim = (keypress == "LMB" and "stab") or "slash"
if cooldown then
-- if the cooldown has <= 0.3 seconds left then let them queue another stab
if anim == "stab" and cooldown._time - (os.clock() - cooldown.start_time) <= 0.3 then
-- Don't queue a miss
if not pointed or pointed.type ~= "object" then
return
end
cooldown._on_end = function(self)
local player = minetest.get_player_by_name(uname)
if not player then return end
local wielded = player:get_wielded_item()
if wielded:get_name() ~= itemstack:get_name() then return end
slash_stab_sword_func(keypress, wielded, user, pointed)
end
end
return
end
local attack_interval = slash_stab_anim_length + EXTRA_ANIM_LENGTH[anim]
ctf_player.set_stab_slash_anim(anim, user, EXTRA_ANIM_LENGTH[anim])
attack_cooldown:set(uname, {
_time = attack_interval,
_on_end = function(self) -- Repeat attack if player is holding down the button
local player = minetest.get_player_by_name(uname)
if not player then return end
local controls = player:get_player_control()
local wielded = player:get_wielded_item()
if wielded:get_name() == itemstack:get_name() and (controls.LMB or controls.RMB) then
if not controls[keypress] then
keypress = (keypress == "LMB") and "RMB" or "LMB"
end
slash_stab_sword_func(keypress, wielded, player, nil)
end
end,
})
local startpos = vector.offset(user:get_pos(), 0, user:get_properties().eye_height, 0)
local def = itemstack:get_definition()
local attack_capabilities = def.tool_capabilities
local dir = user:get_look_dir()
local user_kb_dir = vector.new(dir)
local ignores = {[user] = true}
local section = math.pi/12
local hit_player = false
local axis
local rays
attack_capabilities.damage_groups.knockback = KNOCKBACK[anim]
axis = vector.cross(vector.new(dir.z, 0, -dir.x), dir)
if pointed and pointed.type == "object" then
hit_player = dopunch(pointed, user, ignores, attack_capabilities, dir, attack_interval) or hit_player
pointed = true
end
if anim == "slash" then
user_kb_dir = -user_kb_dir
rays = {
vector.rotate_around_axis(dir, axis, section * 3),
vector.rotate_around_axis(dir, axis, section * 2),
vector.rotate_around_axis(dir, axis, section ),
vector.rotate_around_axis(dir, axis, -section * 3),
vector.rotate_around_axis(dir, axis, -section * 2),
vector.rotate_around_axis(dir, axis, -section ),
(pointed ~= true) and dir or nil,
}
minetest.sound_play("ctf_melee_whoosh", {
object = user,
pitch = 1.1,
gain = 1.2,
max_hear_distance = SWOOSH_SOUND_DISTANCE,
}, true)
else
rays = {
(pointed ~= true) and dir or nil,
vector.rotate_around_axis(dir, axis, -section),
vector.rotate_around_axis(dir, axis, section),
}
minetest.sound_play("ctf_melee_whoosh", {
object = user,
gain = 1.1,
max_hear_distance = SWOOSH_SOUND_DISTANCE,
}, true)
end
user_kb_dir.y = math.max(math.min(user_kb_dir.y, 0.4), 0)
for _, shootdir in ipairs(rays) do
local ray = minetest.raycast(startpos, startpos + (shootdir * 4), true, false)
minetest.add_particle({
pos = startpos,
velocity = shootdir * 44,
expirationtime = 0.1,
size = 5,
collisiondetection = true,
collision_removal = true,
object_collision = false,
texture = "ctf_melee_slash.png",
glow = 5,
})
for hit in ray do
if hit.type ~= "object" then break end
hit_player = dopunch(hit, user, ignores, attack_capabilities, shootdir, attack_interval) or hit_player
end
end
if hit_player then
user:add_velocity(user_kb_dir * HIT_BOOST)
end
end
function ctf_melee.register_sword(name, def)
local base_def = {
description = def.description,
inventory_image = def.inventory_image,
inventory_overlay = def.inventory_overlay,
wield_image = def.wield_image,
damage_groups = def.damage_groups,
disable_mine_anim = true,
tool_capabilities = {
full_punch_interval = slash_stab_anim_length,
max_drop_level=1,
groupcaps={
snappy={times={[1]=2.5, [2]=1.20, [3]=0.35}, uses=0, maxlevel=3},
},
punch_attack_uses = 0,
},
sound = {breaks = "default_tool_breaks"},
groups = def.groups or {},
}
local damage_capabilities = base_def.tool_capabilities
damage_capabilities.damage_groups = base_def.damage_groups
base_def.groups.sword = 1
local function rightclick_func(...)
slash_stab_sword_func("RMB", ...)
if def.rightclick_func then
return def.rightclick_func(...)
end
end
base_def.on_use = function(itemstack, user, pointed, ...)
if pointed then
if pointed.type == "object" then
if not pointed.ref:is_player() then
pointed.ref:punch(user, slash_stab_anim_length, damage_capabilities, vector.new())
return
end
elseif pointed.type == "node" then
local node = minetest.get_node(pointed.under)
local node_on_punch = minetest.registered_nodes[node.name].on_punch
if node_on_punch then
node_on_punch(pointed.under, node, user, pointed)
end
end
end
slash_stab_sword_func("LMB", itemstack, user, pointed, ...)
end
base_def.on_place = function(itemstack, user, pointed, ...)
local pointed_def = false
local node
if pointed and pointed.under then
node = minetest.get_node(pointed.under)
pointed_def = minetest.registered_nodes[node.name]
end
if pointed_def and pointed_def.on_rightclick then
return minetest.item_place(itemstack, user, pointed)
else
return rightclick_func(itemstack, user, pointed, ...)
end
end
base_def.on_secondary_use = rightclick_func
minetest.register_tool(name, base_def)
ctf_melee.registered_swords[name] = base_def
end
for mat, def in pairs(sword_mats) do
ctf_melee.simple_register_sword("ctf_melee:sword_"..mat, def)

View File

@ -1,2 +1,2 @@
name = ctf_melee
depends = ctf_core
depends = ctf_core, ctf_player

Binary file not shown.

After

Width:  |  Height:  |  Size: 589 B

View File

@ -1,8 +1,11 @@
ctf_ranged = {}
local hud = mhud.init()
local shoot_cooldown = ctf_core.init_cooldowns()
local scoped = {}
ctf_ranged = {
scoped = {}
}
local scoped = ctf_ranged.scoped
local scale_const = 6
local timer = 1
@ -23,11 +26,17 @@ local function process_ray(ray, user, look_dir, def)
if hitpoint then
if hitpoint.type == "node" then
local nodedef = minetest.registered_nodes[minetest.get_node(hitpoint.under).name]
local node = minetest.get_node(hitpoint.under)
local nodedef = minetest.registered_nodes[node.name]
if nodedef.groups.snappy or (nodedef.groups.oddly_breakable_by_hand or 0) >= 3 then
if nodedef.groups.snappy or nodedef.groups.ranged_breakable or
(nodedef.groups.oddly_breakable_by_hand or 0) >= 3 then
if not minetest.is_protected(hitpoint.under, user:get_player_name()) then
minetest.dig_node(hitpoint.under)
if nodedef.groups.ranged_breakable and nodedef.on_dig then
nodedef.on_dig(hitpoint.under, node, user)
else
minetest.dig_node(hitpoint.under)
end
end
else
if nodedef.walkable and nodedef.pointable then
@ -112,6 +121,7 @@ function ctf_ranged.simple_register_gun(name, def)
loaded_def.inventory_overlay = def.texture_overlay
loaded_def.wield_image = def.wield_texture or def.texture
loaded_def.groups.not_in_creative_inventory = nil
loaded_def.on_secondary_use = def.on_secondary_use
loaded_def.on_use = function(itemstack, user)
if not ctf_ranged.can_use_gun(user, name) then
minetest.sound_play("ctf_ranged_click", {pos = user:get_pos()}, true)
@ -186,13 +196,17 @@ minetest.register_on_leaveplayer(function(player)
scoped[player:get_player_name()] = nil
end)
local function show_scope(name, item_name, fov_mult)
function ctf_ranged.show_scope(name, item_name, fov_mult)
local player = minetest.get_player_by_name(name)
if not player then
return
end
scoped[name] = item_name
scoped[name] = {
item_name = item_name,
wielditem = player:hud_get_flags().wielditem
}
hud:add(player, "ctf_ranged:scope", {
hud_elem_type = "image",
position = {x = 0.5, y = 0.5},
@ -207,18 +221,17 @@ local function show_scope(name, item_name, fov_mult)
end
local function hide_scope(name)
function ctf_ranged.hide_scope(name)
local player = minetest.get_player_by_name(name)
if not player then
return
end
scoped[name] = nil
hud:remove(name, "ctf_ranged:scope")
player:set_fov(0)
physics.remove(name, "sniper_rifles:scoping")
player:hud_set_flags({ wielditem = true })
player:hud_set_flags({ wielditem = scoped[name].wielditem })
scoped[name] = nil
end
ctf_ranged.simple_register_gun("ctf_ranged:pistol", {
@ -289,10 +302,10 @@ ctf_ranged.simple_register_gun("ctf_ranged:sniper", {
liquid_travel_dist = 10,
rightclick_func = function(itemstack, user, pointed, ...)
if scoped[user:get_player_name()] then
hide_scope(user:get_player_name())
ctf_ranged.hide_scope(user:get_player_name())
else
local item_name = itemstack:get_name():gsub("_loaded", "")
show_scope(user:get_player_name(), item_name, 4)
ctf_ranged.show_scope(user:get_player_name(), item_name, 4)
end
end
})
@ -309,10 +322,10 @@ ctf_ranged.simple_register_gun("ctf_ranged:sniper_magnum", {
liquid_travel_dist = 15,
rightclick_func = function(itemstack, user, pointed, ...)
if scoped[user:get_player_name()] then
hide_scope(user:get_player_name())
ctf_ranged.hide_scope(user:get_player_name())
else
local item_name = itemstack:get_name():gsub("_loaded", "")
show_scope(user:get_player_name(), item_name, 8)
ctf_ranged.show_scope(user:get_player_name(), item_name, 8)
end
end
})
@ -332,11 +345,11 @@ minetest.register_globalstep(function(dtime)
end
time = 0
for name, original_item in pairs(scoped) do
for name, info in pairs(scoped) do
local player = minetest.get_player_by_name(name)
local wielded_item = player:get_wielded_item():get_name():gsub("_loaded", "")
if wielded_item ~= original_item then
hide_scope(name)
if wielded_item ~= info.item_name then
ctf_ranged.hide_scope(name)
end
end
end)

View File

@ -5,7 +5,7 @@ function ctf_core.init_cooldowns()
local pname = PlayerName(player)
if self.players[pname] then
self.players[pname]:cancel()
self.players[pname]._timer:cancel()
if not time then
self.players[pname] = nil
@ -13,7 +13,24 @@ function ctf_core.init_cooldowns()
end
end
self.players[pname] = minetest.after(time, function() self.players[pname] = nil end)
if type(time) ~= "table" then
time = {_time = time}
end
time._timer = minetest.after(time._time, function()
if time._on_end then
local copy = table.copy(self.players[pname])
self.players[pname] = nil
time._on_end(copy)
else
self.players[pname] = nil
end
end)
time.start_time = os.clock()
self.players[pname] = time
end,
get = function(self, player)
return self.players[PlayerName(player)]

View File

@ -43,7 +43,7 @@ minetest.register_node("ctf_map:damage_cobble", {
tiles = {"ctf_map_damage_cobble.png"},
is_ground_content = false,
walkable = true,
groups = {cracky=3, stone=2},
groups = {cracky=3, stone=2, ranged_breakable=1},
on_dig = function(pos, node, digger, extra)
if not digger:is_player() then return end
@ -100,4 +100,4 @@ minetest.register_node("ctf_map:reinforced_cobble", {
is_ground_content = false,
groups = {cracky = 1, stone = 2},
sounds = default.node_sound_stone_defaults(),
})
})

View File

@ -87,7 +87,7 @@ minetest.register_chatcommand("ctf_map", {
inv:add_item("main", "ctf_map:adminpick")
end
if not ctf_core.settings.server_mode == "mapedit" then
if ctf_core.settings.server_mode ~= "mapedit" then
minetest.chat_send_player(name,
minetest.colorize("red", "It is not recommended to edit maps unless the server is in mapedit mode"))
end

View File

@ -3,7 +3,7 @@ local CURRENT_MAP_VERSION = "2"
function ctf_map.skybox_exists(subdir)
local list = minetest.get_dir_list(subdir, true)
return not (table.indexof(list, "skybox") == -1)
return table.indexof(list, "skybox") ~= -1
end
function ctf_map.load_map_meta(idx, dirname)

View File

@ -1,4 +1,4 @@
ctf_gui.init()
ctf_gui.old_init()
local context = {}
@ -40,7 +40,7 @@ function ctf_map.show_map_editor(player)
table.sort(dirlist_sorted)
local selected_map = 1
ctf_gui.show_formspec(player, "ctf_map:start", {
ctf_gui.old_show_formspec(player, "ctf_map:start", {
size = {x = 8, y = 10.2},
title = "Capture The Flag Map Editor",
description = "Would you like to edit an existing map or create a new one?",
@ -92,7 +92,7 @@ function ctf_map.show_map_editor(player)
pos = {0.1, 1.8},
func = function(pname, fields)
minetest.after(0.1, function()
ctf_gui.show_formspec(pname, "ctf_map:loading", {
ctf_gui.old_show_formspec(pname, "ctf_map:loading", {
size = {x = 6, y = 4},
title = "Capture The Flag Map Editor",
description = "Placing map '"..dirlist_sorted[selected_map].."'. This will take a few seconds..."
@ -114,7 +114,7 @@ function ctf_map.show_map_editor(player)
pos = {(8-ctf_gui.ELEM_SIZE.x) - 0.3, 1.8},
func = function(pname, fields)
minetest.after(0.1, function()
ctf_gui.show_formspec(pname, "ctf_map:loading", {
ctf_gui.old_show_formspec(pname, "ctf_map:loading", {
size = {x = 6, y = 4},
title = "Capture The Flag Map Editor",
description = "Resuming map '"..dirlist_sorted[selected_map]..
@ -188,7 +188,7 @@ function ctf_map.show_map_save_form(player, scroll_pos)
-- MAP ENABLED
elements.enabled = {
type = "checkbox", label = "Map Enabled", pos = {0, 2}, default = context[player].enabled,
func = function(pname, fields, name) context[pname].enabled = fields[name] == "true" or false end,
func = function(pname, fields) context[pname].enabled = fields.enabled == "true" or false end,
}
-- FOLDER NAME, MAP NAME, MAP AUTHOR(S), MAP HINT, MAP LICENSE, OTHER INFO
@ -201,8 +201,8 @@ function ctf_map.show_map_save_form(player, scroll_pos)
type = "field", label = label,
pos = {0, ypos}, size = {6, 0.7},
default = context[player][name],
func = function(pname, fields, fname)
context[pname][name] = fields[fname]
func = function(pname, fields)
context[pname][name] = fields[name]
end,
}
@ -213,8 +213,8 @@ function ctf_map.show_map_save_form(player, scroll_pos)
elements.initial_stuff = {
type = "field", label = "Map Initial Stuff", pos = {0, ypos}, size = {6, 0.7},
default = table.concat(context[player].initial_stuff or {"none"}, ","),
func = function(pname, fields, name)
context[pname].initial_stuff = string.split(fields[name]:gsub("%s?,%s?", ","), ",")
func = function(pname, fields)
context[pname].initial_stuff = string.split(fields.initial_stuff:gsub("%s?,%s?", ","), ",")
end,
}
ypos = ypos + 1.4
@ -223,8 +223,8 @@ function ctf_map.show_map_save_form(player, scroll_pos)
elements.treasures = {
type = "textarea", label = "Map Treasures", pos = {0, ypos}, size = {ctf_gui.FORM_SIZE.x-3.6, 2.1},
default = context[player].treasures,
func = function(pname, fields, name)
context[pname].treasures = fields[name]
func = function(pname, fields)
context[pname].treasures = fields.treasures
end,
}
ypos = ypos + 3.1
@ -243,12 +243,12 @@ function ctf_map.show_map_save_form(player, scroll_pos)
size = {6, ctf_gui.ELEM_SIZE.y},
items = ctf_map.skyboxes,
default_idx = table.indexof(ctf_map.skyboxes, context[player].skybox),
func = function(pname, fields, name)
func = function(pname, fields)
local oldval = context[pname].skybox
context[pname].skybox = fields[name]
context[pname].skybox = fields.skybox
if context[pname].skybox ~= oldval then
skybox.set(PlayerObj(pname), table.indexof(ctf_map.skyboxes, fields[name])-1)
skybox.set(PlayerObj(pname), table.indexof(ctf_map.skyboxes, fields.skybox)-1)
end
end,
}
@ -279,7 +279,7 @@ function ctf_map.show_map_save_form(player, scroll_pos)
pos = {0, ypos},
size = {ctf_gui.FORM_SIZE.x/2 - 0.2, 2},
items = context[player].game_modes,
func = function(pname, fields, fname)
func = function(pname, fields)
local event = minetest.explode_textlist_event(fields.game_modes)
if event.type == "DCL" then
@ -289,12 +289,12 @@ function ctf_map.show_map_save_form(player, scroll_pos)
end
end
}
elements["available_game_modes"] = {
elements.available_game_modes = {
type = "textlist",
pos = {ctf_gui.FORM_SIZE.x/2 + 0.2, ypos},
size = {ctf_gui.FORM_SIZE.x/2 - 0.2, 2},
items = available_game_modes,
func = function(pname, fields, fname)
func = function(pname, fields)
local event = minetest.explode_textlist_event(fields.available_game_modes)
if event.type == "DCL" then
@ -312,12 +312,12 @@ function ctf_map.show_map_save_form(player, scroll_pos)
type = "field", label = label,
pos = {0, ypos}, size = {4, 0.7},
default = context[player]["phys_"..name] or 1,
func = function(pname, fields, fname)
func = function(pname, fields)
local oldval = context[pname]["phys_"..name]
context[pname]["phys_"..name] = tonumber(fields[fname]) or 1
context[pname]["phys_"..name] = tonumber(fields[name]) or 1
if context[pname]["phys_"..name] ~= oldval then
physics.set(pname, "ctf_map_editor_"..name, {[name] = tonumber(fields[fname] or 1)})
physics.set(pname, "ctf_map_editor_"..name, {[name] = tonumber(fields[name] or 1)})
end
end,
}
@ -329,9 +329,9 @@ function ctf_map.show_map_save_form(player, scroll_pos)
elements.start_time = {
type = "field", label = "Map start_time", pos = {0, ypos}, size = {4, 0.7},
default = context[player].start_time or ctf_map.DEFAULT_START_TIME,
func = function(pname, fields, name)
func = function(pname, fields)
local oldval = context[pname].start_time
context[pname].start_time = tonumber(fields[name] or ctf_map.DEFAULT_START_TIME)
context[pname].start_time = tonumber(fields.start_time or ctf_map.DEFAULT_START_TIME)
if context[pname].start_time ~= oldval then
minetest.registered_chatcommands["time"].func(pname, tostring(context[pname].start_time))
@ -344,9 +344,9 @@ function ctf_map.show_map_save_form(player, scroll_pos)
elements.time_speed = {
type = "field", label = "Map time_speed (Multiplier)", pos = {0, ypos},
size = {4, 0.7}, default = context[player].time_speed or "1",
func = function(pname, fields, name)
func = function(pname, fields)
local oldval = context[pname].time_speed
context[pname].time_speed = tonumber(fields[name] or "1")
context[pname].time_speed = tonumber(fields.time_speed or "1")
if context[pname].time_speed ~= oldval then
minetest.settings:set("time_speed", context[pname].time_speed * 72)
@ -363,8 +363,8 @@ function ctf_map.show_map_save_form(player, scroll_pos)
label = HumanReadable(teamname) .. " Team",
pos = {0, idx},
default = def.enabled,
func = function(pname, fields, name)
context[pname].teams[teamname].enabled = fields[name] == "true" or false
func = function(pname, fields)
context[pname].teams[teamname].enabled = fields[teamname.."_checkbox"] == "true" or false
end,
}
idx = idx + 1
@ -464,10 +464,10 @@ function ctf_map.show_map_save_form(player, scroll_pos)
pos = {7.2, idx},
size = {1, ctf_gui.ELEM_SIZE.y},
default = context[player].chests[id].amount,
func = function(pname, fields, name)
func = function(pname, fields)
if not context[pname].chests[id] then return end
local newnum = tonumber(fields[name])
local newnum = tonumber(fields["chestzone_chests_"..id])
if newnum then
context[pname].chests[id].amount = newnum
end
@ -530,7 +530,7 @@ function ctf_map.show_map_save_form(player, scroll_pos)
idx = idx + 1
-- Show formspec
ctf_gui.show_formspec(player, "ctf_map:save", {
ctf_gui.old_show_formspec(player, "ctf_map:save", {
title = "Capture The Flag Map Editor",
description = "Save your map or edit the config.\nRemember to press ENTER after writing to a field",
scrollheight = 176 + ((idx - 24) * 10) + 4,

View File

@ -163,6 +163,33 @@ local function end_combat_mode(player, reason, killer, weapon_image)
ctf_combat_mode.end_combat(player)
end
local function can_punchplayer(player, hitter)
if not ctf_modebase.match_started then
return false, "The match hasn't started yet!"
end
local pname, hname = player:get_player_name(), hitter:get_player_name()
local pteam, hteam = ctf_teams.get(player), ctf_teams.get(hitter)
if not ctf_modebase.remove_respawn_immunity(hitter) then
return false, "You can't attack while immune"
end
if not pteam then
return false, pname .. " is not in a team!"
end
if not hteam then
return false, "You are not in a team!"
end
if pteam == hteam and pname ~= hname then
return false, pname .. " is on your team!"
end
return true
end
return {
on_new_match = function()
team_list = {}
@ -424,37 +451,21 @@ return {
return "You need at least 10 score to access this chest", deny_pro
end,
can_punchplayer = can_punchplayer,
on_punchplayer = function(player, hitter, damage, _, tool_capabilities)
if not hitter:is_player() or player:get_hp() <= 0 then return false end
if not ctf_modebase.match_started then
return false, "The match hasn't started yet!"
end
local allowed, message = can_punchplayer(player, hitter)
local pname, hname = player:get_player_name(), hitter:get_player_name()
local pteam, hteam = ctf_teams.get(player), ctf_teams.get(hitter)
if not ctf_modebase.remove_respawn_immunity(hitter) then
return false, "You can't attack while immune"
end
if not pteam then
return false, pname .. " is not in a team!"
end
if not hteam then
return false, "You are not in a team!"
end
if pteam == hteam and pname ~= hname then
return false, pname .. " is on your team!"
if not allowed then
return false, message
end
local weapon_image = get_weapon_image(hitter, tool_capabilities)
if player:get_hp() <= damage then
end_combat_mode(pname, "punch", hname, weapon_image)
elseif pname ~= hname then
end_combat_mode(player:get_player_name(), "punch", hitter:get_player_name(), weapon_image)
elseif player:get_player_name() ~= hitter:get_player_name() then
ctf_combat_mode.add_hitter(player, hitter, weapon_image, 15)
end

View File

@ -15,7 +15,7 @@ if ctf_core.settings.server_mode == "play" then
end
local function show_flag_color_form(player, target_pos, param2)
ctf_gui.show_formspec(player, "ctf_modebase:flag_color_select", {
ctf_gui.old_show_formspec(player, "ctf_modebase:flag_color_select", {
title = "Flag Color Selection",
description = "Choose a color for this flag",
privs = {ctf_map_editor = true},
@ -30,7 +30,7 @@ local function show_flag_color_form(player, target_pos, param2)
label = "Choose",
exit = true,
pos = {"center", 1.5},
func = function(playername, fields, field_name)
func = function(playername, fields)
if not target_pos or not fields.teams then return end
minetest.set_node(target_pos, {name = "ctf_modebase:flag_top_"..fields.teams, param2 = param2})

View File

@ -38,7 +38,7 @@ ctf_modebase = {
flag_captured = {},
}
ctf_gui.init()
ctf_gui.old_init()
function ctf_modebase.announce(msg)
end

View File

@ -21,8 +21,8 @@ local function show_catalog(pname, current_map)
},
rows = ctf_modebase.map_catalog.map_names,
default_idx = current_map,
func = function(_, fields, name)
local evt = minetest.explode_table_event(fields[name])
func = function(_, fields)
local evt = minetest.explode_table_event(fields.list)
if evt.type == "CHG" then
show_catalog(pname, evt.row)
end
@ -153,7 +153,7 @@ local function show_catalog(pname, current_map)
}
end
ctf_gui.show_formspec(pname, "ctf_map:catalog", formspec)
ctf_gui.old_show_formspec(pname, "ctf_map:catalog", formspec)
end
minetest.register_chatcommand("maps", {

View File

@ -1,11 +1,30 @@
local hud = mhud.init()
local marker_cooldown = ctf_core.init_cooldowns()
local markers = {}
local MARKER_LIFETIME = 20
local MARKER_RANGE = 150
local MARKER_PLACE_INTERVAL = 5
ctf_modebase.markers = {}
-- Code taken from mods/mtg/mtg_binoculars, changed default FOV
function binoculars.update_player_property(player)
local new_zoom_fov = 84
if player:get_inventory():contains_item(
"main", "binoculars:binoculars") then
new_zoom_fov = 10
elseif minetest.is_creative_enabled(player:get_player_name()) then
new_zoom_fov = 15
end
-- Only set property if necessary to avoid player mesh reload
if player:get_properties().zoom_fov ~= new_zoom_fov then
player:set_properties({zoom_fov = new_zoom_fov})
end
end
local function add_marker(pname, pteam, message, pos, owner)
if not hud:get(pname, "marker_" .. owner) then
hud:add(pname, "marker_" .. owner, {
@ -88,72 +107,80 @@ ctf_api.register_on_match_end(function()
hud:remove_all()
end)
local function marker_func(name, param)
local pteam = ctf_teams.get(name)
if marker_cooldown:get(name) then
return false, "You can only place a marker every "..MARKER_PLACE_INTERVAL.." seconds"
end
if not pteam then
return false, "You need to be in a team to use markers!"
end
local player = minetest.get_player_by_name(name)
local pos1 = vector.offset(player:get_pos(), 0, player:get_properties().eye_height, 0)
if param == "" then
param = "Look here!"
end
local ray = minetest.raycast(
pos1, vector.add(pos1, vector.multiply(player:get_look_dir(), MARKER_RANGE),
true, false
))
local pointed = ray:next()
if pointed and pointed.type == "object" and pointed.ref == player then
pointed = ray:next()
end
if not pointed then
return false, "Can't find anything to mark, too far away!"
end
local message = string.format("m [%s]: %s", name, param)
local pos
if pointed.type == "object" then
local concat
local obj = pointed.ref
local entity = obj:get_luaentity()
-- If object is a player, append player name to display text
-- Else if obj is item entity, append item description and count to str.
if obj:is_player() then
concat = obj:get_player_name()
elseif entity then
if entity.name == "__builtin:item" then
local stack = ItemStack(entity.itemstring)
local itemdef = minetest.registered_items[stack:get_name()]
-- Fallback to itemstring if description doesn't exist
concat = itemdef.description or entity.itemstring
concat = concat .. " " .. stack:get_count()
end
end
pos = obj:get_pos()
if concat then
message = message .. " <" .. concat .. ">"
end
else
pos = pointed.under
end
ctf_modebase.markers.add(name, message, pos)
marker_cooldown:set(name, MARKER_PLACE_INTERVAL)
return true, "Marker is placed!"
end
minetest.register_chatcommand("m", {
description = "Place a marker in your look direction",
privs = {interact = true, shout = true},
func = function(name, param)
local pteam = ctf_teams.get(name)
if not pteam then
return false, "You need to be in a team to use markers!"
end
local player = minetest.get_player_by_name(name)
local pos1 = vector.offset(player:get_pos(), 0, player:get_properties().eye_height, 0)
if param == "" then
param = "Look here!"
end
local ray = minetest.raycast(
pos1, vector.add(pos1, vector.multiply(player:get_look_dir(), MARKER_RANGE),
true, false
))
local pointed = ray:next()
if pointed and pointed.type == "object" and pointed.ref == player then
pointed = ray:next()
end
if not pointed then
return false, "Can't find anything to mark, too far away!"
end
local message = string.format("m [%s]: %s", name, param)
local pos
if pointed.type == "object" then
local concat
local obj = pointed.ref
local entity = obj:get_luaentity()
-- If object is a player, append player name to display text
-- Else if obj is item entity, append item description and count to str.
if obj:is_player() then
concat = obj:get_player_name()
elseif entity then
if entity.name == "__builtin:item" then
local stack = ItemStack(entity.itemstring)
local itemdef = minetest.registered_items[stack:get_name()]
-- Fallback to itemstring if description doesn't exist
concat = itemdef.description or entity.itemstring
concat = concat .. " " .. stack:get_count()
end
end
pos = obj:get_pos()
if concat then
message = message .. " <" .. concat .. ">"
end
else
pos = pointed.under
end
ctf_modebase.markers.add(name, message, pos)
return true, "Marker is placed!"
end
func = marker_func
})
minetest.register_chatcommand("mr", {
@ -163,3 +190,38 @@ minetest.register_chatcommand("mr", {
return true, "Marker is removed!"
end
})
local check_interval = 0.3
local timer = 0
minetest.register_globalstep(function(dtime)
timer = timer + dtime
if timer < check_interval then return end
timer = 0
for _, player in pairs(minetest.get_connected_players()) do
local controls = player:get_player_control()
if controls.zoom then
local marker_text = false
if controls.LMB then
marker_text = ""
elseif controls.RMB then
marker_text = "Defend!"
end
if marker_text then
local success, msg = marker_func(player:get_player_name(), marker_text)
if not success and msg then
hud_events.new(player, {
text = msg,
color = "warning",
quick = true,
})
end
end
end
end
end)

View File

@ -45,7 +45,7 @@ local function start_new_match()
end
if restart_on_next_match then
minetest.request_shutdown("Restarting server at imperator request.", true)
minetest.request_shutdown("Restarting server at imperator request.\nTip: Count to 7 before clicking reconnect", true)
return
end

View File

@ -1,2 +1,2 @@
name = ctf_modebase
depends = ctf_api, ctf_core, ctf_teams, ctf_gui, ctf_map, ctf_healing, crafting, mhud, default
depends = ctf_api, ctf_core, ctf_teams, ctf_gui, ctf_map, ctf_healing, crafting, mhud, default, binoculars

View File

@ -32,6 +32,7 @@ local function show_modechoose_form(player)
label = i,
exit = true,
pos = {"center", i},
size = {1.4, 0.7},
func = function()
if votes then
player_vote(player, i)
@ -40,8 +41,8 @@ local function show_modechoose_form(player)
}
end
ctf_gui.show_formspec(player, "ctf_modebase:mode_select", {
size = {x = 8, y = 8},
ctf_gui.old_show_formspec(player, "ctf_modebase:mode_select", {
size = {x = 8, y = MAX_ROUNDS + 3},
title = "Mode: "..HumanReadable(new_mode),
description = "Please vote on how many matches you would like to play",
elements = elements,

View File

@ -1,4 +1,5 @@
local RESPAWN_SECONDS = 7
local AUTO_RESPAWN_TIME = 0.4
local respawn_delay = {}
local hud = mhud.init()
@ -75,6 +76,23 @@ local function respawn(player, time)
run_respawn_timer(pname)
end
local function trigger_respawn(pname)
if respawn_delay[pname] then
if respawn_delay[pname].autorespawn then
respawn_delay[pname].autorespawn:cancel()
respawn_delay[pname].autorespawn = nil
end
respawn(minetest.get_player_by_name(pname), RESPAWN_SECONDS)
else
local player = minetest.get_player_by_name(pname)
if player then
ctf_modebase.on_respawnplayer(player)
end
end
end
function ctf_modebase.prepare_respawn_delay(player)
local pname = player:get_player_name()
if respawn_delay[pname] then return end
@ -90,6 +108,11 @@ function ctf_modebase.prepare_respawn_delay(player)
player:set_attach(obj)
respawn_delay[pname].obj = obj
end
respawn_delay[pname].autorespawn = minetest.after(AUTO_RESPAWN_TIME, function()
minetest.close_formspec(pname, "") -- This is the only way to close clientside formspecs
trigger_respawn(pname)
end)
end
ctf_api.register_on_match_end(function()
@ -117,11 +140,9 @@ minetest.register_on_leaveplayer(function(player)
end)
minetest.register_on_respawnplayer(function(player)
if respawn_delay[player:get_player_name()] then
respawn(player, RESPAWN_SECONDS)
else
ctf_modebase.on_respawnplayer(player)
end
local pname = player:get_player_name()
trigger_respawn(pname)
return true
end)

View File

@ -237,7 +237,7 @@ function ctf_modebase.summary.show_gui_sorted(name, rankings, special_rankings,
}
end
ctf_gui.show_formspec(name, "ctf_modebase:summary", formspec)
ctf_gui.old_show_formspec(name, "ctf_modebase:summary", formspec)
end
ctf_core.register_chatcommand_alias("summary", "s", {

Binary file not shown.

After

Width:  |  Height:  |  Size: 630 B

View File

@ -1,14 +1,16 @@
ctf_gui.init()
local cooldowns = ctf_core.init_cooldowns()
local CLASS_SWITCH_COOLDOWN = 30
local readable_class_list = {"Knight", "Ranged", "Support"}
local classes = {}
local class_list = {"knight", "ranged", "support"}
local class_props = {
knight = {
name = "Knight",
description = "High HP class with a sword capable of strong damage bursts, +50% health points",
hp_max = 28,
description = "High HP class with a sword capable of short damage bursts",
hp_max = 30,
visual_size = vector.new(1.1, 1.05, 1.1),
items = {
"ctf_mode_classes:knight_sword",
@ -20,7 +22,7 @@ local class_props = {
},
support = {
name = "Support",
description = "Normal HP class with healing bandages, an immunity ability, and building tools, +10% speed",
description = "Helper class with healing bandages, an immunity ability, and building gear",
physics = {speed = 1.1},
items = {
"ctf_mode_classes:support_bandage",
@ -33,12 +35,14 @@ local class_props = {
"ctf_ranged:shotgun",
"ctf_melee:",
},
disallowed_items_markup = {
["ctf_melee:"] = "default_tool_steelsword.png^ctf_modebase_group.png",
},
},
ranged = {
name = "Ranged",
description = "Low HP ranged class with a rifle/grenade launcher gun, and a scaling ladder for reaching high places",
hp_max = 14,
visual_size = vector.new(0.9, 0.95, 0.9),
name = "Scout",
description = "Ranged class with a scoped rifle/grenade launcher and a scaling ladder for reaching high places",
visual_size = vector.new(0.9, 1, 0.9),
items = {
"ctf_mode_classes:ranged_rifle_loaded",
"ctf_mode_classes:scaling_ladder"
@ -46,9 +50,54 @@ local class_props = {
disallowed_items = {
"ctf_melee:",
},
disallowed_items_markup = {
["ctf_melee:"] = "default_tool_steelsword.png^ctf_modebase_group.png",
},
}
}
minetest.register_on_mods_loaded(function()
for k, class_prop in pairs(class_props) do
local items_markup = ""
local disallowed_items_markup = ""
for _, iname in ipairs(class_prop.items or {}) do
local item = ItemStack(iname)
local count = item:get_count()
if count <= 1 then
count = nil
else
count = " x"..count
end
local desc = string.split(item:get_description(), "\n", false, 1)
items_markup = string.format("%s%s\n<item name=%s float=left width=48>\n\n\n",
items_markup,
minetest.formspec_escape(desc[1]) .. (count and count or ""),
item:get_name()
)
end
for _, iname in ipairs(class_prop.disallowed_items or {}) do
if minetest.registered_items[iname] then
disallowed_items_markup = string.format("%s<item name=%s width=48>",
disallowed_items_markup,
iname
)
else
disallowed_items_markup = string.format("%s<img name=%s width=48>",
disallowed_items_markup,
class_prop.disallowed_items_markup[iname]
)
end
end
class_props[k].items_markup = items_markup:sub(1, -2) -- Remove \n at the end of str
class_props[k].disallowed_items_markup = disallowed_items_markup
end
end)
local function dist_from_flag(player)
local tname = ctf_teams.get(player)
if not tname then return 0 end
@ -60,8 +109,21 @@ end
--- Knight Sword
--
local KNIGHT_COOLDOWN_TIME = 42
local KNIGHT_USAGE_TIME = 12
-- ctf_melee.register_sword("ctf_mode_classes:knight_sword", {
-- description = "Knight Sword",
-- inventory_image = "default_tool_bronzesword.png",
-- damage_groups = {fleshy = 5},
-- })
local KNIGHT_COOLDOWN_TIME = 26
local KNIGHT_USAGE_TIME = 8
ctf_settings.register("ctf_mode_classes:simple_knight_activate", {
type = "bool",
label = "[Classes] Simple Knight sword activation",
description = "If enabled you don't need to hold Sneak/Run to activate the rage ability",
default = "false",
})
ctf_melee.simple_register_sword("ctf_mode_classes:knight_sword", {
description = "Knight Sword\n" .. minetest.colorize("gold",
@ -73,8 +135,10 @@ ctf_melee.simple_register_sword("ctf_mode_classes:knight_sword", {
damage_groups = {fleshy = 7},
full_punch_interval = 0.7,
rightclick_func = function(itemstack, user, pointed)
local ctl = user:get_player_control()
if not ctl.sneak and not ctl.aux1 then return end
if ctf_settings.get(user, "ctf_mode_classes:simple_knight_activate") ~= "true" then
local ctl = user:get_player_control()
if not ctl.sneak and not ctl.aux1 then return end
end
local pname = user:get_player_name()
@ -110,16 +174,19 @@ ctf_melee.simple_register_sword("ctf_mode_classes:knight_sword", {
end,
})
--
--- Ranged Gun
--
local RANGED_COOLDOWN_TIME = 36
local RANGED_COOLDOWN_TIME = 31
local RANGED_ZOOM_MULT = 3
local scoped = ctf_ranged.scoped
ctf_ranged.simple_register_gun("ctf_mode_classes:ranged_rifle", {
type = "classes_rifle",
description = "Rifle\n" .. minetest.colorize("gold",
"(Sneak/Run) + Rightclick to launch grenade ("..RANGED_COOLDOWN_TIME.."s cooldown)"),
description = "Scout Rifle\n" .. minetest.colorize("gold",
"Rightclick + (Sneak/Run) to launch grenade ("..RANGED_COOLDOWN_TIME.."s cooldown), otherwise will toggle scope"),
texture = "ctf_mode_classes_ranged_rifle.png",
texture_overlay = "ctf_modebase_special_item.png^[transformFX",
wield_texture = "ctf_mode_classes_ranged_rifle.png",
@ -131,7 +198,20 @@ ctf_ranged.simple_register_gun("ctf_mode_classes:ranged_rifle", {
liquid_travel_dist = 4,
rightclick_func = function(itemstack, user, pointed)
local ctl = user:get_player_control()
if not ctl.sneak and not ctl.aux1 then return end
if not ctl.sneak and not ctl.aux1 then
local uname = user:get_player_name()
if not ctl.zoom then
if scoped[uname] then
ctf_ranged.hide_scope(uname)
else
ctf_ranged.show_scope(uname, "ctf_mode_classes:ranged_rifle", RANGED_ZOOM_MULT)
end
end
return
end
if itemstack:get_wear() == 0 then
grenades.throw_grenade("grenades:frag", 24, user)
@ -243,8 +323,6 @@ ctf_healing.register_bandage("ctf_mode_classes:support_bandage", {
end
})
local classes = {}
function classes.get_name(player)
local meta = player:get_meta()
@ -261,6 +339,10 @@ function classes.get(player)
return class_props[classes.get_name(player)]
end
function classes.get_skin_overlay(player_or_class, class)
return "^ctf_mode_classes_" .. (class and player_or_class or classes.get_name(player_or_class)) .. "_overlay.png"
end
function classes.update(player)
local class = classes.get(player)
@ -309,14 +391,10 @@ local function select_class(player, classname)
end
end
function classes.show_class_formspec(player, selected)
function classes.show_class_formspec(player)
player = PlayerObj(player)
if not player then return end
if not selected then
selected = table.indexof(class_list, classes.get_name(player))
end
if not cooldowns:get(player) then
if dist_from_flag(player) > 5 then
hud_events.new(player, {
@ -327,38 +405,96 @@ function classes.show_class_formspec(player, selected)
return
end
local elements = {}
local pteam = ctf_teams.get(player)
elements.class_select = {
type = "dropdown",
items = readable_class_list,
default_idx = selected,
pos = {x = 0, y = 0.5},
func = function(playername, fields, field_name)
local new_idx = table.indexof(readable_class_list, fields[field_name])
ctf_gui.show_formspec(player, "ctf_mode_classes:class_form", function(context)
local form_x, form_y = 12, 10
if new_idx ~= selected then
classes.show_class_formspec(playername, new_idx)
local bar_h = 2.2
local bw = 5
local class = context.class
local class_prop = class_props[class]
return ctf_gui.list_to_formspec_str({
"formspec_version[4]",
{"size[%f,%f]", form_x, form_y+1.1},
"real_coordinates[true]",
{"hypertext[0,0.2;%f,1.3;title;<bigger><center><b>Class Selection</b></center></bigger>]", form_x},
{"hypertext[0,%f;%f,1;classname;<bigger><center><style color=#0DD>%s</style></center></bigger>]",
bar_h-0.9,
form_x,
class_prop.name
},
{"box[0,%f;%f,0.8;#00000022]", bar_h-0.9, form_x},
{"image_button[0.1,%f;0.8,0.8;creative_prev_icon.png;prev_class;]", bar_h-0.9},
{"image_button[%f,%f;0.8,0.8;creative_next_icon.png;next_class;]", form_x-0.9, bar_h-0.9},
{"box[0.1,2.3;%f,%f;#00000077]", (form_x/2)-0.8, form_y-2.4},
{"model[0.1,2.3;%f,%f;classpreview;character.b3d;%s;{0,160};;;]",
(form_x/2)-0.8,
form_y-2.4,
ctf_cosmetics.get_colored_skin(player, pteam and ctf_teams.team[pteam].color) ..
classes.get_skin_overlay(class, true) or ""
},
{[[hypertext[%f,2.3;%f,%f;info;<global font=mono background=#00000044>
<center>%s</center>
<img name=heart.png width=20 float=left> %d HP
%s
%s
Disallowed Items
%s
] ]],
(form_x/2)-0.6,
(form_x/2)+0.5,
form_y-2.4,
class_prop.description,
class_prop.hp_max or minetest.PLAYER_MAX_HP_DEFAULT,
class_prop.physics and class_prop.physics.speed and
"<img name=sprint_stamina_icon.png width=20 float=left> "..class_prop.physics.speed.."x Speed\n" or "",
class_prop.items_markup,
class_prop.disallowed_items_markup
},
"style[select;font_size=*1.5]",
{"button_exit[%f,%f;%f,1;select;Choose Class]", (form_x/2) - (bw/2), form_y, bw},
})
end, {
class = classes.get_name(player) or "knight",
_on_formspec_input = function(pname, context, fields)
if fields.prev_class then
local classidx = table.indexof(class_list, context.class) - 1
if classidx < 1 then
classidx = #class_list
end
context.class = class_list[classidx]
return "refresh"
elseif fields.next_class then
local classidx = table.indexof(class_list, context.class) + 1
if classidx > #class_list then
classidx = 1
end
context.class = class_list[classidx]
return "refresh"
elseif fields.select and classes.get_name(player) ~= context.class then
if dist_from_flag(player) > 5 then
hud_events.new(player, {
quick = true,
text = "You can only change class at your flag!",
color = "warning",
})
return
end
select_class(pname, context.class)
end
end,
}
elements.select_class = {
type = "button",
exit = true,
label = "Choose Class",
pos = {x = ctf_gui.ELEM_SIZE.x + 0.5, y = 0.5},
func = function(playername, fields, field_name)
select_class(playername, class_list[selected])
end,
}
ctf_gui.show_formspec(player, "ctf_mode_classes:class_form", {
size = {x = (ctf_gui.ELEM_SIZE.x * 2) + 1, y = 3.5},
title = class_props[class_list[selected]].name,
description = class_props[class_list[selected]].description,
privs = {interact = true},
elements = elements,
})
else
hud_events.new(player, {

View File

@ -60,8 +60,7 @@ ctf_modebase.register_mode("classes", {
"deaths",
"hp_healed"
},
build_timer = 60 * 1.5,
build_timer = 60,
is_bound_item = function(_, name)
if name:match("ctf_mode_classes:") or name:match("ctf_melee:") or name == "ctf_healing:bandage" then
return true
@ -78,7 +77,7 @@ ctf_modebase.register_mode("classes", {
ctf_modebase.bounties.get_next_bounty = ctf_modebase.bounty_algo.kd.get_next_bounty
ctf_cosmetics.get_skin = function(player)
return old_get_skin(player) .. "^ctf_mode_classes_" .. classes.get_name(player) .. "_overlay.png"
return old_get_skin(player) .. classes.get_skin_overlay(player)
end
end,
on_mode_end = function()
@ -107,8 +106,13 @@ ctf_modebase.register_mode("classes", {
end,
get_chest_access = features.get_chest_access,
on_punchplayer = features.on_punchplayer,
can_punchplayer = features.can_punchplayer,
on_healplayer = features.on_healplayer,
calculate_knockback = function()
return 0
calculate_knockback = function(player, hitter, time_from_last_punch, tool_capabilities, dir, distance, damage)
if features.can_punchplayer(player, hitter) then
return 2 * (tool_capabilities.damage_groups.knockback or 1)
else
return 0
end
end,
})

View File

@ -1,2 +1,2 @@
name = ctf_mode_classes
depends = ctf_modebase, ctf_melee, ctf_gui, ctf_cosmetics, ctf_api, hud_events
depends = ctf_modebase, ctf_melee, ctf_gui, ctf_cosmetics, ctf_api, hud_events, ctf_settings

View File

@ -68,6 +68,7 @@ ctf_modebase.register_mode("classic", {
on_flag_capture = features.on_flag_capture,
on_flag_rightclick = function() end,
get_chest_access = features.get_chest_access,
can_punchplayer = features.can_punchplayer,
on_punchplayer = features.on_punchplayer,
on_healplayer = features.on_healplayer,
calculate_knockback = function()

View File

@ -102,6 +102,7 @@ ctf_modebase.register_mode("nade_fight", {
on_flag_capture = features.on_flag_capture,
on_flag_rightclick = function() end,
get_chest_access = features.get_chest_access,
can_punchplayer = features.can_punchplayer,
on_punchplayer = function(player, hitter, damage, unneeded, tool_capabilities, ...)
if tool.holed[player:get_player_name()] then
if tool_capabilities.grenade then

View File

@ -0,0 +1 @@
Player Model taken from Minetest Game and modified for CTF, Licensed CC BY-SA 3.0 (mods/mtg/mtg_player_api/)

View File

@ -0,0 +1,118 @@
local stab_slash_time = 20/60 - 0.2
local stab_slash_cooldown_after = 0.2
ctf_player = {
animation_time = {
-- Animation Frames / Animation Framerate + Cooldown Time - 0.1
stab_slash = stab_slash_time + stab_slash_cooldown_after,
},
}
-- Override player_api model
player_api.registered_models["character.b3d"] = nil
player_api.register_model("character.b3d", {
animation_speed = 30,
textures = {"character.png"},
animations = {
-- Standard animations.
stand = {x = 0, y = 79},
lay = {x = 162, y = 166, eye_height = 0.3,
collisionbox = {-0.6, 0.0, -0.6, 0.6, 0.3, 0.6}},
walk = {x = 168, y = 187},
mine = {x = 189, y = 198},
walk_mine = {x = 200, y = 219},
sit = {x = 81, y = 160, eye_height = 0.8,
collisionbox = {-0.3, 0.0, -0.3, 0.3, 1.0, 0.3}},
stab = {x = 221, y = 241, frame_loop = false},
slash = {x = 242, y = 262, frame_loop = false},
},
collisionbox = {-0.3, 0.0, -0.3, 0.3, 1.7, 0.3},
stepheight = 0.6,
eye_height = 1.47,
})
minetest.register_on_joinplayer(function(player)
player:set_local_animation(nil, nil, nil, nil, 0)
end)
-- Override player_api globalstep
-- Localize for better performance.
local player_set_animation = player_api.set_animation
local get_animation = player_api.get_animation
local player_attached = player_api.player_attached
local models = player_api.registered_models
local stab_slash_timer = {}
minetest.register_globalstep(function(dtime)
for p, timer in pairs(stab_slash_timer) do
timer.timeleft = timer.timeleft - dtime
if timer.timeleft <= 0 then
if timer.state == "anim" then
timer.state = "cooldown"
timer.timeleft = stab_slash_cooldown_after + (timer.extra_time or 0)
else
stab_slash_timer[p] = nil
end
end
end
end)
function ctf_player.set_stab_slash_anim(anim_type, player, extra_time)
stab_slash_timer[player:get_player_name()] = {
timeleft = stab_slash_time,
extra_time = extra_time,
state = "anim"
}
player_set_animation(player, anim_type, 60)
end
function player_api.globalstep()
for _, player in ipairs(minetest.get_connected_players()) do
local name = player:get_player_name()
local player_data = get_animation(player)
local model = models[player_data.model]
if model and not player_attached[name] then
local controls = player:get_player_control()
local animation_speed_mod = model.animation_speed or 30
-- Determine if the player is sneaking, and reduce animation speed if so
if controls.sneak then
animation_speed_mod = animation_speed_mod / 2
end
-- Apply animations based on what the player is doing
if player:get_hp() == 0 then
player_set_animation(player, "lay")
elseif not stab_slash_timer[name] or stab_slash_timer[name].state == "cooldown" then
if controls.up or controls.down or controls.left or controls.right then
if controls.LMB or controls.RMB then
local wielded = player:get_wielded_item()
if not wielded or not wielded:get_definition().disable_mine_anim then
player_set_animation(player, "walk_mine", animation_speed_mod)
else
player_set_animation(player, "walk", animation_speed_mod)
end
else
player_set_animation(player, "walk", animation_speed_mod)
end
elseif controls.LMB or controls.RMB then
local wielded = player:get_wielded_item()
if not wielded or not wielded:get_definition().disable_mine_anim then
player_set_animation(player, "mine", animation_speed_mod)
else
player_set_animation(player, "stand", animation_speed_mod)
end
else
player_set_animation(player, "stand", animation_speed_mod)
end
end
end
end
end

View File

@ -0,0 +1,2 @@
name = ctf_player
depends = player_api, ctf_core

Binary file not shown.

Binary file not shown.

View File

@ -142,7 +142,7 @@ function player_api.set_animation(player, anim_name, speed)
end
end
-- Set the animation seen by everyone else
player:set_animation(anim, speed, animation_blend)
player:set_animation(anim, speed, animation_blend, anim.frame_loop)
-- Update related properties if they changed
if anim._equals ~= previous_anim._equals then
player:set_properties({
@ -178,8 +178,8 @@ function minetest.calculate_knockback(player, ...)
end
-- Check each player and apply animations
minetest.register_globalstep(function()
for _, player in pairs(minetest.get_connected_players()) do
function player_api.globalstep()
for _, player in ipairs(minetest.get_connected_players()) do
local name = player:get_player_name()
local player_data = players[name]
local model = models[player_data.model]
@ -208,6 +208,11 @@ minetest.register_globalstep(function()
end
end
end
end
-- Mods can modify the globalstep by overriding player_api.globalstep
minetest.register_globalstep(function(...)
player_api.globalstep(...)
end)
for _, api_function in pairs({"get_animation", "set_animation", "set_model", "set_textures"}) do

View File

@ -2,7 +2,7 @@ local location = {
"Arm_Right", -- default bone
{x=0, y=5.5, z=3}, -- default position
{x=-90, y=225, z=90}, -- default rotation
{x=0.25, y=0.25}, -- default scale
{x=0.3, y=0.3, z=0.25}, -- default scale
}
local players = {}
@ -21,6 +21,7 @@ minetest.register_entity("wield3d:entity", {
backface_culling = false,
static_save = false,
pointable = false,
glow = 7,
on_punch = function() return true end,
})
@ -43,7 +44,7 @@ end
local globalstep_timer = 0
minetest.register_globalstep(function(dtime)
globalstep_timer = globalstep_timer + dtime
if globalstep_timer < 1 then return end
if globalstep_timer < 0.5 then return end
globalstep_timer = 0
@ -54,18 +55,41 @@ minetest.register_globalstep(function(dtime)
end
end)
minetest.register_on_joinplayer(function(player)
local function add_wielditem(player)
local entity = minetest.add_entity(player:get_pos(), "wield3d:entity")
entity:set_attach(player, location[1], location[2], location[3])
local setting = ctf_settings.get(player, "use_old_wielditem_display")
entity:set_attach(
player,
location[1], location[2], location[3],
setting == "false"
)
players[player:get_player_name()] = {entity=entity, item="wield3d:hand"}
player:hud_set_flags({wielditem = (setting == "true")})
update_entity(player)
end)
end
minetest.register_on_leaveplayer(function(player)
local function remove_wielditem(player)
local pname = player:get_player_name()
if players[pname] ~= nil then
players[pname].entity:remove()
players[pname] = nil
end
end)
end
minetest.register_on_joinplayer(add_wielditem)
minetest.register_on_leaveplayer(remove_wielditem)
ctf_settings.register("use_old_wielditem_display", {
label = "Use old wielditem display",
type = "bool",
default = "true",
description = "Will use Minetest's default method of showing the wielded item.\n" ..
"This won't show custom animations, but might be less jarring",
on_change = function(player, new_value)
remove_wielditem(player)
add_wielditem(player)
end,
})

View File

@ -1 +1,2 @@
name = wield3d
depends = ctf_settings