thirsty/init.lua
2015-04-15 23:41:39 +02:00

277 lines
9.2 KiB
Lua

--[[
Thirsty mod [thirsty]
==========================
A mod that adds a "thirst" mechanic, similar to hunger.
Copyright (C) 2015 Ben Deutsch <ben@bendeutsch.de>
License
-------
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
USA
Terminology: "Thirst" vs. "hydration"
-------------------------------------
"Thirst" is the absence of "hydration" (a term suggested by
everamzah on the Minetest forums, thanks!). The overall mechanic
is still called "thirst", but the visible bar is that of
"hydration", filled with "hydro points".
]]
-- the main module variable
thirsty = {
-- Configuration variables
tick_time = 0.5,
thirst_per_second = 1.0 / 20.0,
damage_per_second = 1.0 / 10.0, -- when out of hydration
stand_still_for_drink = 1.0,
stand_still_for_afk = 120.0, -- 2 Minutes
drink_from_node = {
-- value: hydration regen per second
['default:water_source'] = 0.5,
['default:water_flowing'] = 0.5,
},
-- the players' values
players = {
--[[
name = {
hydro = 20,
last_pos = '-10:3',
time_in_pos = 0.0,
pending_dmg = 0.0,
}
--]]
},
stash_filename = 'thirsty.dat',
-- general settings
time_next_tick = 0.0,
}
thirsty.time_next_tick = thirsty.tick_time
hb.register_hudbar('thirst', 0xffffff, "Hydration", {
bar = 'thirsty_hudbars_bar.png',
icon = 'thirsty_cup_100_16.png'
}, 20, 20, false)
minetest.register_on_joinplayer(function(player)
local name = player:get_player_name()
-- default entry for new players
if not thirsty.players[name] then
local pos = player:getpos()
thirsty.players[name] = {
hydro = 20,
last_pos = math.floor(pos.x) .. ':' .. math.floor(pos.z),
time_in_pos = 0.0,
pending_dmg = 0.0,
}
end
hb.init_hudbar(player, 'thirst', thirsty.players[name].hydro, 20, false)
end)
--[[
Main Loop (Tier 0)
]]
minetest.register_globalstep(function(dtime)
-- get thirsty
thirsty.time_next_tick = thirsty.time_next_tick - dtime
while thirsty.time_next_tick < 0.0 do
-- time for thirst
thirsty.time_next_tick = thirsty.time_next_tick + thirsty.tick_time
for _,player in ipairs(minetest.get_connected_players()) do
local name = player:get_player_name()
local pos = player:getpos()
local pl = thirsty.players[name]
-- how long have we been standing "here"?
-- (the node coordinates in X and Z should be enough)
local pos_hash = math.floor(pos.x) .. ':' .. math.floor(pos.z)
if pl.last_pos == pos_hash then
pl.time_in_pos = pl.time_in_pos + thirsty.tick_time
else
-- you moved!
pl.last_pos = pos_hash
pl.time_in_pos = 0.0
end
local pl_standing = pl.time_in_pos > thirsty.stand_still_for_drink
local pl_afk = pl.time_in_pos > thirsty.stand_still_for_afk
--print("Standing: " .. (pl_standing and 'true' or 'false' ) .. ", AFK: " .. (pl_afk and 'true' or 'false'))
pos.y = pos.y + 0.1
local node = minetest.get_node(pos)
local drink_per_second = thirsty.drink_from_node[node.name]
if drink_per_second ~= nil and drink_per_second > 0 and pl_standing then
pl.hydro = pl.hydro + drink_per_second * thirsty.tick_time
-- Drinking from the ground won't give you more than max
if pl.hydro > 20 then pl.hydro = 20 end
--print("Raising hydration by "..(drink_per_second*thirsty.tick_time).." to "..pl.hydro)
else
if not pl_afk then
-- only get thirsty if not AFK
pl.hydro = pl.hydro - thirsty.thirst_per_second * thirsty.tick_time
if pl.hydro < 0 then pl.hydro = 0 end
--print("Lowering hydration by "..(thirsty.thirst_per_second*thirsty.tick_time).." to "..pl.hydro)
end
end
-- should we only update the hud on an actual change?
hb.change_hudbar(player, 'thirst', math.ceil(pl.hydro), 20)
-- damage, if enabled
if minetest.setting_getbool("enable_damage") then
-- maybe not the best way to do this, but it does mean
-- we can do anything with one tick loop
if pl.hydro <= 0.0 and not pl_afk then
pl.pending_dmg = pl.pending_dmg + thirsty.damage_per_second * thirsty.tick_time
--print("Pending damage at " .. pl.pending_dmg)
if pl.pending_dmg > 1.0 then
local dmg = math.floor(pl.pending_dmg)
pl.pending_dmg = pl.pending_dmg - dmg
player:set_hp( player:get_hp() - dmg )
end
else
-- forget any pending damage when not thirsty
pl.pending_dmg = 0.0
end
end
end
end
end)
--[[
Stash: persist the hydration values in a file in the world directory.
If this is missing or corrupted, then no worries: nobody's thirsty ;-)
]]
function thirsty.read_stash()
local filename = minetest.get_worldpath() .. "/" .. thirsty.stash_filename
local file, err = io.open(filename, "r")
if not file then
-- no problem, it's just not there
-- TODO: or parse err?
return
end
thirsty.players = {}
for line in file:lines() do
if string.match(line, '^%-%-') then
-- comment, ignore
elseif string.match(line, '^P [%d.]+ [%d.]+ .+') then
-- player line
-- is matching again really the best solution?
local hydro, dmg, name = string.match(line, '^P ([%d.]+) ([%d.]+) (.+)')
thirsty.players[name] = {
hydro = tonumber(hydro),
last_pos = '0:0', -- not true, but no matter
time_in_pos = 0.0,
pending_dmg = tonumber(dmg),
}
end
end
file:close()
end
function thirsty.write_stash()
local filename = minetest.get_worldpath() .. "/" .. thirsty.stash_filename
local file, err = io.open(filename, "w")
if not file then
minetest.log("error", "Thirsty: could not write " .. thirsty.stash_filename .. ": " ..err)
return
end
file:write('-- Stash file for Minetest mod [thirsty] --\n')
-- write players:
-- P <hydro> <pending_dmg> <name>
file:write('-- Player format: "P <hydro> <pending damage> <name>"\n')
for name, data in pairs(thirsty.players) do
file:write("P " .. data.hydro .. " " .. data.pending_dmg .. " " .. name .. "\n")
end
file:close()
end
--[[
Drinking containers (Tier 1)
For now, augment the nodes from vessels to enable drinking on use.
]]
-- closure to capture old on_use handler
function thirsty.on_use_drinking_container( old_on_use )
return function (itemstack, user, pointed_thing)
local node = minetest.get_node(pointed_thing.under)
if thirsty.drink_from_node[node.name] ~= nil then
-- we found something to drink!
local pl = thirsty.players[user:get_player_name()]
-- drink until we're full
-- Note: if hydro is > 20, don't lower it!
if pl.hydro < 20 then
pl.hydro = 20
end
-- call original on_use
if old_on_use ~= nil then
return old_on_use(itemstack, user, pointed_thing)
else
-- we're done, no item need be removed
return nil
end
end
end
end
function thirsty.augment_node_for_drinking( nodename )
-- clone the existing definition (shallow copy)
local new_definition = {}
for key, value in pairs(minetest.registered_nodes[nodename]) do
new_definition[key] = value
end
-- we need to be able to point at the water
new_definition.liquids_pointable = true
-- call closure generator with original on_use handler
new_definition.on_use = thirsty.on_use_drinking_container(
new_definition.on_use
)
-- overwrite the node definition with almost the original
minetest.register_node(':' .. nodename, new_definition)
end
-- add more nodes here
thirsty.augment_node_for_drinking('vessels:drinking_glass')
thirsty.augment_node_for_drinking('vessels:glass_bottle')
thirsty.augment_node_for_drinking('vessels:steel_bottle')
-- read on startup
thirsty.read_stash()
-- write on shutdown
minetest.register_on_shutdown(thirsty.write_stash)