Fix number of tool uses being off by 1..32767 (#11110)

This commit is contained in:
Wuzzy 2021-10-31 22:33:33 +00:00 committed by GitHub
parent 38ba813c55
commit 6910c8d920
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 229 additions and 71 deletions

@ -613,7 +613,7 @@ function core.node_dig(pos, node, digger)
if wielded then
local wdef = wielded:get_definition()
local tp = wielded:get_tool_capabilities()
local dp = core.get_dig_params(def and def.groups, tp)
local dp = core.get_dig_params(def and def.groups, tp, wielded:get_wear())
if wdef and wdef.after_use then
wielded = wdef.after_use(wielded, digger, node, dp) or wielded
else

@ -1953,8 +1953,9 @@ to implement this.
### Uses (tools only)
Determines how many uses the tool has when it is used for digging a node,
of this group, of the maximum level. For lower leveled nodes, the use count
is multiplied by `3^leveldiff`.
of this group, of the maximum level. The maximum supported number of
uses is 65535. The special number 0 is used for infinite uses.
For lower leveled nodes, the use count is multiplied by `3^leveldiff`.
`leveldiff` is the difference of the tool's `maxlevel` `groupcaps` and the
node's `level` group. The node cannot be dug if `leveldiff` is less than zero.
@ -3475,8 +3476,8 @@ Helper functions
* `minetest.pointed_thing_to_face_pos(placer, pointed_thing)`: returns a
position.
* returns the exact position on the surface of a pointed node
* `minetest.get_dig_params(groups, tool_capabilities)`: Simulates an item
that digs a node.
* `minetest.get_dig_params(groups, tool_capabilities [, wear])`:
Simulates an item that digs a node.
Returns a table with the following fields:
* `diggable`: `true` if node can be dug, `false` otherwise.
* `time`: Time it would take to dig the node.
@ -3485,7 +3486,8 @@ Helper functions
Parameters:
* `groups`: Table of the node groups of the node that would be dug
* `tool_capabilities`: Tool capabilities table of the item
* `minetest.get_hit_params(groups, tool_capabilities [, time_from_last_punch])`:
* `wear`: Amount of wear the tool starts with (default: 0)
* `minetest.get_hit_params(groups, tool_capabilities [, time_from_last_punch [, wear]])`:
Simulates an item that punches an object.
Returns a table with the following fields:
* `hp`: How much damage the punch would cause.
@ -3494,6 +3496,7 @@ Helper functions
* `groups`: Damage groups of the object
* `tool_capabilities`: Tool capabilities table of the item
* `time_from_last_punch`: time in seconds since last punch action
* `wear`: Amount of wear the item starts with (default: 0)

@ -16,11 +16,11 @@ Tool types:
Tool materials:
* Dirt: dig nodes of rating 3, one use only
* Wood: dig nodes of rating 3
* Stone: dig nodes of rating 3 or 2
* Steel: dig nodes of rating 3, 2 or 1
* Mese: dig "everything" instantly
* n-Uses: can be used n times before breaking
]]
-- The hand
@ -92,20 +92,6 @@ minetest.register_tool("basetools:pick_mese", {
-- Pickaxes: Dig cracky
--
-- This should break after only 1 use
minetest.register_tool("basetools:pick_dirt", {
description = "Dirt Pickaxe".."\n"..
"Digs cracky=3".."\n"..
"1 use only",
inventory_image = "basetools_dirtpick.png",
tool_capabilities = {
max_drop_level=0,
groupcaps={
cracky={times={[3]=2.00}, uses=1, maxlevel=0}
},
},
})
minetest.register_tool("basetools:pick_wood", {
description = "Wooden Pickaxe".."\n"..
"Digs cracky=3",
@ -348,3 +334,31 @@ minetest.register_tool("basetools:dagger_steel", {
damage_groups = {fleshy=2},
}
})
-- Test tool uses and punch_attack_uses
local uses = { 1, 2, 3, 5, 10, 50, 100, 1000, 10000, 65535 }
for i=1, #uses do
local u = uses[i]
local color = string.format("#FF00%02X", math.floor(((i-1)/#uses) * 255))
minetest.register_tool("basetools:pick_uses_"..string.format("%05d", u), {
description = u.."-Uses Pickaxe".."\n"..
"Digs cracky=3",
inventory_image = "basetools_steelpick.png^[colorize:"..color..":127",
tool_capabilities = {
max_drop_level=0,
groupcaps={
cracky={times={[3]=0.1, [2]=0.2, [1]=0.3}, uses=u, maxlevel=0}
},
},
})
minetest.register_tool("basetools:sword_uses_"..string.format("%05d", u), {
description = u.."-Uses Sword".."\n"..
"Damage: fleshy=1",
inventory_image = "basetools_woodsword.png^[colorize:"..color..":127",
tool_capabilities = {
damage_groups = {fleshy=1},
punch_attack_uses = u,
},
})
end

Binary file not shown.

Before

Width:  |  Height:  |  Size: 307 B

@ -114,6 +114,59 @@ minetest.register_chatcommand("detach", {
end,
})
minetest.register_chatcommand("use_tool", {
params = "(dig <group> <leveldiff>) | (hit <damage_group> <time_from_last_punch>) [<uses>]",
description = "Apply tool wear a number of times, as if it were used for digging",
func = function(name, param)
local player = minetest.get_player_by_name(name)
if not player then
return false, "No player."
end
local mode, group, level, uses = string.match(param, "([a-z]+) ([a-z0-9]+) (-?%d+) (%d+)")
if not mode then
mode, group, level = string.match(param, "([a-z]+) ([a-z0-9]+) (-?%d+)")
uses = 1
end
if not mode or not group or not level then
return false
end
if mode ~= "dig" and mode ~= "hit" then
return false
end
local tool = player:get_wielded_item()
local caps = tool:get_tool_capabilities()
if not caps or tool:get_count() == 0 then
return false, "No tool in hand."
end
local actual_uses = 0
for u=1, uses do
local wear = tool:get_wear()
local dp
if mode == "dig" then
dp = minetest.get_dig_params({[group]=3, level=level}, caps, wear)
else
dp = minetest.get_hit_params({[group]=100}, caps, level, wear)
end
tool:add_wear(dp.wear)
actual_uses = actual_uses + 1
if tool:get_count() == 0 then
break
end
end
player:set_wielded_item(tool)
if tool:get_count() == 0 then
return true, string.format("Tool used %d time(s). "..
"The tool broke after %d use(s).", uses, actual_uses)
else
local wear = tool:get_wear()
return true, string.format("Tool used %d time(s). "..
"Final wear=%d", uses, wear)
end
end,
})
-- Use this to test waypoint capabilities
minetest.register_chatcommand("test_waypoints", {
params = "[change_immediate]",

@ -1870,7 +1870,8 @@ bool GenericCAO::directReportPunch(v3f dir, const ItemStack *punchitem,
m_armor_groups,
toolcap,
punchitem,
time_from_last_punch);
time_from_last_punch,
punchitem->wear);
if(result.did_punch && result.damage != 0)
{

@ -3619,7 +3619,8 @@ void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
// cheat detection.
// Get digging parameters
DigParams params = getDigParams(nodedef_manager->get(n).groups,
&selected_item.getToolCapabilities(itemdef_manager));
&selected_item.getToolCapabilities(itemdef_manager),
selected_item.wear);
// If can't dig, try hand
if (!params.diggable) {

@ -1119,8 +1119,8 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
float time_from_last_punch =
playersao->resetTimeFromLastPunch();
u16 wear = pointed_object->punch(dir, &toolcap, playersao,
time_from_last_punch);
u32 wear = pointed_object->punch(dir, &toolcap, playersao,
time_from_last_punch, tool_item.wear);
// Callback may have changed item, so get it again
playersao->getWieldedItem(&selected_item);
@ -1173,7 +1173,8 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
// Get diggability and expected digging time
DigParams params = getDigParams(m_nodedef->get(n).groups,
&selected_item.getToolCapabilities(m_itemdef));
&selected_item.getToolCapabilities(m_itemdef),
selected_item.wear);
// If can't dig, try hand
if (!params.diggable) {
params = getDigParams(m_nodedef->get(n).groups,

@ -174,7 +174,7 @@ int ObjectRef::l_punch(lua_State *L)
v3f dir = readParam<v3f>(L, 5, sao->getBasePosition() - puncher->getBasePosition());
dir.normalize();
u16 wear = sao->punch(dir, &toolcap, puncher, time_from_last_punch);
u32 wear = sao->punch(dir, &toolcap, puncher, time_from_last_punch);
lua_pushnumber(L, wear);
return 1;

@ -160,28 +160,33 @@ int ModApiUtil::l_write_json(lua_State *L)
return 1;
}
// get_dig_params(groups, tool_capabilities)
// get_dig_params(groups, tool_capabilities[, wear])
int ModApiUtil::l_get_dig_params(lua_State *L)
{
NO_MAP_LOCK_REQUIRED;
ItemGroupList groups;
read_groups(L, 1, groups);
ToolCapabilities tp = read_tool_capabilities(L, 2);
if (lua_isnoneornil(L, 3)) {
push_dig_params(L, getDigParams(groups, &tp));
} else {
u16 wear = readParam<int>(L, 3);
push_dig_params(L, getDigParams(groups, &tp, wear));
}
return 1;
}
// get_hit_params(groups, tool_capabilities[, time_from_last_punch])
// get_hit_params(groups, tool_capabilities[, time_from_last_punch, [, wear]])
int ModApiUtil::l_get_hit_params(lua_State *L)
{
NO_MAP_LOCK_REQUIRED;
std::unordered_map<std::string, int> groups;
read_groups(L, 1, groups);
ToolCapabilities tp = read_tool_capabilities(L, 2);
if(lua_isnoneornil(L, 3))
push_hit_params(L, getHitParams(groups, &tp));
else
push_hit_params(L, getHitParams(groups, &tp, readParam<float>(L, 3)));
float time_from_last_punch = readParam<float>(L, 3, 1000000);
int wear = readParam<int>(L, 4, 0);
push_hit_params(L, getHitParams(groups, &tp,
time_from_last_punch, wear));
return 1;
}

@ -50,10 +50,10 @@ private:
// write_json(data[, styled])
static int l_write_json(lua_State *L);
// get_dig_params(groups, tool_capabilities[, time_from_last_punch])
// get_dig_params(groups, tool_capabilities[, wear])
static int l_get_dig_params(lua_State *L);
// get_hit_params(groups, tool_capabilities[, time_from_last_punch])
// get_hit_params(groups, tool_capabilities[, time_from_last_punch[, wear]])
static int l_get_hit_params(lua_State *L);
// check_password_entry(name, entry, password)

@ -305,10 +305,11 @@ void LuaEntitySAO::getStaticData(std::string *result) const
*result = os.str();
}
u16 LuaEntitySAO::punch(v3f dir,
u32 LuaEntitySAO::punch(v3f dir,
const ToolCapabilities *toolcap,
ServerActiveObject *puncher,
float time_from_last_punch)
float time_from_last_punch,
u16 initial_wear)
{
if (!m_registered) {
// Delete unknown LuaEntities when punched
@ -326,7 +327,8 @@ u16 LuaEntitySAO::punch(v3f dir,
m_armor_groups,
toolcap,
&tool_item,
time_from_last_punch);
time_from_last_punch,
initial_wear);
bool damage_handled = m_env->getScriptIface()->luaentity_Punch(m_id, puncher,
time_from_last_punch, toolcap, dir, result.did_punch ? result.damage : 0);

@ -44,9 +44,10 @@ public:
bool isStaticAllowed() const { return m_prop.static_save; }
bool shouldUnload() const { return true; }
void getStaticData(std::string *result) const;
u16 punch(v3f dir, const ToolCapabilities *toolcap = nullptr,
u32 punch(v3f dir, const ToolCapabilities *toolcap = nullptr,
ServerActiveObject *puncher = nullptr,
float time_from_last_punch = 1000000.0f);
float time_from_last_punch = 1000000.0f,
u16 initial_wear = 0);
void rightClick(ServerActiveObject *clicker);
void setPos(const v3f &pos);
void moveTo(v3f pos, bool continuous);

@ -409,10 +409,11 @@ void PlayerSAO::setLookPitchAndSend(const float pitch)
m_env->getGameDef()->SendMovePlayer(m_peer_id);
}
u16 PlayerSAO::punch(v3f dir,
u32 PlayerSAO::punch(v3f dir,
const ToolCapabilities *toolcap,
ServerActiveObject *puncher,
float time_from_last_punch)
float time_from_last_punch,
u16 initial_wear)
{
if (!toolcap)
return 0;
@ -430,7 +431,7 @@ u16 PlayerSAO::punch(v3f dir,
s32 old_hp = getHP();
HitParams hitparams = getHitParams(m_armor_groups, toolcap,
time_from_last_punch);
time_from_last_punch, initial_wear);
PlayerSAO *playersao = m_player->getPlayerSAO();

@ -109,8 +109,8 @@ public:
Interaction interface
*/
u16 punch(v3f dir, const ToolCapabilities *toolcap, ServerActiveObject *puncher,
float time_from_last_punch);
u32 punch(v3f dir, const ToolCapabilities *toolcap, ServerActiveObject *puncher,
float time_from_last_punch, u16 initial_wear = 0);
void rightClick(ServerActiveObject *clicker);
void setHP(s32 hp, const PlayerHPChangeReason &reason) override
{

@ -145,11 +145,12 @@ public:
virtual bool shouldUnload() const
{ return true; }
// Returns tool wear
virtual u16 punch(v3f dir,
// Returns added tool wear
virtual u32 punch(v3f dir,
const ToolCapabilities *toolcap = nullptr,
ServerActiveObject *puncher = nullptr,
float time_from_last_punch = 1000000.0f)
float time_from_last_punch = 1000000.0f,
u16 initial_wear = 0)
{ return 0; }
virtual void rightClick(ServerActiveObject *clicker)
{}

@ -183,9 +183,74 @@ void ToolCapabilities::deserializeJson(std::istream &is)
}
}
DigParams getDigParams(const ItemGroupList &groups,
const ToolCapabilities *tp)
static u32 calculateResultWear(const u32 uses, const u16 initial_wear)
{
if (uses == 0) {
// Trivial case: Infinite uses
return 0;
}
/* Finite uses. This is not trivial,
as the maximum wear is not neatly evenly divisible by
most possible uses numbers. For example, for 128
uses, the calculation of wear is trivial, as
65536 / 128 uses = 512 wear,
so the tool will get 512 wear 128 times in its lifetime.
But for a number like 130, this does not work:
65536 / 130 uses = 504.123... wear.
Since wear must be an integer, we will get
504*130 = 65520, which would lead to the wrong number
of uses.
Instead, we partition the "wear range" into blocks:
A block represents a single use and can be
of two possible sizes: normal and oversized.
A normal block is equal to floor(65536 / uses).
An oversized block is a normal block plus 1.
Then we determine how many oversized and normal
blocks we need and finally, whether we add
the normal wear or the oversized wear.
Example for 130 uses:
* Normal wear = 504
* Number of normal blocks = 114
* Oversized wear = 505
* Number of oversized blocks = 16
If we add everything together, we get:
114*504 + 16*505 = 65536
*/
u32 result_wear;
u32 wear_normal = ((U16_MAX+1) / uses);
// Will be non-zero if its not evenly divisible
u16 blocks_oversize = (U16_MAX+1) % uses;
// Whether to add one extra wear point in case
// of oversized wear.
u16 wear_extra = 0;
if (blocks_oversize > 0) {
u16 blocks_normal = uses - blocks_oversize;
/* When the wear has reached this value, we
know that wear_normal has been applied
for blocks_normal times, therefore,
only oversized blocks remain.
This also implies the raw tool wear number
increases a bit faster after this point,
but this should be barely noticable by the
player.
*/
u16 wear_extra_at = blocks_normal * wear_normal;
if (initial_wear >= wear_extra_at) {
wear_extra = 1;
}
}
result_wear = wear_normal + wear_extra;
return result_wear;
}
DigParams getDigParams(const ItemGroupList &groups,
const ToolCapabilities *tp,
const u16 initial_wear)
{
// Group dig_immediate defaults to fixed time and no wear
if (tp->groupcaps.find("dig_immediate") == tp->groupcaps.cend()) {
switch (itemgroup_get(groups, "dig_immediate")) {
@ -201,7 +266,7 @@ DigParams getDigParams(const ItemGroupList &groups,
// Values to be returned (with a bit of conversion)
bool result_diggable = false;
float result_time = 0.0;
float result_wear = 0.0;
u32 result_wear = 0;
std::string result_main_group;
int level = itemgroup_get(groups, "level");
@ -224,20 +289,22 @@ DigParams getDigParams(const ItemGroupList &groups,
if (!result_diggable || time < result_time) {
result_time = time;
result_diggable = true;
if (cap.uses != 0)
result_wear = 1.0 / cap.uses / pow(3.0, leveldiff);
else
result_wear = 0;
// The actual number of uses increases
// exponentially with leveldiff.
// If the levels are equal, real_uses equals cap.uses.
u32 real_uses = cap.uses * pow(3.0, leveldiff);
real_uses = MYMIN(real_uses, U16_MAX);
result_wear = calculateResultWear(real_uses, initial_wear);
result_main_group = groupname;
}
}
u16 wear_i = U16_MAX * result_wear;
return DigParams(result_diggable, result_time, wear_i, result_main_group);
return DigParams(result_diggable, result_time, result_wear, result_main_group);
}
HitParams getHitParams(const ItemGroupList &armor_groups,
const ToolCapabilities *tp, float time_from_last_punch)
const ToolCapabilities *tp, float time_from_last_punch,
u16 initial_wear)
{
s16 damage = 0;
float result_wear = 0.0f;
@ -249,10 +316,12 @@ HitParams getHitParams(const ItemGroupList &armor_groups,
damage += damageGroup.second * punch_interval_multiplier * armor / 100.0;
}
if (tp->punch_attack_uses > 0)
result_wear = 1.0f / tp->punch_attack_uses * punch_interval_multiplier;
if (tp->punch_attack_uses > 0) {
result_wear = calculateResultWear(tp->punch_attack_uses, initial_wear);
result_wear *= punch_interval_multiplier;
}
u16 wear_i = U16_MAX * result_wear;
u32 wear_i = (u32) result_wear;
return {damage, wear_i};
}
@ -266,7 +335,8 @@ PunchDamageResult getPunchDamage(
const ItemGroupList &armor_groups,
const ToolCapabilities *toolcap,
const ItemStack *punchitem,
float time_from_last_punch
float time_from_last_punch,
u16 initial_wear
){
bool do_hit = true;
{
@ -286,7 +356,8 @@ PunchDamageResult getPunchDamage(
if(do_hit)
{
HitParams hitparams = getHitParams(armor_groups, toolcap,
time_from_last_punch);
time_from_last_punch,
punchitem->wear);
result.did_punch = true;
result.wear = hitparams.wear;
result.damage = hitparams.hp;

@ -88,10 +88,10 @@ struct DigParams
// Digging time in seconds
float time;
// Caused wear
u16 wear;
u32 wear; // u32 because wear could be 65536 (single-use tool)
std::string main_group;
DigParams(bool a_diggable = false, float a_time = 0.0f, u16 a_wear = 0,
DigParams(bool a_diggable = false, float a_time = 0.0f, u32 a_wear = 0,
const std::string &a_main_group = ""):
diggable(a_diggable),
time(a_time),
@ -101,21 +101,24 @@ struct DigParams
};
DigParams getDigParams(const ItemGroupList &groups,
const ToolCapabilities *tp);
const ToolCapabilities *tp,
const u16 initial_wear = 0);
struct HitParams
{
s16 hp;
u16 wear;
// Caused wear
u32 wear; // u32 because wear could be 65536 (single-use weapon)
HitParams(s16 hp_ = 0, u16 wear_ = 0):
HitParams(s16 hp_ = 0, u32 wear_ = 0):
hp(hp_),
wear(wear_)
{}
};
HitParams getHitParams(const ItemGroupList &armor_groups,
const ToolCapabilities *tp, float time_from_last_punch);
const ToolCapabilities *tp, float time_from_last_punch,
u16 initial_wear = 0);
HitParams getHitParams(const ItemGroupList &armor_groups,
const ToolCapabilities *tp);
@ -135,7 +138,8 @@ PunchDamageResult getPunchDamage(
const ItemGroupList &armor_groups,
const ToolCapabilities *toolcap,
const ItemStack *punchitem,
float time_from_last_punch
float time_from_last_punch,
u16 initial_wear = 0
);
f32 getToolRange(const ItemDefinition &def_selected, const ItemDefinition &def_hand);