3 Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
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.
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.
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.
22 #include "itemgroup.h"
24 #include "inventory.h"
25 #include "exceptions.h"
26 #include "convert_json.h"
27 #include "util/serialize.h"
28 #include "util/numeric.h"
30 void ToolGroupCap::toJson(Json::Value &object) const
32 object["maxlevel"] = maxlevel;
33 object["uses"] = uses;
35 Json::Value times_object;
36 for (auto time : times)
37 times_object[time.first] = time.second;
38 object["times"] = std::move(times_object);
41 void ToolGroupCap::fromJson(const Json::Value &json)
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 ×_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();
58 void ToolCapabilities::serialize(std::ostream &os, u16 protocol_version) const
60 if (protocol_version >= 38)
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);
80 writeU32(os, damageGroups.size());
82 for (const auto &damageGroup : damageGroups) {
83 os << serializeString16(damageGroup.first);
84 writeS16(os, damageGroup.second);
87 if (protocol_version >= 38)
88 writeU16(os, rangelim(punch_attack_uses, 0, U16_MAX));
91 void ToolCapabilities::deSerialize(std::istream &is)
93 int version = readU8(is);
95 throw SerializationError("unsupported ToolCapabilities version");
97 full_punch_interval = readF32(is);
98 max_drop_level = readS16(is);
100 u32 groupcaps_size = readU32(is);
101 for (u32 i = 0; i < groupcaps_size; i++) {
102 std::string name = deSerializeString16(is);
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;
112 groupcaps[name] = cap;
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;
123 punch_attack_uses = readU16(is);
126 void ToolCapabilities::serializeJson(std::ostream &os) const
129 root["full_punch_interval"] = full_punch_interval;
130 root["max_drop_level"] = max_drop_level;
131 root["punch_attack_uses"] = punch_attack_uses;
133 Json::Value groupcaps_object;
134 for (const auto &groupcap : groupcaps) {
135 groupcap.second.toJson(groupcaps_object[groupcap.first]);
137 root["groupcaps"] = std::move(groupcaps_object);
139 Json::Value damage_groups_object;
140 for (const auto &damagegroup : damageGroups) {
141 damage_groups_object[damagegroup.first] = damagegroup.second;
143 root["damage_groups"] = std::move(damage_groups_object);
145 fastWriteJson(root, os);
148 void ToolCapabilities::deserializeJson(std::istream &is)
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();
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;
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;
178 damageGroups[dgiter.key().asString()] =
185 u32 calculateResultWear(const u32 uses, const u16 initial_wear)
188 // Trivial case: Infinite uses
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
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.
212 Example for 130 uses:
214 * Number of normal blocks = 114
215 * Oversized wear = 505
216 * Number of oversized blocks = 16
218 If we add everything together, we get:
219 114*504 + 16*505 = 65536
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.
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
239 u16 wear_extra_at = blocks_normal * wear_normal;
240 if (initial_wear >= wear_extra_at) {
244 result_wear = wear_normal + wear_extra;
248 DigParams getDigParams(const ItemGroupList &groups,
249 const ToolCapabilities *tp,
250 const u16 initial_wear)
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")) {
257 return DigParams(true, 0.5, 0, "dig_immediate");
259 return DigParams(true, 0, 0, "dig_immediate");
265 // Values to be returned (with a bit of conversion)
266 bool result_diggable = false;
267 float result_time = 0.0;
269 std::string result_main_group;
271 int level = itemgroup_get(groups, "level");
272 for (const auto &groupcap : tp->groupcaps) {
273 const ToolGroupCap &cap = groupcap.second;
275 int leveldiff = cap.maxlevel - level;
279 const std::string &groupname = groupcap.first;
281 int rating = itemgroup_get(groups, groupname);
282 bool time_exists = cap.getTime(rating, &time);
288 if (!result_diggable || time < result_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;
301 return DigParams(result_diggable, result_time, result_wear, result_main_group);
304 HitParams getHitParams(const ItemGroupList &armor_groups,
305 const ToolCapabilities *tp, float time_from_last_punch,
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);
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;
318 if (tp->punch_attack_uses > 0) {
319 result_wear = calculateResultWear(tp->punch_attack_uses, initial_wear);
320 result_wear *= punch_interval_multiplier;
322 // Keep damage in sane bounds for simplicity
323 damage = rangelim(damage, -U16_MAX, U16_MAX);
325 u32 wear_i = (u32) result_wear;
326 return {damage, wear_i};
329 HitParams getHitParams(const ItemGroupList &armor_groups,
330 const ToolCapabilities *tp)
332 return getHitParams(armor_groups, tp, 1000000);
335 PunchDamageResult getPunchDamage(
336 const ItemGroupList &armor_groups,
337 const ToolCapabilities *toolcap,
338 const ItemStack *punchitem,
339 float time_from_last_punch,
344 if (do_hit && punchitem) {
345 if (itemgroup_get(armor_groups, "punch_operable") &&
346 (toolcap == NULL || punchitem->name.empty()))
351 if(itemgroup_get(armor_groups, "immortal"))
356 PunchDamageResult result;
359 HitParams hitparams = getHitParams(armor_groups, toolcap,
360 time_from_last_punch,
362 result.did_punch = true;
363 result.wear = hitparams.wear;
364 result.damage = hitparams.hp;
370 f32 getToolRange(const ItemDefinition &def_selected, const ItemDefinition &def_hand)
372 float max_d = def_selected.range;
373 float max_d_hand = def_hand.range;
375 if (max_d < 0 && max_d_hand >= 0)