Haste and fatigue expanded and improved

* abstracted and refactored some parts of haste and fatigue
* added and exposed new mcl_potions API functions
* fixed haste and fatigue not altering the hand
* mcl_meshhand now calls into mcl_potions when resetting the hand
This commit is contained in:
the-real-herowl 2024-03-20 01:47:00 +01:00
parent 6d7fe91047
commit ddbc7cd826
2 changed files with 108 additions and 35 deletions

@ -4,6 +4,9 @@ local EF = {}
mcl_potions.registered_effects = {} mcl_potions.registered_effects = {}
local registered_effects = mcl_potions.registered_effects -- shorthand ref local registered_effects = mcl_potions.registered_effects -- shorthand ref
-- effects affecting item speed utilize numerous hacks, so they have to be counted separately
local item_speed_effects = {}
local EFFECT_TYPES = 0 local EFFECT_TYPES = 0
minetest.register_on_mods_loaded(function() minetest.register_on_mods_loaded(function()
for _,_ in pairs(EF) do for _,_ in pairs(EF) do
@ -90,6 +93,7 @@ end
-- on_step - function(dtime, object, factor, duration) - running every step for all objects with this effect -- on_step - function(dtime, object, factor, duration) - running every step for all objects with this effect
-- on_hit_timer - function(object, factor, duration) - if defined runs a hit_timer depending on timer_uses_factor value -- on_hit_timer - function(object, factor, duration) - if defined runs a hit_timer depending on timer_uses_factor value
-- on_end - function(object) - called when the effect wears off -- on_end - function(object) - called when the effect wears off
-- after_end - function(object) - called when the effect wears off, after purging the data of the effect
-- particle_color - string - colorstring for particles - defaults to #3000EE -- particle_color - string - colorstring for particles - defaults to #3000EE
-- uses_factor - bool - whether factor affects the effect -- uses_factor - bool - whether factor affects the effect
-- lvl1_factor - integer - factor for lvl1 effect - defaults to 1 if uses_factor -- lvl1_factor - integer - factor for lvl1 effect - defaults to 1 if uses_factor
@ -100,6 +104,14 @@ end
-- dmg_mod_is_type - bool - damage_modifier string is used as type instead of flag of damage, defaults to false -- dmg_mod_is_type - bool - damage_modifier string is used as type instead of flag of damage, defaults to false
-- modifier_func - function(damage, effect_vals) - see damage_modifier, if not defined damage_modifier defaults to 100% resistance -- modifier_func - function(damage, effect_vals) - see damage_modifier, if not defined damage_modifier defaults to 100% resistance
-- modifier_priority - integer - priority passed when registering damage_modifier - defaults to -50 -- modifier_priority - integer - priority passed when registering damage_modifier - defaults to -50
-- affects_item_speed - table
-- -- if provided, effect gets added to the item_speed_effects table, this should be true if the effect affects item speeds,
-- -- otherwise it won't work properly with other such effects (like haste and fatigue)
-- -- -- factor_is_positive - bool - whether values of factor between 0 and 1 should be considered +factor% or speed multiplier
-- -- -- - obviously +factor% is positive and speed multiplier is negative interpretation
-- -- -- - values of factor higher than 1 will have a positive effect regardless
-- -- -- - values of factor lower than 0 will have a negative effect regardless
-- -- -- - open an issue on our tracker if you have a usage that isn't supported by this API
function mcl_potions.register_effect(def) function mcl_potions.register_effect(def)
local modname = minetest.get_current_modname() local modname = minetest.get_current_modname()
local name = def.name local name = def.name
@ -176,6 +188,7 @@ function mcl_potions.register_effect(def)
end end
registered_effects[name] = pdef registered_effects[name] = pdef
EF[name] = {} EF[name] = {}
item_speed_effects[name] = def.affects_item_speed
end end
mcl_potions.register_effect({ mcl_potions.register_effect({
@ -817,16 +830,68 @@ mcl_potions.register_effect({
uses_factor = true, uses_factor = true,
}) })
-- constants relevant for effects altering mining and attack speed
local LONGEST_MINING_TIME = 300
local LONGEST_PUNCH_INTERVAL = 10
mcl_potions.LONGEST_MINING_TIME = LONGEST_MINING_TIME
mcl_potions.LONGEST_PUNCH_INTERVAL = LONGEST_PUNCH_INTERVAL
function mcl_potions.apply_haste_fatigue(toolcaps, h_fac, f_fac)
if f_fac == 0 then
local fpi = toolcaps.full_punch_interval
toolcaps.full_punch_interval = fpi > LONGEST_PUNCH_INTERVAL and fpi or LONGEST_PUNCH_INTERVAL
else
toolcaps.full_punch_interval = toolcaps.full_punch_interval / (1+h_fac) / f_fac
end
for name, group in pairs(toolcaps.groupcaps) do
local t = group.times
for i=1, #t do
if f_fac == 0 then
t[i] = t[i] > LONGEST_MINING_TIME and t[i] or LONGEST_MINING_TIME
else
local old_time = t[i]
t[i] = t[i] / (1+h_fac) / f_fac
if old_time < LONGEST_MINING_TIME and t[i] > LONGEST_MINING_TIME then
t[i] = LONGEST_MINING_TIME
end
end
end
end
return toolcaps
end
function mcl_potions.hf_update_internal(hand, object)
local meta = hand:get_meta()
local h_fac = mcl_potions.get_total_haste(object)
local f_fac = mcl_potions.get_total_fatigue(object)
local toolcaps = hand:get_tool_capabilities()
meta:set_tool_capabilities(mcl_potions.apply_haste_fatigue(toolcaps, h_fac, f_fac))
return hand
end
local function haste_fatigue_hand_update(object)
local inventory = object:get_inventory()
if not inventory or inventory:get_size("hand") < 1 then return end
local hand = inventory:get_stack("hand", 1)
inventory:set_stack("hand", 1, mcl_potions.hf_update_internal(hand, object))
end
mcl_potions.register_effect({ mcl_potions.register_effect({
name = "haste", name = "haste",
description = S("Haste"), description = S("Haste"),
res_condition = function(object) res_condition = function(object)
return (not object:is_player()) return (not object:is_player())
end, end,
on_start = haste_fatigue_hand_update,
after_end = function(object)
haste_fatigue_hand_update(object)
mcl_potions._reset_haste_fatigue_item_meta(object)
end,
particle_color = "#FFFF00", particle_color = "#FFFF00",
uses_factor = true, uses_factor = true,
lvl1_factor = 0.2, lvl1_factor = 0.2,
lvl2_factor = 0.4, lvl2_factor = 0.4,
affects_item_speed = {factor_is_positive = true},
}) })
mcl_potions.register_effect({ mcl_potions.register_effect({
@ -835,31 +900,27 @@ mcl_potions.register_effect({
res_condition = function(object) res_condition = function(object)
return (not object:is_player()) return (not object:is_player())
end, end,
on_start = haste_fatigue_hand_update,
after_end = function(object)
haste_fatigue_hand_update(object)
mcl_potions._reset_haste_fatigue_item_meta(object)
end,
particle_color = "#64643D", particle_color = "#64643D",
uses_factor = true, uses_factor = true,
lvl1_factor = 0.3, lvl1_factor = 0.3,
lvl2_factor = 0.09, lvl2_factor = 0.09,
affects_item_speed = {},
}) })
-- implementation of haste and fatigue effects -- implementation of haste and fatigue effects
local LONGEST_MINING_TIME = 300
local LONGEST_PUNCH_INTERVAL = 10
function mcl_potions.update_haste_and_fatigue(player) function mcl_potions.update_haste_and_fatigue(player)
if mcl_gamemode.get_gamemode(player) == "creative" then return end if mcl_gamemode.get_gamemode(player) == "creative" then return end
local item = player:get_wielded_item() local item = player:get_wielded_item()
local meta = item:get_meta() local meta = item:get_meta()
local haste = EF.haste[player]
local fatigue = EF.fatigue[player]
local item_haste = meta:get_float("mcl_potions:haste") local item_haste = meta:get_float("mcl_potions:haste")
local item_fatig = 1 - meta:get_float("mcl_potions:fatigue") local item_fatig = 1 - meta:get_float("mcl_potions:fatigue")
local h_fac local h_fac = mcl_potions.get_total_haste(player)
if haste then h_fac = haste.factor local f_fac = mcl_potions.get_total_fatigue(player)
else h_fac = 0 end
if h_fac < 0 then h_fac = 0 end
local f_fac
if fatigue then f_fac = fatigue.factor
else f_fac = 1 end
if f_fac < 0 then f_fac = 0 end
if item_haste ~= h_fac or item_fatig ~= f_fac then if item_haste ~= h_fac or item_fatig ~= f_fac then
if h_fac ~= 0 then meta:set_float("mcl_potions:haste", h_fac) if h_fac ~= 0 then meta:set_float("mcl_potions:haste", h_fac)
else meta:set_string("mcl_potions:haste", "") end else meta:set_string("mcl_potions:haste", "") end
@ -872,39 +933,22 @@ function mcl_potions.update_haste_and_fatigue(player)
return return
end end
local toolcaps = item:get_tool_capabilities() local toolcaps = item:get_tool_capabilities()
if f_fac == 0 then meta:set_tool_capabilities(mcl_potions.apply_haste_fatigue(toolcaps, h_fac, f_fac))
local fpi = toolcaps.full_punch_interval
toolcaps.full_punch_interval = fpi > LONGEST_PUNCH_INTERVAL and fpi or LONGEST_PUNCH_INTERVAL
else
toolcaps.full_punch_interval = toolcaps.full_punch_interval / (1+h_fac) / f_fac
end
for name, group in pairs(toolcaps.groupcaps) do
local t = group.times
for i=1, #t do
if f_fac == 0 then
t[i] = t[i] > LONGEST_MINING_TIME and t[i] or LONGEST_MINING_TIME
else
local old_time = t[i]
t[i] = t[i] / (1+h_fac) / f_fac
if old_time < LONGEST_MINING_TIME and t[i] > LONGEST_MINING_TIME then
t[i] = LONGEST_MINING_TIME
end
end
end
end
meta:set_tool_capabilities(toolcaps)
player:set_wielded_item(item) player:set_wielded_item(item)
end end
haste_fatigue_hand_update(player, h_fac, f_fac)
end end
minetest.register_on_punchnode(function(pos, node, puncher, pointed_thing) minetest.register_on_punchnode(function(pos, node, puncher, pointed_thing)
mcl_potions.update_haste_and_fatigue(puncher) mcl_potions.update_haste_and_fatigue(puncher)
end) end)
minetest.register_on_punchplayer(function(player, hitter) minetest.register_on_punchplayer(function(player, hitter)
if not hitter:is_player() then return end -- TODO implement haste and fatigue support for mobs?
mcl_potions.update_haste_and_fatigue(hitter) mcl_potions.update_haste_and_fatigue(hitter)
end) end)
-- update when hitting mob implemented in mcl_mobs/combat.lua -- update when hitting mob implemented in mcl_mobs/combat.lua
-- ██╗░░░██╗██████╗░██████╗░░█████╗░████████╗███████╗ -- ██╗░░░██╗██████╗░██████╗░░█████╗░████████╗███████╗
-- ██║░░░██║██╔══██╗██╔══██╗██╔══██╗╚══██╔══╝██╔════╝ -- ██║░░░██║██╔══██╗██╔══██╗██╔══██╗╚══██╔══╝██╔════╝
-- ██║░░░██║██████╔╝██║░░██║███████║░░░██║░░░█████╗░░ -- ██║░░░██║██████╔╝██║░░██║███████║░░░██║░░░█████╗░░
@ -1132,6 +1176,7 @@ minetest.register_globalstep(function(dtime)
if not EF[name][object] or EF[name][object].timer >= vals.dur then if not EF[name][object] or EF[name][object].timer >= vals.dur then
if effect.on_end then effect.on_end(object) end if effect.on_end then effect.on_end(object) end
EF[name][object] = nil EF[name][object] = nil
if effect.after_end then effect.after_end(object) end
if object:is_player() then if object:is_player() then
meta = object:get_meta() meta = object:get_meta()
meta:set_string("mcl_potions:"..name, minetest.serialize(EF[name][object])) meta:set_string("mcl_potions:"..name, minetest.serialize(EF[name][object]))
@ -1315,6 +1360,31 @@ function mcl_potions.get_effect_level(object, effect_name)
return registered_effects[effect_name].factor_to_level(effect.factor) return registered_effects[effect_name].factor_to_level(effect.factor)
end end
function mcl_potions.get_total_haste(object)
local accum_factor = 1
for name, def in pairs(item_speed_effects) do
if EF[name][object] then
local factor = EF[name][object].factor
if def.factor_is_positive then factor = factor + 1 end
if factor > 1 then accum_factor = accum_factor * factor end
end
end
return accum_factor - 1
end
function mcl_potions.get_total_fatigue(object)
local accum_factor = 1
for name, def in pairs(item_speed_effects) do
if EF[name][object] then
local factor = EF[name][object].factor
if def.factor_is_positive then factor = factor + 1 end
if factor <= 0 then return 0 end
if factor < 1 then accum_factor = accum_factor * factor end
end
end
return accum_factor
end
function mcl_potions.clear_effect(object, effect) function mcl_potions.clear_effect(object, effect)
EF[effect][object] = nil EF[effect][object] = nil
if not object:is_player() then return end if not object:is_player() then return end

@ -76,13 +76,16 @@ else
end end
function mcl_meshhand.update_player(player) function mcl_meshhand.update_player(player)
local hand
if mcl_skins_enabled then if mcl_skins_enabled then
local node_id = mcl_skins.get_node_id_by_player(player) local node_id = mcl_skins.get_node_id_by_player(player)
player:get_inventory():set_stack("hand", 1, "mcl_meshhand:" .. node_id) hand = ItemStack("mcl_meshhand:" .. node_id)
else else
local creative = minetest.is_creative_enabled(player:get_player_name()) local creative = minetest.is_creative_enabled(player:get_player_name())
player:get_inventory():set_stack("hand", 1, "mcl_meshhand:hand" .. (creative and "_crea" or "_surv")) hand = ItemStack("mcl_meshhand:hand" .. (creative and "_crea" or "_surv"))
end end
if not mcl_potions then player:get_inventory():set_stack("hand", 1, hand) end
player:get_inventory():set_stack("hand", 1, mcl_potions.hf_update_internal(hand, player))
end end
minetest.register_on_joinplayer(function(player) minetest.register_on_joinplayer(function(player)