functions.lua 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. --[[
  2. Core functions for Thirsty.
  3. See init.lua for license.
  4. ]]
  5. local PPA = thirsty.persistent_player_attributes
  6. PPA.register({
  7. name = 'thirsty_hydro',
  8. min = 0,
  9. max = 50,
  10. default = 20,
  11. })
  12. function thirsty.on_joinplayer(player)
  13. local name = player:get_player_name()
  14. -- default entry for new players
  15. if not thirsty.players[name] then
  16. local pos = player:get_pos()
  17. thirsty.players[name] = {
  18. last_pos = math.floor(pos.x) .. ':' .. math.floor(pos.z),
  19. time_in_pos = 0.0,
  20. pending_dmg = 0.0,
  21. thirst_factor = 1.0,
  22. }
  23. end
  24. thirsty.hud_init(player)
  25. end
  26. function thirsty.on_dieplayer(player)
  27. local name = player:get_player_name()
  28. local pl = thirsty.players[name]
  29. -- reset after death
  30. PPA.set_value(player, 'thirsty_hydro', 20)
  31. pl.pending_dmg = 0.0
  32. pl.thirst_factor = 1.0
  33. end
  34. --[[
  35. Getters, setters and such
  36. ]]
  37. function thirsty.drink(player, value, max)
  38. -- if max is not specified, assume 20
  39. if not max then
  40. max = 20
  41. end
  42. local hydro = PPA.get_value(player, 'thirsty_hydro')
  43. -- test whether we're not *above* max;
  44. -- this function should not remove any overhydration
  45. if hydro < max then
  46. hydro = math.min(hydro + value, max)
  47. --print("Drinking by "..value.." to "..hydro)
  48. PPA.set_value(player, 'thirsty_hydro', hydro)
  49. return true
  50. end
  51. return false
  52. end
  53. function thirsty.get_hydro(player)
  54. return PPA.get_value(player, 'thirsty_hydro')
  55. end
  56. function thirsty.set_thirst_factor(player, factor)
  57. local name = player:get_player_name()
  58. local pl = thirsty.players[name]
  59. pl.thirst_factor = factor
  60. end
  61. function thirsty.get_thirst_factor(player)
  62. local name = player:get_player_name()
  63. local pl = thirsty.players[name]
  64. return pl.thirst_factor
  65. end
  66. --[[
  67. Main Loop (Tier 0)
  68. ]]
  69. function thirsty.main_loop(dtime)
  70. -- get thirsty
  71. thirsty.time_next_tick = thirsty.time_next_tick - dtime
  72. while thirsty.time_next_tick < 0.0 do
  73. -- time for thirst
  74. thirsty.time_next_tick = thirsty.time_next_tick + thirsty.config.tick_time
  75. for _,player in ipairs(minetest.get_connected_players()) do
  76. if player:get_hp() <= 0 then
  77. -- dead players don't get thirsty, or full for that matter :-P
  78. break
  79. end
  80. local name = player:get_player_name()
  81. local pos = player:get_pos()
  82. local pl = thirsty.players[name]
  83. local hydro = PPA.get_value(player, 'thirsty_hydro')
  84. -- how long have we been standing "here"?
  85. -- (the node coordinates in X and Z should be enough)
  86. local pos_hash = math.floor(pos.x) .. ':' .. math.floor(pos.z)
  87. if pl.last_pos == pos_hash then
  88. pl.time_in_pos = pl.time_in_pos + thirsty.config.tick_time
  89. else
  90. -- you moved!
  91. pl.last_pos = pos_hash
  92. pl.time_in_pos = 0.0
  93. end
  94. local pl_standing = pl.time_in_pos > thirsty.config.stand_still_for_drink
  95. local pl_afk = pl.time_in_pos > thirsty.config.stand_still_for_afk
  96. --print("Standing: " .. (pl_standing and 'true' or 'false' ) .. ", AFK: " .. (pl_afk and 'true' or 'false'))
  97. pos.y = pos.y + 0.1
  98. local node = minetest.get_node(pos)
  99. local drink_per_second = thirsty.config.regen_from_node[node.name] or 0
  100. -- fountaining (uses pos, slight changes ok)
  101. for k, fountain in pairs(thirsty.fountains) do
  102. local dx = fountain.pos.x - pos.x
  103. local dy = fountain.pos.y - pos.y
  104. local dz = fountain.pos.z - pos.z
  105. local dist2 = dx * dx + dy * dy + dz * dz
  106. local fdist = fountain.level * thirsty.config.fountain_distance_per_level -- max 100 nodes radius
  107. --print (string.format("Distance from %s (%d): %f out of %f", k, fountain.level, math.sqrt(dist2), fdist ))
  108. if dist2 < fdist * fdist then
  109. -- in range, drink as if standing (still) in water
  110. drink_per_second = math.max(thirsty.config.regen_from_fountain or 0, drink_per_second)
  111. pl_standing = true
  112. break -- no need to check the other fountains
  113. end
  114. end
  115. -- amulets
  116. -- TODO: I *guess* we need to optimize this, but I haven't
  117. -- measured it yet. No premature optimizations!
  118. local pl_inv = player:get_inventory()
  119. local extractor_max = 0.0
  120. local injector_max = 0.0
  121. local container_not_full = nil
  122. local container_not_empty = nil
  123. local inv_main = player:get_inventory():get_list('main')
  124. for i, itemstack in ipairs(inv_main) do
  125. local name = itemstack:get_name()
  126. local injector_this = thirsty.config.injection_for_item[name]
  127. if injector_this and injector_this > injector_max then
  128. injector_max = injector_this
  129. end
  130. local extractor_this = thirsty.config.extraction_for_item[name]
  131. if extractor_this and extractor_this > extractor_max then
  132. extractor_max = extractor_this
  133. end
  134. if thirsty.config.container_capacity[name] then
  135. local wear = itemstack:get_wear()
  136. -- can be both!
  137. if wear == 0 or wear > 1 then
  138. container_not_full = { i, itemstack }
  139. end
  140. if wear > 0 and wear < 65534 then
  141. container_not_empty = { i, itemstack }
  142. end
  143. end
  144. end
  145. if extractor_max > 0.0 and container_not_full then
  146. local i = container_not_full[1]
  147. local itemstack = container_not_full[2]
  148. local capacity = thirsty.config.container_capacity[itemstack:get_name()]
  149. local wear = itemstack:get_wear()
  150. if wear == 0 then wear = 65535.0 end
  151. local drink = extractor_max * thirsty.config.tick_time
  152. local drinkwear = drink / capacity * 65535.0
  153. wear = wear - drinkwear
  154. if wear < 1 then wear = 1 end
  155. itemstack:set_wear(wear)
  156. player:get_inventory():set_stack("main", i, itemstack)
  157. end
  158. if injector_max > 0.0 and container_not_empty then
  159. local i = container_not_empty[1]
  160. local itemstack = container_not_empty[2]
  161. local capacity = thirsty.config.container_capacity[itemstack:get_name()]
  162. local wear = itemstack:get_wear()
  163. if wear == 0 then wear = 65535.0 end
  164. local drink = injector_max * thirsty.config.tick_time
  165. local drink_missing = 20 - hydro
  166. drink = math.max(math.min(drink, drink_missing), 0)
  167. local drinkwear = drink / capacity * 65535.0
  168. wear = wear + drinkwear
  169. if wear > 65534 then wear = 65534 end
  170. itemstack:set_wear(wear)
  171. thirsty.drink(player, drink, 20)
  172. hydro = PPA.get_value(player, 'thirsty_hydro')
  173. player:get_inventory():set_stack("main", i, itemstack)
  174. end
  175. if drink_per_second > 0 and pl_standing then
  176. -- Drinking from the ground won't give you more than max
  177. thirsty.drink(player, drink_per_second * thirsty.config.tick_time, 20)
  178. --print("Raising hydration by "..(drink_per_second*thirsty.config.tick_time).." to "..PPA.get_value(player, 'thirsty_hydro'))
  179. else
  180. if not pl_afk then
  181. -- only get thirsty if not AFK
  182. local amount = thirsty.config.thirst_per_second * thirsty.config.tick_time * pl.thirst_factor
  183. PPA.set_value(player, 'thirsty_hydro', hydro - amount)
  184. hydro = PPA.get_value(player, 'thirsty_hydro')
  185. --print("Lowering hydration by "..amount.." to "..hydro)
  186. end
  187. end
  188. -- should we only update the hud on an actual change?
  189. thirsty.hud_update(player, hydro)
  190. -- damage, if enabled
  191. if minetest.settings:get_bool("enable_damage") then
  192. -- maybe not the best way to do this, but it does mean
  193. -- we can do anything with one tick loop
  194. if hydro <= 0.0 and not pl_afk then
  195. pl.pending_dmg = pl.pending_dmg + thirsty.config.damage_per_second * thirsty.config.tick_time
  196. --print("Pending damage at " .. pl.pending_dmg)
  197. if pl.pending_dmg > 1.0 then
  198. local dmg = math.floor(pl.pending_dmg)
  199. pl.pending_dmg = pl.pending_dmg - dmg
  200. player:set_hp( player:get_hp() - dmg )
  201. end
  202. else
  203. -- forget any pending damage when not thirsty
  204. pl.pending_dmg = 0.0
  205. end
  206. end
  207. end -- for players
  208. -- check fountains for expiration
  209. for k, fountain in pairs(thirsty.fountains) do
  210. fountain.time_until_check = fountain.time_until_check - thirsty.config.tick_time
  211. if fountain.time_until_check <= 0 then
  212. -- remove fountain, the abm will set it again
  213. --print("Removing fountain at " .. k)
  214. thirsty.fountains[k] = nil
  215. end
  216. end
  217. end
  218. end
  219. --[[
  220. General handler
  221. Most tools, nodes and craftitems use the same code, so here it is:
  222. ]]
  223. function thirsty.drink_handler(player, itemstack, node)
  224. local pl = thirsty.players[player:get_player_name()]
  225. local hydro = PPA.get_value(player, 'thirsty_hydro')
  226. local old_hydro = hydro
  227. -- selectors, always true, to make the following code easier
  228. local item_name = itemstack and itemstack:get_name() or ':'
  229. local node_name = node and node.name or ':'
  230. if thirsty.config.node_drinkable[node_name] then
  231. -- we found something to drink!
  232. local cont_level = thirsty.config.drink_from_container[item_name] or 0
  233. local node_level = thirsty.config.drink_from_node[node_name] or 0
  234. -- drink until level
  235. local level = math.max(cont_level, node_level)
  236. --print("Drinking to level " .. level)
  237. thirsty.drink(player, level, level)
  238. -- fill container, if applicable
  239. if thirsty.config.container_capacity[item_name] then
  240. --print("Filling a " .. item_name .. " to " .. thirsty.config.container_capacity[item_name])
  241. itemstack:set_wear(1) -- "looks full"
  242. end
  243. elseif thirsty.config.container_capacity[item_name] then
  244. -- drinking from a container
  245. if itemstack:get_wear() ~= 0 then
  246. local capacity = thirsty.config.container_capacity[item_name]
  247. local hydro_missing = 20 - hydro;
  248. if hydro_missing > 0 then
  249. local wear_missing = hydro_missing / capacity * 65535.0;
  250. local wear = itemstack:get_wear()
  251. local new_wear = math.ceil(math.max(wear + wear_missing, 1))
  252. if (new_wear > 65534) then
  253. wear_missing = 65534 - wear
  254. new_wear = 65534
  255. end
  256. itemstack:set_wear(new_wear)
  257. if wear_missing > 0 then -- rounding glitches?
  258. thirsty.drink(player, wear_missing * capacity / 65535.0, 20)
  259. hydro = PPA.get_value(player, 'thirsty_hydro')
  260. end
  261. end
  262. end
  263. end
  264. -- update HUD if value changed
  265. if hydro ~= old_hydro then
  266. thirsty.hud_update(player, hydro)
  267. end
  268. end
  269. --[[
  270. Adapters for drink_handler to on_use and on_rightclick slots.
  271. These close over the next handler to call in a chain, if desired.
  272. ]]
  273. function thirsty.on_use( old_on_use )
  274. return function(itemstack, player, pointed_thing)
  275. local node = nil
  276. if pointed_thing and pointed_thing.type == 'node' then
  277. node = minetest.get_node(pointed_thing.under)
  278. end
  279. thirsty.drink_handler(player, itemstack, node)
  280. -- call original on_use, if provided
  281. if old_on_use ~= nil then
  282. return old_on_use(itemstack, player, pointed_thing)
  283. else
  284. return itemstack
  285. end
  286. end
  287. end
  288. function thirsty.on_rightclick( old_on_rightclick )
  289. return function(pos, node, player, itemstack, pointed_thing)
  290. thirsty.drink_handler(player, itemstack, node)
  291. -- call original on_rightclick, if provided
  292. if old_on_rightclick ~= nil then
  293. return old_on_rightclick(pos, node, player, itemstack, pointed_thing)
  294. else
  295. return itemstack
  296. end
  297. end
  298. end
  299. --[[
  300. Adapter to add "drink_handler" to any item (node, tool, craftitem).
  301. ]]
  302. function thirsty.augment_item_for_drinking( itemname, level )
  303. local new_definition = {}
  304. -- we need to be able to point at the water
  305. new_definition.liquids_pointable = true
  306. -- call closure generator with original on_use handler
  307. new_definition.on_use = thirsty.on_use(
  308. minetest.registered_items[itemname].on_use
  309. )
  310. -- overwrite the node definition with almost the original
  311. minetest.override_item(itemname, new_definition)
  312. -- add configuration settings
  313. thirsty.config.drink_from_container[itemname] = level
  314. end
  315. function thirsty.fountain_abm(pos, node)
  316. local fountain_count = 0
  317. local water_count = 0
  318. local total_count = 0
  319. for y = 0, thirsty.config.fountain_height do
  320. for x = -y, y do
  321. for z = -y, y do
  322. local n = minetest.get_node({
  323. x = pos.x + x,
  324. y = pos.y - y + 1, -- start one *above* the fountain
  325. z = pos.z + z
  326. })
  327. if n then
  328. --print(string.format("%s at %d:%d:%d", n.name, pos.x+x, pos.y-y+1, pos.z+z))
  329. total_count = total_count + 1
  330. local type = thirsty.config.fountain_type[n.name] or ''
  331. if type == 'f' then
  332. fountain_count = fountain_count + 1
  333. elseif type == 'w' then
  334. water_count = water_count + 1
  335. end
  336. end
  337. end
  338. end
  339. end
  340. local level = math.min(thirsty.config.fountain_max_level, math.min(fountain_count, water_count))
  341. --print(string.format("Fountain (%d): %d + %d / %d", level, fountain_count, water_count, total_count))
  342. thirsty.fountains[string.format("%d:%d:%d", pos.x, pos.y, pos.z)] = {
  343. pos = { x=pos.x, y=pos.y, z=pos.z },
  344. level = level,
  345. -- time until check is 20 seconds, or twice the average
  346. -- time until the abm ticks again. Should be enough.
  347. time_until_check = 20,
  348. }
  349. end