]> git.lizzy.rs Git - dragonfireclient.git/blob - src/tool.cpp
075c6b3c5ff13f8bb5e5ada473d85d7c8e80f7ca
[dragonfireclient.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"] = 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"] = groupcaps_object;
138
139         Json::Value damage_groups_object;
140         DamageGroup::const_iterator dgiter;
141         for (dgiter = damageGroups.begin(); dgiter != damageGroups.end(); ++dgiter) {
142                 damage_groups_object[dgiter->first] = dgiter->second;
143         }
144         root["damage_groups"] = damage_groups_object;
145
146         fastWriteJson(root, os);
147 }
148
149 void ToolCapabilities::deserializeJson(std::istream &is)
150 {
151         Json::Value root;
152         is >> root;
153         if (root.isObject()) {
154                 if (root["full_punch_interval"].isDouble())
155                         full_punch_interval = root["full_punch_interval"].asFloat();
156                 if (root["max_drop_level"].isInt())
157                         max_drop_level = root["max_drop_level"].asInt();
158                 if (root["punch_attack_uses"].isInt())
159                         punch_attack_uses = root["punch_attack_uses"].asInt();
160
161                 Json::Value &groupcaps_object = root["groupcaps"];
162                 if (groupcaps_object.isObject()) {
163                         Json::ValueIterator gciter;
164                         for (gciter = groupcaps_object.begin();
165                                         gciter != groupcaps_object.end(); ++gciter) {
166                                 ToolGroupCap groupcap;
167                                 groupcap.fromJson(*gciter);
168                                 groupcaps[gciter.key().asString()] = groupcap;
169                         }
170                 }
171
172                 Json::Value &damage_groups_object = root["damage_groups"];
173                 if (damage_groups_object.isObject()) {
174                         Json::ValueIterator dgiter;
175                         for (dgiter = damage_groups_object.begin();
176                                         dgiter != damage_groups_object.end(); ++dgiter) {
177                                 Json::Value &value = *dgiter;
178                                 if (value.isInt())
179                                         damageGroups[dgiter.key().asString()] =
180                                                 value.asInt();
181                         }
182                 }
183         }
184 }
185
186 static u32 calculateResultWear(const u32 uses, const u16 initial_wear)
187 {
188         if (uses == 0) {
189                 // Trivial case: Infinite uses
190                 return 0;
191         }
192         /* Finite uses. This is not trivial,
193         as the maximum wear is not neatly evenly divisible by
194         most possible uses numbers. For example, for 128
195         uses, the calculation of wear is trivial, as
196         65536 / 128 uses = 512 wear,
197         so the tool will get 512 wear 128 times in its lifetime.
198         But for a number like 130, this does not work:
199         65536 / 130 uses = 504.123... wear.
200         Since wear must be an integer, we will get
201         504*130 = 65520, which would lead to the wrong number
202         of uses.
203
204         Instead, we partition the "wear range" into blocks:
205         A block represents a single use and can be
206         of two possible sizes: normal and oversized.
207         A normal block is equal to floor(65536 / uses).
208         An oversized block is a normal block plus 1.
209         Then we determine how many oversized and normal
210         blocks we need and finally, whether we add
211         the normal wear or the oversized wear.
212
213         Example for 130 uses:
214         * Normal wear = 504
215         * Number of normal blocks = 114
216         * Oversized wear = 505
217         * Number of oversized blocks = 16
218
219         If we add everything together, we get:
220           114*504 + 16*505 = 65536
221         */
222         u32 result_wear;
223         u32 wear_normal = ((U16_MAX+1) / uses);
224         // Will be non-zero if its not evenly divisible
225         u16 blocks_oversize = (U16_MAX+1) % uses;
226         // Whether to add one extra wear point in case
227         // of oversized wear.
228         u16 wear_extra = 0;
229         if (blocks_oversize > 0) {
230                 u16 blocks_normal = uses - blocks_oversize;
231                 /* When the wear has reached this value, we
232                    know that wear_normal has been applied
233                    for blocks_normal times, therefore,
234                    only oversized blocks remain.
235                    This also implies the raw tool wear number
236                    increases a bit faster after this point,
237                    but this should be barely noticable by the
238                    player.
239                 */
240                 u16 wear_extra_at = blocks_normal * wear_normal;
241                 if (initial_wear >= wear_extra_at) {
242                         wear_extra = 1;
243                 }
244         }
245         result_wear = wear_normal + wear_extra;
246         return result_wear;
247 }
248
249 DigParams getDigParams(const ItemGroupList &groups,
250                 const ToolCapabilities *tp,
251                 const u16 initial_wear)
252 {
253
254         // Group dig_immediate defaults to fixed time and no wear
255         if (tp->groupcaps.find("dig_immediate") == tp->groupcaps.cend()) {
256                 switch (itemgroup_get(groups, "dig_immediate")) {
257                 case 2:
258                         return DigParams(true, 0.5, 0, "dig_immediate");
259                 case 3:
260                         return DigParams(true, 0, 0, "dig_immediate");
261                 default:
262                         break;
263                 }
264         }
265
266         // Values to be returned (with a bit of conversion)
267         bool result_diggable = false;
268         float result_time = 0.0;
269         u32 result_wear = 0;
270         std::string result_main_group;
271
272         int level = itemgroup_get(groups, "level");
273         for (const auto &groupcap : tp->groupcaps) {
274                 const ToolGroupCap &cap = groupcap.second;
275
276                 int leveldiff = cap.maxlevel - level;
277                 if (leveldiff < 0)
278                         continue;
279
280                 const std::string &groupname = groupcap.first;
281                 float time = 0;
282                 int rating = itemgroup_get(groups, groupname);
283                 bool time_exists = cap.getTime(rating, &time);
284                 if (!time_exists)
285                         continue;
286
287                 if (leveldiff > 1)
288                         time /= leveldiff;
289                 if (!result_diggable || time < result_time) {
290                         result_time = time;
291                         result_diggable = true;
292                         // The actual number of uses increases
293                         // exponentially with leveldiff.
294                         // If the levels are equal, real_uses equals cap.uses.
295                         u32 real_uses = cap.uses * pow(3.0, leveldiff);
296                         real_uses = MYMIN(real_uses, U16_MAX);
297                         result_wear = calculateResultWear(real_uses, initial_wear);
298                         result_main_group = groupname;
299                 }
300         }
301
302         return DigParams(result_diggable, result_time, result_wear, result_main_group);
303 }
304
305 HitParams getHitParams(const ItemGroupList &armor_groups,
306                 const ToolCapabilities *tp, float time_from_last_punch,
307                 u16 initial_wear)
308 {
309         s32 damage = 0;
310         float result_wear = 0.0f;
311         float punch_interval_multiplier =
312                         rangelim(time_from_last_punch / tp->full_punch_interval, 0.0f, 1.0f);
313
314         for (const auto &damageGroup : tp->damageGroups) {
315                 s16 armor = itemgroup_get(armor_groups, damageGroup.first);
316                 damage += damageGroup.second * punch_interval_multiplier * armor / 100.0;
317         }
318
319         if (tp->punch_attack_uses > 0) {
320                 result_wear = calculateResultWear(tp->punch_attack_uses, initial_wear);
321                 result_wear *= punch_interval_multiplier;
322         }
323         // Keep damage in sane bounds for simplicity
324         damage = rangelim(damage, -U16_MAX, U16_MAX);
325
326         u32 wear_i = (u32) result_wear;
327         return {damage, wear_i};
328 }
329
330 HitParams getHitParams(const ItemGroupList &armor_groups,
331                 const ToolCapabilities *tp)
332 {
333         return getHitParams(armor_groups, tp, 1000000);
334 }
335
336 PunchDamageResult getPunchDamage(
337                 const ItemGroupList &armor_groups,
338                 const ToolCapabilities *toolcap,
339                 const ItemStack *punchitem,
340                 float time_from_last_punch,
341                 u16 initial_wear
342 ){
343         bool do_hit = true;
344         {
345                 if (do_hit && punchitem) {
346                         if (itemgroup_get(armor_groups, "punch_operable") &&
347                                         (toolcap == NULL || punchitem->name.empty()))
348                                 do_hit = false;
349                 }
350
351                 if (do_hit) {
352                         if(itemgroup_get(armor_groups, "immortal"))
353                                 do_hit = false;
354                 }
355         }
356
357         PunchDamageResult result;
358         if(do_hit)
359         {
360                 HitParams hitparams = getHitParams(armor_groups, toolcap,
361                                 time_from_last_punch,
362                                 punchitem->wear);
363                 result.did_punch = true;
364                 result.wear = hitparams.wear;
365                 result.damage = hitparams.hp;
366         }
367
368         return result;
369 }
370
371 f32 getToolRange(const ItemDefinition &def_selected, const ItemDefinition &def_hand)
372 {
373         float max_d = def_selected.range;
374         float max_d_hand = def_hand.range;
375
376         if (max_d < 0 && max_d_hand >= 0)
377                 max_d = max_d_hand;
378         else if (max_d < 0)
379                 max_d = 4.0f;
380
381         return max_d;
382 }
383