2021-05-29 16:12:33 +02:00
local modname = minetest.get_current_modname ( )
local modpath = minetest.get_modpath ( modname )
local S = minetest.get_translator ( modname )
2017-01-16 16:09:07 +01:00
2017-02-16 14:19:34 +01:00
mcl_hunger = { }
2017-08-31 23:13:51 +02:00
2019-02-06 21:23:05 +01:00
--[[ This variable tells you if the hunger gameplay mechanic is active.
The state of the hunger mechanic will be determined at game start .
Hunger is enabled when damage is enabled .
If the damage setting is changed within the game , this does NOT
update the hunger mechanic , so the game must be restarted for this
to take effect . ] ]
2022-11-03 00:24:16 +01:00
mcl_hunger.active = false
if minetest.settings : get_bool ( " enable_damage " ) == true and minetest.settings : get_bool ( " mcl_enable_hunger " ) ~= false then
2019-02-06 21:23:05 +01:00
mcl_hunger.active = true
end
2017-05-22 08:20:58 +02:00
mcl_hunger.HUD_TICK = 0.1
-- Exhaustion increase
mcl_hunger.EXHAUST_DIG = 5 -- after digging node
mcl_hunger.EXHAUST_JUMP = 50 -- jump
2017-08-04 19:49:03 +02:00
mcl_hunger.EXHAUST_SPRINT_JUMP = 200 -- jump while sprinting
2017-05-22 08:20:58 +02:00
mcl_hunger.EXHAUST_ATTACK = 100 -- hit an enemy
2017-05-22 23:21:29 +02:00
mcl_hunger.EXHAUST_SWIM = 10 -- player movement in water
2017-05-22 08:20:58 +02:00
mcl_hunger.EXHAUST_SPRINT = 100 -- sprint (per node)
2019-02-28 16:43:52 +01:00
mcl_hunger.EXHAUST_DAMAGE = 100 -- taking damage (protected by armor)
2017-05-22 08:20:58 +02:00
mcl_hunger.EXHAUST_REGEN = 6000 -- Regenerate 1 HP
2022-06-25 08:41:10 +02:00
mcl_hunger.EXHAUST_HUNGER = 5 -- Hunger status effect at base level.
2017-05-22 08:20:58 +02:00
mcl_hunger.EXHAUST_LVL = 4000 -- at what exhaustion player saturation gets lowered
2024-01-15 06:45:44 +01:00
mcl_hunger.EATING_DELAY = tonumber ( minetest.settings : get ( " mcl_eating_delay " ) ) or 1.61
mcl_hunger.EATING_WALK_SPEED = tonumber ( minetest.settings : get ( " movement_speed_crouch " ) ) / tonumber ( minetest.settings : get ( " movement_speed_walk " ) )
mcl_hunger.EATING_TOUCHSCREEN_DELAY_PADDING = 0.75
2017-06-06 18:04:36 +02:00
mcl_hunger.SATURATION_INIT = 5 -- Initial saturation for new/respawning players
2017-05-22 08:20:58 +02:00
2017-05-21 01:32:05 +02:00
-- Debug Mode. If enabled, saturation and exhaustion are shown as well.
2019-02-06 21:23:05 +01:00
-- NOTE: Only updated when settings are loaded.
mcl_hunger.debug = false
-- Cooldown timers for each player, to force a short delay between consuming 2 food items
mcl_hunger.last_eat = { }
2024-01-15 06:45:44 +01:00
-- Variables for each player, to handle delayed eating
mcl_hunger.eat_internal = { }
-- Set per player internal variables for delayed eating
minetest.register_on_joinplayer ( function ( player )
local name = player : get_player_name ( )
mcl_hunger.eat_internal [ name ] = {
is_eating = false ,
itemname = nil ,
item_definition = nil ,
hp_change = nil ,
replace_with_item = nil ,
itemstack = nil ,
user = nil ,
pointed_thing = nil ,
pitch = nil ,
do_item_eat = false ,
_custom_itemstack = nil , -- Used as comparison to make sure _custom_wrapper only executes when the same item is eaten
_custom_var = { } , -- Variables that can be used by _custom_var and _custom_wrapper
_custom_func = nil , -- Can be executed by _custom_wrapper
_custom_wrapper = nil , -- Will execute alongside minetest.do_item_eat if not empty and _custom_itemstack is equal to current player itemstack
_custom_do_delayed = false , -- If true, then will execute only _custom_wrapper after holding RMB or LMB within a delay specified by mcl_hunger.EATING_DELAY (Use to bypass minetest.do_item_eat entirely)
}
2024-01-16 07:58:10 +01:00
playerphysics.remove_physics_factor ( player , " speed " , " mcl_hunger:eating_speed " )
2024-01-15 06:45:44 +01:00
end )
-- Clear when player leaves
minetest.register_on_leaveplayer ( function ( player )
local name = player : get_player_name ( )
mcl_hunger.eat_internal [ name ] = nil
end )
2021-05-29 16:12:33 +02:00
dofile ( modpath .. " /api.lua " )
dofile ( modpath .. " /hunger.lua " )
dofile ( modpath .. " /register_foods.lua " )
2019-02-06 21:23:05 +01:00
--[[ IF HUNGER IS ENABLED ]]
if mcl_hunger.active == true then
-- Read debug mode setting
-- The setting should only be read at the beginning, this mod is not
2017-05-21 01:32:05 +02:00
-- prepared to change this setting later.
2017-08-09 16:17:00 +02:00
mcl_hunger.debug = minetest.settings : get_bool ( " mcl_hunger_debug " )
2017-05-21 01:32:05 +02:00
if mcl_hunger.debug == nil then
mcl_hunger.debug = false
2017-05-20 22:55:33 +02:00
end
2017-05-20 22:51:40 +02:00
2017-05-20 22:33:10 +02:00
--[[ Data value format notes:
Hunger values is identical to Minecraft ' s and ranges from 0 to 20.
Exhaustion and saturation values are stored as integers , unlike in Minecraft .
Exhaustion is Minecraft exhaustion times 1000 and ranges from 0 to 4000.
2017-06-06 18:04:36 +02:00
Saturation is Minecraft saturation and ranges from 0 to 20.
2017-05-20 22:35:50 +02:00
Food saturation is stored in the custom item definition field _mcl_saturation .
This field uses the original Minecraft value .
2017-05-20 22:33:10 +02:00
] ]
2017-01-16 16:09:07 +01:00
-- Count number of poisonings a player has at once
2017-06-06 18:33:01 +02:00
mcl_hunger.poison_hunger = { } -- food poisoning, increasing hunger
2017-01-16 16:09:07 +01:00
2021-04-15 23:41:34 +02:00
-- HUD
2017-05-20 22:06:12 +02:00
local function init_hud ( player )
2017-05-20 23:26:54 +02:00
hb.init_hudbar ( player , " hunger " , mcl_hunger.get_hunger ( player ) )
2017-05-21 01:32:05 +02:00
if mcl_hunger.debug then
2017-06-06 18:04:36 +02:00
hb.init_hudbar ( player , " saturation " , mcl_hunger.get_saturation ( player ) , mcl_hunger.get_hunger ( player ) )
2017-05-20 22:51:40 +02:00
hb.init_hudbar ( player , " exhaustion " , mcl_hunger.get_exhaustion ( player ) )
end
end
-- HUD updating functions for Debug Mode. No-op if not in Debug Mode
function mcl_hunger . update_saturation_hud ( player , saturation , hunger )
2017-05-21 01:32:05 +02:00
if mcl_hunger.debug then
2017-06-06 18:04:36 +02:00
hb.change_hudbar ( player , " saturation " , saturation , hunger )
2017-05-20 22:51:40 +02:00
end
end
function mcl_hunger . update_exhaustion_hud ( player , exhaustion )
2017-05-21 01:32:05 +02:00
if mcl_hunger.debug then
2022-07-25 12:19:18 +02:00
if not exhaustion then
exhaustion = mcl_hunger.get_exhaustion ( player )
end
2017-05-20 22:51:40 +02:00
hb.change_hudbar ( player , " exhaustion " , exhaustion )
end
2017-01-16 16:09:07 +01:00
end
2017-05-20 16:20:03 +02:00
-- register saturation hudbar
2021-04-07 14:39:49 +02:00
hb.register_hudbar ( " hunger " , 0xFFFFFF , S ( " Food " ) , { icon = " hbhunger_icon.png " , bgicon = " hbhunger_bgicon.png " , bar = " hbhunger_bar.png " } , 1 , 20 , 20 , false )
2017-05-21 01:32:05 +02:00
if mcl_hunger.debug then
2021-11-04 20:58:54 +01:00
hb.register_hudbar ( " saturation " , 0xFFFFFF , S ( " Saturation " ) , { icon = " mcl_hunger_icon_saturation.png " , bgicon = " mcl_hunger_bgicon_saturation.png " , bar = " mcl_hunger_bar_saturation.png " } , 1 , mcl_hunger.SATURATION_INIT , 200 , false )
hb.register_hudbar ( " exhaustion " , 0xFFFFFF , S ( " Exhaust. " ) , { icon = " mcl_hunger_icon_exhaustion.png " , bgicon = " mcl_hunger_bgicon_exhaustion.png " , bar = " mcl_hunger_bar_exhaustion.png " } , 1 , 0 , mcl_hunger.EXHAUST_LVL , false )
2017-05-20 22:51:40 +02:00
end
2017-05-20 21:23:02 +02:00
2017-01-16 16:09:07 +01:00
minetest.register_on_joinplayer ( function ( player )
local name = player : get_player_name ( )
2019-03-09 08:25:00 +01:00
mcl_hunger.init_player ( player )
2017-06-06 18:04:36 +02:00
init_hud ( player )
2017-06-06 18:33:01 +02:00
mcl_hunger.poison_hunger [ name ] = 0
2017-05-23 01:39:19 +02:00
mcl_hunger.last_eat [ name ] = - 1
2017-01-16 16:09:07 +01:00
end )
minetest.register_on_respawnplayer ( function ( player )
2017-05-23 02:20:10 +02:00
-- reset hunger, related values and poison
2017-01-16 16:09:07 +01:00
local name = player : get_player_name ( )
2017-05-23 02:20:10 +02:00
mcl_hunger.stop_poison ( player )
2017-05-23 01:39:19 +02:00
mcl_hunger.last_eat [ name ] = - 1
2017-05-20 22:33:10 +02:00
local h , s , e = 20 , mcl_hunger.SATURATION_INIT , 0
2017-05-20 20:59:30 +02:00
mcl_hunger.set_hunger ( player , h , false )
2017-05-20 22:06:12 +02:00
mcl_hunger.set_saturation ( player , s , false )
mcl_hunger.set_exhaustion ( player , e , false )
2017-05-20 23:26:54 +02:00
hb.change_hudbar ( player , " hunger " , h )
2017-05-20 22:51:40 +02:00
mcl_hunger.update_saturation_hud ( player , s , h )
mcl_hunger.update_exhaustion_hud ( player , e )
2017-01-16 16:09:07 +01:00
end )
2017-05-21 06:20:00 +02:00
-- PvP combat exhaustion
minetest.register_on_punchplayer ( function ( victim , puncher , time_from_last_punch , tool_capabilities , dir , damage )
2019-02-28 16:43:52 +01:00
if puncher : is_player ( ) then
2017-05-21 06:20:00 +02:00
mcl_hunger.exhaust ( puncher : get_player_name ( ) , mcl_hunger.EXHAUST_ATTACK )
end
end )
2019-02-28 16:43:52 +01:00
-- Exhaust on taking damage
minetest.register_on_player_hpchange ( function ( player , hp_change )
if hp_change < 0 then
local name = player : get_player_name ( )
mcl_hunger.exhaust ( name , mcl_hunger.EXHAUST_DAMAGE )
end
end )
2021-11-11 19:28:27 +01:00
local food_tick_timers = { } -- one food_tick_timer per player, keys are the player-objects
2024-01-15 06:45:44 +01:00
local eat_start_timers = { }
local eat_tick_timers = { }
local eat_effects_cooldown = { }
local function clear_eat_internal_and_timers ( player , player_name )
playerphysics.remove_physics_factor ( player , " speed " , " mcl_hunger:eating_speed " )
mcl_hunger.eat_internal [ player_name ] = {
is_eating = false ,
itemname = nil ,
item_definition = nil ,
hp_change = nil ,
replace_with_item = nil ,
itemstack = nil ,
user = nil ,
pointed_thing = nil ,
pitch = nil ,
do_item_eat = false ,
_custom_itemstack = nil ,
_custom_var = { } ,
_custom_func = nil ,
_custom_wrapper = nil ,
_custom_do_delayed = false ,
}
eat_start_timers [ player ] = 0
eat_tick_timers [ player ] = 0
eat_effects_cooldown [ player ] = 0
end
2021-11-04 21:01:28 +01:00
minetest.register_globalstep ( function ( dtime )
2021-11-08 19:15:56 +01:00
for _ , player in pairs ( minetest.get_connected_players ( ) ) do
2021-11-11 19:28:27 +01:00
2021-11-08 19:15:56 +01:00
local food_tick_timer = food_tick_timers [ player ] and food_tick_timers [ player ] + dtime or 0
2021-11-08 15:33:53 +01:00
local player_name = player : get_player_name ( )
local food_level = mcl_hunger.get_hunger ( player )
local food_saturation_level = mcl_hunger.get_saturation ( player )
local player_health = player : get_hp ( )
2023-11-27 03:37:28 +01:00
local max_tick_timer = tonumber ( minetest.settings : get ( " mcl_health_regen_delay " ) ) or 0.5
2021-11-11 19:28:27 +01:00
2023-12-05 01:57:40 +01:00
if food_tick_timer > 4 then
2021-11-08 15:33:53 +01:00
food_tick_timer = 0
2021-11-11 19:28:27 +01:00
2022-06-24 12:38:26 +02:00
-- let hunger work always
if player_health > 0 and player_health <= 20 then
2022-06-25 08:41:10 +02:00
--mcl_hunger.exhaust(player_name, mcl_hunger.EXHAUST_HUNGER) -- later for hunger status effect
2022-07-25 12:19:18 +02:00
mcl_hunger.update_exhaustion_hud ( player )
2022-06-24 12:38:26 +02:00
end
2022-06-25 08:41:10 +02:00
if food_level >= 18 then -- slow regeneration
2021-11-09 19:35:32 +01:00
if player_health > 0 and player_health < 20 then
player : set_hp ( player_health + 1 )
mcl_hunger.exhaust ( player_name , mcl_hunger.EXHAUST_REGEN )
2022-07-25 12:19:18 +02:00
mcl_hunger.update_exhaustion_hud ( player )
2021-11-09 19:35:32 +01:00
end
2021-11-11 19:28:27 +01:00
elseif food_level == 0 then -- starvation
-- the amount of health at which a player will stop to get
-- harmed by starvation (10 for Easy, 1 for Normal, 0 for Hard)
local maximum_starvation = 1
2021-11-04 21:01:28 +01:00
-- TODO: implement Minecraft-like difficulty modes and the update maximumStarvation here
2021-11-08 19:15:56 +01:00
if player_health > maximum_starvation then
2021-11-04 21:01:28 +01:00
mcl_util.deal_damage ( player , 1 , { type = " starve " } )
end
end
2021-11-11 19:28:27 +01:00
2023-11-23 01:32:23 +01:00
elseif food_tick_timer > max_tick_timer and food_level == 20 and food_saturation_level > 0 then -- fast regeneration
2021-11-09 19:35:32 +01:00
if player_health > 0 and player_health < 20 then
food_tick_timer = 0
2022-06-25 08:41:10 +02:00
player : set_hp ( player_health + 1 )
2021-11-09 19:35:32 +01:00
mcl_hunger.exhaust ( player_name , mcl_hunger.EXHAUST_REGEN )
2022-07-25 12:19:18 +02:00
mcl_hunger.update_exhaustion_hud ( player )
2021-11-09 19:35:32 +01:00
end
2017-01-21 15:10:08 +01:00
end
2021-11-11 19:28:27 +01:00
food_tick_timers [ player ] = food_tick_timer -- update food_tick_timer table
2024-01-15 06:45:44 +01:00
-- Eating delay code
if mcl_hunger.eat_internal [ player_name ] . is_eating or mcl_hunger.eat_internal [ player_name ] . _custom_do_delayed then
local control = player : get_player_control ( )
local inv = player : get_inventory ( )
local current_itemstack = player : get_wielded_item ( )
if not eat_start_timers [ player ] then
eat_start_timers [ player ] = 0
end
eat_start_timers [ player ] = eat_start_timers [ player ] + dtime
if not eat_tick_timers [ player ] then
eat_tick_timers [ player ] = 0
end
if not eat_effects_cooldown [ player ] then
eat_effects_cooldown [ player ] = 0
end
if not mcl_hunger.eat_internal [ player_name ] . pitch then
mcl_hunger.eat_internal [ player_name ] . pitch = 1 + math.random ( - 10 , 10 ) * 0.005
end
-- check if holding RMB (or LMB as workaround for touchscreen)
if ( current_itemstack == mcl_hunger.eat_internal [ player_name ] . itemstack or current_itemstack == mcl_hunger.eat_internal [ player_name ] . _custom_itemstack ) and ( control.RMB or control.LMB ) then
eat_tick_timers [ player ] = eat_tick_timers [ player ] + dtime
eat_effects_cooldown [ player ] = eat_effects_cooldown [ player ] + dtime
playerphysics.add_physics_factor ( player , " speed " , " mcl_hunger:eating_speed " , mcl_hunger.EATING_WALK_SPEED )
if eat_effects_cooldown [ player ] > 0.2 then
eat_effects_cooldown [ player ] = 0
2024-01-16 07:58:10 +01:00
if not mcl_hunger.eat_internal [ player_name ] . user then
mcl_hunger.eat_internal [ player_name ] . user = player
end
if not mcl_hunger.eat_internal [ player_name ] . itemname then
mcl_hunger.eat_internal [ player_name ] . itemname = current_itemstack : get_name ( )
end
if not mcl_hunger.eat_internal [ player_name ] . hp_change then
mcl_hunger.eat_internal [ player_name ] . hp_change = 0
end
2024-01-15 06:45:44 +01:00
local pos = player : get_pos ( )
local itemname = mcl_hunger.eat_internal [ player_name ] . itemname
local def = minetest.registered_items [ itemname ]
mcl_hunger.eat_effects (
mcl_hunger.eat_internal [ player_name ] . user ,
mcl_hunger.eat_internal [ player_name ] . itemname ,
pos ,
mcl_hunger.eat_internal [ player_name ] . hp_change ,
def ,
mcl_hunger.eat_internal [ player_name ] . pitch
)
end
-- check if eating delay is over
if eat_tick_timers [ player ] >= mcl_hunger.EATING_DELAY then
if not mcl_hunger.eat_internal [ player_name ] . _custom_do_delayed then
mcl_hunger.eat_internal [ player_name ] . do_item_eat = true
minetest.do_item_eat (
mcl_hunger.eat_internal [ player_name ] . hp_change ,
mcl_hunger.eat_internal [ player_name ] . replace_with_item ,
mcl_hunger.eat_internal [ player_name ] . itemstack ,
mcl_hunger.eat_internal [ player_name ] . user ,
mcl_hunger.eat_internal [ player_name ] . pointed_thing
)
-- bypass minetest.do_item_eat and only execute _custom_wrapper
elseif mcl_hunger.eat_internal [ player_name ] . _custom_itemstack and
mcl_hunger.eat_internal [ player_name ] . _custom_wrapper and
mcl_hunger.eat_internal [ player_name ] . _custom_itemstack == current_itemstack then
mcl_hunger.eat_internal [ player_name ] . _custom_wrapper ( player_name )
player : get_inventory ( ) : set_stack ( " main " , player : get_wield_index ( ) , itemstack )
end
clear_eat_internal_and_timers ( player , player_name )
end
elseif eat_start_timers [ player ] and eat_start_timers [ player ] > 0.2 then
playerphysics.remove_physics_factor ( player , " speed " , " mcl_hunger:eating_speed " )
elseif eat_start_timers [ player ] and eat_start_timers [ player ] > mcl_hunger.EATING_TOUCHSCREEN_DELAY_PADDING then
clear_eat_internal_and_timers ( player , player_name )
end
end
if eat_start_timers [ player ] and eat_start_timers [ player ] > mcl_hunger.EATING_DELAY + mcl_hunger.EATING_TOUCHSCREEN_DELAY_PADDING then
clear_eat_internal_and_timers ( player , player_name )
end
2017-01-16 16:09:07 +01:00
end
end )
2019-02-06 21:23:05 +01:00
--[[ IF HUNGER IS NOT ENABLED ]]
else
minetest.register_on_joinplayer ( function ( player )
2019-03-09 08:25:00 +01:00
mcl_hunger.init_player ( player )
2019-02-06 21:23:05 +01:00
mcl_hunger.last_eat [ player : get_player_name ( ) ] = - 1
end )
2017-01-16 16:09:07 +01:00
end