Customizeable max breath and max hp for players

* it closes https://codeberg.org/minenux/minetest-engine-minetest/issues/33
* Statbars: Downscale bar to full 20 HP when exceeding this value
  Add default max HP for players and breath constants to builtin
  Document the constants
  Rename PLAYER_MAX_HP -> PLAYER_MAX_HP_DEFAULT
* implements PLAYER_MAX_BREATH_DEFAULT Customizeable max breath for players
  edbc533414
  so close https://github.com/minetest/minetest/issues/1972 that
  imports pr https://github.com/minetest/minetest/pull/6411
* implements PLAYER_MAX_HP_DEFAULT Respect object property hp_max field for players
  f7d50a8078
* so close https://github.com/minetest/minetest/issues/2246 that
  imports https://github.com/minetest/minetest/pull/6287
This commit is contained in:
mckaygerhard 2023-09-12 23:54:00 -04:00
parent 6ced19624f
commit ae90f8d699
12 changed files with 76 additions and 41 deletions

View File

@ -21,6 +21,10 @@ core.EMERGE_GENERATED = 4
-- constants.h -- constants.h
-- Size of mapblocks in nodes -- Size of mapblocks in nodes
core.MAP_BLOCKSIZE = 16 core.MAP_BLOCKSIZE = 16
-- Default maximal HP of a player
core.PLAYER_MAX_HP_DEFAULT = 20
-- Default maximal breath of a player
core.PLAYER_MAX_BREATH_DEFAULT = 11
-- light.h -- light.h
-- Maximum value for node 'light_source' parameter -- Maximum value for node 'light_source' parameter

View File

@ -6,7 +6,7 @@ local health_bar_definition =
hud_elem_type = "statbar", hud_elem_type = "statbar",
position = { x=0.5, y=1 }, position = { x=0.5, y=1 },
text = "heart.png", text = "heart.png",
number = 20, number = core.PLAYER_MAX_HP_DEFAULT,
direction = 0, direction = 0,
size = { x=24, y=24 }, size = { x=24, y=24 },
offset = { x=(-10*24)-25, y=-(48+24+16)}, offset = { x=(-10*24)-25, y=-(48+24+16)},
@ -17,7 +17,7 @@ local breath_bar_definition =
hud_elem_type = "statbar", hud_elem_type = "statbar",
position = { x=0.5, y=1 }, position = { x=0.5, y=1 },
text = "bubble.png", text = "bubble.png",
number = 20, number = core.PLAYER_MAX_BREATH_DEFAULT,
direction = 0, direction = 0,
size = { x=24, y=24 }, size = { x=24, y=24 },
offset = {x=25,y=-(48+24+16)}, offset = {x=25,y=-(48+24+16)},
@ -25,6 +25,15 @@ local breath_bar_definition =
local hud_ids = {} local hud_ids = {}
local function scaleToDefault(player, field)
-- Scale "hp" or "breath" to the default dimensions
local current = player["get_" .. field](player)
local nominal = core["PLAYER_MAX_".. field:upper() .. "_DEFAULT"]
local max_display = math.max(nominal,
math.max(player:get_properties()[field .. "_max"], current))
return current / max_display * nominal
end
local function initialize_builtin_statbars(player) local function initialize_builtin_statbars(player)
if not player:is_player() then if not player:is_player() then
@ -37,39 +46,39 @@ local function initialize_builtin_statbars(player)
return return
end end
if (hud_ids[name] == nil) then if not hud_ids[name] then
hud_ids[name] = {} hud_ids[name] = {}
-- flags are not transmitted to client on connect, we need to make sure -- flags are not transmitted to client on connect, we need to make sure
-- our current flags are transmitted by sending them actively -- our current flags are transmitted by sending them actively
player:hud_set_flags(player:hud_get_flags()) player:hud_set_flags(player:hud_get_flags())
end end
local hud = hud_ids[name]
if player:hud_get_flags().healthbar and enable_damage then if player:hud_get_flags().healthbar and enable_damage then
if hud_ids[name].id_healthbar == nil then if hud.id_healthbar == nil then
health_bar_definition.number = player:get_hp() local hud_def = table.copy(health_bar_definition)
hud_ids[name].id_healthbar = player:hud_add(health_bar_definition) hud_def.number = scaleToDefault(player, "hp")
end hud.id_healthbar = player:hud_add(hud_def)
else
if hud_ids[name].id_healthbar ~= nil then
player:hud_remove(hud_ids[name].id_healthbar)
hud_ids[name].id_healthbar = nil
end end
elseif hud.id_healthbar ~= nil then
player:hud_remove(hud.id_healthbar)
hud.id_healthbar = nil
end end
if (player:get_breath() < 11) then local breath_max = core.PLAYER_MAX_BREATH_DEFAULT
if player:hud_get_flags().breathbar and enable_damage then
if hud_ids[name].id_breathbar == nil then if player:get_properties().breath_max then breath_max = player:get_properties().breath_max end
hud_ids[name].id_breathbar = player:hud_add(breath_bar_definition)
if player:hud_get_flags().breathbar and enable_damage and
player:get_breath() < breath_max then
if hud.id_breathbar == nil then
local hud_def = table.copy(breath_bar_definition)
hud_def.number = 2 * scaleToDefault(player, "breath")
hud.id_breathbar = player:hud_add(hud_def)
end end
else elseif hud.id_breathbar ~= nil then
if hud_ids[name].id_breathbar ~= nil then player:hud_remove(hud.id_breathbar)
player:hud_remove(hud_ids[name].id_breathbar) hud.id_breathbar = nil
hud_ids[name].id_breathbar = nil
end
end
elseif hud_ids[name].id_breathbar ~= nil then
player:hud_remove(hud_ids[name].id_breathbar)
hud_ids[name].id_breathbar = nil
end end
end end
@ -101,7 +110,8 @@ local function player_event_handler(player,eventname)
initialize_builtin_statbars(player) initialize_builtin_statbars(player)
if hud_ids[name].id_healthbar ~= nil then if hud_ids[name].id_healthbar ~= nil then
player:hud_change(hud_ids[name].id_healthbar,"number",player:get_hp()) player:hud_change(hud_ids[name].id_healthbar,
"number", scaleToDefault(player, "hp"))
return true return true
end end
end end
@ -110,7 +120,8 @@ local function player_event_handler(player,eventname)
initialize_builtin_statbars(player) initialize_builtin_statbars(player)
if hud_ids[name].id_breathbar ~= nil then if hud_ids[name].id_breathbar ~= nil then
player:hud_change(hud_ids[name].id_breathbar,"number",player:get_breath()*2) player:hud_change(hud_ids[name].id_breathbar,
"number", 2 * scaleToDefault(player, "breath"))
return true return true
end end
end end

View File

@ -3345,7 +3345,8 @@ This is basically a reference to a C++ `ServerActiveObject`
* values: * values:
* `0`: player is drowning, * `0`: player is drowning,
* `1`-`10`: remaining number of bubbles * `1`-`10`: remaining number of bubbles
* `11`: bubbles bar is not shown * max (or 11): bubbles bar is not shown
* See Object Properties for more information
* `set_attribute(attribute, value)`: * `set_attribute(attribute, value)`:
* Sets an extra attribute with value on player. * Sets an extra attribute with value on player.
* `value` must be a string, or a number which will be converted to a string. * `value` must be a string, or a number which will be converted to a string.
@ -4023,6 +4024,9 @@ Definition tables
{ {
hp_max = 1, hp_max = 1,
-- ^ For players: Defaults to `minetest.PLAYER_MAX_HP_DEFAULT`
breath_max = 0,
-- ^ For players only. Defaults to `minetest.PLAYER_MAX_BREATH_DEFAULT`
physical = true, physical = true,
collide_with_objects = true, -- collide with other objects if physical = true collide_with_objects = true, -- collide with other objects if physical = true
weight = 5, weight = 5,

View File

@ -88,10 +88,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#define PLAYER_INVENTORY_SIZE (8 * 4) #define PLAYER_INVENTORY_SIZE (8 * 4)
// Maximum hit points of a player // Maximum hit points of a player
#define PLAYER_MAX_HP 20 #define PLAYER_MAX_HP_DEFAULT 20
// Maximal breath of a player // Maximal breath of a player
#define PLAYER_MAX_BREATH 11 #define PLAYER_MAX_BREATH_DEFAULT 11
// Number of different files to try to save a player to if the first fails // Number of different files to try to save a player to if the first fails
// (because of a case-insensitive filesystem) // (because of a case-insensitive filesystem)

View File

@ -779,7 +779,7 @@ PlayerSAO::PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, u16 peer_id
m_wield_index(0), m_wield_index(0),
m_position_not_sent(false), m_position_not_sent(false),
m_is_singleplayer(is_singleplayer), m_is_singleplayer(is_singleplayer),
m_breath(PLAYER_MAX_BREATH), m_breath(PLAYER_MAX_BREATH_DEFAULT),
m_pitch(0), m_pitch(0),
m_fov(0), m_fov(0),
m_wanted_range(0), m_wanted_range(0),
@ -795,7 +795,8 @@ PlayerSAO::PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, u16 peer_id
{ {
assert(m_peer_id != 0); // pre-condition assert(m_peer_id != 0); // pre-condition
m_prop.hp_max = PLAYER_MAX_HP; m_prop.hp_max = PLAYER_MAX_HP_DEFAULT;
m_prop.breath_max = PLAYER_MAX_BREATH_DEFAULT;
m_prop.physical = false; m_prop.physical = false;
m_prop.weight = 75; m_prop.weight = 75;
m_prop.collisionbox = aabb3f(-0.3f, -1.0f, -0.3f, 0.3f, 0.75f, 0.3f); m_prop.collisionbox = aabb3f(-0.3f, -1.0f, -0.3f, 0.3f, 0.75f, 0.3f);
@ -811,7 +812,8 @@ PlayerSAO::PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, u16 peer_id
// end of default appearance // end of default appearance
m_prop.is_visible = true; m_prop.is_visible = true;
m_prop.makes_footstep_sound = true; m_prop.makes_footstep_sound = true;
m_hp = PLAYER_MAX_HP; m_hp = m_prop.hp_max;
m_breath = m_prop.breath_max;
} }
PlayerSAO::~PlayerSAO() PlayerSAO::~PlayerSAO()
@ -1248,8 +1250,8 @@ void PlayerSAO::setHP(s16 hp)
if (hp < 0) if (hp < 0)
hp = 0; hp = 0;
else if (hp > PLAYER_MAX_HP) else if (hp > m_prop.hp_max)
hp = PLAYER_MAX_HP; hp = m_prop.hp_max;
if (hp < oldhp && !g_settings->getBool("enable_damage")) { if (hp < oldhp && !g_settings->getBool("enable_damage")) {
return; return;
@ -1270,7 +1272,7 @@ void PlayerSAO::setBreath(const u16 breath, bool send)
if (m_player && breath != m_breath) if (m_player && breath != m_breath)
m_player->setDirty(true); m_player->setDirty(true);
m_breath = MYMIN(breath, PLAYER_MAX_BREATH); m_breath = MYMIN(breath, m_prop.breath_max);
if (send) if (send)
m_env->getGameDef()->SendPlayerBreath(this); m_env->getGameDef()->SendPlayerBreath(this);

View File

@ -35,7 +35,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
LocalPlayer::LocalPlayer(Client *client, const char *name): LocalPlayer::LocalPlayer(Client *client, const char *name):
Player(name, client->idef()), Player(name, client->idef()),
parent(NULL), parent(NULL),
hp(PLAYER_MAX_HP), hp(PLAYER_MAX_HP_DEFAULT),
isAttached(false), isAttached(false),
touching_ground(false), touching_ground(false),
in_liquid(false), in_liquid(false),
@ -76,7 +76,7 @@ LocalPlayer::LocalPlayer(Client *client, const char *name):
m_old_node_below(32767,32767,32767), m_old_node_below(32767,32767,32767),
m_old_node_below_type("air"), m_old_node_below_type("air"),
m_can_jump(false), m_can_jump(false),
m_breath(PLAYER_MAX_BREATH), m_breath(PLAYER_MAX_BREATH_DEFAULT),
m_yaw(0), m_yaw(0),
m_pitch(0), m_pitch(0),
camera_barely_in_ceiling(false), camera_barely_in_ceiling(false),

View File

@ -54,6 +54,7 @@ std::string ObjectProperties::dump()
{ {
std::ostringstream os(std::ios::binary); std::ostringstream os(std::ios::binary);
os<<"hp_max="<<hp_max; os<<"hp_max="<<hp_max;
os<<", breath_max="<<breath_max;
os<<", physical="<<physical; os<<", physical="<<physical;
os<<", collideWithObjects="<<collideWithObjects; os<<", collideWithObjects="<<collideWithObjects;
os<<", weight="<<weight; os<<", weight="<<weight;
@ -118,6 +119,7 @@ void ObjectProperties::serialize(std::ostream &os) const
writeF1000(os, automatic_face_movement_max_rotation_per_sec); writeF1000(os, automatic_face_movement_max_rotation_per_sec);
os << serializeString(infotext); os << serializeString(infotext);
os << serializeString(wield_item); os << serializeString(wield_item);
writeU16(os, breath_max);
// Add stuff only at the bottom. // Add stuff only at the bottom.
// Never remove anything, because we don't want new versions of this // Never remove anything, because we don't want new versions of this
@ -161,6 +163,7 @@ void ObjectProperties::deSerialize(std::istream &is)
automatic_face_movement_max_rotation_per_sec = readF1000(is); automatic_face_movement_max_rotation_per_sec = readF1000(is);
infotext = deSerializeString(is); infotext = deSerializeString(is);
wield_item = deSerializeString(is); wield_item = deSerializeString(is);
breath_max = readU16(is);
}catch(SerializationError &e){} }catch(SerializationError &e){}
} }
else else

View File

@ -30,6 +30,7 @@ struct ObjectProperties
{ {
// Values are BS=1 // Values are BS=1
s16 hp_max; s16 hp_max;
u16 breath_max;
bool physical; bool physical;
bool collideWithObjects; bool collideWithObjects;
float weight; float weight;

View File

@ -109,7 +109,7 @@ void RemotePlayer::deSerialize(std::istream &is, const std::string &playername,
try { try {
sao->setHPRaw(args.getS32("hp")); sao->setHPRaw(args.getS32("hp"));
} catch(SettingNotFoundException &e) { } catch(SettingNotFoundException &e) {
sao->setHPRaw(PLAYER_MAX_HP); sao->setHPRaw(PLAYER_MAX_HP_DEFAULT);
} }
try { try {

View File

@ -183,8 +183,11 @@ void read_object_properties(lua_State *L, int index,
if(!lua_istable(L, index)) if(!lua_istable(L, index))
return; return;
prop->hp_max = getintfield_default(L, -1, "hp_max", 10); int hp_max = 0;
if (getintfield(L, -1, "hp_max", hp_max))
prop->hp_max = (s16)rangelim(hp_max, 0, S16_MAX);
getintfield(L, -1, "breath_max", prop->breath_max);
getboolfield(L, -1, "physical", prop->physical); getboolfield(L, -1, "physical", prop->physical);
getboolfield(L, -1, "collide_with_objects", prop->collideWithObjects); getboolfield(L, -1, "collide_with_objects", prop->collideWithObjects);
@ -286,6 +289,8 @@ void push_object_properties(lua_State *L, ObjectProperties *prop)
lua_newtable(L); lua_newtable(L);
lua_pushnumber(L, prop->hp_max); lua_pushnumber(L, prop->hp_max);
lua_setfield(L, -2, "hp_max"); lua_setfield(L, -2, "hp_max");
lua_pushnumber(L, prop->breath_max);
lua_setfield(L, -2, "breath_max");
lua_pushboolean(L, prop->physical); lua_pushboolean(L, prop->physical);
lua_setfield(L, -2, "physical"); lua_setfield(L, -2, "physical");
lua_pushboolean(L, prop->collideWithObjects); lua_pushboolean(L, prop->collideWithObjects);

View File

@ -750,6 +750,11 @@ int ObjectRef::l_set_properties(lua_State *L)
if (!prop) if (!prop)
return 0; return 0;
read_object_properties(L, 2, prop, getServer(L)->idef()); read_object_properties(L, 2, prop, getServer(L)->idef());
if (prop->hp_max < co->getHP()) {
co->setHP(prop->hp_max);
if (co->getType() == ACTIVEOBJECT_TYPE_PLAYER)
getServer(L)->SendPlayerHPOrDie((PlayerSAO *)co);
}
co->notifyObjectPropertiesModified(); co->notifyObjectPropertiesModified();
return 0; return 0;
} }

View File

@ -2687,8 +2687,8 @@ void Server::RespawnPlayer(u16 peer_id)
<< playersao->getPlayer()->getName() << playersao->getPlayer()->getName()
<< " respawns" << std::endl; << " respawns" << std::endl;
playersao->setHP(PLAYER_MAX_HP); playersao->setHP(playersao->accessObjectProperties()->hp_max);
playersao->setBreath(PLAYER_MAX_BREATH); playersao->setBreath(playersao->accessObjectProperties()->breath_max);
bool repositioned = m_script->on_respawnplayer(playersao); bool repositioned = m_script->on_respawnplayer(playersao);
if (!repositioned) { if (!repositioned) {