1 local S
= minetest
.get_translator("mcl_hunger")
2 local mod_death_messages
= minetest
.get_modpath("mcl_death_messages")
4 -- wrapper for minetest.item_eat (this way we make sure other mods can't break this one)
5 local org_eat
= minetest
.do_item_eat
6 minetest
.do_item_eat
= function(hp_change
, replace_with_item
, itemstack
, user
, pointed_thing
)
8 if not user
or user
:is_player() == false then
12 -- Call on_rightclick if the pointed node defines it
13 if pointed_thing
.type == "node" then
14 local node
= minetest
.get_node(pointed_thing
.under
)
15 if user
and not user
:get_player_control().sneak
then
16 if minetest
.registered_nodes
[node
.name
] and minetest
.registered_nodes
[node
.name
].on_rightclick
then
17 return minetest
.registered_nodes
[node
.name
].on_rightclick(pointed_thing
.under
, node
, user
, itemstack
) or itemstack
22 local old_itemstack
= itemstack
24 local name
= user
:get_player_name()
26 local creative
= minetest
.is_creative_enabled(name
)
28 -- Special foodstuffs like the cake may disable the eating delay
29 local no_eat_delay
= creative
or (minetest
.get_item_group(itemstack
:get_name(), "no_eat_delay") == 1)
31 -- Allow eating only after a delay of 2 seconds. This prevents eating as an excessive speed.
32 -- FIXME: time() is not a precise timer, so the actual delay may be +- 1 second, depending on which fraction
33 -- of the second the player made the first eat.
34 -- FIXME: In singleplayer, there's a cheat to circumvent this, simply by pausing the game between eats.
35 -- This is because os.time() obviously does not care about the pause. A fix needs a different timer mechanism.
36 if no_eat_delay
or (mcl_hunger
.last_eat
[name
] < 0) or (os
.difftime(os
.time(), mcl_hunger
.last_eat
[name
]) >= 2) then
37 local can_eat_when_full
= creative
or (mcl_hunger
.active
== false) or minetest
.get_item_group(itemstack
:get_name(), "can_eat_when_full") == 1
38 -- Don't allow eating when player has full hunger bar (some exceptional items apply)
39 if can_eat_when_full
or (mcl_hunger
.get_hunger(user
) < 20) then
40 itemstack
= mcl_hunger
.eat(hp_change
, replace_with_item
, itemstack
, user
, pointed_thing
)
41 for _
, callback
in pairs(minetest
.registered_on_item_eats
) do
42 local result
= callback(hp_change
, replace_with_item
, itemstack
, user
, pointed_thing
, old_itemstack
)
47 mcl_hunger
.last_eat
[name
] = os
.time()
54 function mcl_hunger
.eat(hp_change
, replace_with_item
, itemstack
, user
, pointed_thing
)
55 local item
= itemstack
:get_name()
56 local def
= mcl_hunger
.registered_foods
[item
]
59 if type(hp_change
) ~= "number" then
61 minetest
.log("error", "Wrong on_use() definition for item '" .. item
.. "'")
63 def
.saturation
= hp_change
64 def
.replace
= replace_with_item
66 local func
= mcl_hunger
.item_eat(def
.saturation
, def
.replace
, def
.poisontime
, def
.poison
, def
.exhaust
, def
.poisonchance
, def
.sound
)
67 return func(itemstack
, user
, pointed_thing
)
70 -- Reset HUD bars after food poisoning
72 function mcl_hunger
.reset_bars_poison_hunger(player
)
73 hb
.change_hudbar(player
, "hunger", nil, nil, "hbhunger_icon.png", nil, "hbhunger_bar.png")
74 if mcl_hunger
.debug
then
75 hb
.change_hudbar(player
, "exhaustion", nil, nil, nil, nil, "mcl_hunger_bar_exhaustion.png")
80 local function poisonp(tick
, time
, time_left
, damage
, exhaustion
, name
)
81 if not mcl_hunger
.active
then
84 local player
= minetest
.get_player_by_name(name
)
85 -- First check if player is still there
89 local name
= player
:get_player_name()
90 -- Abort if food poisonings have been stopped
91 if mcl_hunger
.poison_hunger
[name
] == 0 then
94 time_left
= time_left
+ tick
95 if time_left
< time
then
96 minetest
.after(tick
, poisonp
, tick
, time
, time_left
, damage
, exhaustion
, name
)
98 if exhaustion
> 0 then
99 mcl_hunger
.poison_hunger
[name
] = mcl_hunger
.poison_hunger
[name
] - 1
101 if mcl_hunger
.poison_hunger
[name
] <= 0 then
102 mcl_hunger
.reset_bars_poison_hunger(player
)
106 -- Deal damage and exhaust player
107 -- TODO: Introduce fatal poison at higher difficulties
108 if player
:get_hp()-damage
> 0 then
109 if mod_death_messages
then
110 mcl_death_messages
.player_damage(player
, S("@1 succumbed to the poison.", name
))
112 player
:set_hp(player
:get_hp()-damage
)
115 mcl_hunger
.exhaust(name
, exhaustion
)
119 local poisonrandomizer
= PseudoRandom(os
.time())
121 function mcl_hunger
.item_eat(hunger_change
, replace_with_item
, poisontime
, poison
, exhaust
, poisonchance
, sound
)
122 return function(itemstack
, user
, pointed_thing
)
123 local itemname
= itemstack
:get_name()
124 local creative
= minetest
.is_creative_enabled(user
:get_player_name())
125 if itemstack
:peek_item() ~= nil and user
~= nil then
127 itemstack
:take_item()
129 local name
= user
:get_player_name()
130 local hp
= user
:get_hp()
132 local pos
= user
:get_pos()
135 local foodtype
= minetest
.get_item_group(itemname
, "food")
136 if foodtype
== 3 then
137 -- Item is a drink, only play drinking sound (no particle)
138 minetest
.sound_play("survival_thirst_drink", {
139 max_hear_distance
= 12,
141 pitch
= 1 + math
.random(-10, 10)*0.005,
145 -- Assume the item is a food
146 -- Add eat particle effect and sound
147 local def
= minetest
.registered_items
[itemname
]
148 local texture
= def
.inventory_image
149 if not texture
or texture
== "" then
150 texture
= def
.wield_image
152 -- Special item definition field: _food_particles
153 -- If false, force item to not spawn any food partiles when eaten
154 if def
._food_particles
~= false and texture
and texture
~= "" then
155 local v
= user
:get_player_velocity()
156 local minvel
= vector
.add(v
, {x
=-1, y
=1, z
=-1})
157 local maxvel
= vector
.add(v
, {x
=1, y
=2, z
=1})
159 minetest
.add_particlespawner({
160 amount
= math
.min(math
.max(8, hunger_change
*2), 25),
162 minpos
= {x
=pos
.x
, y
=pos
.y
, z
=pos
.z
},
163 maxpos
= {x
=pos
.x
, y
=pos
.y
, z
=pos
.z
},
166 minacc
= {x
=0, y
=-5, z
=0},
167 maxacc
= {x
=0, y
=-9, z
=0},
172 collisiondetection
= true,
177 minetest
.sound_play("mcl_hunger_bite", {
178 max_hear_distance
= 12,
180 pitch
= 1 + math
.random(-10, 10)*0.005,
185 if mcl_hunger
.active
and hunger_change
then
186 -- Add saturation (must be defined in item table)
187 local _mcl_saturation
= minetest
.registered_items
[itemname
]._mcl_saturation
189 if not _mcl_saturation
then
192 saturation
= minetest
.registered_items
[itemname
]._mcl_saturation
194 mcl_hunger
.saturate(name
, saturation
, false)
197 local h
= mcl_hunger
.get_hunger(user
)
198 if h
< 20 and hunger_change
then
199 h
= h
+ hunger_change
200 if h
> 20 then h
= 20 end
201 mcl_hunger
.set_hunger(user
, h
, false)
204 hb
.change_hudbar(user
, "hunger", h
)
205 mcl_hunger
.update_saturation_hud(user
, mcl_hunger
.get_saturation(user
), h
)
208 if mcl_hunger
.active
and poisontime
then
209 local do_poison
= false
211 if poisonrandomizer
:next(0,100) < poisonchance
then
218 -- Set food poison bars
219 if exhaust
and exhaust
> 0 then
220 hb
.change_hudbar(user
, "hunger", nil, nil, "mcl_hunger_icon_foodpoison.png", nil, "mcl_hunger_bar_foodpoison.png")
221 if mcl_hunger
.debug
then
222 hb
.change_hudbar(user
, "exhaustion", nil, nil, nil, nil, "mcl_hunger_bar_foodpoison.png")
224 mcl_hunger
.poison_hunger
[name
] = mcl_hunger
.poison_hunger
[name
] + 1
226 poisonp(1, poisontime
, 0, poison
, exhaust
, user
:get_player_name())
231 itemstack
:add_item(replace_with_item
)
238 if mcl_hunger
.active
then
239 -- player-action based hunger changes
240 minetest
.register_on_dignode(function(pos
, oldnode
, player
)
241 -- is_fake_player comes from the pipeworks, we are not interested in those
242 if not player
or not player
:is_player() or player
.is_fake_player
== true then
245 local name
= player
:get_player_name()
247 mcl_hunger
.exhaust(name
, mcl_hunger
.EXHAUST_DIG
)