1 //       _________ __                 __
2 //      /   _____//  |_____________ _/  |______     ____  __ __  ______
3 //      \_____  \\   __\_  __ \__  \\   __\__  \   / ___\|  |  \/  ___/
4 //      /        \|  |  |  | \// __ \|  |  / __ \_/ /_/  >  |  /\___ |
5 //     /_______  /|__|  |__|  (____  /__| (____  /\___  /|____//____  >
6 //             \/                  \/          \//_____/            \/
7 //  ______________________                           ______________________
8 //                        T H E   W A R   B E G I N S
9 //         Stratagus - A free fantasy real time strategy game engine
10 //
11 /**@name item.cpp - The items. */
12 //
13 //      (c) Copyright 2015-2019 by Andrettin
14 //
15 //      This program is free software; you can redistribute it and/or modify
16 //      it under the terms of the GNU General Public License as published by
17 //      the Free Software Foundation; only version 2 of the License.
18 //
19 //      This program is distributed in the hope that it will be useful,
20 //      but WITHOUT ANY WARRANTY; without even the implied warranty of
21 //      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22 //      GNU General Public License for more details.
23 //
24 //      You should have received a copy of the GNU General Public License
25 //      along with this program; if not, write to the Free Software
26 //      Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
27 //      02111-1307, USA.
28 //
29 
30 //@{
31 
32 /*----------------------------------------------------------------------------
33 --  Includes
34 ----------------------------------------------------------------------------*/
35 
36 #include "stratagus.h"
37 
38 #include "item.h"
39 
40 #include <ctype.h>
41 
42 #include <string>
43 #include <map>
44 
45 #include "character.h"
46 #include "config.h"
47 #include "game.h"
48 #include "network.h"
49 #include "parameters.h"
50 #include "player.h"
51 #include "spells.h"
52 #include "unit/unit.h"
53 #include "unit/unit_manager.h"
54 #include "unit/unittype.h"
55 #include "upgrade/upgrade.h"
56 #include "upgrade/upgrade_modifier.h"
57 
58 /*----------------------------------------------------------------------------
59 --  Variables
60 ----------------------------------------------------------------------------*/
61 
62 std::vector<CUniqueItem *> UniqueItems;
63 
64 /*----------------------------------------------------------------------------
65 --  Functions
66 ----------------------------------------------------------------------------*/
67 
GetItemSlotIdByName(const std::string & item_slot)68 int GetItemSlotIdByName(const std::string &item_slot)
69 {
70 	if (item_slot == "weapon") {
71 		return WeaponItemSlot;
72 	} else if (item_slot == "shield") {
73 		return ShieldItemSlot;
74 	} else if (item_slot == "helmet") {
75 		return HelmetItemSlot;
76 	} else if (item_slot == "armor") {
77 		return ArmorItemSlot;
78 	} else if (item_slot == "gloves") {
79 		return GlovesItemSlot;
80 	} else if (item_slot == "boots") {
81 		return BootsItemSlot;
82 	} else if (item_slot == "belt") {
83 		return BeltItemSlot;
84 	} else if (item_slot == "amulet") {
85 		return AmuletItemSlot;
86 	} else if (item_slot == "ring") {
87 		return RingItemSlot;
88 	} else if (item_slot == "arrows") {
89 		return ArrowsItemSlot;
90 	}
91 
92 	return -1;
93 }
94 
GetItemSlotNameById(int item_slot)95 std::string GetItemSlotNameById(int item_slot)
96 {
97 	if (item_slot == WeaponItemSlot) {
98 		return "weapon";
99 	} else if (item_slot == ShieldItemSlot) {
100 		return "shield";
101 	} else if (item_slot == HelmetItemSlot) {
102 		return "helmet";
103 	} else if (item_slot == ArmorItemSlot) {
104 		return "armor";
105 	} else if (item_slot == GlovesItemSlot) {
106 		return "gloves";
107 	} else if (item_slot == BootsItemSlot) {
108 		return "boots";
109 	} else if (item_slot == BeltItemSlot) {
110 		return "belt";
111 	} else if (item_slot == AmuletItemSlot) {
112 		return "amulet";
113 	} else if (item_slot == RingItemSlot) {
114 		return "ring";
115 	} else if (item_slot == ArrowsItemSlot) {
116 		return "arrows";
117 	}
118 
119 	return "";
120 }
121 
GetItemClassIdByName(const std::string & item_class)122 int GetItemClassIdByName(const std::string &item_class)
123 {
124 	if (item_class == "dagger") {
125 		return DaggerItemClass;
126 	} else if (item_class == "sword") {
127 		return SwordItemClass;
128 	} else if (item_class == "thrusting-sword") {
129 		return ThrustingSwordItemClass;
130 	} else if (item_class == "axe") {
131 		return AxeItemClass;
132 	} else if (item_class == "mace") {
133 		return MaceItemClass;
134 	} else if (item_class == "spear") {
135 		return SpearItemClass;
136 	} else if (item_class == "bow") {
137 		return BowItemClass;
138 	} else if (item_class == "throwing-axe") {
139 		return ThrowingAxeItemClass;
140 	} else if (item_class == "javelin") {
141 		return JavelinItemClass;
142 	} else if (item_class == "gun") {
143 		return GunItemClass;
144 	} else if (item_class == "shield") {
145 		return ShieldItemClass;
146 	} else if (item_class == "horn") {
147 		return HornItemClass;
148 	} else if (item_class == "helmet") {
149 		return HelmetItemClass;
150 	} else if (item_class == "armor") {
151 		return ArmorItemClass;
152 	} else if (item_class == "cloak") {
153 		return CloakItemClass;
154 	} else if (item_class == "gloves") {
155 		return GlovesItemClass;
156 	} else if (item_class == "belt") {
157 		return BeltItemClass;
158 	} else if (item_class == "boots") {
159 		return BootsItemClass;
160 	} else if (item_class == "amulet") {
161 		return AmuletItemClass;
162 	} else if (item_class == "ring") {
163 		return RingItemClass;
164 	} else if (item_class == "arrows") {
165 		return ArrowsItemClass;
166 	} else if (item_class == "food") {
167 		return FoodItemClass;
168 	} else if (item_class == "potion") {
169 		return PotionItemClass;
170 	} else if (item_class == "scroll") {
171 		return ScrollItemClass;
172 	} else if (item_class == "book") {
173 		return BookItemClass;
174 	}
175 
176 	return -1;
177 }
178 
GetItemClassNameById(int item_class)179 std::string GetItemClassNameById(int item_class)
180 {
181 	if (item_class == DaggerItemClass) {
182 		return "dagger";
183 	} else if (item_class == SwordItemClass) {
184 		return "sword";
185 	} else if (item_class == ThrustingSwordItemClass) {
186 		return "thrusting-sword";
187 	} else if (item_class == AxeItemClass) {
188 		return "axe";
189 	} else if (item_class == MaceItemClass) {
190 		return "mace";
191 	} else if (item_class == SpearItemClass) {
192 		return "spear";
193 	} else if (item_class == BowItemClass) {
194 		return "bow";
195 	} else if (item_class == ThrowingAxeItemClass) {
196 		return "throwing-axe";
197 	} else if (item_class == JavelinItemClass) {
198 		return "javelin";
199 	} else if (item_class == GunItemClass) {
200 		return "gun";
201 	} else if (item_class == ShieldItemClass) {
202 		return "shield";
203 	} else if (item_class == HornItemClass) {
204 		return "horn";
205 	} else if (item_class == HelmetItemClass) {
206 		return "helmet";
207 	} else if (item_class == ArmorItemClass) {
208 		return "armor";
209 	} else if (item_class == CloakItemClass) {
210 		return "cloak";
211 	} else if (item_class == GlovesItemClass) {
212 		return "gloves";
213 	} else if (item_class == BeltItemClass) {
214 		return "belt";
215 	} else if (item_class == BootsItemClass) {
216 		return "boots";
217 	} else if (item_class == AmuletItemClass) {
218 		return "amulet";
219 	} else if (item_class == RingItemClass) {
220 		return "ring";
221 	} else if (item_class == ArrowsItemClass) {
222 		return "arrows";
223 	} else if (item_class == FoodItemClass) {
224 		return "food";
225 	} else if (item_class == PotionItemClass) {
226 		return "potion";
227 	} else if (item_class == ScrollItemClass) {
228 		return "scroll";
229 	} else if (item_class == BookItemClass) {
230 		return "book";
231 	}
232 
233 	return "";
234 }
235 
GetItemClassSlot(int item_class)236 int GetItemClassSlot(int item_class)
237 {
238 	if (
239 		item_class == DaggerItemClass
240 		|| item_class == SwordItemClass
241 		|| item_class == ThrustingSwordItemClass
242 		|| item_class == AxeItemClass
243 		|| item_class == MaceItemClass
244 		|| item_class == SpearItemClass
245 		|| item_class == BowItemClass
246 		|| item_class == ThrowingAxeItemClass
247 		|| item_class == JavelinItemClass
248 		|| item_class == GunItemClass
249 	) {
250 		return WeaponItemSlot;
251 	} else if (item_class == ShieldItemClass || item_class == HornItemClass) {
252 		return ShieldItemSlot;
253 	} else if (item_class == HelmetItemClass) {
254 		return HelmetItemSlot;
255 	} else if (item_class == ArmorItemClass || item_class == CloakItemClass) {
256 		return ArmorItemSlot;
257 	} else if (item_class == GlovesItemClass) {
258 		return GlovesItemSlot;
259 	} else if (item_class == BootsItemClass) {
260 		return BootsItemSlot;
261 	} else if (item_class == BeltItemClass) {
262 		return BeltItemSlot;
263 	} else if (item_class == AmuletItemClass) {
264 		return AmuletItemSlot;
265 	} else if (item_class == RingItemClass) {
266 		return RingItemSlot;
267 	} else if (item_class == ArrowsItemClass) {
268 		return ArrowsItemSlot;
269 	}
270 
271 	return -1;
272 }
273 
IsItemClassConsumable(int item_class)274 bool IsItemClassConsumable(int item_class)
275 {
276 	if (item_class == FoodItemClass || item_class == PotionItemClass || item_class == ScrollItemClass || item_class == BookItemClass) {
277 		return true;
278 	}
279 
280 	return false;
281 }
282 
CanDrop() const283 bool CUniqueItem::CanDrop() const
284 {
285 	// unique items cannot drop if a persistent hero owns them already, or if there's already one of them in the current scenario; unless it's a character-specific bound item, in which case it can still drop
286 	if (!IsNetworkGame()) {
287 		for (CCharacter *character : CCharacter::Characters) {
288 			for (CPersistentItem *item : character->Items) {
289 				if (item->Unique == this && !item->Bound) {
290 					return false;
291 				}
292 			}
293 		}
294 
295 		for (std::map<std::string, CCharacter *>::iterator iterator = CustomHeroes.begin(); iterator != CustomHeroes.end(); ++iterator) {
296 			for (CPersistentItem *item : iterator->second->Items) {
297 				if (item->Unique == this && !item->Bound) {
298 					return false;
299 				}
300 			}
301 		}
302 	}
303 
304 	if (GameRunning) {
305 		for (CUnitManager::Iterator it = UnitManager.begin(); it != UnitManager.end(); ++it) {
306 			CUnit &unit = **it;
307 			if (unit.Unique == this && !unit.Bound) {
308 				return false;
309 			}
310 		}
311 	}
312 
313 	return true;
314 }
315 
GetIcon() const316 IconConfig CUniqueItem::GetIcon() const
317 {
318 	if (this->Icon.Icon) {
319 		return this->Icon;
320 	} else {
321 		return this->Type->Icon;
322 	}
323 }
324 
GetMagicLevel() const325 int CUniqueItem::GetMagicLevel() const
326 {
327 	int magic_level = 0;
328 
329 	if (this->Prefix) {
330 		magic_level += this->Prefix->MagicLevel;
331 	}
332 
333 	if (this->Suffix) {
334 		magic_level += this->Suffix->MagicLevel;
335 	}
336 
337 	if (this->Set) {
338 		magic_level += this->Set->MagicLevel;
339 	}
340 
341 	if (this->Work) {
342 		magic_level += this->Work->MagicLevel;
343 	}
344 
345 	if (this->Elixir) {
346 		magic_level += this->Elixir->MagicLevel;
347 	}
348 
349 	return magic_level;
350 }
351 
CleanUniqueItems()352 void CleanUniqueItems()
353 {
354 	for (size_t i = 0; i < UniqueItems.size(); ++i) {
355 		delete UniqueItems[i];
356 	}
357 	UniqueItems.clear();
358 }
359 
GetUniqueItem(const std::string & item_ident)360 CUniqueItem *GetUniqueItem(const std::string &item_ident)
361 {
362 	for (size_t i = 0; i < UniqueItems.size(); ++i) {
363 		if (item_ident == UniqueItems[i]->Ident) {
364 			return UniqueItems[i];
365 		}
366 	}
367 	for (size_t i = 0; i < UniqueItems.size(); ++i) { // for backwards compatibility, search the name of the unique too
368 		if (NameToIdent(item_ident) == UniqueItems[i]->Ident) {
369 			return UniqueItems[i];
370 		}
371 	}
372 	return nullptr;
373 }
374 
375 /**
376 **	@brief	Process data provided by a configuration file
377 **
378 **	@param	config_data	The configuration data
379 */
ProcessConfigData(const CConfigData * config_data)380 void CPersistentItem::ProcessConfigData(const CConfigData *config_data)
381 {
382 	bool is_equipped = false;
383 
384 	for (size_t i = 0; i < config_data->Properties.size(); ++i) {
385 		std::string key = config_data->Properties[i].first;
386 		std::string value = config_data->Properties[i].second;
387 
388 		if (key == "name") {
389 			this->Name = value;
390 		} else if (key == "type") {
391 			value = FindAndReplaceString(value, "_", "-");
392 			CUnitType *unit_type = UnitTypeByIdent(value);
393 			if (unit_type) {
394 				this->Type = unit_type;
395 			} else {
396 				fprintf(stderr, "Unit type \"%s\" doesn't exist.\n", value.c_str());
397 			}
398 		} else if (key == "prefix") {
399 			value = FindAndReplaceString(value, "_", "-");
400 			CUpgrade *upgrade = CUpgrade::Get(value);
401 			if (upgrade) {
402 				this->Prefix = upgrade;
403 			} else {
404 				fprintf(stderr, "Upgrade \"%s\" doesn't exist.\n", value.c_str());
405 			}
406 		} else if (key == "suffix") {
407 			value = FindAndReplaceString(value, "_", "-");
408 			CUpgrade *upgrade = CUpgrade::Get(value);
409 			if (upgrade) {
410 				this->Suffix = upgrade;
411 			} else {
412 				fprintf(stderr, "Upgrade \"%s\" doesn't exist.\n", value.c_str());
413 			}
414 		} else if (key == "spell") {
415 			value = FindAndReplaceString(value, "_", "-");
416 			CSpell *spell = CSpell::GetSpell(value);
417 			if (spell) {
418 				this->Spell = spell;
419 			} else {
420 				fprintf(stderr, "Spell \"%s\" doesn't exist.\n", value.c_str());
421 			}
422 		} else if (key == "work") {
423 			value = FindAndReplaceString(value, "_", "-");
424 			CUpgrade *upgrade = CUpgrade::Get(value);
425 			if (upgrade) {
426 				this->Work = upgrade;
427 			} else {
428 				fprintf(stderr, "Upgrade \"%s\" doesn't exist.\n", value.c_str());
429 			}
430 		} else if (key == "elixir") {
431 			value = FindAndReplaceString(value, "_", "-");
432 			CUpgrade *upgrade = CUpgrade::Get(value);
433 			if (upgrade) {
434 				this->Elixir = upgrade;
435 			} else {
436 				fprintf(stderr, "Upgrade \"%s\" doesn't exist.\n", value.c_str());
437 			}
438 		} else if (key == "unique") {
439 			value = FindAndReplaceString(value, "_", "-");
440 			CUniqueItem *unique_item = GetUniqueItem(value);
441 			if (unique_item) {
442 				this->Unique = unique_item;
443 				this->Name = unique_item->Name;
444 				if (unique_item->Type != nullptr) {
445 					this->Type = unique_item->Type;
446 				} else {
447 					fprintf(stderr, "Unique item \"%s\" has no type.\n", unique_item->Ident.c_str());
448 				}
449 				this->Prefix = unique_item->Prefix;
450 				this->Suffix = unique_item->Suffix;
451 				this->Spell = unique_item->Spell;
452 				this->Work = unique_item->Work;
453 				this->Elixir = unique_item->Elixir;
454 			} else {
455 				fprintf(stderr, "Unique item \"%s\" doesn't exist.\n", value.c_str());
456 			}
457 		} else if (key == "bound") {
458 			this->Bound = StringToBool(value);
459 		} else if (key == "identified") {
460 			this->Identified = StringToBool(value);
461 		} else if (key == "equipped") {
462 			is_equipped = StringToBool(value);
463 		} else {
464 			fprintf(stderr, "Invalid item property: \"%s\".\n", key.c_str());
465 		}
466 	}
467 
468 	if (is_equipped && this->Owner && GetItemClassSlot(this->Type->ItemClass) != -1) {
469 		this->Owner->EquippedItems[GetItemClassSlot(this->Type->ItemClass)].push_back(this);
470 	}
471 }
472 
GetItemEffectsString(const std::string & item_ident)473 std::string GetItemEffectsString(const std::string &item_ident)
474 {
475 	const CUnitType *item = UnitTypeByIdent(item_ident);
476 
477 	if (item) {
478 		std::string item_effects_string;
479 
480 		bool first_var = true;
481 		for (size_t var = 0; var < UnitTypeVar.GetNumberVariable(); ++var) {
482 			if (
483 				!(var == BASICDAMAGE_INDEX || var == PIERCINGDAMAGE_INDEX || var == THORNSDAMAGE_INDEX
484 				|| var == FIREDAMAGE_INDEX || var == COLDDAMAGE_INDEX || var == ARCANEDAMAGE_INDEX || var == LIGHTNINGDAMAGE_INDEX
485 				|| var == AIRDAMAGE_INDEX || var == EARTHDAMAGE_INDEX || var == WATERDAMAGE_INDEX || var == ACIDDAMAGE_INDEX
486 				|| var == ARMOR_INDEX || var == FIRERESISTANCE_INDEX || var == COLDRESISTANCE_INDEX || var == ARCANERESISTANCE_INDEX || var == LIGHTNINGRESISTANCE_INDEX
487 				|| var == AIRRESISTANCE_INDEX || var == EARTHRESISTANCE_INDEX || var == WATERRESISTANCE_INDEX || var == ACIDRESISTANCE_INDEX
488 				|| var == HACKRESISTANCE_INDEX || var == PIERCERESISTANCE_INDEX || var == BLUNTRESISTANCE_INDEX
489 				|| var == ACCURACY_INDEX || var == EVASION_INDEX || var == SPEED_INDEX || var == CHARGEBONUS_INDEX || var == BACKSTAB_INDEX
490 				|| var == HITPOINTHEALING_INDEX || var == HITPOINTBONUS_INDEX || var == SIGHTRANGE_INDEX || var == DAYSIGHTRANGEBONUS_INDEX || var == NIGHTSIGHTRANGEBONUS_INDEX || var == HP_INDEX || var == MANA_INDEX
491 				|| var == ATTACKRANGE_INDEX)
492 			) {
493 				continue;
494 			}
495 
496 			if (var != HP_INDEX) { //only for elixirs, equippable items use the hit point bonus variable instead
497 				if (item->DefaultStat.Variables[var].Enable) {
498 					if (!first_var) {
499 						item_effects_string += ", ";
500 					} else {
501 						first_var = false;
502 					}
503 
504 					if (IsBooleanVariable(var) && item->DefaultStat.Variables[var].Value < 0) {
505 						item_effects_string += "Lose ";
506 					}
507 
508 					if (!IsBooleanVariable(var)) {
509 						if (item->DefaultStat.Variables[var].Value >= 0 && var != HITPOINTHEALING_INDEX) {
510 							item_effects_string += "+";
511 						}
512 						item_effects_string += std::to_string((long long) item->DefaultStat.Variables[var].Value);
513 						if (IsPercentageVariable(var)) {
514 							item_effects_string += "%";
515 						}
516 						item_effects_string += " ";
517 					}
518 
519 					item_effects_string += GetVariableDisplayName(var);
520 				}
521 
522 				if (item->DefaultStat.Variables[var].Increase != 0) {
523 					if (!first_var) {
524 						item_effects_string += ", ";
525 					} else {
526 						first_var = false;
527 					}
528 
529 					if (item->DefaultStat.Variables[var].Increase > 0) {
530 						item_effects_string += "+";
531 					}
532 					item_effects_string += std::to_string((long long) item->DefaultStat.Variables[var].Increase);
533 					item_effects_string += " ";
534 
535 					item_effects_string += GetVariableDisplayName(var, true);
536 				}
537 			}
538 
539 			if (item->Elixir) {
540 				for (size_t z = 0; z < item->Elixir->UpgradeModifiers.size(); ++z) {
541 					if (item->Elixir->UpgradeModifiers[z]->Modifier.Variables[var].Value != 0) {
542 						if (!first_var) {
543 							item_effects_string += ", ";
544 						} else {
545 							first_var = false;
546 						}
547 
548 						if (IsBooleanVariable(var) && item->Elixir->UpgradeModifiers[z]->Modifier.Variables[var].Value < 0) {
549 							item_effects_string += "Lose ";
550 						}
551 
552 						if (!IsBooleanVariable(var)) {
553 							if (item->Elixir->UpgradeModifiers[z]->Modifier.Variables[var].Value >= 0 && var != HITPOINTHEALING_INDEX) {
554 								item_effects_string += "+";
555 							}
556 							item_effects_string += std::to_string((long long) item->Elixir->UpgradeModifiers[z]->Modifier.Variables[var].Value);
557 							if (IsPercentageVariable(var)) {
558 								item_effects_string += "%";
559 							}
560 							item_effects_string += " ";
561 						}
562 
563 						item_effects_string += GetVariableDisplayName(var);
564 					}
565 
566 					if (item->Elixir->UpgradeModifiers[z]->Modifier.Variables[var].Increase != 0) {
567 						if (!first_var) {
568 							item_effects_string += ", ";
569 						} else {
570 							first_var = false;
571 						}
572 
573 						if (item->Elixir->UpgradeModifiers[z]->Modifier.Variables[var].Increase > 0) {
574 							item_effects_string += "+";
575 						}
576 						item_effects_string += std::to_string((long long) item->Elixir->UpgradeModifiers[z]->Modifier.Variables[var].Increase);
577 						item_effects_string += " ";
578 
579 						item_effects_string += GetVariableDisplayName(var, true);
580 					}
581 				}
582 			}
583 		}
584 
585 		return item_effects_string;
586 	}
587 
588 	return "";
589 }
590 
GetUniqueItemEffectsString(const std::string & item_ident)591 std::string GetUniqueItemEffectsString(const std::string &item_ident)
592 {
593 	const CUniqueItem *item = GetUniqueItem(item_ident);
594 
595 	if (item) {
596 		std::string item_effects_string;
597 
598 		bool first_var = true;
599 
600 		for (size_t var = 0; var < UnitTypeVar.GetNumberVariable(); ++var) {
601 			if (
602 				!(var == BASICDAMAGE_INDEX || var == PIERCINGDAMAGE_INDEX || var == THORNSDAMAGE_INDEX
603 				|| var == FIREDAMAGE_INDEX || var == COLDDAMAGE_INDEX || var == ARCANEDAMAGE_INDEX || var == LIGHTNINGDAMAGE_INDEX
604 				|| var == AIRDAMAGE_INDEX || var == EARTHDAMAGE_INDEX || var == WATERDAMAGE_INDEX || var == ACIDDAMAGE_INDEX
605 				|| var == ARMOR_INDEX || var == FIRERESISTANCE_INDEX || var == COLDRESISTANCE_INDEX || var == ARCANERESISTANCE_INDEX || var == LIGHTNINGRESISTANCE_INDEX
606 				|| var == AIRRESISTANCE_INDEX || var == EARTHRESISTANCE_INDEX || var == WATERRESISTANCE_INDEX || var == ACIDRESISTANCE_INDEX
607 				|| var == HACKRESISTANCE_INDEX || var == PIERCERESISTANCE_INDEX || var == BLUNTRESISTANCE_INDEX
608 				|| var == ACCURACY_INDEX || var == EVASION_INDEX || var == SPEED_INDEX || var == CHARGEBONUS_INDEX || var == BACKSTAB_INDEX
609 				|| var == HITPOINTHEALING_INDEX || var == HITPOINTBONUS_INDEX
610 				|| var == SIGHTRANGE_INDEX || var == DAYSIGHTRANGEBONUS_INDEX || var == NIGHTSIGHTRANGEBONUS_INDEX
611 				|| var == GIVERESOURCE_INDEX || var == TIMEEFFICIENCYBONUS_INDEX || var == RESEARCHSPEEDBONUS_INDEX || var == GARRISONEDRANGEBONUS_INDEX
612 				|| var == KNOWLEDGEMAGIC_INDEX || var == KNOWLEDGEWARFARE_INDEX || var == KNOWLEDGEMINING_INDEX
613 				|| var == BONUSAGAINSTMOUNTED_INDEX|| var == BONUSAGAINSTBUILDINGS_INDEX || var == BONUSAGAINSTAIR_INDEX || var == BONUSAGAINSTGIANTS_INDEX || var == BONUSAGAINSTDRAGONS_INDEX
614 				|| var == SUPPLY_INDEX || var == ETHEREALVISION_INDEX
615 				|| var == ATTACKRANGE_INDEX)
616 			) {
617 				continue;
618 			}
619 
620 			int variable_value = 0;
621 			int variable_increase = 0;
622 			if (item->Type->BoolFlag[ITEM_INDEX].value && item->Work == nullptr && item->Elixir == nullptr) {
623 				variable_value = item->Type->DefaultStat.Variables[var].Value;
624 				variable_increase = item->Type->DefaultStat.Variables[var].Increase;
625 			}
626 
627 			if (var == GIVERESOURCE_INDEX && item->ResourcesHeld != 0) {
628 				variable_value = item->ResourcesHeld;
629 			}
630 
631 			for (const CUpgradeModifier *modifier : CUpgradeModifier::UpgradeModifiers) {
632 				if (
633 					(item->Prefix != nullptr && modifier->UpgradeId == item->Prefix->ID)
634 					|| (item->Suffix != nullptr && modifier->UpgradeId == item->Suffix->ID)
635 					|| (item->Work != nullptr && modifier->UpgradeId == item->Work->ID)
636 					|| (item->Elixir != nullptr && modifier->UpgradeId == item->Elixir->ID)
637 				) {
638 					variable_value += modifier->Modifier.Variables[var].Value;
639 					variable_increase += modifier->Modifier.Variables[var].Increase;
640 				}
641 			}
642 
643 			if ((item->Type->BoolFlag[ITEM_INDEX].value && item->Type->DefaultStat.Variables[var].Enable && item->Work == nullptr && item->Elixir == nullptr) || variable_value != 0) {
644 				if (!first_var) {
645 					item_effects_string += ", ";
646 				} else {
647 					first_var = false;
648 				}
649 
650 				if (IsBooleanVariable(var) && variable_value < 0) {
651 					item_effects_string += "Lose ";
652 				}
653 
654 				if (!IsBooleanVariable(var)) {
655 					if (variable_value >= 0 && var != HITPOINTHEALING_INDEX && var != GIVERESOURCE_INDEX) {
656 						item_effects_string += "+";
657 					}
658 					item_effects_string += std::to_string((long long) variable_value);
659 					if (IsPercentageVariable(var)) {
660 						item_effects_string += "%";
661 					}
662 					item_effects_string += " ";
663 				}
664 
665 				item_effects_string += GetVariableDisplayName(var);
666 			}
667 
668 			if (variable_increase != 0) {
669 				if (!first_var) {
670 					item_effects_string += ", ";
671 				} else {
672 					first_var = false;
673 				}
674 
675 				if (variable_increase > 0) {
676 					item_effects_string += "+";
677 				}
678 				item_effects_string += std::to_string((long long) variable_increase);
679 				item_effects_string += " ";
680 
681 				item_effects_string += GetVariableDisplayName(var, true);
682 			}
683 		}
684 
685 		if (item->Set) {
686 			for (size_t var = 0; var < UnitTypeVar.GetNumberVariable(); ++var) {
687 				if (
688 					!(var == BASICDAMAGE_INDEX || var == PIERCINGDAMAGE_INDEX || var == THORNSDAMAGE_INDEX
689 					|| var == FIREDAMAGE_INDEX || var == COLDDAMAGE_INDEX || var == ARCANEDAMAGE_INDEX || var == LIGHTNINGDAMAGE_INDEX
690 					|| var == AIRDAMAGE_INDEX || var == EARTHDAMAGE_INDEX || var == WATERDAMAGE_INDEX || var == ACIDDAMAGE_INDEX
691 					|| var == ARMOR_INDEX || var == FIRERESISTANCE_INDEX || var == COLDRESISTANCE_INDEX || var == ARCANERESISTANCE_INDEX || var == LIGHTNINGRESISTANCE_INDEX
692 					|| var == AIRRESISTANCE_INDEX || var == EARTHRESISTANCE_INDEX || var == WATERRESISTANCE_INDEX || var == ACIDRESISTANCE_INDEX
693 					|| var == HACKRESISTANCE_INDEX || var == PIERCERESISTANCE_INDEX || var == BLUNTRESISTANCE_INDEX
694 					|| var == ACCURACY_INDEX || var == EVASION_INDEX || var == SPEED_INDEX || var == CHARGEBONUS_INDEX || var == BACKSTAB_INDEX
695 					|| var == HITPOINTHEALING_INDEX || var == HITPOINTBONUS_INDEX
696 					|| var == SIGHTRANGE_INDEX || var == DAYSIGHTRANGEBONUS_INDEX || var == NIGHTSIGHTRANGEBONUS_INDEX
697 					|| var == GIVERESOURCE_INDEX || var == TIMEEFFICIENCYBONUS_INDEX || var == RESEARCHSPEEDBONUS_INDEX || var == GARRISONEDRANGEBONUS_INDEX
698 					|| var == KNOWLEDGEMAGIC_INDEX || var == KNOWLEDGEWARFARE_INDEX || var == KNOWLEDGEMINING_INDEX
699 					|| var == BONUSAGAINSTMOUNTED_INDEX|| var == BONUSAGAINSTBUILDINGS_INDEX || var == BONUSAGAINSTAIR_INDEX || var == BONUSAGAINSTGIANTS_INDEX || var == BONUSAGAINSTDRAGONS_INDEX
700 					|| var == SUPPLY_INDEX
701 					|| var == ATTACKRANGE_INDEX)
702 				) {
703 					continue;
704 				}
705 
706 				int variable_value = 0;
707 				int variable_increase = 0;
708 
709 				for (size_t z = 0; z < item->Set->UpgradeModifiers.size(); ++z) {
710 					variable_value += item->Set->UpgradeModifiers[z]->Modifier.Variables[var].Value;
711 					variable_increase += item->Set->UpgradeModifiers[z]->Modifier.Variables[var].Increase;
712 				}
713 
714 				if (variable_value != 0) {
715 					if (!first_var) {
716 						item_effects_string += ", ";
717 					} else {
718 						first_var = false;
719 					}
720 
721 					if (IsBooleanVariable(var) && variable_value < 0) {
722 						item_effects_string += "Lose ";
723 					}
724 
725 					if (!IsBooleanVariable(var)) {
726 						if (variable_value >= 0 && var != HITPOINTHEALING_INDEX && var != GIVERESOURCE_INDEX) {
727 							item_effects_string += "+";
728 						}
729 						item_effects_string += std::to_string((long long) variable_value);
730 						if (IsPercentageVariable(var)) {
731 							item_effects_string += "%";
732 						}
733 						item_effects_string += " ";
734 					}
735 
736 					item_effects_string += GetVariableDisplayName(var);
737 					item_effects_string += " (Set Bonus)";
738 				}
739 
740 				if (variable_increase != 0) {
741 					if (!first_var) {
742 						item_effects_string += ", ";
743 					} else {
744 						first_var = false;
745 					}
746 
747 					if (variable_increase > 0) {
748 						item_effects_string += "+";
749 					}
750 					item_effects_string += std::to_string((long long) variable_increase);
751 					item_effects_string += " ";
752 
753 					item_effects_string += GetVariableDisplayName(var, true);
754 					item_effects_string += " (Set Bonus)";
755 				}
756 			}
757 		}
758 
759 		return item_effects_string;
760 	}
761 
762 	return "";
763 }
764 
765 //@}
766