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"] = 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"] = groupcaps_object;
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;
144 root["damage_groups"] = damage_groups_object;
146 fastWriteJson(root, os);
149 void ToolCapabilities::deserializeJson(std::istream &is)
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();
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;
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;
179 damageGroups[dgiter.key().asString()] =
186 static u32 calculateResultWear(const u32 uses, const u16 initial_wear)
189 // Trivial case: Infinite uses
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
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.
213 Example for 130 uses:
215 * Number of normal blocks = 114
216 * Oversized wear = 505
217 * Number of oversized blocks = 16
219 If we add everything together, we get:
220 114*504 + 16*505 = 65536
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.
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
240 u16 wear_extra_at = blocks_normal * wear_normal;
241 if (initial_wear >= wear_extra_at) {
245 result_wear = wear_normal + wear_extra;
249 DigParams getDigParams(const ItemGroupList &groups,
250 const ToolCapabilities *tp,
251 const u16 initial_wear)
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")) {
258 return DigParams(true, 0.5, 0, "dig_immediate");
260 return DigParams(true, 0, 0, "dig_immediate");
266 // Values to be returned (with a bit of conversion)
267 bool result_diggable = false;
268 float result_time = 0.0;
270 std::string result_main_group;
272 int level = itemgroup_get(groups, "level");
273 for (const auto &groupcap : tp->groupcaps) {
274 const ToolGroupCap &cap = groupcap.second;
276 int leveldiff = cap.maxlevel - level;
280 const std::string &groupname = groupcap.first;
282 int rating = itemgroup_get(groups, groupname);
283 bool time_exists = cap.getTime(rating, &time);
289 if (!result_diggable || time < result_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;
302 return DigParams(result_diggable, result_time, result_wear, result_main_group);
305 HitParams getHitParams(const ItemGroupList &armor_groups,
306 const ToolCapabilities *tp, float time_from_last_punch,
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);
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;
319 if (tp->punch_attack_uses > 0) {
320 result_wear = calculateResultWear(tp->punch_attack_uses, initial_wear);
321 result_wear *= punch_interval_multiplier;
323 // Keep damage in sane bounds for simplicity
324 damage = rangelim(damage, -U16_MAX, U16_MAX);
326 u32 wear_i = (u32) result_wear;
327 return {damage, wear_i};
330 HitParams getHitParams(const ItemGroupList &armor_groups,
331 const ToolCapabilities *tp)
333 return getHitParams(armor_groups, tp, 1000000);
336 PunchDamageResult getPunchDamage(
337 const ItemGroupList &armor_groups,
338 const ToolCapabilities *toolcap,
339 const ItemStack *punchitem,
340 float time_from_last_punch,
345 if (do_hit && punchitem) {
346 if (itemgroup_get(armor_groups, "punch_operable") &&
347 (toolcap == NULL || punchitem->name.empty()))
352 if(itemgroup_get(armor_groups, "immortal"))
357 PunchDamageResult result;
360 HitParams hitparams = getHitParams(armor_groups, toolcap,
361 time_from_last_punch,
363 result.did_punch = true;
364 result.wear = hitparams.wear;
365 result.damage = hitparams.hp;
371 f32 getToolRange(const ItemDefinition &def_selected, const ItemDefinition &def_hand)
373 float max_d = def_selected.range;
374 float max_d_hand = def_hand.range;
376 if (max_d < 0 && max_d_hand >= 0)