functions.lua 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. -- read/write
  2. function hunger.read(player)
  3. local inv = player:get_inventory()
  4. if not inv then
  5. return nil
  6. end
  7. local hgp = inv:get_stack("hunger", 1):get_count()
  8. if hgp == 0 then
  9. hgp = 21
  10. inv:set_stack("hunger", 1, ItemStack({name = ":", count = hgp}))
  11. else
  12. hgp = hgp
  13. end
  14. if tonumber(hgp) > HUNGER_MAX + 1 then
  15. hgp = HUNGER_MAX + 1
  16. end
  17. return hgp - 1
  18. end
  19. function hunger.save(player)
  20. local inv = player:get_inventory()
  21. local name = player:get_player_name()
  22. local value = hunger.players[name].lvl
  23. if not inv or not value then
  24. return nil
  25. end
  26. if value > HUNGER_MAX then
  27. value = HUNGER_MAX
  28. end
  29. if value < 0 then
  30. value = 0
  31. end
  32. inv:set_stack("hunger", 1, ItemStack({name = ":", count = value + 1}))
  33. return true
  34. end
  35. function hunger.get_hunger(player)
  36. local pname = player:get_player_name()
  37. local data = hunger.players[pname]
  38. if not data then
  39. return 0
  40. end
  41. return data.lvl
  42. end
  43. function hunger.update_hunger(player, new_lvl)
  44. local name = player:get_player_name() or nil
  45. if not name then
  46. return false
  47. end
  48. local lvl = hunger.players[name].lvl
  49. if new_lvl then
  50. lvl = new_lvl
  51. end
  52. -- Clamp hunger value within range.
  53. if lvl > HUNGER_MAX then lvl = HUNGER_MAX end
  54. if lvl < 0 then lvl = 0 end
  55. hunger.players[name].lvl = lvl
  56. hud.change_item(player, "hunger", {number = lvl, max = HUNGER_MAX})
  57. hunger.save(player)
  58. end
  59. local update_hunger = hunger.update_hunger
  60. -- Function added by MustTest. Hunger only happens outside the city, where players are on their own.
  61. local function distance(player)
  62. local p = player:getpos()
  63. local d = math.sqrt((p.x*p.x)+(p.y*p.y)+(p.z*p.z))
  64. local r = 200 -- Radius of city
  65. local o = d-r
  66. if o < 0 then o = 0 end
  67. return o -- Number of meters player is outside the city radius.
  68. end
  69. local function get_dig_exhaustion(player)
  70. -- Note: this code will skip obtaining the 'hand' tool capabilities whenever
  71. -- the player is wielding a non-tool, but since the hand doesn't have a dig
  72. -- exhaustion modifier anyway, that's OK.
  73. local tool = player:get_wielded_item()
  74. local tdef = tool:get_definition()
  75. local tcap = tdef.tool_capabilities
  76. if tcap and tcap.dig_exhaustion_modifier then
  77. --minetest.log("Exhaustion modifier: " .. tcap.dig_exhaustion_modifier)
  78. return (HUNGER_EXHAUST_DIG * tcap.dig_exhaustion_modifier)
  79. end
  80. return HUNGER_EXHAUST_DIG
  81. end
  82. -- player-action based hunger changes
  83. function hunger.handle_node_actions(pos, oldnode, player, ext)
  84. if not player or not player:is_player() then
  85. return
  86. end
  87. local name = player:get_player_name()
  88. if not hunger.players[name] then
  89. return
  90. end
  91. local exhaus = hunger.players[name].exhaus
  92. if not exhaus then
  93. hunger.players[name].exhaus = 0
  94. --return
  95. end
  96. local new = HUNGER_EXHAUST_PLACE
  97. -- placenode event
  98. if not ext then
  99. new = get_dig_exhaustion(player)
  100. end
  101. -- assume its send by action_timer(globalstep)
  102. if not pos and not oldnode then
  103. new = HUNGER_EXHAUST_MOVE
  104. -- If player is walking through tough material, they get exhausted faster.
  105. if sprint.get_speed_multiplier(name) < default.NORM_SPEED then
  106. --minetest.chat_send_all(name .. " hungers faster b/c of slow movement!")
  107. new = HUNGER_EXHAUST_MOVE * 4
  108. end
  109. end
  110. -- Player doesn't get exhausted as quickly if fit and in good health.
  111. if player:get_hp() >= 18 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[name].lvl)
  118. if h > 0 and distance(player) > 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 ~= name then
  123. loss = -2
  124. end
  125. update_hunger(player, h + loss)
  126. end
  127. end
  128. hunger.players[name].exhaus = exhaus
  129. end
  130. -- API function to increase a player's hunger. Called from other mods.
  131. function hunger.increase_hunger(player, amount)
  132. local pname = player:get_player_name()
  133. if hunger.players[pname] then
  134. local h = tonumber(hunger.players[pname].lvl)
  135. hunger.update_hunger(player, h - amount)
  136. end
  137. end
  138. function hunger.increase_exhaustion(player, amount)
  139. local pname = player:get_player_name()
  140. if hunger.players[pname] then
  141. if not hunger.players[pname].exhaus then
  142. hunger.players[pname].exhaus = 0
  143. end
  144. -- Player doesn't get exhausted as quickly if fit and in good health.
  145. if player:get_hp() >= 18 then
  146. amount = math.floor(amount / 3.0)
  147. end
  148. hunger.players[pname].exhaus = hunger.players[pname].exhaus + amount
  149. end
  150. end
  151. -- Time based hunger functions
  152. local hunger_timer = 0
  153. local health_timer = 0
  154. local action_timer = 0
  155. local function hunger_globaltimer(dtime)
  156. hunger_timer = hunger_timer + dtime
  157. health_timer = health_timer + dtime
  158. action_timer = action_timer + dtime
  159. if action_timer > HUNGER_MOVE_TICK then
  160. for _,player in ipairs(minetest.get_connected_players()) do
  161. local controls = player:get_player_control()
  162. -- Determine if the player is walking
  163. if (controls.up or controls.down or controls.left or controls.right) and distance(player) > 0 then
  164. hunger.handle_node_actions(nil, nil, player)
  165. end
  166. end
  167. action_timer = 0
  168. end
  169. -- lower saturation by 1 point after <HUNGER_TICK> second(s)
  170. if hunger_timer > HUNGER_TICK then
  171. for _,player in ipairs(minetest.get_connected_players()) do
  172. local name = player:get_player_name()
  173. local tab = hunger.players[name]
  174. if tab then
  175. local hunger = tab.lvl
  176. if hunger > 0 and distance(player) > 0 then
  177. update_hunger(player, hunger - 1)
  178. end
  179. end
  180. end
  181. hunger_timer = 0
  182. end
  183. -- heal or damage player, depending on saturation
  184. if health_timer > HUNGER_HEALTH_TICK then
  185. for _,player in ipairs(minetest.get_connected_players()) do
  186. local name = player:get_player_name()
  187. local tab = hunger.players[name]
  188. if tab then
  189. local air = player:get_breath() or 0
  190. local hp = player:get_hp()
  191. local hp_max = player:get_properties().hp_max
  192. -- heal player by 1 hp if not dead and saturation is > 15 (of 30) player is not drowning
  193. if tonumber(tab.lvl) > HUNGER_HEAL_LVL and hp > 0 and air > 0 then
  194. -- If player's health is >= 2/3rds, they may passively heal completely.
  195. if hp >= (hp_max/3)*2 then
  196. local new_hp = hp + HUNGER_HEAL
  197. player:set_hp(new_hp)
  198. else
  199. -- Otherwise (if player's heath < 2/3rds), then player's passive healing is capped to 1/3 of full health.
  200. local heal_cap = math.floor(hp_max / 3)
  201. local new_hp = math.min((hp + HUNGER_HEAL), heal_cap)
  202. -- But don't reduce player's health, only increase it.
  203. if new_hp > hp then
  204. player:set_hp(new_hp)
  205. end
  206. end
  207. end
  208. -- or damage player by 1 hp if saturation is < 2 (of 30)
  209. if tonumber(tab.lvl) < HUNGER_STARVE_LVL then
  210. if player:get_hp() > 2 then -- Hunger doesn't kill players. Mobs do. By MustTest.
  211. player:set_hp(hp - HUNGER_STARVE)
  212. end
  213. end
  214. end
  215. end
  216. health_timer = 0
  217. end
  218. end
  219. if minetest.setting_getbool("enable_damage") then
  220. minetest.register_globalstep(hunger_globaltimer)
  221. end
  222. -- food functions
  223. local food = hunger.food
  224. function hunger.register_food(name, hunger_change, replace_with_item, poisen, heal, sound)
  225. food[name] = {}
  226. food[name].saturation = hunger_change -- hunger points added
  227. food[name].replace = replace_with_item -- what item is given back after eating
  228. food[name].poisen = poisen -- time its poisening
  229. food[name].healing = heal -- amount of HP
  230. food[name].sound = sound -- special sound that is played when eating
  231. end
  232. -- Poison player
  233. local function poisenp(tick, time, time_left, player, gorged)
  234. --[[
  235. {name="player", step=2, min=1, max=3, msg="He died!", poison=true}
  236. --]]
  237. local name = player:get_player_name()
  238. local data = {
  239. name = name,
  240. step = time,
  241. min = 1,
  242. max = 1,
  243. poison = true,
  244. }
  245. data.msg = "# Server: <" .. rename.gpn(name) .. "> was poisoned!"
  246. if gorged then
  247. local sex = skins.get_gender_strings(name)
  248. data.msg = "# Server: <" .. rename.gpn(name) .. "> gorged " .. sex.himself .. " to death."
  249. -- Overeating will not damage player below 2 hp!
  250. data.hp_min = 2
  251. end
  252. hb4.delayed_harm2(data)
  253. end
  254. -- wrapper for minetest.item_eat (this way we make sure other mods can't break this one)
  255. local org_eat = core.do_item_eat
  256. core.do_item_eat = function(hp_change, replace_with_item, itemstack, user, pointed_thing)
  257. local old_itemstack = itemstack
  258. itemstack = hunger.eat(hp_change, replace_with_item, itemstack, user, pointed_thing)
  259. -- Don't call item_eat callbacks unless item was actually eaten.
  260. if itemstack and itemstack:get_count() == old_itemstack:get_count() then
  261. return itemstack
  262. end
  263. for _, callback in pairs(core.registered_on_item_eats) do
  264. local result = callback(hp_change, replace_with_item, itemstack, user, pointed_thing, old_itemstack)
  265. if result then
  266. return result
  267. end
  268. end
  269. return itemstack
  270. end
  271. function hunger.eat(hp_change, replace_with_item, itemstack, user, pointed_thing)
  272. local item = itemstack:get_name()
  273. local def = food[item]
  274. if not def then
  275. def = {}
  276. if type(hp_change) ~= "number" then
  277. hp_change = 1
  278. core.log("error", "Wrong on_use() definition for item '" .. item .. "'")
  279. end
  280. def.saturation = hp_change * 1.3
  281. def.replace = replace_with_item
  282. end
  283. local func = hunger.item_eat(def.saturation, def.replace, def.poisen, def.healing, def.sound)
  284. return func(itemstack, user, pointed_thing)
  285. end
  286. function hunger.item_eat(hunger_change, replace_with_item, poisen, heal, sound)
  287. return function(itemstack, user, pointed_thing)
  288. if not user or not user:is_player() then return end
  289. local name = user:get_player_name()
  290. if not hunger.players[name] then
  291. return itemstack
  292. end
  293. local sat = tonumber(hunger.players[name].lvl or 0)
  294. -- If food would put our saturation over the max, then behave as if poisoned instead.
  295. local gorged = false
  296. if sat >= HUNGER_MAX then
  297. gorged = true
  298. heal = nil
  299. poisen = 3 -- 3 seconds?
  300. end
  301. -- Remove food from itemstack only if eating was successful.
  302. local result = itemstack:take_item()
  303. if not result or result:get_count() == 0 then return end
  304. local hp = user:get_hp()
  305. local hp_max = user:get_properties().hp_max
  306. -- Saturation
  307. if sat < HUNGER_MAX and hunger_change then
  308. sat = sat + hunger_change
  309. hunger.update_hunger(user, sat)
  310. end
  311. -- Healing
  312. if hp < hp_max and heal then
  313. hp = hp + heal
  314. if hp > hp_max then
  315. hp = hp_max
  316. end
  317. user:set_hp(hp)
  318. end
  319. -- Poison
  320. if poisen then
  321. if gorged then
  322. minetest.chat_send_player(name, "# Server: You have eaten too much!")
  323. end
  324. poisenp(1.0, poisen, 0, user, gorged)
  325. end
  326. -- eating sound
  327. if not sound then
  328. sound = "hunger_eat"
  329. end
  330. --minetest.sound_play(sound, {to_player = name, gain = 0.7})
  331. ambiance.sound_play(sound, user:getpos(), 0.7, 10)
  332. ambiance.particles_eat_item(user, itemstack:get_name())
  333. if replace_with_item then
  334. if itemstack:is_empty() then
  335. itemstack:add_item(replace_with_item)
  336. else
  337. local inv = user:get_inventory()
  338. if inv:room_for_item("main", {name=replace_with_item}) then
  339. inv:add_item("main", replace_with_item)
  340. else
  341. local pos = user:getpos()
  342. pos.y = math.floor(pos.y + 0.5)
  343. core.add_item(pos, replace_with_item)
  344. end
  345. end
  346. end
  347. return itemstack
  348. end -- End of function.
  349. end