This commit is contained in:
Rainer 2025-08-22 02:04:48 +02:00
commit 2d26db1c5b
38 changed files with 2837 additions and 0 deletions

View file

@ -0,0 +1,79 @@
-- Localize Hunger NG
local a = hunger_ng.attributes
local c = hunger_ng.configuration
local e = hunger_ng.effects
local f = hunger_ng.functions
local s = hunger_ng.settings
local S = hunger_ng.configuration.translator
-- Localize Luanti
local registered_items = core.registered_items
local override_item = core.override_item
-- Add custom _hunger_ng attribute to items
--
-- Checks if the item already has the attribute. If not, then the provided
-- values are set.
--
-- The data table has to be the following.
--
-- {
-- heals = n,
-- satiates = n,
-- returns = 'id'
-- }
--
-- Where n is a number (can be negative to damage the player or make the
-- player hungry) or a fraction of a number. If `returns` is set to an ID of
-- a registered item this item will be returned when the food is eaten.
--
-- @param id The ID of the item to be modified.
-- @param data The data table as described
hunger_ng.functions.add_hunger_data = function (id, data)
if registered_items[id] == nil then return end
if registered_items[id]._hunger_ng ~= nil then return end
if registered_items[id].on_use == nil then return end
local item_data = registered_items[id]
local info = ''
local satiates = data.satiates or 0
local heals = data.heals or 0
local returns = data.returns or false
local timeout = data.timeout or s.hunger.timeout
if satiates > 0 then
info = info..'\n'..S('Satiates: @1', satiates)
hunger_ng.food_items.satiating = hunger_ng.food_items.satiating + 1
elseif satiates < 0 then
info = info..'\n'..S('Deprives: @1', math.abs(satiates))
hunger_ng.food_items.starving = hunger_ng.food_items.starving + 1
end
if heals > 0 then
info = info..'\n'..S('Heals: @1', data.heals)
hunger_ng.food_items.healing = hunger_ng.food_items.healing + 1
elseif heals < 0 then
info = info..'\n'..S('Injures: @1', math.abs(heals))
hunger_ng.food_items.injuring = hunger_ng.food_items.injuring + 1
end
if returns and registered_items[returns] then
local return_name = registered_items[returns].description
info = info..'\n'..S('Returns: @1', return_name)
end
if data.returns and not registered_items[returns] then
data.returns = nil
end
if timeout > 0 then
info = info..'\n'..S('Eating timeout: @1 seconds', timeout)
end
override_item(id, {
description = item_data.description..info,
_hunger_ng = data
})
end

252
system/chat_commands.lua Normal file
View file

@ -0,0 +1,252 @@
-- Localize Hunger NG
local a = hunger_ng.attributes
local c = hunger_ng.configuration
local e = hunger_ng.effects
local f = hunger_ng.functions
local s = hunger_ng.settings
local S = hunger_ng.configuration.translator
-- Localize Luanto
local chat_send = core.chat_send_player
local log = core.log
local player_exists = core.player_exists
local get_player_by_name = core.get_player_by_name
local get_connected_players = core.get_connected_players
-- Set hunger to given value
--
-- Sets the hunger of the given player to the given value. If the player name
-- is omitted own hunger is set.
--
-- @param name The name of the target player
-- @param value The hunger value to set to
-- @param caller The player who invoked the command
-- @return mixed `void` if player exists, otherwise `nil`
local set_hunger = function (name, value, caller)
local message = ''
if not get_player_by_name(name) then
chat_send(caller, S('The player @1 is not online', name))
return
end
if f.hunger_disabled(name) then
chat_send(caller, S('Hunger for @1 is disabled', name))
return
end
if value > s.hunger.maximum then value = s.hunger.maximum end
if value < 0 then value = 0 end
f.alter_hunger(name, -s.hunger.maximum, 'chat set 0')
f.alter_hunger(name, value, 'chat set target')
if name ~= caller then
chat_send(caller, S('Hunger for @1 set to @2', name, value))
chat_send(name, S('@1 set your hunger to @2', caller, value))
message = caller..' sets hunger for '..name..' to '..value
else
chat_send(caller, S('Hunger set to @1', value))
message = caller..' sets own hunger to '..value
end
log('action', '[hunger_ng] '..message)
end
-- Change the hunger value
--
-- Changes the hunger value of the given player by the given value. Use
-- negative values to substract hunger. If the player name is omitted the own
-- hunger gets changed.
--
-- @param name The name of the target player
-- @param value The hunger value to change by
-- @param caller The player who invoked the command
-- @return mixed `void` if player exists, otherwise `nil`
local change_hunger = function (name, value, caller)
local message = ''
if not get_player_by_name(name) then
chat_send(caller, S('The player @1 is not online', name))
return
end
if f.hunger_disabled(name) then
chat_send(caller, S('Hunger for @1 is disabled', name))
return
end
if value > s.hunger.maximum then value = s.hunger.maximum end
if value < -s.hunger.maximum then value = -s.hunger.maximum end
f.alter_hunger(name, value, 'chat change')
if name ~= caller then
chat_send(caller, S('Hunger for @1 changed by @2', name, value))
chat_send(name, S('@1 changed your hunger by @2', caller, value))
message = caller..'changes hunger for '..name..' by '..value
else
chat_send(caller, S('Hunger changed by @1', value))
message = caller..' changes own hunger by '..value
end
log('action', '[hunger_ng] '..message)
end
-- Toggle hunger being enabled
--
-- Toggles the hunger for the given player from enabled to disabled.
--
-- @param name The name of the target player
-- @param caller The player who invoked the command
-- @return mixed `void` if player exists, otherwise `nil`
local toggle_hunger = function (name, value, caller)
local message = ''
local action = ''
local name = name == '' and caller or name
if not get_player_by_name(name) then
chat_send(caller, S('The player @1 is not online', name))
return
end
if f.hunger_disabled(name) then
hunger_ng.configure_hunger(name, 'enable')
action = 'enabled'
else
hunger_ng.configure_hunger(name, 'disable')
action = 'disabled'
end
if name ~= caller then
chat_send(caller, S('Hunger for @1 was toggled', name))
chat_send(name, S('@1 toggled your hunger', caller))
message = caller..' '..action..' hunger for '..name
else
chat_send(caller, S('Own hunger was toggled'))
message = caller..' '..action..' own hunger'
end
log('action', '[hunger_ng] '..message)
end
-- Get the hunger value
--
-- When called without name parameter it gets the hunger values from all
-- currently connected players. If a name is given the hunger value for that
-- player is returned if the player is online
--
-- @param name The name of the target player
-- @param caller The player who invoked the command
-- @return void
local get_hunger = function(name, caller)
local message = ''
if name == '' then name = get_connected_players()
else name = { get_player_by_name(name) } end
for _,player in pairs(name) do
if player:is_player() then
local player_name = player:get_player_name()
local player_hunger = f.get_data(player_name, a.hunger_value)
local hunger_disabled = f.hunger_disabled(player_name)
if not hunger_disabled then
chat_send(caller, player_name..': '..player_hunger)
else
chat_send(caller, player_name..': '..S('Hunger is disabled'))
end
if player_name == caller then
message = caller..' gets own hunger value'
else
message = caller..' gets hunger value for '..player_name
end
log('action', '[hunger_ng] '..message)
end
end
if #name == 0 then
chat_send(caller, S('No player matches your criteria'))
end
end
-- Show the help message
--
-- Shows the help message to the caller
--
-- @param caller The player who invoked the command
-- @return void
local show_help = function (caller)
chat_send(caller, S('run `/help hunger` to show help'))
end
-- Register privilege for hunger control
core.register_privilege('manage_hunger', {
description = S('Player can view and alter own and others hunger values.')
})
-- Administrative chat command definition
core.register_chatcommand('hunger', {
params = '<set/change/get/toggle> <name> <value>',
description = S('Modify or get hunger values'),
privs = { manage_hunger = true },
func = function (caller, parameters)
local pt= {}
for p in parameters:gmatch("%S+") do table.insert(pt, p) end
local action = pt[1] or ''
local name = pt[2] or ''
local value = pt[3] or ''
-- Name parameter missing
if not player_exists(name) and tonumber(name) and value == '' then
value = name
name = caller
end
-- Convert value to number or print error message when trying to set
-- a value but no proper value was given
if tonumber(value) then
value = tonumber(value)
else
if action ~= 'get' and action ~= 'toggle' then
show_help(caller)
return
end
end
-- Execute the corresponding function for the defined action
if action == 'set' then set_hunger(name, value, caller)
elseif action == 'change' then change_hunger(name, value, caller)
elseif action == 'get' then get_hunger(name, caller)
elseif action == 'toggle' then toggle_hunger(name, value, caller)
else show_help(caller) end
end
})
-- Personal information chat command
core.register_chatcommand('myhunger', {
params = 'name',
description = S('Show own hunger value'),
privs = { interact = true },
func = function (caller)
local player_hunger = f.get_data(caller, a.hunger_value)
local hunger_disabled = f.hunger_disabled(caller)
if hunger_disabled then
chat_send(caller, S('Your hunger is disabled'))
else
chat_send(caller, S('Your hunger value is @1', player_hunger))
end
end
})

283
system/hunger_functions.lua Normal file
View file

@ -0,0 +1,283 @@
-- Localize Hunger NG
local a = hunger_ng.attributes
local c = hunger_ng.configuration
local e = hunger_ng.effects
local f = hunger_ng.functions
local s = hunger_ng.settings
local S = hunger_ng.configuration.translator
-- Localize Luanti
local core_log = core.log
local get_player_by_name = core.get_player_by_name
local get_current_modname = core.get_current_modname
-- Gets and returns the given player-related data
--
-- To gain more flexibility this function is used wherever something from the
-- player has to be loaded either as custom player attribute or as planned
-- player meta data.
--
-- @param playername The name of the player to get the information from
-- @param field The field that has to be get.
-- @param as_string Optionally return the value of the field as string
-- @return bool|string|number
local get_data = function (playername, field, as_string)
local player = get_player_by_name(playername)
if not player then return false end
local player_meta = player:get_meta()
if as_string then
return tostring(player_meta:get(field) or 'invalid')
else
return tonumber(player_meta:get(field) or nil)
end
end
-- Sets a player-related attribute
--
-- To gain more flexibility on the player-related functions this function can
-- be used wherever a player-related attribute has to be set.
--
-- @param playername The name of the player to set the attribute to
-- @param field The field to set
-- @param value The value to set the field to
-- @return void
local set_data = function (playername, field, value)
local player = get_player_by_name(playername)
if not player then return false end
local player_meta = player:get_meta()
player_meta:set_string(field, value)
end
-- Print health and hunger changes
--
-- This function prints all health and hunger changes that are triggered by
-- this mod. The following information will be shown for every change.
--
-- t: Ingame time when the change was applied
-- p: player name affected by the change
-- w: Information on what was changes (hunger/health)
-- n: The new value as defined by the definition
-- d: definition (calculation) of the change (old + change = new)
--
-- @param playername Name of the player (p)
-- @param what Description on what was changed (w, hunger/health)
-- @param old The old value
-- @param new The new value
-- @param change The change amount
-- @param reason The given reason for the change
-- @return void
local debug_log = function (playername, what, old, new, change, reason)
if not c.debug_mode then return end
local timestamp = 24 * 60 * core.get_timeofday()
local h = tostring((math.floor(timestamp/60) % 60))
local m = tostring((math.floor(timestamp) % 60))
local text = ('t: +t, p: +p, w: +w, n: +n, d: +o + +c, r: +r'):gsub('+.', {
['+t'] = string.rep('0', 2-#h)..h..':'..string.rep('0', 2-#m)..m,
['+p'] = playername,
['+w'] = what,
['+o'] = old,
['+n'] = new,
['+c'] = change,
['+r'] = reason or 'n/a'
})
core_log('action', c.log_prefix..text)
end
-- Returns if hunger is disabled for the given player
--
-- When the player is no `interact` permission or has the `hunger_disabled`
-- parameter set then this function returns boolean true. Otherwise boolean
-- false will be returned.
--
-- @param playername The name of the player to check
-- @return bool
local hunger_disabled = function (playername)
local interact = core.check_player_privs(playername, { interact=true })
local disabled = get_data(playername, a.hunger_disabled)
if core.is_yes(disabled) or not interact then return true end
return false
end
-- Configures hunger effects for the player
--
-- The function can enable or disable hunger for a player. It is meant to be
-- used by other mods to disable or enable hunger effects for a specific
-- player. For example a magic item that prevents players from getting hungry.
--
-- The parameter `action` can be either `disable`, `enable`. The actions are
-- very self-explainatory.
--
-- @param playername The name of the player whose hunger is to be configured
-- @param action The action that will be taken as described
local configure_hunger = function (playername, action)
if not action then return end
if action == 'enable' then
set_data(playername, a.hunger_disabled, 0)
elseif action == 'disable' then
set_data(playername, a.hunger_disabled, 1)
end
end
-- Get the current hunger information
--
-- Gets (Returns) the current hunger information for the given player. See API
-- documentation for a detailled overview of the returned table.
--
-- @param playername The name of the player whose hunger value is to be get
-- @return table The table as described
hunger_ng.functions.get_hunger_information = function (playername)
local player = get_player_by_name(playername)
if not player then return { invalid = true, player_name = playername } end
local last_eaten = get_data(playername, a.eating_timestamp) or 0
local current_hunger = get_data(playername, a.hunger_value)
local player_properties = player:get_properties()
local e_heal = get_data(playername, a.effect_heal, true) == 'enabled'
local e_hunger = get_data(playername, a.effect_hunger, true) == 'enabled'
local e_starve = get_data(playername, a.effect_starve, true) == 'enabled'
return {
player_name = playername,
hunger = {
floored = math.floor(current_hunger),
ceiled = math.ceil(current_hunger),
disabled = hunger_disabled(playername),
exact = current_hunger,
enabled = e_heal
},
maximum = {
hunger = s.hunger.maximum,
health = player_properties.hp_max,
breath = player_properties.breath_max
},
effects = {
starving = {
enabled = e_starve,
status = current_hunger < e.starve.below
},
healing = {
enabled = e_heal,
status = current_hunger > e.heal.above
},
current_breath = player:get_breath(),
},
timestamps = {
last_eaten = tonumber(last_eaten),
request = tonumber(os.time())
}
}
end
-- Alter health by given value
--
-- @param playername The name of a player whose health value should be altered
-- @param change The health change (can be negative to damage the player)
hunger_ng.functions.alter_health = function (playername, change, reason)
local player = get_player_by_name(playername)
local hp_max = player:get_properties().hp_max
if player == nil then return end
if hunger_disabled(playername) then return end
local current_health = player:get_hp()
local new_health = current_health + change
if new_health > hp_max then new_health = hp_max end
if new_health < 0 then new_health = 0 end
player:set_hp(new_health, { hunger = reason })
debug_log(playername, 'health', current_health, new_health, change, reason)
end
-- Alter hunger by the given value
--
-- @param playername The name of a player whose hunger value should be altered
-- @param change The hunger change (can be negative to make player hungry)
hunger_ng.functions.alter_hunger = function (playername, change, reason)
local player = get_player_by_name(playername)
if player == nil then return end
if hunger_disabled(playername) then return end
local current_hunger = get_data(playername, a.hunger_value)
local new_hunger = current_hunger + change
local bar_id = get_data(playername, a.hunger_bar_id)
if new_hunger > s.hunger.maximum then new_hunger = s.hunger.maximum end
if new_hunger < 0 then new_hunger = 0 end
set_data(playername, a.hunger_value, new_hunger)
if s.hunger_bar.use then
player:hud_change(bar_id, 'number', math.ceil(new_hunger))
end
debug_log(playername, 'hunger', current_hunger, new_hunger, change, reason)
end
-- Set hunger effect metadata
--
-- The hunger effect meta data can be set by mods to temporary disable hunger
-- effects for the given player. Everything works normal but hunger effects
-- like hunger itself, starving and healing are not performed even if the
-- player is in a state where this would happen.
--
-- The effect is not persistent. When a player rejoins the setting is actively
-- removed during join time of that player. Mods need to actively track the
-- status if they want the setting persist between joins.
--
-- @see hunger_ng.alter_hunger
-- @see system/timers.lua
--
-- @param playername Name of the player to set the effect for
-- @param effect The effect name as described
-- @param setting Either `enabled` or `disabled`
-- @return void
hunger_ng.set_effect = function (playername, effect, setting)
local attribute = a['effect_'..effect] or false
local allowed_values = { enabled = true, disabled = true }
-- Warn in server log when a mod tries to configure an unknown effect
if attribute == false then
core_log('warning', ('+t +m tried to set +v for +p'):gsub('+.', {
['+t'] = '[hunger_ng]',
['+m'] = get_current_modname(),
['+v'] = 'unknown effect '..effect,
['+p'] = playername
}))
return
end
-- Set the attribute according to what the mod wants and log that setting
if allowed_values[setting] == true then
set_data(playername, attribute, setting)
core_log('verbose', ('+t +m sets +a to +v for +p'):gsub('+.', {
['+t'] = '[hunger_ng]',
['+m'] = get_current_modname(),
['+a'] = attribute,
['+v'] = setting,
['+p'] = playername
}))
end
end
-- Globalize the set and get function for player data for use in other files
hunger_ng.functions.get_data = get_data
hunger_ng.functions.set_data = set_data
hunger_ng.functions.hunger_disabled = hunger_disabled
hunger_ng.functions.configure_hunger = configure_hunger

View file

@ -0,0 +1,37 @@
-- Localize Hunger NG
local a = hunger_ng.attributes
local c = hunger_ng.configuration
local e = hunger_ng.effects
local f = hunger_ng.functions
local s = hunger_ng.settings
local S = hunger_ng.configuration.translator
-- Localize Luanti
local get_modpath = core.get_modpath
local get_dir_list = core.get_dir_list
local log = core.log
-- Load needed data
local mod_path = core.get_modpath('hunger_ng')
local i14y_path = mod_path..DIR_DELIM..'interoperability'..DIR_DELIM
-- Load interoperability file when the corresponding mod was loaded
core.register_on_mods_loaded(function()
for _,i14y_file in pairs(get_dir_list(i14y_path)) do
local modname = i14y_file:gsub('%..*', '')
if get_modpath(modname) and i14y_file ~= 'README.md' then
dofile(i14y_path..i14y_file)
log('info', c.log_prefix..'Loaded built-in '..modname..' support')
end
end
if hunger_ng.food_items.satiating == 0 then
local message = {
'There are NO satiating food items registered!',
'Hunger is disabled!',
'Enable at least one of the supported mods.'
}
log('warning', '[hunger_ng] '..table.concat(message, ' '))
end
end)

173
system/register_on.lua Normal file
View file

@ -0,0 +1,173 @@
-- vim: set sw=2:
-- Set Vims shiftwidth to 2 instead of 4 because the functions in this file
-- are deeply nested and there are some long lines. 2 instead of 4 gives a tiny
-- bit more space for each indentation level.
-- Localize Hunger NG
local a = hunger_ng.attributes
local c = hunger_ng.configuration
local e = hunger_ng.effects
local f = hunger_ng.functions
local s = hunger_ng.settings
local S = hunger_ng.configuration.translator
local costs = hunger_ng.costs
-- Localize Luanti
local chat_send = core.chat_send_player
local core_log = core.log
-- When a player digs or places a node the corresponding hunger alteration
-- will be applied
core.register_on_dignode(function(p, on, digger)
if digger then
f.alter_hunger(digger:get_player_name(), -costs.dig, 'digging')
end
end)
core.register_on_placenode(function(p, nn, placer, on, is, pt)
if placer then
f.alter_hunger(placer:get_player_name(), -costs.place, 'placing')
end
end)
-- If a player dies and respawns the hunger value will be properly deleted and
-- after respawn it will be set again. This avoids the player healing even if
-- the player died.
core.register_on_dieplayer(function(player)
f.alter_hunger(player:get_player_name(), -s.hunger.maximum, 'death')
return true
end)
core.register_on_respawnplayer(function(player)
f.alter_hunger(player:get_player_name(), s.hunger.start_with, 'respawn')
end)
-- Custom eating function
--
-- When the player eats an item it is checked if the item has the custom
-- _hunger_ng attribute set. If no, the eating wont be intercepted by the
-- function and the item will be eat regularly.
--
-- If the item has the attribute set then it will be processed and the heal
-- and hunger values will be applied according to the items settings.
--
-- If the item has a timeout and the timeout is still active the user gets an
-- information about this mentioning the timeout and how long the user has to
-- wait before being able to eat again.
core.register_on_item_eat(function(hpc, rwi, itemstack, user, pt)
local definition = itemstack:get_definition()
local hunger_def = definition._hunger_ng
-- Make sure to run the Hunger NG actions only if the item has hunger
-- information registered.
if user:is_player() ~= true or hunger_def == nil then return end
local player_name = user:get_player_name()
local current_hunger = f.get_data(player_name, a.hunger_value)
local hunger_disabled = f.get_data(player_name, a.hunger_disabled)
local item_sound = definition.sound or {}
local eating_sound = item_sound.eat or 'hunger_ng_eat'
-- If hunger is disabled by configuration the reular eating functionality
-- is restored with chat message on eating.
if core.is_yes(hunger_disabled) then
chat_send(player_name, S('Hunger is disabled for you! Eating normally.'))
return
end
-- If a mod disabled the hunger effect the regular eating functionality
-- is restored without chat message.
if f.get_data(player_name,a.effect_hunger,true) == 'disabled' then return end
local heals = hunger_def.heals or 0
local satiates = hunger_def.satiates or 0
if current_hunger == s.hunger.maximum and heals <= 0 and satiates >= 0 then
chat_send(player_name, S('Youre fully satiated already!'))
return itemstack
end
if hunger_def.returns then
local inventory = user:get_inventory()
if not inventory:room_for_item('main', hunger_def.returns..' 1') then
local message = S('You have no inventory space to keep the leftovers.')
chat_send(player_name, message)
return itemstack
end
end
local timeout = hunger_def.timeout or s.hunger.timeout
local current_timestamp = os.time()
local player_timestamp = f.get_data(player_name, a.eating_timestamp)
if current_timestamp < player_timestamp + timeout then
local wait = player_timestamp + timeout - current_timestamp
local message = S('Youre eating too fast!')
local info = S('Wait for eating timeout to end: @1s', wait)
chat_send(player_name, message..' '..info)
return itemstack
else
f.set_data(player_name, a.eating_timestamp, current_timestamp)
end
core.sound_play(eating_sound, { to_player = player_name })
f.alter_hunger(player_name, satiates, 'eating')
f.alter_health(player_name, heals, 'eating')
itemstack:take_item(1)
if hunger_def.returns then
local inventory = user:get_inventory()
inventory:add_item('main', hunger_def.returns..' 1')
end
return itemstack
end)
-- Initial hunger and hunger bar configuration
--
-- When a player joins it is checked if the custom attribute for hunger is set.
-- If hunger persistence is used then the value gets read and applied to the
-- hunger bar.
--
-- If the value is not set or hunger persistence is not used then the hunger
-- value to start with will be used. This can be different from the maximum
-- hunger value.
core.register_on_joinplayer(function(player)
local player_name = player:get_player_name()
local unset = not f.get_data(player_name, a.hunger_value)
local reset = f.get_data(player_name, a.hunger_value) and not s.hunger.persistent
-- Only set if the value is not set or if hunger is configured not
-- being persistent.
if unset or reset then
if c.debug_mode then
local message = 'Set initial hunger values for '..player_name
core_log('action', c.log_prefix..message)
end
f.set_data(player_name, a.hunger_value, s.hunger.start_with)
f.set_data(player_name, a.eating_timestamp, 0)
f.set_data(player_name, a.hunger_disabled, 0)
end
-- Always reset (enable) hunger effect settings
f.set_data(player_name, a.effect_hunger, 'enabled')
f.set_data(player_name, a.effect_heal, 'enabled')
f.set_data(player_name, a.effect_starve, 'enabled')
-- Only set hunger bar ID if hunger bar is configured to be used
if s.hunger_bar.use then
f.set_data(player_name, a.hunger_bar_id, player:hud_add({
type = 'statbar',
position = { x=0.5, y=1 },
text = hunger_ng.hunger_bar_image,
direction = 0,
number = f.get_data(player_name, a.hunger_value),
size = { x=24, y=24 },
offset = {x=25,y=-(48+24+16)},
}))
end
end)

130
system/timers.lua Normal file
View file

@ -0,0 +1,130 @@
-- vim: set sw=2:
-- Set Vims shiftwidth to 2 instead of 4 because the functions in this file
-- are deeply nested and there are some long lines. 2 instead of 4 gives a tiny
-- bit more space for each indentation level.
local alter_health = hunger_ng.functions.alter_health
local alter_hunger = hunger_ng.functions.alter_hunger
local base_interval = hunger_ng.settings.timers.basal_metabolism
local costs_base = hunger_ng.costs.base
local costs_movement = hunger_ng.costs.movement
local effect_heal = hunger_ng.attributes.effect_heal
local effect_hunger = hunger_ng.attributes.effect_hunger
local effect_starve = hunger_ng.attributes.effect_starve
local get_data = hunger_ng.functions.get_data
local heal_above = hunger_ng.effects.heal.above
local heal_amount = hunger_ng.effects.heal.amount
local heal_interval = hunger_ng.settings.timers.heal
local hunger_attribute = hunger_ng.attributes.hunger_value
local hunger_bar_id = hunger_ng.attributes.hunger_bar_id
local hunger_disabled_attribute = hunger_ng.attributes.hunger_disabled
local move_interval = hunger_ng.settings.timers.movement
local starve_amount = hunger_ng.effects.starve.amount
local starve_below = hunger_ng.effects.starve.below
local starve_die = hunger_ng.effects.starve.die
local starve_interval = hunger_ng.settings.timers.starve
local use_hunger_bar = hunger_ng.settings.hunger_bar.use
-- Localize Luanti
is_yes = core.is_yes
get_connected_players = core.get_connected_players
-- Initiate globalstep timers
local base_timer = 0
local heal_timer = 0
local move_timer = 0
local starve_timer = 0
core.register_globalstep(function(dtime)
-- Do not run if there are no satiating food items registered
if hunger_ng.food_items.satiating == 0 then return end
-- Raise timer values if needed
if costs_base ~= 0 then base_timer = base_timer + dtime end
if heal_amount ~= 0 then heal_timer = heal_timer + dtime end
if costs_movement ~= 0 then move_timer = move_timer + dtime end
if starve_amount ~= 0 then starve_timer = starve_timer + dtime end
-- Reset timers if needed
if costs_base ~= 0 and base_timer >= base_interval then base_timer = 0 end
if heal_amount ~= 0 and heal_timer >= heal_interval then heal_timer = 0 end
if costs_movement ~= 0 and move_timer >= move_interval then move_timer=0 end
if starve_amount~=0 and starve_timer>=starve_interval then starve_timer=0 end
-- Iterate over all players
--
-- If the value and the timer for the corresponding attribute are not zero
-- (value) and zero (timer) then the alteration of that attribute is executed.
for _,player in ipairs(get_connected_players()) do
if player:is_player() then
local playername = player:get_player_name()
local hp_max = player:get_properties().hp_max
local e_heal = get_data(playername, effect_heal, true) == 'enabled'
local e_hunger = get_data(playername, effect_hunger, true) == 'enabled'
local e_starve = get_data(playername, effect_starve, true) == 'enabled'
-- Basal metabolism costs
if costs_base ~= 0 and base_timer == 0 and e_hunger then
alter_hunger(player:get_player_name(), -costs_base, 'base')
end
-- Heal player if possible and needed
if heal_amount ~= 0 and heal_timer == 0 then
local hunger = get_data(playername, hunger_attribute)
local health = player:get_hp()
local awash = player:get_breath() < player:get_properties().breath_max
local can_heal = hunger >= heal_above and not awash
local needs_health = health < hp_max
if can_heal and needs_health and e_heal then
alter_health(playername, heal_amount, 'healing')
end
end
-- Alter hunger based on movement costs
if costs_movement ~= 0 and move_timer == 0 then
local move = player:get_player_control()
local moving = move.up or move.down or move.left or move.right
if moving and e_hunger then
alter_hunger(playername, -costs_movement, 'movement')
end
end
-- Starve player if starvation requirements are fulfilled
if starve_amount ~= 0 and starve_timer == 0 then
local hunger = get_data(playername, hunger_attribute)
local health = player:get_hp()
local starves = hunger < starve_below
local playername = player:get_player_name()
if starves and e_starve then
if health == 1 and not starve_die then return end
alter_health(playername, -starve_amount, 'starving')
end
end
end -- is player
end -- players iteration
end)
-- Show/hide hunger bar on player breath status or functionality status
if use_hunger_bar then
core.register_globalstep(function(dtime)
for _,player in ipairs(get_connected_players()) do
if player:is_player() then
local player_name = player:get_player_name()
local bar_id = get_data(player_name, hunger_bar_id)
local awash = player:get_breath() < player:get_properties().breath_max
local disabled = get_data(player_name, hunger_disabled_attribute)
local no_food = hunger_ng.food_items.satiating == 0
if awash or is_yes(disabled) or no_food then
player:hud_change(bar_id, 'text', '')
else
player:hud_change(bar_id, 'text', hunger_ng.hunger_bar_image)
end
end
end
end)
end