Rework tool_capabilities a bit (maxwear->uses, scale dig time according to leveldiff)

This commit is contained in:
Perttu Ahola 2012-03-29 13:35:20 +03:00
parent ace005bf7c
commit 440e9cdbef
5 changed files with 137 additions and 75 deletions

@ -255,8 +255,8 @@ Groups of items can define what kind of an item it is (eg. wool).
Groups of nodes Groups of nodes
---------------- ----------------
In addition to the general item things, whether a node is diggable and how In addition to the general item things, groups are used to define whether
long it takes is defined by using groups. a node is destroyable and how long it takes to destroy by a tool.
Groups of entities Groups of entities
------------------- -------------------
@ -292,6 +292,7 @@ Special groups
damage, and get weared out much faster, or not be able to get drops damage, and get weared out much faster, or not be able to get drops
from destroyed nodes. from destroyed nodes.
- 0 is something that is directly accessible at the start of gameplay - 0 is something that is directly accessible at the start of gameplay
- There is no upper limit
- dig_immediate: (player can always pick up node without tool wear) - dig_immediate: (player can always pick up node without tool wear)
- 2: node is removed without tool wear after 0.5 seconds or so - 2: node is removed without tool wear after 0.5 seconds or so
(rail, sign) (rail, sign)
@ -299,6 +300,7 @@ Special groups
Known damage and digging time defining groups Known damage and digging time defining groups
---------------------------------------------- ----------------------------------------------
Valid ratings for these are 0, 1, 2 and 3, unless otherwise stated.
- crumbly: dirt, sand - crumbly: dirt, sand
- cracky: tough but crackable stuff like stone. - cracky: tough but crackable stuff like stone.
- snappy: something that can be cut using fine tools; eg. leaves, small - snappy: something that can be cut using fine tools; eg. leaves, small
@ -334,59 +336,105 @@ Groups such as **crumbly**, **cracky** and **snappy** are used for this
purpose. Rating is 1, 2 or 3. A higher rating for such a group implies purpose. Rating is 1, 2 or 3. A higher rating for such a group implies
faster digging time. faster digging time.
Also, the **level** group is used. The **level** group is used to limit the toughness of nodes a tool can dig
and to scale the digging times / damage to a greater extent.
^ PLEASE DO UNDERSTAND THIS, otherwise you cannot use the system to it's
full potential.
Tools define their properties by a list of parameters for groups. They Tools define their properties by a list of parameters for groups. They
cannot dig other groups; thus it is important to use a standard bunch of cannot dig other groups; thus it is important to use a standard bunch of
groups to enable interaction with tools. groups to enable interaction with tools.
**Example definition of the digging capabilities of a tool:** **Tools define:**
* Full punch interval
* Maximum drop level
* For an arbitrary list of groups:
* Uses (until the tool breaks)
* Maximum level (usually 0, 1, 2 or 3)
* Digging times
**Full punch interval**:
When used as a weapon, the tool will do full damage if this time is spent
between punches. If eg. half the time is spent, the tool will do half
damage.
**Maximum drop level**
Suggests the maximum level of node, when dug with the tool, that will drop
it's useful item. (eg. iron ore to drop a lump of iron).
- This is not automated; it is the responsibility of the node definition
to implement this
**Uses**
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.
- uses=10, leveldiff=0 -> actual_uses=10
- uses=10, leveldiff=1 -> actual_uses=30
- uses=10, leveldiff=2 -> actual_uses=90
**Maximum level**
Tells what is the maximum level of a node of this group that the tool will
be able to dig.
**Digging times**
List of digging times for different ratings of the group, for nodes of the
maximum level.
* For example, as a lua table, ''times={2=2.00, 3=0.70}''. This would
result the tool to be able to dig nodes that have a rating of 2 or 3
for this group, and unable to dig the rating 1, which is the toughest.
Unless there is a matching group that enables digging otherwise.
* For entities, damage equals the amount of nodes dug in the time spent
between hits, with a maximum time of ''full_punch_interval''.
Example definition of the capabilities of a tool
-------------------------------------------------
tool_capabilities = { tool_capabilities = {
full_punch_interval=1.5, full_punch_interval=1.5,
max_drop_level=1, max_drop_level=1,
groupcaps={ groupcaps={
crumbly={maxwear=0.01, maxlevel=2, times={[1]=0.80, [2]=0.60, [3]=0.40}} crumbly={maxlevel=2, uses=20, times={[1]=1.60, [2]=1.20, [3]=0.80}}
} }
} }
**Tools define:** This makes the tool be able to dig nodes that fullfill both of these:
* Full punch interval Maximum drop level For an arbitrary list of groups: - Have the **crumbly** group
* Maximum level (usually 0, 1, 2 or 3) Maximum wear (0...1) Digging times - Have a **level** group less or equal to 2
**Full punch interval**: When used as a weapon, the tool will do full Table of resulting digging times:
damage if this time is spent between punches. If eg. half the time is crumbly 0 1 2 3 4 <- level
spent, the tool will do half damage. -> 0 - - - - -
1 0.80 1.60 1.60 - -
2 0.60 1.20 1.20 - -
3 0.40 0.80 0.80 - -
**Maximum drop level** suggests the maximum level of node, when dug with level diff: 2 1 0 -1 -2
the tool, that will drop it's useful item. (eg. iron ore to drop a lump of
iron).
**Maximum level** tells what is the maximum level of a node of this group Table of resulting tool uses:
that the tool will be able to dig. -> 0 - - - - -
1 180 60 20 - -
2 180 60 20 - -
3 180 60 20 - -
**Maximum wear** determines how much the tool wears out when it is used for Notes:
digging a node, of this group, of the maximum level. For lower leveled - At crumbly=0, the node is not diggable.
tools, the wear is divided by //4// to the exponent //level difference//. - At crumbly=3, the level difference digging time divider kicks in and makes
This means for a maximum wear of 0.1, a level difference 1 will result in easy nodes to be quickly breakable.
wear=0.1/4=0.025, and a level difference of 2 will result in - At level > 2, the node is not diggable, because it's level > maxlevel
wear=0.1/(4*4)=0.00625.
**Digging times** is basically a list of digging times for different
ratings of the group. It also determines the damage done to entities, based
on their "armor groups".
* For example, as a lua table, ''times={2=2.00, 3=0.70}''. This would
* result the tool to be able to dig nodes that have a rating of 2 or 3
* for this group, and unable to dig the rating 1, which is the toughest.
* Unless there is a matching group that enables digging otherwise. For
* entities, damage equals the amount of nodes dug in the time spent
* between hits, with a maximum of ''full_punch_interval''.
Entity damage mechanism Entity damage mechanism
------------------------ ------------------------
Damage calculation:
- Take the time spent after the last hit
- Limit time to full_punch_interval
- Take the damage groups, assume a node has them
- Damage in HP is the amount of nodes destroyed in this time.
Client predicts damage based on damage groups. Because of this, it is able to Client predicts damage based on damage groups. Because of this, it is able to
give an immediate response when an entity is damaged or dies; the response is give an immediate response when an entity is damaged or dies; the response is
pre-defined somehow (eg. by defining a sprite animation) (not implemented; pre-defined somehow (eg. by defining a sprite animation) (not implemented;
TODO). TODO).
- Currently a smoke puff will appear when an entity dies.
The group **immortal** will completely disable normal damage. The group **immortal** will completely disable normal damage.

@ -24,10 +24,10 @@ minetest.register_item(":", {
full_punch_interval = 1.0, full_punch_interval = 1.0,
max_drop_level = 0, max_drop_level = 0,
groupcaps = { groupcaps = {
fleshy = {times={[2]=2.00, [3]=1.00}, maxwear=0, maxlevel=1}, fleshy = {times={[2]=2.00, [3]=1.00}, uses=0, maxlevel=1},
crumbly = {times={[3]=0.70}, maxwear=0, maxlevel=1}, crumbly = {times={[2]=3.00, [3]=0.70}, uses=0, maxlevel=1},
snappy = {times={[3]=0.40}, maxwear=0, maxlevel=1}, snappy = {times={[3]=0.40}, uses=0, maxlevel=1},
oddly_breakable_by_hand = {times={[1]=3.50,[2]=2.00,[3]=0.70}, maxwear=0, maxlevel=3}, oddly_breakable_by_hand = {times={[1]=7.00,[2]=4.00,[3]=1.40}, uses=0, maxlevel=3},
} }
} }
}) })
@ -38,7 +38,7 @@ minetest.register_tool("default:pick_wood", {
tool_capabilities = { tool_capabilities = {
max_drop_level=0, max_drop_level=0,
groupcaps={ groupcaps={
cracky={times={[2]=1.50, [3]=0.80}, maxwear=0.1, maxlevel=1} cracky={times={[2]=2.00, [3]=1.20}, uses=10, maxlevel=1}
} }
}, },
}) })
@ -48,7 +48,7 @@ minetest.register_tool("default:pick_stone", {
tool_capabilities = { tool_capabilities = {
max_drop_level=0, max_drop_level=0,
groupcaps={ groupcaps={
cracky={times={[1]=1.50, [2]=0.80, [3]=0.60}, maxwear=0.05, maxlevel=1} cracky={times={[1]=2.00, [2]=1.20, [3]=0.80}, uses=20, maxlevel=1}
} }
}, },
}) })
@ -58,7 +58,7 @@ minetest.register_tool("default:pick_steel", {
tool_capabilities = { tool_capabilities = {
max_drop_level=1, max_drop_level=1,
groupcaps={ groupcaps={
cracky={times={[1]=1.00, [2]=0.60, [3]=0.40}, maxwear=0.1, maxlevel=2} cracky={times={[1]=4.00, [2]=1.60, [3]=1.00}, uses=10, maxlevel=2}
} }
}, },
}) })
@ -69,9 +69,9 @@ minetest.register_tool("default:pick_mese", {
full_punch_interval = 1.0, full_punch_interval = 1.0,
max_drop_level=3, max_drop_level=3,
groupcaps={ groupcaps={
cracky={times={[1]=0.2, [2]=0.2, [3]=0.2}, maxwear=0.05, maxlevel=3}, cracky={times={[1]=2.0, [2]=1.0, [3]=0.5}, uses=20, maxlevel=3},
crumbly={times={[1]=0.2, [2]=0.2, [3]=0.2}, maxwear=0.05, maxlevel=3}, crumbly={times={[1]=2.0, [2]=1.0, [3]=0.5}, uses=20, maxlevel=3},
snappy={times={[1]=0.2, [2]=0.2, [3]=0.2}, maxwear=0.05, maxlevel=3} snappy={times={[1]=2.0, [2]=1.0, [3]=0.5}, uses=20, maxlevel=3}
} }
}, },
}) })
@ -81,7 +81,7 @@ minetest.register_tool("default:shovel_wood", {
tool_capabilities = { tool_capabilities = {
max_drop_level=0, max_drop_level=0,
groupcaps={ groupcaps={
crumbly={times={[1]=1.50, [2]=0.80, [3]=0.50}, maxwear=0.1, maxlevel=1} crumbly={times={[1]=2.00, [2]=0.80, [3]=0.50}, uses=10, maxlevel=1}
} }
}, },
}) })
@ -91,7 +91,7 @@ minetest.register_tool("default:shovel_stone", {
tool_capabilities = { tool_capabilities = {
max_drop_level=0, max_drop_level=0,
groupcaps={ groupcaps={
crumbly={times={[1]=0.80, [2]=0.50, [3]=0.30}, maxwear=0.05, maxlevel=1} crumbly={times={[1]=1.20, [2]=0.50, [3]=0.30}, uses=20, maxlevel=1}
} }
}, },
}) })
@ -101,7 +101,7 @@ minetest.register_tool("default:shovel_steel", {
tool_capabilities = { tool_capabilities = {
max_drop_level=1, max_drop_level=1,
groupcaps={ groupcaps={
crumbly={times={[1]=0.50, [2]=0.35, [3]=0.30}, maxwear=0.1, maxlevel=2} crumbly={times={[1]=1.00, [2]=0.70, [3]=0.60}, uses=10, maxlevel=2}
} }
}, },
}) })
@ -111,8 +111,8 @@ minetest.register_tool("default:axe_wood", {
tool_capabilities = { tool_capabilities = {
max_drop_level=0, max_drop_level=0,
groupcaps={ groupcaps={
choppy={times={[2]=1.40, [3]=0.80}, maxwear=0.1, maxlevel=1}, choppy={times={[2]=1.40, [3]=0.80}, uses=10, maxlevel=1},
fleshy={times={[2]=1.50, [3]=0.80}, maxwear=0.1, maxlevel=1} fleshy={times={[2]=1.50, [3]=0.80}, uses=10, maxlevel=1}
} }
}, },
}) })
@ -122,8 +122,8 @@ minetest.register_tool("default:axe_stone", {
tool_capabilities = { tool_capabilities = {
max_drop_level=0, max_drop_level=0,
groupcaps={ groupcaps={
choppy={times={[1]=1.50, [2]=1.00, [3]=0.60}, maxwear=0.05, maxlevel=1}, choppy={times={[1]=1.50, [2]=1.00, [3]=0.60}, uses=20, maxlevel=1},
fleshy={times={[2]=1.30, [3]=0.70}, maxwear=0.05, maxlevel=1} fleshy={times={[2]=1.30, [3]=0.70}, uses=20, maxlevel=1}
} }
}, },
}) })
@ -133,8 +133,8 @@ minetest.register_tool("default:axe_steel", {
tool_capabilities = { tool_capabilities = {
max_drop_level=1, max_drop_level=1,
groupcaps={ groupcaps={
choppy={times={[1]=1.00, [2]=0.80, [3]=0.50}, maxwear=0.1, maxlevel=2}, choppy={times={[1]=2.00, [2]=1.60, [3]=1.00}, uses=10, maxlevel=2},
fleshy={times={[2]=1.10, [3]=0.60}, maxwear=0.03, maxlevel=1} fleshy={times={[2]=1.10, [3]=0.60}, uses=40, maxlevel=1}
} }
}, },
}) })
@ -145,9 +145,9 @@ minetest.register_tool("default:sword_wood", {
full_punch_interval = 1.0, full_punch_interval = 1.0,
max_drop_level=0, max_drop_level=0,
groupcaps={ groupcaps={
fleshy={times={[2]=1.10, [3]=0.60}, maxwear=0.1, maxlevel=1}, fleshy={times={[2]=1.10, [3]=0.60}, uses=10, maxlevel=1},
snappy={times={[2]=1.00, [3]=0.50}, maxwear=0.1, maxlevel=1}, snappy={times={[2]=1.00, [3]=0.50}, uses=10, maxlevel=1},
choppy={times={[3]=1.00}, maxwear=0.05, maxlevel=0} choppy={times={[3]=1.00}, uses=20, maxlevel=0}
} }
} }
}) })
@ -158,9 +158,9 @@ minetest.register_tool("default:sword_stone", {
full_punch_interval = 1.0, full_punch_interval = 1.0,
max_drop_level=0, max_drop_level=0,
groupcaps={ groupcaps={
fleshy={times={[2]=0.80, [3]=0.40}, maxwear=0.05, maxlevel=1}, fleshy={times={[2]=0.80, [3]=0.40}, uses=20, maxlevel=1},
snappy={times={[2]=0.80, [3]=0.40}, maxwear=0.05, maxlevel=1}, snappy={times={[2]=0.80, [3]=0.40}, uses=20, maxlevel=1},
choppy={times={[3]=0.90}, maxwear=0.05, maxlevel=0} choppy={times={[3]=0.90}, uses=20, maxlevel=0}
} }
} }
}) })
@ -171,9 +171,9 @@ minetest.register_tool("default:sword_steel", {
full_punch_interval = 1.0, full_punch_interval = 1.0,
max_drop_level=1, max_drop_level=1,
groupcaps={ groupcaps={
fleshy={times={[1]=1.00, [2]=0.40, [3]=0.20}, maxwear=0.1, maxlevel=2}, fleshy={times={[1]=2.00, [2]=0.80, [3]=0.40}, uses=10, maxlevel=2},
snappy={times={[2]=0.70, [3]=0.30}, maxwear=0.03, maxlevel=1}, snappy={times={[2]=0.70, [3]=0.30}, uses=40, maxlevel=1},
choppy={times={[3]=0.70}, maxwear=0.03, maxlevel=0} choppy={times={[3]=0.70}, uses=40, maxlevel=0}
} }
} }
}) })
@ -962,7 +962,7 @@ minetest.register_node("default:mese", {
description = "Mese", description = "Mese",
tile_images = {"default_mese.png"}, tile_images = {"default_mese.png"},
is_ground_content = true, is_ground_content = true,
groups = {cracky=1}, groups = {cracky=1,level=2},
sounds = default.node_sound_defaults(), sounds = default.node_sound_defaults(),
}) })

@ -670,8 +670,19 @@ static ToolCapabilities read_tool_capabilities(
// This will be created // This will be created
ToolGroupCap groupcap; ToolGroupCap groupcap;
// Read simple parameters // Read simple parameters
getfloatfield(L, table_groupcap, "maxwear", groupcap.maxwear);
getintfield(L, table_groupcap, "maxlevel", groupcap.maxlevel); getintfield(L, table_groupcap, "maxlevel", groupcap.maxlevel);
getintfield(L, table_groupcap, "uses", groupcap.uses);
// DEPRECATED: maxwear
float maxwear = 0;
if(getfloatfield(L, table_groupcap, "maxwear", maxwear)){
if(maxwear != 0)
groupcap.uses = 1.0/maxwear;
else
groupcap.uses = 0;
infostream<<script_get_backtrace(L)<<std::endl;
infostream<<"WARNING: field \"maxwear\" is deprecated; "
<<"should replace with uses=1/maxwear"<<std::endl;
}
// Read "times" table // Read "times" table
lua_getfield(L, table_groupcap, "times"); lua_getfield(L, table_groupcap, "times");
if(lua_istable(L, -1)){ if(lua_istable(L, -1)){
@ -725,8 +736,8 @@ static void set_tool_capabilities(lua_State *L, int table,
// Set subtable "times" // Set subtable "times"
lua_setfield(L, -2, "times"); lua_setfield(L, -2, "times");
// Set simple parameters // Set simple parameters
setfloatfield(L, -1, "maxwear", groupcap.maxwear);
setintfield(L, -1, "maxlevel", groupcap.maxlevel); setintfield(L, -1, "maxlevel", groupcap.maxlevel);
setintfield(L, -1, "uses", groupcap.uses);
// Insert groupcap table into groupcaps table // Insert groupcap table into groupcaps table
lua_setfield(L, -2, name.c_str()); lua_setfield(L, -2, name.c_str());
} }

@ -25,7 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
void ToolCapabilities::serialize(std::ostream &os) const void ToolCapabilities::serialize(std::ostream &os) const
{ {
writeU8(os, 0); // version writeU8(os, 1); // version
writeF1000(os, full_punch_interval); writeF1000(os, full_punch_interval);
writeS16(os, max_drop_level); writeS16(os, max_drop_level);
writeU32(os, groupcaps.size()); writeU32(os, groupcaps.size());
@ -34,8 +34,8 @@ void ToolCapabilities::serialize(std::ostream &os) const
const std::string *name = &i->first; const std::string *name = &i->first;
const ToolGroupCap *cap = &i->second; const ToolGroupCap *cap = &i->second;
os<<serializeString(*name); os<<serializeString(*name);
writeF1000(os, cap->maxwear); writeS16(os, cap->uses);
writeF1000(os, cap->maxlevel); writeS16(os, cap->maxlevel);
writeU32(os, cap->times.size()); writeU32(os, cap->times.size());
for(std::map<int, float>::const_iterator for(std::map<int, float>::const_iterator
i = cap->times.begin(); i != cap->times.end(); i++){ i = cap->times.begin(); i != cap->times.end(); i++){
@ -48,7 +48,7 @@ void ToolCapabilities::serialize(std::ostream &os) const
void ToolCapabilities::deSerialize(std::istream &is) void ToolCapabilities::deSerialize(std::istream &is)
{ {
int version = readU8(is); int version = readU8(is);
if(version != 0) throw SerializationError( if(version != 1) throw SerializationError(
"unsupported ToolCapabilities version"); "unsupported ToolCapabilities version");
full_punch_interval = readF1000(is); full_punch_interval = readF1000(is);
max_drop_level = readS16(is); max_drop_level = readS16(is);
@ -57,8 +57,8 @@ void ToolCapabilities::deSerialize(std::istream &is)
for(u32 i=0; i<groupcaps_size; i++){ for(u32 i=0; i<groupcaps_size; i++){
std::string name = deSerializeString(is); std::string name = deSerializeString(is);
ToolGroupCap cap; ToolGroupCap cap;
cap.maxwear = readF1000(is); cap.uses = readS16(is);
cap.maxlevel = readF1000(is); cap.maxlevel = readS16(is);
u32 times_size = readU32(is); u32 times_size = readU32(is);
for(u32 i=0; i<times_size; i++){ for(u32 i=0; i<times_size; i++){
int level = readS16(is); int level = readS16(is);
@ -102,11 +102,14 @@ DigParams getDigParams(const ItemGroupList &groups,
float time = 0; float time = 0;
bool time_exists = cap.getTime(rating, &time); bool time_exists = cap.getTime(rating, &time);
if(!result_diggable || time < result_time){ if(!result_diggable || time < result_time){
if(cap.maxlevel > level && time_exists){ if(cap.maxlevel >= level && time_exists){
result_diggable = true; result_diggable = true;
result_time = time;
int leveldiff = cap.maxlevel - level; int leveldiff = cap.maxlevel - level;
result_wear = cap.maxwear / pow(4.0, (double)leveldiff); result_time = time / MYMAX(1, leveldiff);
if(cap.uses != 0)
result_wear = 1.0 / cap.uses / pow(3.0, (double)leveldiff);
else
result_wear = 0;
result_main_group = name; result_main_group = name;
} }
} }

@ -29,12 +29,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
struct ToolGroupCap struct ToolGroupCap
{ {
std::map<int, float> times; std::map<int, float> times;
float maxwear;
int maxlevel; int maxlevel;
int uses;
ToolGroupCap(): ToolGroupCap():
maxwear(0.05), maxlevel(1),
maxlevel(1) uses(20)
{} {}
bool getTime(int rating, float *time) const bool getTime(int rating, float *time) const