init.lua 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. stamina = {}
  2. local stamina_players = {}
  3. STAMINA_TICK = 800 -- time in seconds after that 1 stamina point is taken
  4. STAMINA_TICK_MIN = 4 -- stamina ticks won't reduce stamina below this level
  5. STAMINA_HEALTH_TICK = 4 -- time in seconds after player gets healed/damaged
  6. STAMINA_MOVE_TICK = 0.5 -- time in seconds after the movement is checked
  7. STAMINA_EXHAUST_DIG = 3 -- exhaustion increased this value after digged node
  8. STAMINA_EXHAUST_PLACE = 1 -- .. after digging node
  9. STAMINA_EXHAUST_MOVE = 1.5 -- .. if player movement detected
  10. STAMINA_EXHAUST_JUMP = 5 -- .. if jumping
  11. STAMINA_EXHAUST_CRAFT = 20 -- .. if player crafts
  12. STAMINA_EXHAUST_PUNCH = 40 -- .. if player punches another player
  13. STAMINA_EXHAUST_LVL = 160 -- at what exhaustion player saturation gets lowered
  14. STAMINA_HEAL = 1 -- number of HP player gets healed after STAMINA_HEALTH_TICK
  15. STAMINA_HEAL_LVL = 5 -- lower level of saturation needed to get healed
  16. STAMINA_STARVE = 1 -- number of HP player gets damaged by stamina after STAMINA_HEALTH_TICK
  17. STAMINA_STARVE_LVL = 3 -- level of staturation that causes starving
  18. STAMINA_VISUAL_MAX = 20 -- hud bar extends only to 20
  19. SPRINT_SPEED = 0.8 -- how much faster player can run if satiated
  20. SPRINT_JUMP = 0.1 -- how much higher player can jump if satiated
  21. SPRINT_DRAIN = 0.15 -- how fast to drain satation while sprinting (0-1)
  22. local function stamina_read(player)
  23. local meta = player:get_meta()
  24. if meta:get_int("stamina:stamina") == 0 then
  25. meta:set_int("stamina:stamina", 21) --Offset by 1
  26. end
  27. return meta:get_int("stamina:stamina") - 1
  28. end
  29. local function stamina_save(player)
  30. local meta = player:get_meta()
  31. if not meta then
  32. return nil
  33. end
  34. local name = player:get_player_name()
  35. local level = stamina_players[name].level
  36. level = math.max(level, 0)
  37. meta:set_int("stamina:stamina", level + 1) --Offset by 1
  38. return true
  39. end
  40. local function stamina_update(player, level)
  41. local name = player:get_player_name()
  42. if not name then
  43. return false
  44. end
  45. local old = stamina_players[name].level
  46. if level == old then
  47. return
  48. end
  49. stamina_players[name].level = level
  50. player:hud_change(stamina_players[name].hud_id, "number", math.min(STAMINA_VISUAL_MAX, level))
  51. stamina_save(player)
  52. end
  53. -- global function for mods to amend stamina level
  54. stamina.change = function(player, change)
  55. local name = player:get_player_name()
  56. if not name or not change or change == 0 then
  57. return false
  58. end
  59. local level = stamina_players[name].level + change
  60. if level < 0 then level = 0 end
  61. if level > STAMINA_VISUAL_MAX then level = STAMINA_VISUAL_MAX end
  62. stamina_update(player, level)
  63. return true
  64. end
  65. local function exhaust_player(player, v)
  66. if not player or not player:is_player() then
  67. return
  68. end
  69. local name = player:get_player_name()
  70. if not name then
  71. return
  72. end
  73. local s = stamina_players[name]
  74. if not s then
  75. return
  76. end
  77. local e = s.exhaust
  78. if not e then
  79. s.exhaust = 0
  80. end
  81. e = e + v
  82. if e > STAMINA_EXHAUST_LVL then
  83. e = 0
  84. local h = tonumber(stamina_players[name].level)
  85. if h > 0 then
  86. stamina_update(player, h - 1)
  87. end
  88. end
  89. s.exhaust = e
  90. end
  91. -- Sprint settings and function
  92. local enable_sprint = minetest.setting_getbool("sprint") ~= false
  93. local armor_mod = minetest.get_modpath("lottarmor")
  94. function set_sprinting(name, sprinting)
  95. if stamina_players[name] then
  96. local player = minetest.get_player_by_name(name)
  97. local meta = player:get_meta()
  98. local def = {}
  99. if armor_mod and armor and armor.def then
  100. def = armor.def[name] -- get player physics from armor
  101. end
  102. local potion = lottpotion.players[name]
  103. def.speed = potion.speed or 1
  104. def.jump = potion.jump or 1
  105. def.gravity = potion.gravity or 1
  106. if sprinting == true then
  107. if meta:get_int("stamina:sprinting") == 0 then
  108. meta:set_int("stamina:sprinting", 1)
  109. lottpotion.addPrefs(name, 0.8, 0, 0)
  110. lottpotion.refresh(name)
  111. end
  112. elseif sprinting == false then
  113. if meta:get_int("stamina:sprinting") == 1 then
  114. meta:set_int("stamina:sprinting", 0)
  115. lottpotion.addPrefs(name, -0.8, 0, 0)
  116. lottpotion.refresh(name)
  117. end
  118. end
  119. return true
  120. end
  121. return false
  122. end
  123. -- Time based stamina functions
  124. local stamina_timer = 0
  125. local health_timer = 0
  126. local action_timer = 0
  127. local function stamina_globaltimer(dtime)
  128. stamina_timer = stamina_timer + dtime
  129. health_timer = health_timer + dtime
  130. action_timer = action_timer + dtime
  131. if action_timer > STAMINA_MOVE_TICK then
  132. for _,player in ipairs(minetest.get_connected_players()) do
  133. local controls = player:get_player_control()
  134. -- Determine if the player is walking
  135. if controls.jump then
  136. exhaust_player(player, STAMINA_EXHAUST_JUMP)
  137. elseif controls.up or controls.down or controls.left or controls.right then
  138. exhaust_player(player, STAMINA_EXHAUST_MOVE)
  139. end
  140. if enable_sprint then
  141. local name = player:get_player_name()
  142. -- check if player can sprint (stamina must be over 6 points)
  143. if controls.aux1 and controls.up
  144. and not minetest.check_player_privs(player, {fast = true})
  145. and stamina_players[name].level > 8 then
  146. local sprint = set_sprinting(name, true)
  147. -- Lower the player's stamina when sprinting
  148. local level = tonumber(stamina_players[name].level)
  149. if sprint == true then
  150. stamina_update(player, level - (SPRINT_DRAIN * STAMINA_MOVE_TICK))
  151. end
  152. else
  153. set_sprinting(name, false)
  154. end
  155. end
  156. end
  157. action_timer = 0
  158. end
  159. -- lower saturation by 1 point after STAMINA_TICK second(s)
  160. if stamina_timer > STAMINA_TICK then
  161. for _,player in ipairs(minetest.get_connected_players()) do
  162. local name = player:get_player_name()
  163. local tab = stamina_players[name]
  164. if tab then
  165. local h = tab.level
  166. if h > STAMINA_TICK_MIN then
  167. stamina_update(player, h - 1)
  168. end
  169. end
  170. end
  171. stamina_timer = 0
  172. end
  173. -- heal or damage player, depending on saturation
  174. if health_timer > STAMINA_HEALTH_TICK then
  175. for _,player in ipairs(minetest.get_connected_players()) do
  176. local name = player:get_player_name()
  177. local tab = stamina_players[name]
  178. if tab then
  179. local air = player:get_breath() or 0
  180. local hp = player:get_hp()
  181. -- don't heal if drowning or dead
  182. -- TODO: don't heal if poisoned?
  183. local h = tonumber(tab.level)
  184. if h >= STAMINA_HEAL_LVL and h >= hp and hp > 0 and air > 0
  185. and tab.poison == false then
  186. player:set_hp(hp + STAMINA_HEAL)
  187. stamina_update(player, h - 1)
  188. end
  189. -- or damage player by 1 hp if saturation is < 2 (of 30)
  190. if tonumber(tab.level) < STAMINA_STARVE_LVL then
  191. player:set_hp(hp - STAMINA_STARVE)
  192. end
  193. end
  194. end
  195. health_timer = 0
  196. end
  197. end
  198. local function poison_player(ticks, time, elapsed, user)
  199. local name = user:get_player_name()
  200. if elapsed <= ticks then
  201. minetest.after(time, poison_player, ticks, time, elapsed + 1, user)
  202. stamina_players[name].poison = true
  203. else
  204. user:hud_change(stamina_players[name].hud_id, "text", "stamina_hud_fg.png")
  205. stamina_players[name].poison = false
  206. end
  207. local hp = user:get_hp() -1 or 0
  208. if hp > 0 then
  209. user:set_hp(hp)
  210. end
  211. end
  212. -- override core.do_item_eat() so we can redirect hp_change to stamina
  213. core.do_item_eat = function(hp_change, replace_with_item, itemstack, user, pointed_thing)
  214. local old_itemstack = itemstack
  215. if not stamina_players[user:get_player_name()] then
  216. return
  217. end
  218. local old_level = stamina_players[user:get_player_name()].level
  219. itemstack = stamina.eat(hp_change, replace_with_item, itemstack, user, pointed_thing)
  220. local level = stamina_players[user:get_player_name()].level
  221. for _, callback in pairs(core.registered_on_item_eats) do
  222. local result = callback(hp_change, replace_with_item, itemstack, user,
  223. pointed_thing, old_itemstack, old_level, level)
  224. if result then
  225. return result
  226. end
  227. end
  228. return itemstack
  229. end
  230. -- not local since it's called from within core context
  231. function stamina.eat(hp_change, replace_with_item, itemstack, user, pointed_thing)
  232. if not itemstack then
  233. return itemstack
  234. end
  235. if not user then
  236. return itemstack
  237. end
  238. local name = user:get_player_name()
  239. if not stamina_players[name] then
  240. return itemstack
  241. end
  242. local level = tonumber(stamina_players[name].level or 0)
  243. if level >= STAMINA_VISUAL_MAX then
  244. return itemstack
  245. end
  246. if hp_change > 0 then
  247. level = level + hp_change
  248. stamina_update(user, level)
  249. else
  250. -- assume hp_change < 0.
  251. user:hud_change(stamina_players[name].hud_id, "text", "stamina_hud_poison.png")
  252. poison_player(2.0, -hp_change, 0, user)
  253. end
  254. minetest.sound_play("stamina_eat", {to_player = name, gain = 0.7})
  255. itemstack:take_item()
  256. if replace_with_item then
  257. if itemstack:is_empty() then
  258. itemstack:add_item(replace_with_item)
  259. else
  260. local inv = user:get_inventory()
  261. if inv:room_for_item("main", {name=replace_with_item}) then
  262. inv:add_item("main", replace_with_item)
  263. else
  264. local pos = user:getpos()
  265. pos.y = math.floor(pos.y + 0.5)
  266. core.add_item(pos, replace_with_item)
  267. end
  268. end
  269. end
  270. return itemstack
  271. end
  272. -- stamina is disabled if damage is disabled
  273. if minetest.setting_getbool("enable_damage") and minetest.is_yes(minetest.setting_get("enable_stamina") or "1") then
  274. minetest.register_on_joinplayer(function(player)
  275. local name = player:get_player_name()
  276. stamina_players[name] = {}
  277. stamina_players[name].level = stamina_read(player)
  278. stamina_players[name].exhaust = 0
  279. stamina_players[name].poison = false
  280. local level = math.min(stamina_players[name].level, STAMINA_VISUAL_MAX)
  281. local id = player:hud_add({
  282. name = "stamina",
  283. hud_elem_type = "statbar",
  284. position = {x = 0.5, y = 1},
  285. size = {x = 24, y = 24},
  286. text = "stamina_hud_fg.png",
  287. number = level,
  288. alignment = {x = -1, y = -1},
  289. offset = {x = -266, y = -110},
  290. max = 0,
  291. })
  292. stamina_players[name].hud_id = id
  293. end)
  294. minetest.register_globalstep(stamina_globaltimer)
  295. minetest.register_on_placenode(function(pos, oldnode, player, ext)
  296. exhaust_player(player, STAMINA_EXHAUST_PLACE)
  297. end)
  298. minetest.register_on_dignode(function(pos, oldnode, player, ext)
  299. exhaust_player(player, STAMINA_EXHAUST_DIG)
  300. end)
  301. minetest.register_on_craft(function(itemstack, player, old_craft_grid, craft_inv)
  302. exhaust_player(player, STAMINA_EXHAUST_CRAFT)
  303. end)
  304. minetest.register_on_punchplayer(function(player, hitter, time_from_last_punch, tool_capabilities, dir, damage)
  305. exhaust_player(hitter, STAMINA_EXHAUST_PUNCH)
  306. end)
  307. minetest.register_on_respawnplayer(function(player)
  308. stamina_update(player, STAMINA_VISUAL_MAX)
  309. end)
  310. end