functions.lua 11 KB

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