Stamina mod.

This mod implements a reduced version of BlockMen's hunger+hud
mod, implementing a player "stamina" stat that converts food
hp_change to stamina points. Stamina points heal the player
over time if they are available and higher than the HP amount.
This commit is contained in:
Auke Kok 2016-02-16 16:33:58 -08:00
commit 503a0f7b62
11 changed files with 363 additions and 0 deletions

45
README.txt Normal file
View File

@ -0,0 +1,45 @@
Minetest mod "Stamina"
=====================
(C) 2015 - BlockMen
(C) 2016 - Auke Kok <sofar@foo-projects.org>
About this mod:
~~~~~~~~~~~~~~~
This mod adds a stamina, or "hunger" echanic to Minetest. Actions like
crafting, walking, digging or fighting make the player exhausted. When
enough exhaustion has been accumulated, the player gets more hungry,
and loses stamina.
If a player is low on stamina, they start taking periodical damage,
and ultimately will die if they do not eat food.
Eating food no longer heals the player. Instead, it increases the
stamina of the player. The stamina bar shows how well fed the player
is. More bread pieces means more stamina.
For Modders:
~~~~~~~~~~~~
This mod intercepts minetest.item_eat(), and applies the hp_change
as staminachange. The value can be positive (increase stamina) or
negative (periodically damage the player by 1 hp).
Callbacks that are registered via minetest.register_on_item_eat()
are called after this mod, so the itemstack will have changed already
when callbacks are called. You can get the original itemstack as 6th
parameter of your function then.
License:
~~~~~~~~
Code:
- all code LGPL-2.1+
Textures:
- stamina_hud_poison.png - BlockMen (CC-BY 3.0)
- stamina_hud_fg.png - PilzAdam (WTFPL), modified by BlockMen
- stamina_hud_bg.png - PilzAdam (WTFPL), modified by BlockMen
Sounds:
- stamina_eat.*.ogg - http://www.freesound.org/people/sonictechtonic/sounds/242215/ CC-BY-3.0

1
depends.txt Normal file
View File

@ -0,0 +1 @@
default

1
description.txt Normal file
View File

@ -0,0 +1 @@
Adds stamina and hunger effects.

315
init.lua Normal file
View File

@ -0,0 +1,315 @@
stamina = {}
local stamina_players = {}
STAMINA_TICK = 800 -- time in seconds after that 1 stamina point is taken
STAMINA_HEALTH_TICK = 4 -- time in seconds after player gets healed/damaged
STAMINA_MOVE_TICK = 0.5 -- time in seconds after the movement is checked
STAMINA_EXHAUST_DIG = 3 -- exhaustion increased this value after digged node
STAMINA_EXHAUST_PLACE = 1 -- exhaustion increased this value after placed
STAMINA_EXHAUST_MOVE = 1.5 -- exhaustion increased this value if player movement detected
STAMINA_EXHAUST_LVL = 160 -- at what exhaustion player saturation gets lowered
STAMINA_HEAL = 1 -- number of HP player gets healed after STAMINA_HEALTH_TICK
STAMINA_HEAL_LVL = 5 -- lower level of saturation needed to get healed
STAMINA_STARVE = 1 -- number of HP player gets damaged by stamina after STAMINA_HEALTH_TICK
STAMINA_STARVE_LVL = 3 -- level of staturation that causes starving
STAMINA_MAX = 30 -- maximum level of saturation
STAMINA_VISUAL_MAX = 20 -- hud bar extends only to 20
local function stamina_read(player)
local inv = player:get_inventory()
if not inv then
return nil
end
-- itemstack storage is offest by 1 to make the math work
local hgp = inv:get_stack("stamina", 1):get_count()
if hgp == 0 then
hgp = 21
inv:set_stack("stamina", 1, ItemStack({name = ":", count = hgp}))
end
return math.min(STAMINA_MAX + 1, hgp) - 1
end
local function stamina_save(player)
local inv = player:get_inventory()
if not inv then
return nil
end
local name = player:get_player_name()
local level = stamina_players[name].level
level = math.min(STAMINA_MAX, level)
level = math.max(level, 0)
inv:set_stack("stamina", 1, ItemStack({name = ":", count = level + 1}))
return true
end
local function stamina_update(player, level)
local name = player:get_player_name()
if not name then
return false
end
local old = stamina_players[name].level
if level == old then
return
end
stamina_players[name].level = level
player:hud_change(stamina_players[name].hud_id, "number", math.min(STAMINA_VISUAL_MAX, level))
stamina_save(player)
end
local function stamina_node_actions(pos, oldnode, player, ext)
if not player or not player:is_player() then
return
end
local name = player:get_player_name()
if not name or not stamina_players[name] then
return
end
local exhaust = stamina_players[name].exhaust
if not exhaust then
stamina_players[name].exhaust = 0
end
local new = STAMINA_EXHAUST_PLACE
-- placenode event
if not ext then
new = STAMINA_EXHAUST_DIG
end
-- assume its send by action_timer(globalstep)
if not pos and not oldnode then
new = STAMINA_EXHAUST_MOVE
end
exhaust = exhaust + new
if exhaust > STAMINA_EXHAUST_LVL then
exhaust = 0
local h = tonumber(stamina_players[name].level)
if h > 0 then
stamina_update(player, h - 1)
end
end
stamina_players[name].exhaust = exhaust
end
local function stamina_craft_actions(itemstack, player, old_craft_grid, craft_inv)
if not player or not player:is_player() then
return
end
local name = player:get_player_name()
if not name or not stamina_players[name] then
return
end
local exhaust = stamina_players[name].exhaust
if not exhaust then
stamina_players[name].exhaust = 0
return
end
if exhaust > STAMINA_EXHAUST_LVL then
exhaust = 0
local h = tonumber(stamina_players[name].level)
if h > 0 then
stamina_update(player, h - 1)
end
end
stamina_players[name].exhaust = exhaust
end
-- TODO: add stamina for fighting
-- Time based stamina functions
local stamina_timer = 0
local health_timer = 0
local action_timer = 0
local function stamina_globaltimer(dtime)
stamina_timer = stamina_timer + dtime
health_timer = health_timer + dtime
action_timer = action_timer + dtime
if action_timer > STAMINA_MOVE_TICK then
for _,player in ipairs(minetest.get_connected_players()) do
local controls = player:get_player_control()
-- Determine if the player is walking
if controls.up or controls.down or controls.left or controls.right then
stamina_node_actions(nil, nil, player)
end
end
action_timer = 0
end
-- lower saturation by 1 point after STAMINA_TICK second(s)
if stamina_timer > STAMINA_TICK then
for _,player in ipairs(minetest.get_connected_players()) do
local name = player:get_player_name()
local tab = stamina_players[name]
if tab then
local h = tab.level
if h > 0 then
stamina_update(player, h - 1)
end
end
end
stamina_timer = 0
end
-- heal or damage player, depending on saturation
if health_timer > STAMINA_HEALTH_TICK then
for _,player in ipairs(minetest.get_connected_players()) do
local name = player:get_player_name()
local tab = stamina_players[name]
if tab then
local air = player:get_breath() or 0
local hp = player:get_hp()
-- don't heal if drowning or dead
-- TODO: don't heal if poisoned?
local h = tonumber(tab.level)
if h >= STAMINA_HEAL_LVL and h >= hp and hp > 0 and air > 0 then
player:set_hp(hp + STAMINA_HEAL)
stamina_update(player, h - 1)
end
-- or damage player by 1 hp if saturation is < 2 (of 30)
if tonumber(tab.level) < STAMINA_STARVE_LVL then
player:set_hp(hp - STAMINA_STARVE)
end
end
end
health_timer = 0
end
end
local function poison_player(ticks, time, elapsed, user)
if elapsed <= ticks then
minetest.after(time, poison_player, ticks, time, elapsed + 1, user)
else
local name = user:get_player_name()
user:hud_change(stamina_players[name].hud_id, "text", "stamina_hud_fg.png")
end
local hp = user:get_hp() -1 or 0
if hp > 0 then
user:set_hp(hp)
end
end
-- override core.do_item_eat() so we can redirect hp_change to stamina
core.do_item_eat = function(hp_change, replace_with_item, itemstack, user, pointed_thing)
local old_itemstack = itemstack
itemstack = stamina.eat(hp_change, replace_with_item, itemstack, user, pointed_thing)
for _, callback in pairs(core.registered_on_item_eats) do
local result = callback(hp_change, replace_with_item, itemstack, user, pointed_thing, old_itemstack)
if result then
return result
end
end
return itemstack
end
-- not local since it's called from within core context
function stamina.eat(hp_change, replace_with_item, itemstack, user, pointed_thing)
if not itemstack then
return itemstack
end
if not user then
return itemstack
end
local name = user:get_player_name()
if not stamina_players[name] then
return itemstack
end
local level = tonumber(stamina_players[name].level or 0)
if level >= STAMINA_MAX then
return itemstack
end
if level < STAMINA_MAX and hp_change > 0 then
level = level + hp_change
stamina_update(user, level)
end
if hp_change < 0 then
user:hud_change(stamina_players[name].hud_id, "text", "stamina_hud_poison.png")
poison_player(2.0, -hp_change, 0, user)
end
minetest.sound_play("stamina_eat", {to_player = name, gain = 0.7})
if replace_with_item then
if itemstack:is_empty() then
itemstack:add_item(replace_with_item)
else
local inv = user:get_inventory()
if inv:room_for_item("main", {name=replace_with_item}) then
inv:add_item("main", replace_with_item)
else
local pos = user:getpos()
pos.y = math.floor(pos.y + 0.5)
core.add_item(pos, replace_with_item)
end
end
else
itemstack:take_item()
end
return itemstack
end
-- stamina is disabled if damage is disabled
if minetest.setting_getbool("enable_damage") and minetest.is_yes(minetest.setting_get("enable_stamina") or "1") then
minetest.register_on_joinplayer(function(player)
local inv = player:get_inventory()
inv:set_size("stamina", 1)
local name = player:get_player_name()
stamina_players[name] = {}
stamina_players[name].level = stamina_read(player)
stamina_players[name].exhaust = 0
local level = math.min(stamina_players[name].level, STAMINA_VISUAL_MAX)
local id = player:hud_add({
name = "stamina",
hud_elem_type = "statbar",
position = {x = 0.5, y = 1},
size = {x = 24, y = 24},
text = "stamina_hud_fg.png",
number = level,
alignment = {x = -1, y = -1},
offset = {x = -266, y = -110},
background = "stamina_hud_bg.png",
max = 0,
})
stamina_players[name].hud_id = id
end)
minetest.register_globalstep(stamina_globaltimer)
minetest.register_on_placenode(stamina_node_actions)
minetest.register_on_dignode(stamina_node_actions)
minetest.register_on_craft(stamina_craft_actions)
minetest.register_on_respawnplayer(function(player)
stamina_update(player, STAMINA_VISUAL_MAX)
end)
end

1
mod.conf Normal file
View File

@ -0,0 +1 @@
name = stamina

BIN
sounds/stamina_eat.1.ogg Normal file

Binary file not shown.

BIN
sounds/stamina_eat.2.ogg Normal file

Binary file not shown.

BIN
sounds/stamina_eat.3.ogg Normal file

Binary file not shown.

BIN
textures/stamina_hud_bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 417 B

BIN
textures/stamina_hud_fg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 522 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB