]> git.lizzy.rs Git - minetest.git/blob - src/tool.cpp
Replace minetest_game with "Minetest Game" where appropriate
[minetest.git] / src / tool.cpp
1 /*
2 Minetest
3 Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include "tool.h"
21 #include "itemdef.h"
22 #include "itemgroup.h"
23 #include "log.h"
24 #include "inventory.h"
25 #include "exceptions.h"
26 #include "convert_json.h"
27 #include "util/serialize.h"
28 #include "util/numeric.h"
29
30 void ToolGroupCap::toJson(Json::Value &object) const
31 {
32         object["maxlevel"] = maxlevel;
33         object["uses"] = uses;
34
35         Json::Value times_object;
36         for (auto time : times)
37                 times_object[time.first] = time.second;
38         object["times"] = std::move(times_object);
39 }
40
41 void ToolGroupCap::fromJson(const Json::Value &json)
42 {
43         if (json.isObject()) {
44                 if (json["maxlevel"].isInt())
45                         maxlevel = json["maxlevel"].asInt();
46                 if (json["uses"].isInt())
47                         uses = json["uses"].asInt();
48                 const Json::Value &times_object = json["times"];
49                 if (times_object.isArray()) {
50                         Json::ArrayIndex size = times_object.size();
51                         for (Json::ArrayIndex i = 0; i < size; ++i)
52                                 if (times_object[i].isDouble())
53                                         times[i] = times_object[i].asFloat();
54                 }
55         }
56 }
57
58 void ToolCapabilities::serialize(std::ostream &os, u16 protocol_version) const
59 {
60         if (protocol_version >= 38)
61                 writeU8(os, 5);
62         else
63                 writeU8(os, 4); // proto == 37
64         writeF32(os, full_punch_interval);
65         writeS16(os, max_drop_level);
66         writeU32(os, groupcaps.size());
67         for (const auto &groupcap : groupcaps) {
68                 const std::string *name = &groupcap.first;
69                 const ToolGroupCap *cap = &groupcap.second;
70                 os << serializeString16(*name);
71                 writeS16(os, cap->uses);
72                 writeS16(os, cap->maxlevel);
73                 writeU32(os, cap->times.size());
74                 for (const auto &time : cap->times) {
75                         writeS16(os, time.first);
76                         writeF32(os, time.second);
77                 }
78         }
79
80         writeU32(os, damageGroups.size());
81
82         for (const auto &damageGroup : damageGroups) {
83                 os << serializeString16(damageGroup.first);
84                 writeS16(os, damageGroup.second);
85         }
86
87         if (protocol_version >= 38)
88                 writeU16(os, rangelim(punch_attack_uses, 0, U16_MAX));
89 }
90
91 void ToolCapabilities::deSerialize(std::istream &is)
92 {
93         int version = readU8(is);
94         if (version < 4)
95                 throw SerializationError("unsupported ToolCapabilities version");
96
97         full_punch_interval = readF32(is);
98         max_drop_level = readS16(is);
99         groupcaps.clear();
100         u32 groupcaps_size = readU32(is);
101         for (u32 i = 0; i < groupcaps_size; i++) {
102                 std::string name = deSerializeString16(is);
103                 ToolGroupCap cap;
104                 cap.uses = readS16(is);
105                 cap.maxlevel = readS16(is);
106                 u32 times_size = readU32(is);
107                 for(u32 i = 0; i < times_size; i++) {
108                         int level = readS16(is);
109                         float time = readF32(is);
110                         cap.times[level] = time;
111                 }
112                 groupcaps[name] = cap;
113         }
114
115         u32 damage_groups_size = readU32(is);
116         for (u32 i = 0; i < damage_groups_size; i++) {
117                 std::string name = deSerializeString16(is);
118                 s16 rating = readS16(is);
119                 damageGroups[name] = rating;
120         }
121
122         if (version >= 5)
123                 punch_attack_uses = readU16(is);
124 }
125
126 void ToolCapabilities::serializeJson(std::ostream &os) const
127 {
128         Json::Value root;
129         root["full_punch_interval"] = full_punch_interval;
130         root["max_drop_level"] = max_drop_level;
131         root["punch_attack_uses"] = punch_attack_uses;
132
133         Json::Value groupcaps_object;
134         for (const auto &groupcap : groupcaps) {
135                 groupcap.second.toJson(groupcaps_object[groupcap.first]);
136         }
137         root["groupcaps"] = std::move(groupcaps_object);
138
139         Json::Value damage_groups_object;
140         for (const auto &damagegroup : damageGroups) {
141                 damage_groups_object[damagegroup.first] = damagegroup.second;
142         }
143         root["damage_groups"] = std::move(damage_groups_object);
144
145         fastWriteJson(root, os);
146 }
147
148 void ToolCapabilities::deserializeJson(std::istream &is)
149 {
150         Json::Value root;
151         is >> root;
152         if (root.isObject()) {
153                 if (root["full_punch_interval"].isDouble())
154                         full_punch_interval = root["full_punch_interval"].asFloat();
155                 if (root["max_drop_level"].isInt())
156                         max_drop_level = root["max_drop_level"].asInt();
157                 if (root["punch_attack_uses"].isInt())
158                         punch_attack_uses = root["punch_attack_uses"].asInt();
159
160                 Json::Value &groupcaps_object = root["groupcaps"];
161                 if (groupcaps_object.isObject()) {
162                         Json::ValueIterator gciter;
163                         for (gciter = groupcaps_object.begin();
164                                         gciter != groupcaps_object.end(); ++gciter) {
165                                 ToolGroupCap groupcap;
166                                 groupcap.fromJson(*gciter);
167                                 groupcaps[gciter.key().asString()] = groupcap;
168                         }
169                 }
170
171                 Json::Value &damage_groups_object = root["damage_groups"];
172                 if (damage_groups_object.isObject()) {
173                         Json::ValueIterator dgiter;
174                         for (dgiter = damage_groups_object.begin();
175                                         dgiter != damage_groups_object.end(); ++dgiter) {
176                                 Json::Value &value = *dgiter;
177                                 if (value.isInt())
178                                         damageGroups[dgiter.key().asString()] =
179                                                 value.asInt();
180                         }
181                 }
182         }
183 }
184
185 u32 calculateResultWear(const u32 uses, const u16 initial_wear)
186 {
187         if (uses == 0) {
188                 // Trivial case: Infinite uses
189                 return 0;
190         }
191         /* Finite uses. This is not trivial,
192         as the maximum wear is not neatly evenly divisible by
193         most possible uses numbers. For example, for 128
194         uses, the calculation of wear is trivial, as
195         65536 / 128 uses = 512 wear,
196         so the tool will get 512 wear 128 times in its lifetime.
197         But for a number like 130, this does not work:
198         65536 / 130 uses = 504.123... wear.
199         Since wear must be an integer, we will get
200         504*130 = 65520, which would lead to the wrong number
201         of uses.
202
203         Instead, we partition the "wear range" into blocks:
204         A block represents a single use and can be
205         of two possible sizes: normal and oversized.
206         A normal block is equal to floor(65536 / uses).
207         An oversized block is a normal block plus 1.
208         Then we determine how many oversized and normal
209         blocks we need and finally, whether we add
210         the normal wear or the oversized wear.
211
212         Example for 130 uses:
213         * Normal wear = 504
214         * Number of normal blocks = 114
215         * Oversized wear = 505
216         * Number of oversized blocks = 16
217
218         If we add everything together, we get:
219           114*504 + 16*505 = 65536
220         */
221         u32 result_wear;
222         u32 wear_normal = ((U16_MAX+1) / uses);
223         // Will be non-zero if its not evenly divisible
224         u16 blocks_oversize = (U16_MAX+1) % uses;
225         // Whether to add one extra wear point in case
226         // of oversized wear.
227         u16 wear_extra = 0;
228         if (blocks_oversize > 0) {
229                 u16 blocks_normal = uses - blocks_oversize;
230                 /* When the wear has reached this value, we
231                    know that wear_normal has been applied
232                    for blocks_normal times, therefore,
233                    only oversized blocks remain.
234                    This also implies the raw tool wear number
235                    increases a bit faster after this point,
236                    but this should be barely noticeable by the
237                    player.
238                 */
239                 u16 wear_extra_at = blocks_normal * wear_normal;
240                 if (initial_wear >= wear_extra_at) {
241                         wear_extra = 1;
242                 }
243         }
244         result_wear = wear_normal + wear_extra;
245         return result_wear;
246 }
247
248 DigParams getDigParams(const ItemGroupList &groups,
249                 const ToolCapabilities *tp,
250                 const u16 initial_wear)
251 {
252
253         // Group dig_immediate defaults to fixed time and no wear
254         if (tp->groupcaps.find("dig_immediate") == tp->groupcaps.cend()) {
255                 switch (itemgroup_get(groups, "dig_immediate")) {
256                 case 2:
257                         return DigParams(true, 0.5, 0, "dig_immediate");
258                 case 3:
259                         return DigParams(true, 0, 0, "dig_immediate");
260                 default:
261                         break;
262                 }
263         }
264
265         // Values to be returned (with a bit of conversion)
266         bool result_diggable = false;
267         float result_time = 0.0;
268         u32 result_wear = 0;
269         std::string result_main_group;
270
271         int level = itemgroup_get(groups, "level");
272         for (const auto &groupcap : tp->groupcaps) {
273                 const ToolGroupCap &cap = groupcap.second;
274
275                 int leveldiff = cap.maxlevel - level;
276                 if (leveldiff < 0)
277                         continue;
278
279                 const std::string &groupname = groupcap.first;
280                 float time = 0;
281                 int rating = itemgroup_get(groups, groupname);
282                 bool time_exists = cap.getTime(rating, &time);
283                 if (!time_exists)
284                         continue;
285
286                 if (leveldiff > 1)
287                         time /= leveldiff;
288                 if (!result_diggable || time < result_time) {
289                         result_time = time;
290                         result_diggable = true;
291                         // The actual number of uses increases
292                         // exponentially with leveldiff.
293                         // If the levels are equal, real_uses equals cap.uses.
294                         u32 real_uses = cap.uses * pow(3.0, leveldiff);
295                         real_uses = MYMIN(real_uses, U16_MAX);
296                         result_wear = calculateResultWear(real_uses, initial_wear);
297                         result_main_group = groupname;
298                 }
299         }
300
301         return DigParams(result_diggable, result_time, result_wear, result_main_group);
302 }
303
304 HitParams getHitParams(const ItemGroupList &armor_groups,
305                 const ToolCapabilities *tp, float time_from_last_punch,
306                 u16 initial_wear)
307 {
308         s32 damage = 0;
309         float result_wear = 0.0f;
310         float punch_interval_multiplier =
311                         rangelim(time_from_last_punch / tp->full_punch_interval, 0.0f, 1.0f);
312
313         for (const auto &damageGroup : tp->damageGroups) {
314                 s16 armor = itemgroup_get(armor_groups, damageGroup.first);
315                 damage += damageGroup.second * punch_interval_multiplier * armor / 100.0;
316         }
317
318         if (tp->punch_attack_uses > 0) {
319                 result_wear = calculateResultWear(tp->punch_attack_uses, initial_wear);
320                 result_wear *= punch_interval_multiplier;
321         }
322         // Keep damage in sane bounds for simplicity
323         damage = rangelim(damage, -U16_MAX, U16_MAX);
324
325         u32 wear_i = (u32) result_wear;
326         return {damage, wear_i};
327 }
328
329 HitParams getHitParams(const ItemGroupList &armor_groups,
330                 const ToolCapabilities *tp)
331 {
332         return getHitParams(armor_groups, tp, 1000000);
333 }
334
335 PunchDamageResult getPunchDamage(
336                 const ItemGroupList &armor_groups,
337                 const ToolCapabilities *toolcap,
338                 const ItemStack *punchitem,
339                 float time_from_last_punch,
340                 u16 initial_wear
341 ){
342         bool do_hit = true;
343         {
344                 if (do_hit && punchitem) {
345                         if (itemgroup_get(armor_groups, "punch_operable") &&
346                                         (toolcap == NULL || punchitem->name.empty()))
347                                 do_hit = false;
348                 }
349
350                 if (do_hit) {
351                         if(itemgroup_get(armor_groups, "immortal"))
352                                 do_hit = false;
353                 }
354         }
355
356         PunchDamageResult result;
357         if(do_hit)
358         {
359                 HitParams hitparams = getHitParams(armor_groups, toolcap,
360                                 time_from_last_punch,
361                                 punchitem->wear);
362                 result.did_punch = true;
363                 result.wear = hitparams.wear;
364                 result.damage = hitparams.hp;
365         }
366
367         return result;
368 }
369
370 f32 getToolRange(const ItemDefinition &def_selected, const ItemDefinition &def_hand)
371 {
372         float max_d = def_selected.range;
373         float max_d_hand = def_hand.range;
374
375         if (max_d < 0 && max_d_hand >= 0)
376                 max_d = max_d_hand;
377         else if (max_d < 0)
378                 max_d = 4.0f;
379
380         return max_d;
381 }
382