Replace getpos() with get_pos()
[MineClone/MineClone2.git] / mods / PLAYER / mcl_hunger / hunger.lua
blob875d1be3357eeb654632a28a54da3d7952f7d7cf
1 -- wrapper for minetest.item_eat (this way we make sure other mods can't break this one)
2 local org_eat = core.do_item_eat
3 core.do_item_eat = function(hp_change, replace_with_item, itemstack, user, pointed_thing)
4 -- Call on_rightclick if the pointed node defines it
5 if pointed_thing.type == "node" then
6 local node = minetest.get_node(pointed_thing.under)
7 if user and not user:get_player_control().sneak then
8 if minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].on_rightclick then
9 return minetest.registered_nodes[node.name].on_rightclick(pointed_thing.under, node, user, itemstack) or itemstack
10 end
11 end
12 end
14 local old_itemstack = itemstack
16 local name = user:get_player_name()
18 -- Special foodstuffs like the cake may disable the eating delay
19 local no_eat_delay = minetest.get_item_group(itemstack:get_name(), "no_eat_delay") == 1
21 -- Allow eating only after a delay of 2 seconds. This prevents eating as an excessive speed.
22 -- FIXME: time() is not a precise timer, so the actual delay may be +- 1 second, depending on which fraction
23 -- of the second the player made the first eat.
24 -- FIXME: In singleplayer, there's a cheat to circumvent this, simply by pausing the game between eats.
25 -- This is because os.time() obviously does not care about the pause. A fix needs a different timer mechanism.
26 if no_eat_delay or (mcl_hunger.last_eat[name] < 0) or (os.difftime(os.time(), mcl_hunger.last_eat[name]) >= 2) then
27 local can_eat_when_full = minetest.get_item_group(itemstack:get_name(), "can_eat_when_full") == 1
28 -- Don't allow eating when player has full hunger bar (some exceptional items apply)
29 if can_eat_when_full or (mcl_hunger.get_hunger(user) < 20) then
30 itemstack = mcl_hunger.eat(hp_change, replace_with_item, itemstack, user, pointed_thing)
31 for _, callback in pairs(core.registered_on_item_eats) do
32 local result = callback(hp_change, replace_with_item, itemstack, user, pointed_thing, old_itemstack)
33 if result then
34 return result
35 end
36 end
37 mcl_hunger.last_eat[name] = os.time()
38 end
39 end
41 return itemstack
42 end
44 -- food functions
45 local food = {}
47 function mcl_hunger.register_food(name, hunger_change, replace_with_item, poisontime, poison, exhaust, poisonchance, sound)
48 food[name] = {}
49 food[name].saturation = hunger_change -- hunger points added
50 food[name].replace = replace_with_item -- what item is given back after eating
51 food[name].poisontime = poisontime -- time it is poisoning. If this is set, this item is considered poisonous,
52 -- otherwise the following poison/exhaust fields are ignored
53 food[name].poison = poison -- poison damage per tick for poisonous food
54 food[name].exhaust = exhaust -- exhaustion per tick for poisonous food
55 food[name].poisonchance = poisonchance -- chance percentage that this item poisons the player (default: 100% if poisoning is enabled)
56 food[name].sound = sound -- special sound that is played when eating
57 end
59 function mcl_hunger.eat(hp_change, replace_with_item, itemstack, user, pointed_thing)
60 local item = itemstack:get_name()
61 local def = food[item]
62 if not def then
63 def = {}
64 if type(hp_change) ~= "number" then
65 hp_change = 1
66 core.log("error", "Wrong on_use() definition for item '" .. item .. "'")
67 end
68 def.saturation = hp_change
69 def.replace = replace_with_item
70 end
71 local func = mcl_hunger.item_eat(def.saturation, def.replace, def.poisontime, def.poison, def.exhaust, def.poisonchance, def.sound)
72 return func(itemstack, user, pointed_thing)
73 end
75 -- Reset HUD bars after poisoning
76 local function reset_bars_poison_damage(player)
77 hb.change_hudbar(player, "health", nil, nil, "hudbars_icon_health.png", nil, "hudbars_bar_health.png")
78 end
80 local function reset_bars_poison_hunger(player)
81 hb.change_hudbar(player, "hunger", nil, nil, "hbhunger_icon.png", nil, "hbhunger_bar.png")
82 if mcl_hunger.debug then
83 hb.change_hudbar(player, "exhaustion", nil, nil, nil, nil, "mcl_hunger_bar_exhaustion.png")
84 end
85 end
87 -- Poison player
88 local function poisonp(tick, time, time_left, damage, exhaustion, name)
89 local player = minetest.get_player_by_name(name)
90 -- First check if player is still there
91 if not player then
92 return
93 end
94 local name = player:get_player_name()
95 -- Abort if poisonings have been stopped
96 if mcl_hunger.poison_damage[name] == 0 and mcl_hunger.poison_hunger[name] == 0 then
97 return
98 end
99 time_left = time_left + tick
100 if time_left < time then
101 minetest.after(tick, poisonp, tick, time, time_left, damage, exhaustion, name)
102 else
103 if damage > 0 then
104 mcl_hunger.poison_damage[name] = mcl_hunger.poison_damage[name] - 1
106 if exhaustion > 0 then
107 mcl_hunger.poison_hunger [name] = mcl_hunger.poison_hunger[name] - 1
109 if mcl_hunger.poison_damage[name] <= 0 then
110 reset_bars_poison_damage(player)
112 if mcl_hunger.poison_hunger[name] <= 0 then
113 reset_bars_poison_hunger(player)
117 -- Deal damage and exhaust player
118 if player:get_hp()-damage > 0 then
119 player:set_hp(player:get_hp()-damage)
122 mcl_hunger.exhaust(name, exhaustion)
126 -- Immediately stop all poisonings for this player
127 function mcl_hunger.stop_poison(player)
128 mcl_hunger.poison_damage[player:get_player_name()] = 0
129 mcl_hunger.poison_hunger[player:get_player_name()] = 0
130 reset_bars_poison_damage(player)
131 reset_bars_poison_hunger(player)
134 local poisonrandomizer = PseudoRandom(os.time())
136 function mcl_hunger.item_eat(hunger_change, replace_with_item, poisontime, poison, exhaust, poisonchance, sound)
137 return function(itemstack, user, pointed_thing)
138 local itemname = itemstack:get_name()
140 if itemstack:take_item() ~= nil and user ~= nil then
141 local name = user:get_player_name()
142 local hp = user:get_hp()
144 local pos = user:get_pos()
145 -- player height
146 pos.y = pos.y + 1.5
147 local foodtype = minetest.get_item_group(itemname, "food")
148 if foodtype == 3 then
149 -- Item is a drink, only play drinking sound (no particle)
150 minetest.sound_play("survival_thirst_drink", {
151 pos = pos,
152 max_hear_distance = 12,
153 gain = 1.0,
155 else
156 -- Assume the item is a food
157 -- Add eat particle effect and sound
158 local def = minetest.registered_items[itemname]
159 local texture = def.inventory_image
160 if not texture or texture == "" then
161 texture = def.wield_image
163 -- Special item definition field: _food_particles
164 -- If false, force item to not spawn any food partiles when eaten
165 if def._food_particles ~= false and texture and texture ~= "" then
166 local v = user:get_player_velocity()
167 local minvel = vector.add(v, {x=-1, y=1, z=-1})
168 local maxvel = vector.add(v, {x=1, y=2, z=1})
170 minetest.add_particlespawner({
171 amount = math.min(math.max(8, hunger_change*2), 25),
172 time = 0.1,
173 minpos = {x=pos.x, y=pos.y, z=pos.z},
174 maxpos = {x=pos.x, y=pos.y, z=pos.z},
175 minvel = minvel,
176 maxvel = maxvel,
177 minacc = {x=0, y=-5, z=0},
178 maxacc = {x=0, y=-9, z=0},
179 minexptime = 1,
180 maxexptime = 1,
181 minsize = 1,
182 maxsize = 2,
183 collisiondetection = true,
184 vertical = false,
185 texture = texture,
188 minetest.sound_play("mcl_hunger_bite", {
189 pos = pos,
190 max_hear_distance = 12,
191 gain = 1.0,
195 if hunger_change then
196 -- Add saturation (must be defined in item table)
197 local _mcl_saturation = minetest.registered_items[itemname]._mcl_saturation
198 local saturation
199 if not _mcl_saturation then
200 saturation = 0
201 else
202 saturation = minetest.registered_items[itemname]._mcl_saturation
204 mcl_hunger.saturate(name, saturation, false)
206 -- Add food points
207 local h = mcl_hunger.get_hunger(user)
208 if h < 20 and hunger_change then
209 h = h + hunger_change
210 if h > 20 then h = 20 end
211 mcl_hunger.set_hunger(user, h, false)
214 hb.change_hudbar(user, "hunger", h)
215 mcl_hunger.update_saturation_hud(user, mcl_hunger.get_saturation(user), h)
217 -- Poison
218 if poisontime then
219 local do_poison = false
220 if poisonchance then
221 if poisonrandomizer:next(0,100) < poisonchance then
222 do_poison = true
224 else
225 do_poison = true
227 if do_poison then
228 -- Set poison bars
229 if poison and poison > 0 then
230 hb.change_hudbar(user, "health", nil, nil, "hbhunger_icon_health_poison.png", nil, "hbhunger_bar_health_poison.png")
231 mcl_hunger.poison_damage[name] = mcl_hunger.poison_damage[name] + 1
233 if exhaust and exhaust > 0 then
234 hb.change_hudbar(user, "hunger", nil, nil, "mcl_hunger_icon_foodpoison.png", nil, "mcl_hunger_bar_foodpoison.png")
235 if mcl_hunger.debug then
236 hb.change_hudbar(user, "exhaustion", nil, nil, nil, nil, "mcl_hunger_bar_foodpoison.png")
238 mcl_hunger.poison_hunger[name] = mcl_hunger.poison_hunger[name] + 1
240 poisonp(1, poisontime, 0, poison, exhaust, user:get_player_name())
244 --sound:eat
245 itemstack:add_item(replace_with_item)
247 return itemstack
251 -- player-action based hunger changes
252 minetest.register_on_dignode(function(pos, oldnode, player)
253 -- is_fake_player comes from the pipeworks, we are not interested in those
254 if not player or not player:is_player() or player.is_fake_player == true then
255 return
257 local name = player:get_player_name()
258 -- dig event
259 mcl_hunger.exhaust(name, mcl_hunger.EXHAUST_DIG)
260 end)
262 -- Apply simple poison effect as long there are no real status effect
263 -- TODO: Remove this when status effects are in place
265 mcl_hunger.register_food("mcl_farming:potato_item_poison", 2, "", 4, 1, 0, 60)
267 mcl_hunger.register_food("mcl_mobitems:rotten_flesh", 4, "", 30, 0, 100, 80)
268 mcl_hunger.register_food("mcl_mobitems:chicken", 2, "", 30, 0, 100, 30)
269 mcl_hunger.register_food("mcl_mobitems:spider_eye", 2, "", 4, 1, 0)
271 mcl_hunger.register_food("mcl_fishing:pufferfish_raw", 1, "", 60, 1, 300)