functions.lua 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591
  1. -- Localize for performance.
  2. local vector_round = vector.round
  3. local math_floor = math.floor
  4. local math_min = math.min
  5. function hunger.on_joinplayer(player)
  6. local inv = player:get_inventory()
  7. inv:set_size("hunger", 1)
  8. local name = player:get_player_name()
  9. hunger.players[name] = {}
  10. hunger.players[name].lvl = hunger.read(player)
  11. hunger.players[name].exhaus = 0
  12. local lvl = hunger.players[name].lvl
  13. if lvl > 30 then
  14. lvl = 30
  15. end
  16. minetest.after(0.8, function()
  17. hud.change_item(player, "hunger", {number = lvl, max = HUNGER_MAX})
  18. end)
  19. end
  20. function hunger.on_respawnplayer(player)
  21. hunger.update_hunger(player, 20)
  22. return true
  23. end
  24. function hunger.on_leaveplayer(player, timeout)
  25. local pname = player:get_player_name()
  26. hunger.players[pname] = nil
  27. end
  28. -- read/write
  29. function hunger.read(player)
  30. local inv = player:get_inventory()
  31. if not inv then
  32. return nil
  33. end
  34. local hgp = inv:get_stack("hunger", 1):get_count()
  35. if hgp == 0 then
  36. hgp = 21
  37. inv:set_stack("hunger", 1, ItemStack({name = ":", count = hgp}))
  38. else
  39. hgp = hgp
  40. end
  41. if tonumber(hgp) > HUNGER_MAX + 1 then
  42. hgp = HUNGER_MAX + 1
  43. end
  44. return hgp - 1
  45. end
  46. function hunger.save(player)
  47. local inv = player:get_inventory()
  48. local name = player:get_player_name()
  49. local value = hunger.players[name].lvl
  50. if not inv or not value then
  51. return nil
  52. end
  53. if value > HUNGER_MAX then
  54. value = HUNGER_MAX
  55. end
  56. if value < 0 then
  57. value = 0
  58. end
  59. inv:set_stack("hunger", 1, ItemStack({name = ":", count = value + 1}))
  60. return true
  61. end
  62. function hunger.get_hunger(player)
  63. local pname = player:get_player_name()
  64. local data = hunger.players[pname]
  65. if not data then
  66. return 0
  67. end
  68. return data.lvl
  69. end
  70. function hunger.update_hunger(player, new_lvl)
  71. local name = player:get_player_name() or nil
  72. if not name then
  73. return false
  74. end
  75. local lvl = hunger.players[name].lvl
  76. if new_lvl then
  77. lvl = new_lvl
  78. end
  79. -- Clamp hunger value within range.
  80. if lvl > HUNGER_MAX then lvl = HUNGER_MAX end
  81. if lvl < 0 then lvl = 0 end
  82. hunger.players[name].lvl = lvl
  83. hud.change_item(player, "hunger", {number = lvl, max = HUNGER_MAX})
  84. hunger.save(player)
  85. end
  86. local update_hunger = hunger.update_hunger
  87. local function get_dig_exhaustion(player)
  88. -- Note: this code will skip obtaining the 'hand' tool capabilities whenever
  89. -- the player is wielding a non-tool, but since the hand doesn't have a dig
  90. -- exhaustion modifier anyway, that's OK.
  91. local tool = player:get_wielded_item()
  92. local tdef = tool:get_definition()
  93. local tcap = tdef.tool_capabilities
  94. if tcap and tcap.dig_exhaustion_modifier then
  95. --minetest.log("Exhaustion modifier: " .. tcap.dig_exhaustion_modifier)
  96. return (HUNGER_EXHAUST_DIG * tcap.dig_exhaustion_modifier)
  97. end
  98. return HUNGER_EXHAUST_DIG
  99. end
  100. function hunger.handle_action_event(player, new)
  101. local pname = player:get_player_name()
  102. if not hunger.players[pname] then
  103. return
  104. end
  105. local exhaus = hunger.players[pname].exhaus
  106. if not exhaus then
  107. hunger.players[pname].exhaus = 0
  108. end
  109. -- Player doesn't get exhausted as quickly if fit and in good health.
  110. local max_hp = pova.get_active_modifier(player, "properties").hp_max
  111. if player:get_hp() >= (max_hp * 0.9) then
  112. new = math_floor(new / 2.0)
  113. end
  114. exhaus = exhaus + new
  115. if exhaus > HUNGER_EXHAUST_LVL then
  116. exhaus = 0
  117. local h = tonumber(hunger.players[pname].lvl)
  118. if h > 0 then
  119. -- Player gets hungrier faster when away from their support base.
  120. local loss = -1
  121. local owner = protector.get_node_owner(vector_round(player:get_pos())) or ""
  122. if owner ~= pname then
  123. loss = -2
  124. end
  125. update_hunger(player, h + loss)
  126. end
  127. end
  128. hunger.players[pname].exhaus = exhaus
  129. end
  130. -- Dignode event.
  131. function hunger.on_dignode(pos, oldnode, player)
  132. if not player or not player:is_player() then
  133. return
  134. end
  135. local pname = player:get_player_name()
  136. local pinfo = hunger.players[pname]
  137. -- Enforce rate limit of once per second, as otherwise this would trigger many
  138. -- many times for certain actions, like digging papyrus or scaffolding. This
  139. -- bug was, I think, actually present in the original old code, but harder to
  140. -- notice because the effect was not immediately apparent.
  141. if os.time() > (pinfo.dig_time or 0) then
  142. pinfo.dig_time = os.time()
  143. -- Use drawtype to guess how costly this node should be to dig.
  144. -- Just need a quick approximation.
  145. local cost = -2
  146. local ndef = minetest.registered_nodes[oldnode.name]
  147. if ndef then
  148. local dt = ndef.drawtype or ""
  149. if dt == "normal" then
  150. cost = -3
  151. -- Special: anything that could be an ore should be a little bit harder.
  152. local nn = oldnode.name
  153. if nn:find("ore") or nn:find("_with_") or nn:find("mineral") then
  154. cost = -5
  155. end
  156. elseif dt == "torchlike" or dt == "signlike" or dt == "plantlike"
  157. or dt == "firelike" or dt == "nodebox" or dt == "mesh" then
  158. cost = -1
  159. elseif dt == "airlike" then
  160. cost = 0
  161. end
  162. end
  163. -- The amount of exhaustion added is based the percentage of stamina.
  164. local maxsta = SPRINT_STAMINA
  165. local cursta = sprint.get_stamina(player)
  166. local pccsta = (cursta / maxsta)
  167. local invsta = (1.0 - pccsta)
  168. sprint.add_stamina(player, cost)
  169. local new = get_dig_exhaustion(player) * invsta
  170. hunger.handle_action_event(player, new)
  171. end
  172. end
  173. -- Placenode event.
  174. function hunger.on_placenode(pos, newnode, player, oldnode)
  175. if not player or not player:is_player() then
  176. return
  177. end
  178. local pname = player:get_player_name()
  179. local pinfo = hunger.players[pname]
  180. -- Enforce rate limit of once per second, as otherwise this would trigger many
  181. -- many times for certain actions, like digging papyrus or scaffolding. This
  182. -- bug was, I think, actually present in the original old code, but harder to
  183. -- notice because the effect was not immediately apparent.
  184. if os.time() > (pinfo.place_time or 0) then
  185. pinfo.place_time = os.time()
  186. -- The amount of exhaustion added is based the percentage of stamina.
  187. local maxsta = SPRINT_STAMINA
  188. local cursta = sprint.get_stamina(player)
  189. local pccsta = (cursta / maxsta)
  190. local invsta = (1.0 - pccsta)
  191. sprint.add_stamina(player, -1)
  192. local new = HUNGER_EXHAUST_PLACE * invsta
  193. hunger.handle_action_event(player, new)
  194. end
  195. end
  196. -- Player moving event.
  197. function hunger.on_move(player)
  198. if not player or not player:is_player() then
  199. return
  200. end
  201. local name = player:get_player_name()
  202. local new = HUNGER_EXHAUST_MOVE
  203. local speed = pova.get_active_modifier(player, "physics").speed
  204. -- If player is walking through tough material, they get exhausted faster.
  205. if speed < default.NORM_SPEED then
  206. --minetest.chat_send_all(name .. " hungers faster b/c of slow movement!")
  207. new = HUNGER_EXHAUST_MOVE * 4
  208. end
  209. hunger.handle_action_event(player, new)
  210. end
  211. -- API function to increase a player's hunger. Called from other mods.
  212. function hunger.increase_hunger(player, amount)
  213. local pname = player:get_player_name()
  214. if hunger.players[pname] then
  215. local h = tonumber(hunger.players[pname].lvl)
  216. hunger.update_hunger(player, h - amount)
  217. end
  218. end
  219. function hunger.increase_exhaustion(player, amount)
  220. local pname = player:get_player_name()
  221. if hunger.players[pname] then
  222. if not hunger.players[pname].exhaus then
  223. hunger.players[pname].exhaus = 0
  224. end
  225. -- Player doesn't get exhausted as quickly if fit and in good health.
  226. local max_hp = pova.get_active_modifier(player, "properties").hp_max
  227. if player:get_hp() >= (max_hp * 0.9) then
  228. amount = math_floor(amount / 3.0)
  229. end
  230. hunger.players[pname].exhaus = hunger.players[pname].exhaus + amount
  231. end
  232. end
  233. -- Time based hunger functions
  234. local hunger_timer = 0
  235. local health_timer = 0
  236. local action_timer = 0
  237. function hunger.on_globalstep(dtime)
  238. hunger_timer = hunger_timer + dtime
  239. health_timer = health_timer + dtime
  240. action_timer = action_timer + dtime
  241. if action_timer > HUNGER_MOVE_TICK then
  242. for _,player in ipairs(minetest.get_connected_players()) do
  243. local controls = player:get_player_control()
  244. -- Determine if the player is walking
  245. if (controls.up or controls.down or controls.left or controls.right) then
  246. hunger.on_move(player)
  247. end
  248. end
  249. action_timer = 0
  250. end
  251. -- lower saturation by 1 point after <HUNGER_TICK> second(s)
  252. if hunger_timer > HUNGER_TICK then
  253. for _,player in ipairs(minetest.get_connected_players()) do
  254. local name = player:get_player_name()
  255. local tab = hunger.players[name]
  256. if tab then
  257. local hunger = tab.lvl
  258. if hunger > 0 then
  259. update_hunger(player, hunger - 1)
  260. end
  261. end
  262. end
  263. hunger_timer = 0
  264. end
  265. -- heal or damage player, depending on saturation
  266. if health_timer > HUNGER_HEALTH_TICK then
  267. for _,player in ipairs(minetest.get_connected_players()) do
  268. local name = player:get_player_name()
  269. local tab = hunger.players[name]
  270. if tab then
  271. local air = player:get_breath() or 0
  272. local hp = player:get_hp()
  273. local hp_max = pova.get_active_modifier(player, "properties").hp_max
  274. local healmod = (hp / hp_max)
  275. healmod = healmod * healmod * hunger.get_hpgen_boost(name)
  276. -- Treat these as percentages.
  277. local hp_heal = math.floor(HUNGER_HEAL * hp_max) * healmod
  278. local hp_stav = math.floor(HUNGER_STARVE * hp_max)
  279. --minetest.log('heal: ' .. hp_heal)
  280. -- heal player
  281. if tonumber(tab.lvl) > HUNGER_HEAL_LVL and hp > 0 and air > 0 then
  282. if hp < hp_max then
  283. local new_hp = hp + hp_heal
  284. player:set_hp(new_hp)
  285. end
  286. end
  287. -- or damage player
  288. if tonumber(tab.lvl) < HUNGER_STARVE_LVL then
  289. -- but don't kill player
  290. if hp > hp_stav then
  291. player:set_hp(hp - hp_stav, {reason="hunger"})
  292. end
  293. end
  294. end
  295. end
  296. health_timer = 0
  297. end
  298. end
  299. -- food functions
  300. function hunger.register_food(name, hunger_change, replace_with_item, poisen, heal, sound)
  301. local food = hunger.food
  302. food[name] = {}
  303. food[name].saturation = hunger_change -- hunger points added
  304. food[name].replace = replace_with_item -- what item is given back after eating
  305. food[name].poisen = poisen -- time its poisening
  306. food[name].healing = heal -- amount of HP
  307. food[name].sound = sound -- special sound that is played when eating
  308. end
  309. -- Poison player
  310. local function poisenp(tick, time, time_left, player, gorged)
  311. --[[
  312. {name="player", step=2, min=1, max=3, msg="He died!", poison=true}
  313. --]]
  314. local name = player:get_player_name()
  315. local data = {
  316. name = name,
  317. step = time,
  318. min = 1*100,
  319. max = 1*500,
  320. poison = true,
  321. }
  322. data.msg = "# Server: <" .. rename.gpn(name) .. "> was poisoned!"
  323. if gorged then
  324. local sex = skins.get_gender_strings(name)
  325. data.msg = "# Server: <" .. rename.gpn(name) .. "> gorged " .. sex.himself .. " to death."
  326. -- Overeating will not damage player below 2 hp!
  327. data.hp_min = 2
  328. end
  329. hb4.delayed_harm2(data)
  330. end
  331. -- wrapper for minetest.item_eat (this way we make sure other mods can't break this one)
  332. function hunger.do_item_eat(hp_change, replace_with_item, itemstack, user, pointed_thing)
  333. local old_itemstack = itemstack
  334. itemstack = hunger.eat(hp_change, replace_with_item, itemstack, user, pointed_thing)
  335. -- Don't call item_eat callbacks unless item was actually eaten.
  336. if itemstack and itemstack:get_count() == old_itemstack:get_count() then
  337. return itemstack
  338. end
  339. for _, callback in ipairs(core.registered_on_item_eats) do
  340. local result = callback(hp_change, replace_with_item, itemstack, user, pointed_thing, old_itemstack)
  341. if result then
  342. return result
  343. end
  344. end
  345. return itemstack
  346. end
  347. function hunger.eat(hp_change, replace_with_item, itemstack, user, pointed_thing)
  348. local item = itemstack:get_name()
  349. local def = hunger.food[item]
  350. -- In case food isn't defined at this time.
  351. if not def then
  352. def = {}
  353. if type(hp_change) ~= "number" then
  354. hp_change = 1
  355. core.log("error", "Wrong on_use() definition for item '" .. item .. "'")
  356. end
  357. def.saturation = hp_change * 1.3
  358. def.replace = replace_with_item
  359. end
  360. def = hunger.adjust_from_diet(user:get_player_name(), item, def)
  361. local func = hunger.item_eat(def.saturation, def.replace, def.poisen, def.healing, def.sound)
  362. return func(itemstack, user, pointed_thing)
  363. end
  364. -- Note: due to the way this function works, changes to it require a server
  365. -- restart; reloading the hunger mod WILL NOT work!
  366. function hunger.item_eat2(hunger_change, replace_with_item, poisen, heal, sound)
  367. -- Returns 'on_use' callback closure.
  368. return function(itemstack, user, pointed_thing)
  369. if not user or not user:is_player() then return end
  370. local name = user:get_player_name()
  371. if not hunger.players[name] then
  372. return itemstack
  373. end
  374. local sat = tonumber(hunger.players[name].lvl or 0)
  375. -- If food would put our saturation over the max, then behave as if poisoned instead.
  376. local gorged = false
  377. if sat >= HUNGER_MAX then
  378. gorged = true
  379. heal = nil
  380. if not poisen then
  381. poisen = 3 -- 3 seconds?
  382. end
  383. end
  384. -- Remove food from itemstack only if eating was successful.
  385. local result = itemstack:take_item()
  386. if not result or result:get_count() == 0 then return end
  387. local hp = user:get_hp()
  388. local hp_max = pova.get_active_modifier(user, "properties").hp_max
  389. -- Saturation
  390. if sat < HUNGER_MAX and hunger_change then
  391. sat = sat + hunger_change
  392. hunger.update_hunger(user, sat)
  393. end
  394. -- Healing (or damage!)
  395. if hp <= hp_max and heal then
  396. -- Warning: this might kill the player, causing other code to do
  397. -- who-knows-what to the player inventory. Consequently this must be
  398. -- executed out-of-band.
  399. minetest.after(0, function()
  400. local user = minetest.get_player_by_name(name)
  401. if user then
  402. local hp = user:get_hp()
  403. local newhp = hp + heal
  404. if newhp > hp_max then newhp = hp_max end
  405. if newhp < 0 then newhp = 0 end
  406. user:set_hp(newhp, {reason="hunger"})
  407. end
  408. end)
  409. end
  410. -- Poison
  411. if poisen then
  412. if gorged then
  413. minetest.chat_send_player(name, "# Server: You have eaten too much!")
  414. end
  415. poisenp(1.0, poisen, 0, user, gorged)
  416. end
  417. -- eating sound
  418. if not sound then
  419. sound = "hunger_eat"
  420. end
  421. ambiance.sound_play(sound, user:get_pos(), 0.7, 10)
  422. ambiance.particles_eat_item(user, itemstack:get_name())
  423. if replace_with_item then
  424. if itemstack:is_empty() then
  425. itemstack:add_item(replace_with_item)
  426. else
  427. local inv = user:get_inventory()
  428. if inv:room_for_item("main", {name=replace_with_item}) then
  429. inv:add_item("main", replace_with_item)
  430. else
  431. local pos = user:get_pos()
  432. pos.y = math_floor(pos.y + 0.5)
  433. core.add_item(pos, replace_with_item)
  434. end
  435. end
  436. end
  437. return itemstack
  438. end -- End of function.
  439. end
  440. function hunger.on_dieplayer(player)
  441. local pname = player:get_player_name()
  442. local tab = hunger.players[pname]
  443. if not tab then
  444. return
  445. end
  446. -- Set timers on all active effects to 0.
  447. local timers = {}
  448. -- Collect timers.
  449. for k, v in pairs(tab) do
  450. if k:find("^effect_time_") then
  451. timers[#timers + 1] = k
  452. end
  453. end
  454. -- Zero timers.
  455. for k, v in ipairs(timers) do
  456. tab[v] = 0
  457. end
  458. end