diff --git a/.luacheckrc b/.luacheckrc index f1bafe3..a6b4003 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -3,7 +3,7 @@ allow_defined_top = true read_globals = { "DIR_DELIM", - "minetest", "core", + "minetest", "dump", "vector", "nodeupdate", "VoxelManip", "VoxelArea", @@ -11,9 +11,10 @@ read_globals = { "intllib", "default", "armor", + "player_monoids", } globals = { - core = { fields = { "do_item_eat" }}, + minetest = { fields = { "do_item_eat" }}, } diff --git a/API.txt b/API.txt new file mode 100644 index 0000000..334a2d0 --- /dev/null +++ b/API.txt @@ -0,0 +1,64 @@ +stamina.get_saturation(player) + Gets a player's saturation + +stamina.set_saturation(player, level) + Set a player's saturation. + Updates the HUD to reflect saturation level. + +stamina.update_saturation(player, level) + Update's a player's saturation. + Callbacks are processed first. + +stamina.change_saturation(player, change) + Update a player's saturation by a delta, instead of an absolute value. + +stamina.register_on_update_saturation(function(player, level)) + Register a callback for when saturation is updated. + If the callback returns True, no further callbacks are called, + and the default behavior is bypassed. +----------------------- +stamina.is_poisoned(player) + Check if a player is poisoned + +stamina.set_poisoned(player, poisoned) + Set a player's "poisoned" status (boolean) + Updates the HUD to reflect poison status. + +stamina.poison(player, ticks, interval) + Poison a player for a certain amount of time. + Ticks is how many times to poison the player. + Interval is the time between poison ticks. + Processes callbacks before changing status. + +stamina.register_on_poison(function(player, ticks, interval)) + Register a callback for when a player is poisoned. + If the callback returns True, no further callbacks are called, + and the default behavior is bypassed. +------------------------- +stamina.get_exhaustion(player) + Gets a player's exhaustion level. + Value is between 0 and `stamina.exhaust_lvl` (default: 160) + +stamina.set_exhaustion(player, level) + Sets a player's exhaustion level. + +stamina.exhaust_player(player, change, cause) + Update a player's exhaustion by "change". + If the player's exhaustion exceeds `stamina.exhaust_lvl`, stamina + is taken and exhaustion is reset. + Cause is an optional argument indicating the origin of the exhaustion. + Callbacks are processed before exhausting the player. + +stamina.register_on_exhaust_player(function(player, change, cause)) + Register a callback for when exhaustion is updated. + If the callback returns True, no further callbacks are called, + and the default behavior is bypassed. +------------------------ +stamina.set_sprinting(name, sprinting) + Sets whether a player is sprinting or not. + Callbacks are processed before setting sprint status. + +stamina.register_on_sprinting(function(player, sprinting)) + Register a callback for when a player's sprinting status is updated. + If the callback returns True, no further callbacks are called, + and the default behavior is bypassed. diff --git a/README.txt b/README.txt index 923fa9d..388c32b 100644 --- a/README.txt +++ b/README.txt @@ -35,11 +35,7 @@ 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. - +See API.txt for information on the API. License: -------- diff --git a/depends.txt b/depends.txt index c12cc51..bdb6c20 100644 --- a/depends.txt +++ b/depends.txt @@ -1,2 +1,3 @@ default 3d_armor? +player_monoids? diff --git a/init.lua b/init.lua index 580921d..9636b6c 100644 --- a/init.lua +++ b/init.lua @@ -1,29 +1,70 @@ +if not minetest.settings:get_bool("enable_damage") then + minetest.log("warning", "[stamina] Stamina will not load if damage is disabled (enable_damage=false)") + return +end stamina = {} +local modname = minetest.get_current_modname() +local armor_mod = minetest.get_modpath("3d_armor") and minetest.global_exists("armor") and armor.def +local player_monoids_mod = minetest.get_modpath("player_monoids") and minetest.global_exists("player_monoids") -STAMINA_TICK = 800 -- time in seconds after that 1 stamina point is taken -STAMINA_TICK_MIN = 4 -- stamina ticks won't reduce stamina below this level -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 +function stamina.log(level, message, ...) + return minetest.log(level, ("[%s] %s"):format(modname, message:format(...))) +end -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 +local function get_setting(key, default) + local setting = minetest.settings:get("stamina." .. key) + if setting and not tonumber(setting) then + stamina.log("warning", "Invalid value for setting %s: %q. Using default %q.", key, setting, default) + end + return tonumber(setting) or default +end -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.settings = { + -- see settingtypes.txt for descriptions + eat_particles = minetest.settings:get_bool("stamina.eat_particles", true), + sprint = minetest.settings:get_bool("stamina.sprint", true), + sprint_particles = minetest.settings:get_bool("stamina.sprint_particles", true), + sprint_lvl = get_setting("sprint_lvl", 6), + sprint_speed = get_setting("sprint_speed", 0.8), + sprint_jump = get_setting("sprint_jump", 0.1), + tick = get_setting("tick", 800), + tick_min = get_setting("tick_min", 4), + health_tick = get_setting("health_tick", 4), + move_tick = get_setting("move_tick", 0.5), + poison_tick = get_setting("poison_tick", 2.0), + exhaust_dig = get_setting("exhaust_dig", 3), + exhaust_place = get_setting("exhaust_place", 1), + exhaust_move = get_setting("exhaust_move", 1.5), + exhaust_jump = get_setting("exhaust_jump", 5), + exhaust_craft = get_setting("exhaust_craft", 20), + exhaust_punch = get_setting("exhaust_punch", 40), + exhaust_sprint = get_setting("exhaust_sprint", 28), + exhaust_lvl = get_setting("exhaust_lvl", 160), + heal = get_setting("heal", 1), + heal_lvl = get_setting("heal_lvl", 5), + starve = get_setting("starve", 1), + starve_lvl = get_setting("starve_lvl", 3), + visual_max = get_setting("visual_max", 20), +} +local settings = stamina.settings -STAMINA_VISUAL_MAX = 20 -- hud bar extends only to 20 +local attribute = { + saturation = "stamina:level", + hud_id = "stamina:hud_id", + poisoned = "stamina:poisoned", + exhaustion = "stamina:exhaustion", +} -SPRINT_SPEED = 0.8 -- how much faster player can run if satiated -SPRINT_JUMP = 0.1 -- how much higher player can jump if satiated -SPRINT_DRAIN = 0.35 -- how fast to drain satation while sprinting (0-1) +local function is_player(player) + return ( + player and + not player.is_fake_player and + player.get_attribute and -- check for pipeworks fake player + player.is_player and + player:is_player() + ) +end local function get_int_attribute(player, key) local level = player:get_attribute(key) @@ -33,111 +74,309 @@ local function get_int_attribute(player, key) return nil end end +--- SATURATION API --- +function stamina.get_saturation(player) + return get_int_attribute(player, attribute.saturation) +end -local function stamina_update_level(player, level) - local old = get_int_attribute(player, "stamina:level") +function stamina.set_saturation(player, level) + player:set_attribute(attribute.saturation, level) + player:hud_change( + player:get_attribute(attribute.hud_id), + "number", + math.min(settings.visual_max, level) + ) +end + +stamina.registered_on_update_saturations = {} +function stamina.register_on_update_saturation(fun) + table.insert(stamina.registered_on_update_saturations, fun) +end + +function stamina.update_saturation(player, level) + for _, callback in ipairs(stamina.registered_on_update_saturations) do + local result = callback(player, level) + if result then + return result + end + end + + local old = stamina.get_saturation(player) if level == old then -- To suppress HUD update return end -- players without interact priv cannot eat - if old < STAMINA_HEAL_LVL and not minetest.check_player_privs(player, {interact=true}) then + if old < settings.heal_lvl and not minetest.check_player_privs(player, {interact=true}) then return end - player:set_attribute("stamina:level", level) - - player:hud_change(player:get_attribute("stamina:hud_id"), "number", math.min(STAMINA_VISUAL_MAX, level)) + stamina.set_saturation(player, level) end -local function stamina_is_poisoned(player) - return player:get_attribute("stamina:poisoned") == "yes" -end - -local function stamina_set_poisoned(player, poisoned) - if poisoned then - player:set_attribute("stamina:poisoned", "yes") - else - player:set_attribute("stamina:poisoned", "no") - end -end - -local function stamina_get_exhaustion(player) - return get_int_attribute(player, "stamina:exhaustion") -end - --- global function for mods to amend stamina level -stamina.change = function(player, change) - local name = player:get_player_name() - if not name or not change or change == 0 then +function stamina.change_saturation(player, change) + if not is_player(player) or not change or change == 0 then return false end - local level = get_int_attribute(player, "stamina:level") + change - if level < 0 then level = 0 end - if level > STAMINA_VISUAL_MAX then level = STAMINA_VISUAL_MAX end - stamina_update_level(player, level) + local level = stamina.get_saturation(player) + change + level = math.max(level, 0) + level = math.min(level, settings.visual_max) + stamina.update_saturation(player, level) return true end -local function exhaust_player(player, v) - if not player or not player.is_player or not player:is_player() or not player.set_attribute then +stamina.change = stamina.change_saturation -- for backwards compatablity +--- END SATURATION API --- +--- POISON API --- +function stamina.is_poisoned(player) + return player:get_attribute(attribute.poisoned) == "yes" +end + +function stamina.set_poisoned(player, poisoned) + if poisoned then + player:hud_change(player:get_attribute(attribute.hud_id), "text", "stamina_hud_poison.png") + player:set_attribute(attribute.poisoned, "yes") + else + player:hud_change(player:get_attribute(attribute.hud_id), "text", "stamina_hud_fg.png") + player:set_attribute(attribute.poisoned, "no") + end +end + +local function poison_tick(player, ticks, interval, elapsed) + if not stamina.is_poisoned(player) then + return + elseif elapsed > ticks then + stamina.set_poisoned(player, false) + else + local hp = player:get_hp() - 1 + if hp > 0 then + player:set_hp(hp) + end + minetest.after(interval, poison_tick, player, ticks, interval, elapsed + 1) + end +end + +stamina.registered_on_poisons = {} +function stamina.register_on_poison(fun) + table.insert(stamina.registered_on_poisons, fun) +end + +function stamina.poison(player, ticks, interval) + for _, fun in ipairs(stamina.registered_on_poisons) do + local rv = fun(player, ticks, interval) + if rv == true then + return + end + end + if not is_player(player) then return end + stamina.set_poisoned(player, true) + poison_tick(player, ticks, interval, 0) +end +--- END POISON API --- +--- EXHAUSTION API --- +stamina.exhaustion_reasons = { + craft = "craft", + dig = "dig", + heal = "heal", + jump = "jump", + move = "move", + place = "place", + punch = "punch", + sprint = "sprint", +} - local name = player:get_player_name() - if not name then - return - end +function stamina.get_exhaustion(player) + return get_int_attribute(player, attribute.exhaustion) +end - local exhaustion = stamina_get_exhaustion(player) or 0 +function stamina.set_exhaustion(player, exhaustion) + player:set_attribute(attribute.exhaustion, exhaustion) +end - exhaustion = exhaustion + v +stamina.registered_on_exhaust_players = {} +function stamina.register_on_exhaust_player(fun) + table.insert(stamina.registered_on_exhaust_players, fun) +end - if exhaustion > STAMINA_EXHAUST_LVL then - exhaustion = 0 - local h = get_int_attribute(player, "stamina:level") - if h > 0 then - stamina_update_level(player, h - 1) +function stamina.exhaust_player(player, change, cause) + for _, callback in ipairs(stamina.registered_on_exhaust_players) do + local result = callback(player, change, cause) + if result then + return result end end - player:set_attribute("stamina:exhaustion", exhaustion) -end - --- Sprint settings and function -local enable_sprint = minetest.setting_getbool("sprint") ~= false -local enable_sprint_particles = minetest.setting_getbool("sprint_particles") ~= false -local armor_mod = minetest.get_modpath("3d_armor") - -function set_sprinting(name, sprinting) - local player = minetest.get_player_by_name(name) - local def = {} - -- Get player physics from 3d_armor mod - if armor_mod and armor and armor.def then - def.speed = armor.def[name].speed - def.jump = armor.def[name].jump - def.gravity = armor.def[name].gravity + if not is_player(player) then + return end - def.speed = def.speed or 1 - def.jump = def.jump or 1 - def.gravity = def.gravity or 1 + local exhaustion = stamina.get_exhaustion(player) or 0 - if sprinting == true then + exhaustion = exhaustion + change - def.speed = def.speed + SPRINT_SPEED - def.jump = def.jump + SPRINT_JUMP + if exhaustion >= settings.exhaust_lvl then + exhaustion = exhaustion - settings.exhaust_lvl + stamina.change(player, -1) end - player:set_physics_override({ - speed = def.speed, - jump = def.jump, - gravity = def.gravity - }) + stamina.set_exhaustion(player, exhaustion) end +--- END EXHAUSTION API --- +--- SPRINTING API --- +stamina.registered_on_sprintings = {} +function stamina.register_on_sprinting(fun) + table.insert(stamina.registered_on_sprintings, fun) +end + +function stamina.set_sprinting(player, sprinting) + for _, fun in ipairs(stamina.registered_on_sprintings) do + local rv = fun(player, sprinting) + if rv == true then + return + end + end + + if player_monoids_mod then + if sprinting then + player_monoids.speed:add_change(player, 1 + settings.sprint_speed, "stamina:physics") + player_monoids.jump:add_change(player, 1 + settings.sprint_jump, "stamina:physics") + else + player_monoids.speed:del_change(player, "stamina:physics") + player_monoids.jump:del_change(player, "stamina:physics") + end + else + local def + if armor_mod then + -- Get player physics from 3d_armor mod + local name = player:get_player_name() + def = { + speed=armor.def[name].speed, + jump=armor.def[name].jump, + gravity=armor.def[name].gravity + } + else + def = { + speed=1, + jump=1, + gravity=1 + } + end + + if sprinting then + def.speed = def.speed + settings.sprint_speed + def.jump = def.jump + settings.sprint_jump + end + + player:set_physics_override(def) + end + + if settings.sprint_particles and sprinting then + local pos = player:getpos() + local node = minetest.get_node({x = pos.x, y = pos.y - 1, z = pos.z}) + local def = minetest.registered_nodes[node.name] or {} + local drawtype = def.drawtype + if drawtype ~= "airlike" and drawtype ~= "liquid" and drawtype ~= "flowingliquid" then + minetest.add_particlespawner({ + amount = 5, + time = 0.01, + minpos = {x = pos.x - 0.25, y = pos.y + 0.1, z = pos.z - 0.25}, + maxpos = {x = pos.x + 0.25, y = pos.y + 0.1, z = pos.z + 0.25}, + minvel = {x = -0.5, y = 1, z = -0.5}, + maxvel = {x = 0.5, y = 2, z = 0.5}, + minacc = {x = 0, y = -5, z = 0}, + maxacc = {x = 0, y = -12, z = 0}, + minexptime = 0.25, + maxexptime = 0.5, + minsize = 0.5, + maxsize = 1.0, + vertical = false, + collisiondetection = false, + texture = "default_dirt.png", + }) + end + end +end +--- END SPRINTING API --- -- Time based stamina functions +local function move_tick() + for _,player in ipairs(minetest.get_connected_players()) do + local controls = player:get_player_control() + local is_moving = controls.up or controls.down or controls.left or controls.right + local velocity = player:get_player_velocity() + velocity.y = 0 + local horizontal_speed = vector.length(velocity) + local has_velocity = horizontal_speed > 0.05 + + if controls.jump then + stamina.exhaust_player(player, settings.exhaust_jump, stamina.exhaustion_reasons.jump) + elseif is_moving and has_velocity then + stamina.exhaust_player(player, settings.exhaust_move, stamina.exhaustion_reasons.move) + end + + if settings.sprint then + local can_sprint = ( + controls.aux1 and + not player:get_attach() and + not minetest.check_player_privs(player, {fast = true}) and + stamina.get_saturation(player) > settings.sprint_lvl + ) + + if can_sprint then + stamina.set_sprinting(player, true) + if is_moving and has_velocity then + stamina.exhaust_player(player, settings.exhaust_sprint, stamina.exhaustion_reasons.sprint) + end + else + stamina.set_sprinting(player, false) + end + end + end +end + +local function stamina_tick() + -- lower saturation by 1 point after settings.tick second(s) + for _,player in ipairs(minetest.get_connected_players()) do + local saturation = stamina.get_saturation(player) + if saturation > settings.tick_min then + stamina.update_saturation(player, saturation - 1) + end + end +end + +local function health_tick() + -- heal or damage player, depending on saturation + for _,player in ipairs(minetest.get_connected_players()) do + local air = player:get_breath() or 0 + local hp = player:get_hp() + local saturation = stamina.get_saturation(player) + + -- don't heal if dead, drowning, or poisoned + local should_heal = ( + saturation >= settings.heal_lvl and + saturation >= hp and + hp > 0 and + air > 0 + and not stamina.is_poisoned(player) + ) + -- or damage player by 1 hp if saturation is < 2 (of 30) + local is_starving = ( + saturation < settings.starve_lvl and + hp > 0 + ) + + if should_heal then + player:set_hp(hp + settings.heal) + stamina.exhaust_player(player, settings.exhaust_lvl, stamina.exhaustion_reasons.heal) + elseif is_starving then + player:set_hp(hp - settings.starve) + end + end +end + local stamina_timer = 0 local health_timer = 0 local action_timer = 0 @@ -147,182 +386,78 @@ local function stamina_globaltimer(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 - - --- START sprint - if enable_sprint then - - local name = player:get_player_name() - - -- check if player can sprint (stamina must be over 6 points) - if controls.aux1 and controls.up - and not minetest.check_player_privs(player, {fast = true}) - and get_int_attribute(player, "stamina:level") > 6 then - - set_sprinting(name, true) - - -- create particles behind player when sprinting - if enable_sprint_particles then - - local pos = player:getpos() - local node = minetest.get_node({ - x = pos.x, y = pos.y - 1, z = pos.z}) - - if node.name ~= "air" then - - minetest.add_particlespawner({ - amount = 5, - time = 0.01, - minpos = {x = pos.x - 0.25, y = pos.y + 0.1, z = pos.z - 0.25}, - maxpos = {x = pos.x + 0.25, y = pos.y + 0.1, z = pos.z + 0.25}, - minvel = {x = -0.5, y = 1, z = -0.5}, - maxvel = {x = 0.5, y = 2, z = 0.5}, - minacc = {x = 0, y = -5, z = 0}, - maxacc = {x = 0, y = -12, z = 0}, - minexptime = 0.25, - maxexptime = 0.5, - minsize = 0.5, - maxsize = 1.0, - vertical = false, - collisiondetection = false, - texture = "default_dirt.png", - }) - - end - end - - -- Lower the player's stamina when sprinting - local level = get_int_attribute(player, "stamina:level") - stamina_update_level(player, level - (SPRINT_DRAIN * STAMINA_MOVE_TICK)) - else - set_sprinting(name, false) - end - end - -- END sprint - - end + if action_timer > settings.move_tick then action_timer = 0 + move_tick() 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 h = get_int_attribute(player, "stamina:level") - if h > STAMINA_TICK_MIN then - stamina_update_level(player, h - 1) - end - end + if stamina_timer > settings.tick then stamina_timer = 0 + stamina_tick() 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 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 = get_int_attribute(player, "stamina:level") - if h >= STAMINA_HEAL_LVL and h >= hp and hp > 0 and air > 0 - and not stamina_is_poisoned(player) then - player:set_hp(hp + STAMINA_HEAL) - stamina_update_level(player, h - 1) - end - - -- or damage player by 1 hp if saturation is < 2 (of 30) - if get_int_attribute(player, "stamina:level") < STAMINA_STARVE_LVL and hp > 0 then - player:set_hp(hp - STAMINA_STARVE) - end - end - + if health_timer > settings.health_tick then health_timer = 0 + health_tick() end end -local function poison_player(ticks, time, elapsed, user) - if elapsed <= ticks then - minetest.after(time, poison_player, ticks, time, elapsed + 1, user) - stamina_set_poisoned(user,true) - else - user:hud_change(user:get_attribute("stamina:hud_id"), "text", "stamina_hud_fg.png") - stamina_set_poisoned(user,false) - 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) +-- override minetest.do_item_eat() so we can redirect hp_change to stamina +stamina.core_item_eat = minetest.do_item_eat +function minetest.do_item_eat(hp_change, replace_with_item, itemstack, player, pointed_thing) + for _, callback in ipairs(minetest.registered_on_item_eats) do + local result = callback(hp_change, replace_with_item, itemstack, player, pointed_thing) 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 + if not is_player(player) or not itemstack then return itemstack end - if not user then + local level = stamina.get_saturation(player) or 0 + if level >= settings.visual_max then + -- don't eat if player is full return itemstack end - local level = get_int_attribute(user, "stamina:level") or 0 - if level >= STAMINA_VISUAL_MAX then - return itemstack - end + local itemname = itemstack:get_name() + stamina.log("action", "%s eats %s for %s stamina, replace with %s", + player:get_player_name(), itemname, hp_change, replace_with_item) + minetest.sound_play("stamina_eat", {to_player = player:get_player_name(), gain = 0.7}) if hp_change > 0 then - level = level + hp_change - stamina_update_level(user, level) + stamina.change_saturation(player, hp_change) + stamina.set_exhaustion(player, 0) else -- assume hp_change < 0. - user:hud_change(user:get_attribute("stamina:hud_id"), "text", "stamina_hud_poison.png") - poison_player(2.0, -hp_change, 0, user) + stamina.poison(player, -hp_change, settings.poison_tick) end - minetest.sound_play("stamina_eat", {to_player = user:get_player_name(), gain = 0.7}) + if settings.eat_particles then + -- particle effect when eating + local pos = player:getpos() + pos.y = pos.y + 1.5 -- mouth level + local texture = minetest.registered_items[itemname].inventory_image + local dir = player:get_look_dir() - -- particle effect when eating - local pos = user:getpos() - pos.y = pos.y + 1.5 -- mouth level - local itemname = itemstack:get_name() - local texture = minetest.registered_items[itemname].inventory_image - local dir = user:get_look_dir() - - minetest.add_particlespawner({ - amount = 5, - time = 0.1, - minpos = pos, - maxpos = pos, - minvel = {x = dir.x - 1, y = dir.y, z = dir.z - 1}, - maxvel = {x = dir.x + 1, y = dir.y, z = dir.z + 1}, - minacc = {x = 0, y = -5, z = 0}, - maxacc = {x = 0, y = -9, z = 0}, - minexptime = 1, - maxexptime = 1, - minsize = 1, - maxsize = 2, - texture = texture, - }) + minetest.add_particlespawner({ + amount = 5, + time = 0.1, + minpos = pos, + maxpos = pos, + minvel = {x = dir.x - 1, y = dir.y, z = dir.z - 1}, + maxvel = {x = dir.x + 1, y = dir.y, z = dir.z + 1}, + minacc = {x = 0, y = -5, z = 0}, + maxacc = {x = 0, y = -9, z = 0}, + minexptime = 1, + maxexptime = 1, + minsize = 1, + maxsize = 2, + texture = texture, + }) + end itemstack:take_item() @@ -330,12 +465,13 @@ function stamina.eat(hp_change, replace_with_item, itemstack, user, pointed_thin if itemstack:is_empty() then itemstack:add_item(replace_with_item) else - local inv = user:get_inventory() + local inv = player:get_inventory() if inv:room_for_item("main", {name=replace_with_item}) then inv:add_item("main", replace_with_item) else + local pos = player:getpos() pos.y = math.floor(pos.y - 1.0) - core.add_item(pos, replace_with_item) + minetest.add_item(pos, replace_with_item) end end end @@ -343,47 +479,39 @@ function stamina.eat(hp_change, replace_with_item, itemstack, user, pointed_thin 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 level = STAMINA_VISUAL_MAX -- TODO - if get_int_attribute(player, "stamina:level") then - level = math.min(get_int_attribute(player, "stamina:level"), STAMINA_VISUAL_MAX) - else - player:set_attribute("stamina:level", level) - end - 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}, - max = 0, - }) - player:set_attribute("stamina:hud_id", id) - -- reset poisoned - player:set_attribute("stamina:poisoned", "no") - end) +minetest.register_on_joinplayer(function(player) + local level = stamina.get_saturation(player) or settings.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}, + max = 0, + }) + stamina.set_saturation(player, level) + player:set_attribute(attribute.hud_id, id) + -- reset poisoned + stamina.set_poisoned(player, false) +end) - minetest.register_globalstep(stamina_globaltimer) +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_level(player, STAMINA_VISUAL_MAX) - end) -end +minetest.register_on_placenode(function(pos, oldnode, player, ext) + stamina.exhaust_player(player, settings.exhaust_place, stamina.exhaustion_reasons.place) +end) +minetest.register_on_dignode(function(pos, oldnode, player, ext) + stamina.exhaust_player(player, settings.exhaust_dig, stamina.exhaustion_reasons.dig) +end) +minetest.register_on_craft(function(itemstack, player, old_craft_grid, craft_inv) + stamina.exhaust_player(player, settings.exhaust_craft, stamina.exhaustion_reasons.craft) +end) +minetest.register_on_punchplayer(function(player, hitter, time_from_last_punch, tool_capabilities, dir, damage) + stamina.exhaust_player(hitter, settings.exhaust_punch, stamina.exhaustion_reasons.punch) +end) +minetest.register_on_respawnplayer(function(player) + stamina.update_saturation(player, settings.visual_max) +end) diff --git a/settingtypes.txt b/settingtypes.txt new file mode 100644 index 0000000..310199d --- /dev/null +++ b/settingtypes.txt @@ -0,0 +1,23 @@ +stamina.enabled (Is stamina enabled?) bool true +stamina.sprint (Is sprint enabled?) bool true +stamina.sprint_particles (Are sprint particles enabled?) bool true +stamina.tick (time in seconds after that 1 saturation point is taken) float 800 +stamina.tick_min (stamina ticks won't reduce saturation below this level) int 4 +stamina.health_tick (time in seconds after player gets healed/damaged) float 4 +stamina.move_tick (time in seconds after the movement is checked) float 0.5 +stamina.exhaust_dig (exhaustion for digging a node) float 3 +stamina.exhaust_place (exhaustion for placing a node) float 1 +stamina.exhaust_move (exhaustion for moving) float 1.5 +stamina.exhaust_jump (exhaustion for jumping) float 5 +stamina.exhaust_craft (exhaustion for crafting) float 20 +stamina.exhaust_punch (exhaustion for punching) float 40 +stamina.exhaust_sprint (exhaustion for sprinting) float 28 +stamina.exhaust_lvl (exhaustion level at which saturation gets lowered) float 160 +stamina.heal (amount of HP a player gains per stamina.health_tick) int 1 0 20 +stamina.heal_lvl (minimum saturation needed for healing) int 5 1 20 +stamina.starve (amount of HP a player loses per stamina.health_tick) int 1 0 20 +stamina.starve_lvl (maximum stamina needed for starving) int 3 0 19 +stamina.visual_max (hud bar only extends to 20) int 20 2 20 +stamina.sprint_speed (how much faster a player can run if satiated) float 0.8 0 2 +stamina.sprint_jump (how much faster a player can jump if satiated) float 0.1 0 2 +stamina.eat_particles (Whether to generate particles when eating) bool true