~~~Hunger.~~~ Stamina

This code adds ~~~hunger~~~ stamina to minetest_game.

This code is entirely derived from Blockmen's hunger+hud mod.  Credits
and copyright are preserved as they are compatible with minetest_game.

gameplay:

The player stat 'stamina' is added. The higher the value, the better. A
value of 5 or higher causes the player to slowly get healed over time,
at the cost of stamina. One can only be healed if the stamina value is
higher than, or equal to the health value.

Eating food causes 'stamina' to increase. The visual range of stamina
is 0-20, but one can eat slightly over that. If the stamina is at max,
items are not eaten anymore.

At stamina 2 or lower, the player is damaged periodically, and will
die if the stamina value isn't increased due to eating.

If eating a food that has a negative hp_change value, such as red
mushrooms, the player takes periodical damage to the amount of the
hp_change value. During this time, the player is "poisoned". This
poison damage can not kill the player.

Added by me is code to add exhaustion on crafting, and on player
combat. I also added extra exhaustion when the player is jumping

code:

This mod started as BlockMen's hunger+hud mod. I trimmed the entire
hud mod away and placed the stamina bar on top of the hp hud element,
which will make the relation between stamina and hp a bit more clear,
and doesn't require moving the air hud element.

The stamina mod was reduced to fit a single file. I trimmed some
legacy code, revised all local/global usage. Renamed most internal
variables, and generally did a lot of removals of needlessly complex
code favoring simplicity.

The food registration code is entirely removed. Instead we assume food
just adds stamina or ticks damage as poison. Food eating callbacks are
still called. Subsequently, this mod will likely not properly work
with BlockMen's hud code anymore, nor is that intended to work. Food
mods just have to call item_eat().

I've moved and credited authors for the used textures. I replaced
the food sound of this mod with 3 different sounds.

The code points out several issues with core: There's no decent
place to store player stats. This mod solves it by creating a per
player inventory purely for storing an integer value. Intercepting
core.do_item_eat() also isn't the nicest way to make this work.
This commit is contained in:
Auke Kok 2016-02-13 09:24:27 -08:00
parent 7d2dfe4101
commit d905f66d30
9 changed files with 338 additions and 0 deletions

45
mods/stamina/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" mechanic 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 stamina change. 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
mods/stamina/depends.txt Normal file
View file

@ -0,0 +1 @@
default

292
mods/stamina/init.lua Normal file
View file

@ -0,0 +1,292 @@
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 -- .. after digging node
STAMINA_EXHAUST_MOVE = 1.5 -- .. if player movement detected
STAMINA_EXHAUST_JUMP = 5 -- .. if jumping
STAMINA_EXHAUST_CRAFT = 20 -- .. if player crafts
STAMINA_EXHAUST_PUNCH = 40 -- .. if player punches another player
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_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 v = inv:get_stack("stamina", 1):get_count()
if v == 0 then
v = 21
inv:set_stack("stamina", 1, ItemStack({name = ":", count = v}))
end
return v - 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.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 exhaust_player(player, v)
if not player or not player:is_player() then
return
end
local name = player:get_player_name()
if not name then
return
end
local s = stamina_players[name]
if not s then
return
end
local e = s.exhaust
if not e then
s.exhaust = 0
end
e = e + v
if e > STAMINA_EXHAUST_LVL then
e = 0
local h = tonumber(stamina_players[name].level)
if h > 0 then
stamina_update(player, h - 1)
end
end
s.exhaust = e
end
-- 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.jump then
exhaust_player(player, STAMINA_EXHAUST_JUMP)
elseif controls.up or controls.down or controls.left or controls.right then
exhaust_player(player, STAMINA_EXHAUST_MOVE)
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_VISUAL_MAX then
return itemstack
end
if hp_change > 0 then
level = level + hp_change
stamina_update(user, level)
else
-- assume hp_change < 0.
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(function(pos, oldnode, player, ext)
exhaust_player(player, STAMINA_EXHAUST_PLACE)
end)
minetest.register_on_dignode(function(pos, oldnode, player, ext)
exhaust_player(player, STAMINA_EXHAUST_DIG)
end)
minetest.register_on_craft(function(itemstack, player, old_craft_grid, craft_inv)
exhaust_player(player, STAMINA_EXHAUST_CRAFT)
end)
minetest.register_on_punchplayer(function(player, hitter, time_from_last_punch, tool_capabilities, dir, damage)
exhaust_player(hitter, STAMINA_EXHAUST_PUNCH)
end)
minetest.register_on_respawnplayer(function(player)
stamina_update(player, STAMINA_VISUAL_MAX)
end)
end

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 417 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 522 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB