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 unittype.cpp - The unit type source file. */
12 //
13 //      (c) Copyright 1998-2019 by Lutz Sammer, Jimmy Salmon and 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 --  Includes
32 ----------------------------------------------------------------------------*/
33 
34 #include "stratagus.h"
35 
36 #include "unit/unittype.h"
37 
38 //Wyrmgus start
39 #include "../ai/ai_local.h" //for using AiHelpers
40 //Wyrmgus end
41 #include "animation.h"
42 #include "animation/animation_exactframe.h"
43 #include "animation/animation_frame.h"
44 #include "civilization.h"
45 #include "config.h"
46 #include "construct.h"
47 //Wyrmgus start
48 #include "editor.h" //for personal name generation
49 //Wyrmgus end
50 #include "iolib.h"
51 #include "luacallback.h"
52 #include "map/map.h"
53 #include "map/terrain_type.h"
54 #include "map/tileset.h"
55 #include "missile.h"
56 #include "mod.h"
57 #include "player.h"
58 #include "script.h"
59 #include "sound.h"
60 #include "spells.h"
61 #include "translate.h"
62 #include "ui/button_action.h"
63 #include "ui/button_level.h"
64 #include "ui/ui.h"
65 #include "unit/unit_type_variation.h"
66 #include "unitsound.h"
67 #include "upgrade/dependency.h"
68 //Wyrmgus start
69 #include "upgrade/upgrade.h"
70 //Wyrmgus end
71 #include "util.h"
72 #include "video.h"
73 
74 #include <ctype.h>
75 
76 #include <string>
77 #include <map>
78 #include <cstring>
79 
80 /*----------------------------------------------------------------------------
81 -- Documentation
82 ----------------------------------------------------------------------------*/
83 
84 /**
85 **  @class CUnitType unittype.h
86 **
87 **  \#include "unit/unittype.h"
88 **
89 **  This class contains the information that is shared between all
90 **  units of the same type and determins if a unit is a building,
91 **  a person, ...
92 **
93 **  The unit-type class members:
94 **
95 **  CUnitType::Ident
96 **
97 **    Unique identifier of the unit-type, used to reference it in
98 **    config files and during startup. As convention they start with
99 **    "unit-" fe. "unit-farm".
100 **  @note Don't use this member in game, use instead the pointer
101 **  to this structure. See UnitTypeByIdent().
102 **
103 **  CUnitType::Name
104 **
105 **    Pretty name shown by the engine. The name should be shorter
106 **    than 17 characters and no word can be longer than 8 characters.
107 **
108 **  CUnitType::File
109 **
110 **    Path file name of the sprite file.
111 **
112 **  CUnitType::ShadowFile
113 **
114 **    Path file name of shadow sprite file.
115 **
116 **  CUnitType::DrawLevel
117 **
118 **    The Level/Order to draw this type of unit in. 0-255 usually.
119 **
120 **  CUnitType::Width CUnitType::Height
121 **
122 **    Size of a sprite frame in pixels. All frames of a sprite have
123 **    the same size. Also all sprites (tilesets) must have the same
124 **    size.
125 **
126 **  CUnitType::ShadowWidth CUnitType::ShadowHeight
127 **
128 **    Size of a shadow sprite frame in pixels. All frames of a sprite
129 **    have the same size. Also all sprites (tilesets) must have the
130 **    same size.
131 **
132 **  CUnitType::ShadowOffsetX CUnitType::ShadowOffsetY
133 **
134 **    Vertical offset to draw the shadow in pixels.
135 **
136 **  CUnitType::Animations
137 **
138 **    Animation scripts for the different actions. Currently the
139 **    animations still, move, attack and die are supported.
140 **  @see CAnimations
141 **  @see CAnimation
142 **
143 **  CUnitType::Icon
144 **
145 **    Icon to display for this unit-type. Contains configuration and
146 **    run time variable.
147 **  @note This icon can be used for training, but isn't used.
148 **
149 **  CUnitType::Missile
150 **
151 **    Configuration and run time variable of the missile weapon.
152 **  @note It is planned to support more than one weapons.
153 **  And the sound of the missile should be used as fire sound.
154 **
155 **  CUnitType::Explosion
156 **
157 **    Configuration and run time variable of the missile explosion.
158 **    This is the explosion that happens if unit is set to
159 **    ExplodeWhenKilled
160 **
161 **  CUnitType::CorpseName
162 **
163 **    Corpse unit-type name, should only be used during setup.
164 **
165 **  CUnitType::CorpseType
166 **
167 **    Corpse unit-type pointer, only this should be used during run
168 **    time. Many unit-types can share the same corpse.
169 **
170 **
171 **  @todo continue this documentation
172 **
173 **  CUnitType::Construction
174 **
175 **    What is shown in construction phase.
176 **
177 **  CUnitType::SightRange
178 **
179 **    Sight range
180 **
181 **  CUnitType::_HitPoints
182 **
183 **    Maximum hit points
184 **
185 **
186 **  CUnitType::_Costs[::MaxCosts]
187 **
188 **    How many resources needed
189 **
190 **  CUnitType::RepairHP
191 **
192 **    The HP given to a unit each cycle it's repaired.
193 **    If zero, unit cannot be repaired
194 **
195 **    CUnitType::RepairCosts[::MaxCosts]
196 **
197 **    Costs per repair cycle to fix a unit.
198 **
199 **  CUnitType::TileSize
200 **
201 **    Tile size on map
202 **
203 **  CUnitType::BoxWidth
204 **
205 **    Selected box size width
206 **
207 **  CUnitType::BoxHeight
208 **
209 **    Selected box size height
210 **
211 **  CUnitType::NumDirections
212 **
213 **    Number of directions the unit can face
214 **
215 **  CUnitType::MinAttackRange
216 **
217 **    Minimal attack range
218 **
219 **  CUnitType::ReactRangeComputer
220 **
221 **    Reacts on enemy for computer
222 **
223 **  CUnitType::ReactRangePerson
224 **
225 **    Reacts on enemy for person player
226 **
227 **  CUnitType::Priority
228 **
229 **    Priority value / AI Treatment
230 **
231 **  CUnitType::BurnPercent
232 **
233 **    The burning limit in percents. If the unit has lees than
234 **    this it will start to burn.
235 **
236 **  CUnitType::BurnDamageRate
237 **
238 **    Burn rate in HP per second
239 **
240 **  CUnitType::UnitType
241 **
242 **    Land / fly / naval
243 **
244 **  @note original only visual effect, we do more with this!
245 **
246 **  CUnitType::DecayRate
247 **
248 **    Decay rate in 1/6 seconds
249 **
250 **  CUnitType::AnnoyComputerFactor
251 **
252 **    How much this annoys the computer
253 **
254 **  @todo not used
255 **
256 **  CUnitType::MouseAction
257 **
258 **    Right click action
259 **
260 **  CUnitType::Points
261 **
262 **    How many points you get for unit. Used in the final score table.
263 **
264 **  CUnitType::CanTarget
265 **
266 **    Which units can it attack
267 **
268 **  CUnitType::LandUnit
269 **
270 **    Land animated
271 **
272 **  CUnitType::AirUnit
273 **
274 **    Air animated
275 **
276 **  CUnitType::SeaUnit
277 **
278 **    Sea animated
279 **
280 **  CUnitType::ExplodeWhenKilled
281 **
282 **    Death explosion animated
283 **
284 **  CUnitType::RandomMovementProbability
285 **
286 **    When the unit is idle this is the probability that it will
287 **    take a step in a random direction, in percents.
288 **
289 **  CUnitType::ClicksToExplode
290 **
291 **    If this is non-zero, then after that many clicks the unit will
292 **    commit suicide. Doesn't work with resource workers/resources.
293 **
294 **  CUnitType::Building
295 **
296 **    Unit is a Building
297 **
298 **  CUnitType::Transporter
299 **
300 **    Can transport units
301 **
302 **  CUnitType::MaxOnBoard
303 **
304 **    Maximum units on board (for transporters), and resources
305 **
306 **  CUnitType::StartingResources
307 **    Amount of Resources a unit has when It's Built
308 **
309 **  CUnitType::DamageType
310 **    Unit's missile damage type (used for extra death animations)
311 **
312 **  CUnitType::GivesResource
313 **
314 **    This equals to the resource Id of the resource given
315 **    or 0 (TimeCost) for other buildings.
316 **
317 **  CUnitType::ResInfo[::MaxCosts]
318 **
319 **    Information about resource harvesting. If null, it can't
320 **    harvest it.
321 **
322 **  CUnitType::NeutralMinimapColorRGB
323 **
324 **    Says what color a unit will have when it's neutral and
325 **    is displayed on the minimap.
326 **
327 **  CUnitType::CanStore[::MaxCosts]
328 **
329 **    What resource types we can store here.
330 **
331 **  CUnitType::Spells
332 **
333 **    Spells the unit is able to use
334 **
335 **  CUnitType::CanAttack
336 **
337 **    Unit is able to attack.
338 **
339 **  CUnitType::RepairRange
340 **
341 **    Unit can repair buildings. It will use the actack animation.
342 **    It will heal 4 points for every repair cycle, and cost 1 of
343 **    each resource, alternatively(1 cycle wood, 1 cycle gold)
344 **  @todo The above should be more configurable.
345 **    If units have a repair range, they can repair, and this is the
346 **    distance.
347 **
348 **    CUnitType::ShieldPiercing
349 **
350 **    Can directly damage shield-protected units, without shield damaging.
351 **
352 **  CUnitType::Sound
353 **
354 **    Sounds for events
355 **
356 **  CUnitType::Weapon
357 **
358 **    Current sound for weapon
359 **
360 **  @todo temporary solution
361 **
362 **  CUnitType::FieldFlags
363 **
364 **    Flags that are set, if a unit enters a map field or cleared, if
365 **    a unit leaves a map field.
366 **
367 **  CUnitType::MovementMask
368 **
369 **    Movement mask, this value is and'ed to the map field flags, to
370 **    see if a unit can enter or placed on the map field.
371 **
372 **  CUnitType::Stats[::PlayerMax]
373 **
374 **    Unit status for each player
375 **  @todo This stats should? be moved into the player struct
376 **
377 **  CUnitType::Type
378 **
379 **    Type as number
380 **  @todo Should us a general name f.e. Slot here?
381 **
382 **  CUnitType::Sprite
383 **
384 **    Sprite images
385 **
386 **  CUnitType::ShadowSprite
387 **
388 **    Shadow sprite images
389 **
390 **  CUnitType::PlayerColorSprite
391 **
392 **    Sprite images of the player colors.  This image is drawn
393 **    over CUnitType::Sprite.  Used with OpenGL only.
394 **
395 **
396 */
397 /**
398 **
399 **  @class ResourceInfo unittype.h
400 **
401 ** \#include "unit/unittype.h"
402 **
403 **    This class contains information about how a unit will harvest a resource.
404 **
405 **  ResourceInfo::FileWhenLoaded
406 **
407 **    The harvester's animation file will change when it's loaded.
408 **
409 **  ResourceInfo::FileWhenEmpty;
410 **
411 **    The harvester's animation file will change when it's empty.
412 **    The standard animation is used only when building/repairing.
413 **
414 **
415 **  ResourceInfo::HarvestFromOutside
416 **
417 **    Unit will harvest from the outside. The unit will use it's
418 **    Attack animation (seems it turned into a generic Action anim.)
419 **
420 **  ResourceInfo::ResourceId
421 **
422 **    The resource this is for. Mostly redundant.
423 **
424 **  ResourceInfo::FinalResource
425 **
426 **    The resource is converted to this at the depot. Useful for
427 **    a fisherman who harvests fish, but it all turns to food at the
428 **    depot.
429 **
430 **  ResourceInfo::FinalResourceConversionRate
431 **
432 **    The rate at which the resource is converted to the final resource at the depot. Useful for
433 **    silver mines returning a lower amount of gold.
434 **
435 **  ResourceInfo::WaitAtResource
436 **
437 **    Cycles the unit waits while inside a resource.
438 **
439 **  ResourceInfo::ResourceStep
440 **
441 **    The unit makes so-caled mining cycles. Each mining cycle
442 **    it does some sort of animation and gains ResourceStep
443 **    resources. You can stop after any number of steps.
444 **    when the quantity in the harvester reaches the maximum
445 **    (ResourceCapacity) it will return home. I this is 0 then
446 **    it's considered infinity, and ResourceCapacity will now
447 **    be the limit.
448 **
449 **  ResourceInfo::ResourceCapacity
450 **
451 **    Maximum amount of resources a harvester can carry. The
452 **    actual amount can be modified while unloading.
453 **
454 **  ResourceInfo::LoseResources
455 **
456 **    Special lossy behaviour for loaded harvesters. Harvesters
457 **    with loads other than 0 and ResourceCapacity will lose their
458 **    cargo on any new order.
459 **
460 **  ResourceInfo::WaitAtDepot
461 **
462 **    Cycles the unit waits while inside the depot to unload.
463 **
464 **  ResourceInfo::TerrainHarvester
465 **
466 **    The unit will harvest terrain. For now this only works
467 **    for wood. maybe it could be made to work for rocks, but
468 **    more than that requires a tileset rewrite.
469 **  @todo more configurable.
470 **
471 */
472 
473 /*----------------------------------------------------------------------------
474 --  Variables
475 ----------------------------------------------------------------------------*/
476 
477 std::vector<CUnitType *> UnitTypes;   /// unit-types definition
478 std::map<std::string, CUnitType *> UnitTypeMap;
479 
480 /**
481 **  Next unit type are used hardcoded in the source.
482 **
483 **  @todo find a way to make it configurable!
484 */
485 //Wyrmgus start
486 //CUnitType *UnitTypeHumanWall;       /// Human wall
487 //CUnitType *UnitTypeOrcWall;         /// Orc wall
488 //Wyrmgus end
489 
490 /**
491 **  Resource objects.
492 */
493 CResource Resources[MaxCosts];
494 
495 /**
496 **  Default names for the resources.
497 */
498 std::string DefaultResourceNames[MaxCosts];
499 
500 std::vector<int> LuxuryResources;
501 
502 /**
503 **  Default names for the resources.
504 */
505 std::string ExtraDeathTypes[ANIMATIONS_DEATHTYPES];
506 
507 //Wyrmgus start
508 std::vector<std::string> UnitTypeClasses;
509 std::map<std::string, int> UnitTypeClassStringToIndex;
510 std::vector<std::vector<CUnitType *>> ClassUnitTypes;
511 std::vector<std::string> UpgradeClasses;
512 std::map<std::string, int> UpgradeClassStringToIndex;
513 CUnitType *SettlementSiteUnitType;
514 
515 std::vector<CSpecies *> Species;
516 std::vector<CSpeciesGenus *> SpeciesGenuses;
517 std::vector<CSpeciesFamily *> SpeciesFamilies;
518 std::vector<CSpeciesOrder *> SpeciesOrders;
519 std::vector<CSpeciesClass *> SpeciesClasses;
520 std::vector<CSpeciesPhylum *> SpeciesPhylums;
521 //Wyrmgus end
522 
523 /*----------------------------------------------------------------------------
524 --  Functions
525 ----------------------------------------------------------------------------*/
526 
GetResourceIdByName(const char * resourceName)527 int GetResourceIdByName(const char *resourceName)
528 {
529 	for (unsigned int res = 0; res < MaxCosts; ++res) {
530 		if (!strcmp(resourceName, DefaultResourceNames[res].c_str())) {
531 			return res;
532 		}
533 	}
534 	return -1;
535 }
536 
GetResourceIdByName(lua_State * l,const char * resourceName)537 int GetResourceIdByName(lua_State *l, const char *resourceName)
538 {
539 	const int res = GetResourceIdByName(resourceName);
540 	if (res == -1) {
541 		LuaError(l, "Resource not found: %s" _C_ resourceName);
542 	}
543 	return res;
544 }
545 
546 //Wyrmgus start
GetResourceNameById(int resource_id)547 std::string GetResourceNameById(int resource_id)
548 {
549 	if (resource_id > 0 && resource_id < MaxCosts) {
550 		return DefaultResourceNames[resource_id];
551 	} else {
552 		return "";
553 	}
554 }
555 
IsMineResource() const556 bool CResource::IsMineResource() const
557 {
558 	return this->ID == CopperCost || this->ID == SilverCost || this->ID == GoldCost || this->ID == IronCost || this->ID == MithrilCost || this->ID == CoalCost || this->ID == DiamondsCost || this->ID == EmeraldsCost;
559 }
560 //Wyrmgus end
561 
CUnitType()562 CUnitType::CUnitType() :
563 	Slot(0), Width(0), Height(0), OffsetX(0), OffsetY(0), DrawLevel(0),
564 	ShadowWidth(0), ShadowHeight(0), ShadowOffsetX(0), ShadowOffsetY(0),
565 	//Wyrmgus start
566 	TrainQuantity(0), CostModifier(0), ItemClass(-1),
567 	Class(-1), Civilization(-1), Faction(-1), Species(nullptr), TerrainType(nullptr),
568 	//Wyrmgus end
569 	Animations(nullptr), StillFrame(0),
570 	DeathExplosion(nullptr), OnHit(nullptr), OnEachCycle(nullptr), OnEachSecond(nullptr), OnInit(nullptr),
571 	TeleportCost(0), TeleportEffectIn(nullptr), TeleportEffectOut(nullptr),
572 	CorpseType(nullptr), Construction(nullptr), RepairHP(0), TileSize(0, 0),
573 	BoxWidth(0), BoxHeight(0), BoxOffsetX(0), BoxOffsetY(0), NumDirections(0),
574 	//Wyrmgus start
575 //	MinAttackRange(0), ReactRangeComputer(0), ReactRangePerson(0),
576 	MinAttackRange(0),
577 	//Wyrmgus end
578 	BurnPercent(0), BurnDamageRate(0), RepairRange(0),
579 	AutoCastActive(nullptr),
580 	AutoBuildRate(0), RandomMovementProbability(0), RandomMovementDistance(1), ClicksToExplode(0),
581 	//Wyrmgus start
582 //	MaxOnBoard(0), BoardSize(1), ButtonLevelForTransporter(0), StartingResources(0),
583 	MaxOnBoard(0), BoardSize(1), ButtonLevelForTransporter(nullptr), ButtonPos(0), ButtonLevel(0),
584 	//Wyrmgus end
585 	UnitType(UnitTypeLand), DecayRate(0), AnnoyComputerFactor(0), AiAdjacentRange(-1),
586 	MouseAction(0), CanTarget(0),
587 	Flip(1), LandUnit(0), AirUnit(0), SeaUnit(0),
588 	ExplodeWhenKilled(0),
589 	CanAttack(0),
590 	Neutral(0),
591 	GivesResource(0), PoisonDrain(0), FieldFlags(0), MovementMask(0),
592 	//Wyrmgus start
593 	Elixir(nullptr),
594 //	Sprite(nullptr), ShadowSprite(nullptr)
595 	Sprite(nullptr), ShadowSprite(nullptr), LightSprite(nullptr)
596 	//Wyrmgus end
597 {
598 #ifdef USE_MNG
599 	memset(&Portrait, 0, sizeof(Portrait));
600 #endif
601 	memset(RepairCosts, 0, sizeof(RepairCosts));
602 	memset(CanStore, 0, sizeof(CanStore));
603 	//Wyrmgus start
604 	memset(GrandStrategyProductionEfficiencyModifier, 0, sizeof(GrandStrategyProductionEfficiencyModifier));
605 	//Wyrmgus end
606 	memset(ResInfo, 0, sizeof(ResInfo));
607 	memset(MissileOffsets, 0, sizeof(MissileOffsets));
608 	//Wyrmgus start
609 	memset(LayerSprites, 0, sizeof(LayerSprites));
610 	//Wyrmgus end
611 }
612 
~CUnitType()613 CUnitType::~CUnitType()
614 {
615 	delete DeathExplosion;
616 	delete OnHit;
617 	delete OnEachCycle;
618 	delete OnEachSecond;
619 	delete OnInit;
620 	delete TeleportEffectIn;
621 	delete TeleportEffectOut;
622 
623 	//Wyrmgus start
624 	SoldUnits.clear();
625 	SpawnUnits.clear();
626 	Drops.clear();
627 	AiDrops.clear();
628 	DropSpells.clear();
629 	Affixes.clear();
630 	Traits.clear();
631 	StartingAbilities.clear();
632 	//Wyrmgus end
633 
634 	BoolFlag.clear();
635 
636 	// Free Building Restrictions if there are any
637 	for (std::vector<CBuildRestriction *>::iterator b = BuildingRules.begin();
638 		 b != BuildingRules.end(); ++b) {
639 		delete *b;
640 	}
641 	BuildingRules.clear();
642 	for (std::vector<CBuildRestriction *>::iterator b = AiBuildingRules.begin();
643 		 b != AiBuildingRules.end(); ++b) {
644 		delete *b;
645 	}
646 	AiBuildingRules.clear();
647 
648 	delete[] AutoCastActive;
649 
650 	for (int res = 0; res < MaxCosts; ++res) {
651 		if (this->ResInfo[res]) {
652 			if (this->ResInfo[res]->SpriteWhenLoaded) {
653 				CGraphic::Free(this->ResInfo[res]->SpriteWhenLoaded);
654 			}
655 			if (this->ResInfo[res]->SpriteWhenEmpty) {
656 				CGraphic::Free(this->ResInfo[res]->SpriteWhenEmpty);
657 			}
658 			delete this->ResInfo[res];
659 		}
660 	}
661 
662 	for (CUnitTypeVariation *variation : this->Variations) {
663 		delete variation;
664 	}
665 
666 	for (int i = 0; i < MaxImageLayers; ++i) {
667 		for (CUnitTypeVariation *layer_variation : this->LayerVariations[i]) {
668 			delete layer_variation;
669 		}
670 	}
671 
672 	CGraphic::Free(Sprite);
673 	CGraphic::Free(ShadowSprite);
674 	//Wyrmgus start
675 	CGraphic::Free(LightSprite);
676 	for (int i = 0; i < MaxImageLayers; ++i) {
677 		CGraphic::Free(LayerSprites[i]);
678 	}
679 	//Wyrmgus end
680 #ifdef USE_MNG
681 	if (this->Portrait.Num) {
682 		for (int j = 0; j < this->Portrait.Num; ++j) {
683 			delete this->Portrait.Mngs[j];
684 			// delete[] this->Portrait.Files[j];
685 		}
686 		delete[] this->Portrait.Mngs;
687 		delete[] this->Portrait.Files;
688 	}
689 #endif
690 }
691 
692 /**
693 **	@brief	Process data provided by a configuration file
694 **
695 **	@param	config_data	The configuration data
696 */
ProcessConfigData(const CConfigData * config_data)697 void CUnitType::ProcessConfigData(const CConfigData *config_data)
698 {
699 	this->RemoveButtons(ButtonMove);
700 	this->RemoveButtons(ButtonStop);
701 	this->RemoveButtons(ButtonAttack);
702 	this->RemoveButtons(ButtonPatrol);
703 	this->RemoveButtons(ButtonStandGround);
704 	this->RemoveButtons(ButtonReturn);
705 
706 	for (size_t i = 0; i < config_data->Properties.size(); ++i) {
707 		std::string key = config_data->Properties[i].first;
708 		std::string value = config_data->Properties[i].second;
709 
710 		if (key == "name") {
711 			this->Name = value;
712 		} else if (key == "parent") {
713 			value = FindAndReplaceString(value, "_", "-");
714 			CUnitType *parent_type = UnitTypeByIdent(value);
715 			if (!parent_type) {
716 				fprintf(stderr, "Unit type \"%s\" does not exist.\n", value.c_str());
717 			}
718 			this->SetParent(parent_type);
719 		} else if (key == "civilization") {
720 			value = FindAndReplaceString(value, "_", "-");
721 			CCivilization *civilization = CCivilization::GetCivilization(value);
722 			if (civilization) {
723 				this->Civilization = civilization->ID;
724 			}
725 		} else if (key == "faction") {
726 			value = FindAndReplaceString(value, "_", "-");
727 			CFaction *faction = PlayerRaces.GetFaction(value);
728 			if (faction) {
729 				this->Faction = faction->ID;
730 			} else {
731 				fprintf(stderr, "Faction \"%s\" does not exist.\n", value.c_str());
732 			}
733 		} else if (key == "animations") {
734 			value = FindAndReplaceString(value, "_", "-");
735 			this->Animations = AnimationsByIdent(value);
736 			if (!this->Animations) {
737 				fprintf(stderr, "Animations \"%s\" do not exist.\n", value.c_str());
738 			}
739 		} else if (key == "icon") {
740 			value = FindAndReplaceString(value, "_", "-");
741 			this->Icon.Name = value;
742 			this->Icon.Icon = nullptr;
743 			this->Icon.Load();
744 			this->Icon.Icon->Load();
745 		} else if (key == "tile_width") {
746 			this->TileSize.x = std::stoi(value);
747 		} else if (key == "tile_height") {
748 			this->TileSize.y = std::stoi(value);
749 		} else if (key == "box_width") {
750 			this->BoxWidth = std::stoi(value);
751 		} else if (key == "box_height") {
752 			this->BoxHeight = std::stoi(value);
753 		} else if (key == "draw_level") {
754 			this->DrawLevel = std::stoi(value);
755 		} else if (key == "type") {
756 			if (value == "land") {
757 				this->UnitType = UnitTypeLand;
758 				this->LandUnit = true;
759 			} else if (value == "fly") {
760 				this->UnitType = UnitTypeFly;
761 				this->AirUnit = true;
762 			} else if (value == "fly_low") {
763 				this->UnitType = UnitTypeFlyLow;
764 				this->AirUnit = true;
765 			} else if (value == "naval") {
766 				this->UnitType = UnitTypeNaval;
767 				this->SeaUnit = true;
768 			} else {
769 				fprintf(stderr, "Invalid unit type type: \"%s\".\n", value.c_str());
770 			}
771 		} else if (key == "priority") {
772 			this->DefaultStat.Variables[PRIORITY_INDEX].Value = std::stoi(value);
773 			this->DefaultStat.Variables[PRIORITY_INDEX].Max  = std::stoi(value);
774 		} else if (key == "description") {
775 			this->Description = value;
776 		} else if (key == "background") {
777 			this->Background = value;
778 		} else if (key == "quote") {
779 			this->Quote = value;
780 		} else if (key == "requirements_string") {
781 			this->RequirementsString = value;
782 		} else if (key == "experience_requirements_string") {
783 			this->ExperienceRequirementsString = value;
784 		} else if (key == "building_rules_string") {
785 			this->BuildingRulesString = value;
786 		} else if (key == "max_attack_range") {
787 			this->DefaultStat.Variables[ATTACKRANGE_INDEX].Value = std::stoi(value);
788 			this->DefaultStat.Variables[ATTACKRANGE_INDEX].Max = std::stoi(value);
789 			this->DefaultStat.Variables[ATTACKRANGE_INDEX].Enable = 1;
790 		} else if (key == "missile") {
791 			value = FindAndReplaceString(value, "_", "-");
792 			this->Missile.Name = value;
793 			this->Missile.Missile = nullptr;
794 		} else if (key == "fire_missile") {
795 			value = FindAndReplaceString(value, "_", "-");
796 			this->FireMissile.Name = value;
797 			this->FireMissile.Missile = nullptr;
798 		} else if (key == "corpse") {
799 			value = FindAndReplaceString(value, "_", "-");
800 			this->CorpseName = value;
801 			this->CorpseType = nullptr;
802 		} else if (key == "weapon_class") {
803 			value = FindAndReplaceString(value, "_", "-");
804 			const int weapon_class_id = GetItemClassIdByName(value);
805 			if (weapon_class_id != -1) {
806 				this->WeaponClasses.push_back(weapon_class_id);
807 			} else {
808 				fprintf(stderr, "Invalid weapon class: \"%s\".\n", value.c_str());
809 			}
810 		} else if (key == "ai_drop") {
811 			value = FindAndReplaceString(value, "_", "-");
812 			CUnitType *drop_type = UnitTypeByIdent(value);
813 			if (drop_type) {
814 				this->AiDrops.push_back(drop_type);
815 			} else {
816 				fprintf(stderr, "Invalid unit type: \"%s\".\n", value.c_str());
817 			}
818 		} else if (key == "item_class") {
819 			value = FindAndReplaceString(value, "_", "-");
820 			const int item_class_id = GetItemClassIdByName(value);
821 			if (item_class_id != -1) {
822 				this->ItemClass = item_class_id;
823 			} else {
824 				fprintf(stderr, "Invalid item class: \"%s\".\n", value.c_str());
825 			}
826 		} else if (key == "species") {
827 			value = FindAndReplaceString(value, "_", "-");
828 			this->Species = GetSpecies(value);
829 			if (this->Species) {
830 				this->Species->Type = this;
831 			} else {
832 				fprintf(stderr, "Invalid species: \"%s\".\n", value.c_str());
833 			}
834 		} else if (key == "right_mouse_action") {
835 			if (value == "none") {
836 				this->MouseAction = MouseActionNone;
837 			} else if (value == "attack") {
838 				this->MouseAction = MouseActionAttack;
839 			} else if (value == "move") {
840 				this->MouseAction = MouseActionMove;
841 			} else if (value == "harvest") {
842 				this->MouseAction = MouseActionHarvest;
843 			} else if (value == "spell_cast") {
844 				this->MouseAction = MouseActionSpellCast;
845 			} else if (value == "sail") {
846 				this->MouseAction = MouseActionSail;
847 			} else if (value == "rally_point") {
848 				this->MouseAction = MouseActionRallyPoint;
849 			} else if (value == "trade") {
850 				this->MouseAction = MouseActionTrade;
851 			} else {
852 				fprintf(stderr, "Invalid right mouse action: \"%s\".\n", value.c_str());
853 			}
854 		} else if (key == "can_attack") {
855 			this->CanAttack = StringToBool(value);
856 		} else if (key == "can_target_land") {
857 			const bool can_target_land = StringToBool(value);
858 			if (can_target_land) {
859 				this->CanTarget |= CanTargetLand;
860 			} else {
861 				this->CanTarget &= ~CanTargetLand;
862 			}
863 		} else if (key == "can_target_sea") {
864 			const bool can_target_sea = StringToBool(value);
865 			if (can_target_sea) {
866 				this->CanTarget |= CanTargetSea;
867 			} else {
868 				this->CanTarget &= ~CanTargetSea;
869 			}
870 		} else if (key == "can_target_air") {
871 			const bool can_target_air = StringToBool(value);
872 			if (can_target_air) {
873 				this->CanTarget |= CanTargetAir;
874 			} else {
875 				this->CanTarget &= ~CanTargetAir;
876 			}
877 		} else if (key == "random_movement_probability") {
878 			this->RandomMovementProbability = std::stoi(value);
879 		} else if (key == "random_movement_distance") {
880 			this->RandomMovementDistance = std::stoi(value);
881 		} else if (key == "can_cast_spell") {
882 			value = FindAndReplaceString(value, "_", "-");
883 			CSpell *spell = CSpell::GetSpell(value);
884 			if (spell != nullptr) {
885 				this->Spells.push_back(spell);
886 			}
887 		} else if (key == "autocast_active") {
888 			if (!this->AutoCastActive) {
889 				this->AutoCastActive = new char[CSpell::Spells.size()];
890 				memset(this->AutoCastActive, 0, CSpell::Spells.size() * sizeof(char));
891 			}
892 
893 			if (value == "false") {
894 				delete[] this->AutoCastActive;
895 				this->AutoCastActive = nullptr;
896 			} else {
897 				value = FindAndReplaceString(value, "_", "-");
898 				const CSpell *spell = CSpell::GetSpell(value);
899 				if (spell != nullptr) {
900 					if (spell->AutoCast) {
901 						this->AutoCastActive[spell->Slot] = 1;
902 					} else {
903 						fprintf(stderr, "AutoCastActive : Define autocast method for \"%s\".\n", value.c_str());
904 					}
905 				}
906 			}
907 		} else {
908 			key = SnakeCaseToPascalCase(key);
909 
910 			int index = UnitTypeVar.VariableNameLookup[key.c_str()]; // variable index
911 			if (index != -1) { // valid index
912 				if (IsStringNumber(value)) {
913 					this->DefaultStat.Variables[index].Enable = 1;
914 					this->DefaultStat.Variables[index].Value = std::stoi(value);
915 					this->DefaultStat.Variables[index].Max = std::stoi(value);
916 				} else if (IsStringBool(value)) {
917 					this->DefaultStat.Variables[index].Enable = StringToBool(value);
918 				} else { // error
919 					fprintf(stderr, "Invalid value (\"%s\") for variable \"%s\" when defining unit type \"%s\".\n", value.c_str(), key.c_str(), this->Ident.c_str());
920 				}
921 			} else {
922 				if (this->BoolFlag.size() < UnitTypeVar.GetNumberBoolFlag()) {
923 					this->BoolFlag.resize(UnitTypeVar.GetNumberBoolFlag());
924 				}
925 
926 				index = UnitTypeVar.BoolFlagNameLookup[key.c_str()];
927 				if (index != -1) {
928 					if (IsStringNumber(value)) {
929 						this->BoolFlag[index].value = (std::stoi(value) != 0);
930 					} else {
931 						this->BoolFlag[index].value = StringToBool(value);
932 					}
933 				} else {
934 					fprintf(stderr, "Invalid unit type property: \"%s\".\n", key.c_str());
935 				}
936 			}
937 		}
938 	}
939 
940 	for (const CConfigData *child_config_data : config_data->Children) {
941 		if (child_config_data->Tag == "costs") {
942 			for (size_t j = 0; j < child_config_data->Properties.size(); ++j) {
943 				std::string key = child_config_data->Properties[j].first;
944 				std::string value = child_config_data->Properties[j].second;
945 
946 				key = FindAndReplaceString(key, "_", "-");
947 
948 				const int resource = GetResourceIdByName(key.c_str());
949 				if (resource != -1) {
950 					this->DefaultStat.Costs[resource] = std::stoi(value);
951 				} else {
952 					fprintf(stderr, "Invalid resource: \"%s\".\n", key.c_str());
953 				}
954 			}
955 		} else if (child_config_data->Tag == "image") {
956 			for (size_t j = 0; j < child_config_data->Properties.size(); ++j) {
957 				std::string key = child_config_data->Properties[j].first;
958 				std::string value = child_config_data->Properties[j].second;
959 
960 				if (key == "file") {
961 					this->File = CMod::GetCurrentModPath() + value;
962 				} else if (key == "width") {
963 					this->Width = std::stoi(value);
964 				} else if (key == "height") {
965 					this->Height = std::stoi(value);
966 				} else {
967 					fprintf(stderr, "Invalid image property: \"%s\".\n", key.c_str());
968 				}
969 			}
970 
971 			if (this->File.empty()) {
972 				fprintf(stderr, "Image has no file.\n");
973 			}
974 
975 			if (this->Width == 0) {
976 				fprintf(stderr, "Image has no width.\n");
977 			}
978 
979 			if (this->Height == 0) {
980 				fprintf(stderr, "Image has no height.\n");
981 			}
982 
983 			if (this->Sprite) {
984 				CGraphic::Free(this->Sprite);
985 				this->Sprite = nullptr;
986 			}
987 		} else if (child_config_data->Tag == "default_equipment") {
988 			for (size_t j = 0; j < child_config_data->Properties.size(); ++j) {
989 				std::string key = child_config_data->Properties[j].first;
990 				std::string value = child_config_data->Properties[j].second;
991 				key = FindAndReplaceString(key, "_", "-");
992 				value = FindAndReplaceString(value, "_", "-");
993 
994 				int item_slot = GetItemSlotIdByName(key);
995 				if (item_slot == -1) {
996 					fprintf(stderr, "Invalid item slot for default equipment: \"%s\".\n", key.c_str());
997 					continue;
998 				}
999 
1000 				CUnitType *item = UnitTypeByIdent(value);
1001 				if (!item) {
1002 					fprintf(stderr, "Invalid item for default equipment: \"%s\".\n", value.c_str());
1003 					continue;
1004 				}
1005 
1006 				this->DefaultEquipment[item_slot] = item;
1007 			}
1008 		} else if (child_config_data->Tag == "sounds") {
1009 			for (size_t j = 0; j < child_config_data->Properties.size(); ++j) {
1010 				std::string key = child_config_data->Properties[j].first;
1011 				std::string value = child_config_data->Properties[j].second;
1012 				value = FindAndReplaceString(value, "_", "-");
1013 
1014 				if (key == "selected") {
1015 					this->Sound.Selected.Name = value;
1016 				} else if (key == "acknowledge") {
1017 					this->Sound.Acknowledgement.Name = value;
1018 				} else if (key == "attack") {
1019 					this->Sound.Attack.Name = value;
1020 				} else if (key == "idle") {
1021 					this->Sound.Idle.Name = value;
1022 				} else if (key == "hit") {
1023 					this->Sound.Hit.Name = value;
1024 				} else if (key == "miss") {
1025 					this->Sound.Miss.Name = value;
1026 				} else if (key == "fire_missile") {
1027 					this->Sound.FireMissile.Name = value;
1028 				} else if (key == "step") {
1029 					this->Sound.Step.Name = value;
1030 				} else if (key == "step_dirt") {
1031 					this->Sound.StepDirt.Name = value;
1032 				} else if (key == "step_grass") {
1033 					this->Sound.StepGrass.Name = value;
1034 				} else if (key == "step_gravel") {
1035 					this->Sound.StepGravel.Name = value;
1036 				} else if (key == "step_mud") {
1037 					this->Sound.StepMud.Name = value;
1038 				} else if (key == "step_stone") {
1039 					this->Sound.StepStone.Name = value;
1040 				} else if (key == "used") {
1041 					this->Sound.Used.Name = value;
1042 				} else if (key == "build") {
1043 					this->Sound.Build.Name = value;
1044 				} else if (key == "ready") {
1045 					this->Sound.Ready.Name = value;
1046 				} else if (key == "repair") {
1047 					this->Sound.Repair.Name = value;
1048 				} else if (key.find("harvest_") != std::string::npos) {
1049 					std::string resource_ident = FindAndReplaceString(key, "harvest_", "");
1050 					resource_ident = FindAndReplaceString(resource_ident, "_", "-");
1051 					const int res = GetResourceIdByName(resource_ident.c_str());
1052 					if (res != -1) {
1053 						this->Sound.Harvest[res].Name = value;
1054 					} else {
1055 						fprintf(stderr, "Invalid resource: \"%s\".\n", resource_ident.c_str());
1056 					}
1057 				} else if (key == "help") {
1058 					this->Sound.Help.Name = value;
1059 				} else if (key == "dead") {
1060 					this->Sound.Dead[ANIMATIONS_DEATHTYPES].Name = value;
1061 				} else if (key.find("dead_") != std::string::npos) {
1062 					std::string death_type_ident = FindAndReplaceString(key, "dead_", "");
1063 					death_type_ident = FindAndReplaceString(death_type_ident, "_", "-");
1064 					int death;
1065 					for (death = 0; death < ANIMATIONS_DEATHTYPES; ++death) {
1066 						if (death_type_ident == ExtraDeathTypes[death]) {
1067 							this->Sound.Dead[death].Name = value;
1068 							break;
1069 						}
1070 					}
1071 					if (death == ANIMATIONS_DEATHTYPES) {
1072 						fprintf(stderr, "Invalid death type: \"%s\".\n", death_type_ident.c_str());
1073 					}
1074 				} else {
1075 					fprintf(stderr, "Invalid sound tag: \"%s\".\n", key.c_str());
1076 				}
1077 			}
1078 		} else if (child_config_data->Tag == "predependencies") {
1079 			this->Predependency = new CAndDependency;
1080 			this->Predependency->ProcessConfigData(child_config_data);
1081 		} else if (child_config_data->Tag == "dependencies") {
1082 			this->Dependency = new CAndDependency;
1083 			this->Dependency->ProcessConfigData(child_config_data);
1084 		} else if (child_config_data->Tag == "variation") {
1085 			this->DefaultStat.Variables[VARIATION_INDEX].Enable = 1;
1086 			this->DefaultStat.Variables[VARIATION_INDEX].Value = 0;
1087 
1088 			CUnitTypeVariation *variation = new CUnitTypeVariation;
1089 			variation->ProcessConfigData(child_config_data);
1090 
1091 			if (variation->ImageLayer == -1) {
1092 				variation->ID = this->Variations.size();
1093 				this->Variations.push_back(variation);
1094 			} else {
1095 				variation->ID = this->LayerVariations[variation->ImageLayer].size();
1096 				this->LayerVariations[variation->ImageLayer].push_back(variation);
1097 			}
1098 
1099 			this->DefaultStat.Variables[VARIATION_INDEX].Max = this->Variations.size();
1100 		} else {
1101 			std::string tag = SnakeCaseToPascalCase(child_config_data->Tag);
1102 
1103 			const int index = UnitTypeVar.VariableNameLookup[tag.c_str()]; // variable index
1104 
1105 			if (index != -1) { // valid index
1106 				for (size_t j = 0; j < child_config_data->Properties.size(); ++j) {
1107 					std::string key = child_config_data->Properties[j].first;
1108 					std::string value = child_config_data->Properties[j].second;
1109 
1110 					if (key == "enable") {
1111 						this->DefaultStat.Variables[index].Enable = StringToBool(value);
1112 					} else if (key == "value") {
1113 						this->DefaultStat.Variables[index].Value = std::stoi(value);
1114 					} else if (key == "max") {
1115 						this->DefaultStat.Variables[index].Max = std::stoi(value);
1116 					} else if (key == "increase") {
1117 						this->DefaultStat.Variables[index].Increase = std::stoi(value);
1118 					} else {
1119 						fprintf(stderr, "Invalid variable property: \"%s\".\n", key.c_str());
1120 					}
1121 				}
1122 			} else {
1123 				fprintf(stderr, "Invalid unit type property: \"%s\".\n", child_config_data->Tag.c_str());
1124 			}
1125 		}
1126 	}
1127 
1128 	if (this->Class != -1) { //if class is defined, then use this unit type to help build the classes table, and add this unit to the civilization class table (if the civilization is defined)
1129 		int class_id = this->Class;
1130 
1131 		//see if this unit type is set as the civilization class unit type or the faction class unit type of any civilization/class (or faction/class) combination, and remove it from there (to not create problems with redefinitions)
1132 		for (int i = 0; i < MAX_RACES; ++i) {
1133 			for (std::map<int, int>::reverse_iterator iterator = PlayerRaces.CivilizationClassUnitTypes[i].rbegin(); iterator != PlayerRaces.CivilizationClassUnitTypes[i].rend(); ++iterator) {
1134 				if (iterator->second == this->Slot) {
1135 					PlayerRaces.CivilizationClassUnitTypes[i].erase(iterator->first);
1136 					break;
1137 				}
1138 			}
1139 		}
1140 		for (size_t i = 0; i < PlayerRaces.Factions.size(); ++i) {
1141 			for (std::map<int, int>::reverse_iterator iterator = PlayerRaces.Factions[i]->ClassUnitTypes.rbegin(); iterator != PlayerRaces.Factions[i]->ClassUnitTypes.rend(); ++iterator) {
1142 				if (iterator->second == this->Slot) {
1143 					PlayerRaces.Factions[i]->ClassUnitTypes.erase(iterator->first);
1144 					break;
1145 				}
1146 			}
1147 		}
1148 
1149 		if (this->Civilization != -1) {
1150 			int civilization_id = this->Civilization;
1151 
1152 			if (this->Faction != -1) {
1153 				int faction_id = this->Faction;
1154 				if (faction_id != -1 && class_id != -1) {
1155 					PlayerRaces.Factions[faction_id]->ClassUnitTypes[class_id] = this->Slot;
1156 				}
1157 			} else {
1158 				if (civilization_id != -1 && class_id != -1) {
1159 					PlayerRaces.CivilizationClassUnitTypes[civilization_id][class_id] = this->Slot;
1160 				}
1161 			}
1162 		}
1163 	}
1164 
1165 	// If number of directions is not specified, make a guess
1166 	// Building have 1 direction and units 8
1167 	if (this->BoolFlag[BUILDING_INDEX].value && this->NumDirections == 0) {
1168 		this->NumDirections = 1;
1169 	} else if (this->NumDirections == 0) {
1170 		this->NumDirections = 8;
1171 	}
1172 
1173 	//Wyrmgus start
1174 	//unit type's level must be at least 1
1175 	if (this->DefaultStat.Variables[LEVEL_INDEX].Value == 0) {
1176 		this->DefaultStat.Variables[LEVEL_INDEX].Enable = 1;
1177 		this->DefaultStat.Variables[LEVEL_INDEX].Value = 1;
1178 		this->DefaultStat.Variables[LEVEL_INDEX].Max = 1;
1179 	}
1180 	//Wyrmgus end
1181 
1182 	// FIXME: try to simplify/combine the flags instead
1183 	if (this->MouseAction == MouseActionAttack && !this->CanAttack) {
1184 		fprintf(stderr, "Unit-type '%s': right-attack is set, but can-attack is not.\n", this->Name.c_str());
1185 	}
1186 	this->UpdateDefaultBoolFlags();
1187 	//Wyrmgus start
1188 	if (GameRunning || Editor.Running == EditorEditing) {
1189 		InitUnitType(*this);
1190 		LoadUnitType(*this);
1191 	}
1192 	//Wyrmgus end
1193 	//Wyrmgus start
1194 //	if (!CclInConfigFile) {
1195 	if (!CclInConfigFile || GameRunning || Editor.Running == EditorEditing) {
1196 	//Wyrmgus end
1197 		UpdateUnitStats(*this, 1);
1198 	}
1199 	//Wyrmgus start
1200 	if (Editor.Running == EditorEditing && std::find(Editor.UnitTypes.begin(), Editor.UnitTypes.end(), this->Ident) == Editor.UnitTypes.end()) {
1201 		Editor.UnitTypes.push_back(this->Ident);
1202 		RecalculateShownUnits();
1203 	}
1204 
1205 	for (size_t i = 0; i < this->Trains.size(); ++i) {
1206 		std::string button_definition = "DefineButton({\n";
1207 		button_definition += "\tPos = " + std::to_string((long long) this->Trains[i]->ButtonPos) + ",\n";
1208 		if (this->Trains[i]->ButtonLevel) {
1209 			button_definition += "\tLevel = " + this->Trains[i]->ButtonLevel->Ident + ",\n";
1210 		}
1211 		button_definition += "\tAction = ";
1212 		if (this->Trains[i]->BoolFlag[BUILDING_INDEX].value) {
1213 			button_definition += "\"build\"";
1214 		} else {
1215 			button_definition += "\"train-unit\"";
1216 		}
1217 		button_definition += ",\n";
1218 		button_definition += "\tValue = \"" + this->Trains[i]->Ident + "\",\n";
1219 		if (!this->Trains[i]->ButtonPopup.empty()) {
1220 			button_definition += "\tPopup = \"" + this->Trains[i]->ButtonPopup + "\",\n";
1221 		}
1222 		button_definition += "\tKey = \"" + this->Trains[i]->ButtonKey + "\",\n";
1223 		button_definition += "\tHint = \"" + this->Trains[i]->ButtonHint + "\",\n";
1224 		button_definition += "\tForUnit = {\"" + this->Ident + "\"},\n";
1225 		button_definition += "})";
1226 		CclCommand(button_definition);
1227 	}
1228 
1229 	if (this->CanMove()) {
1230 		std::string button_definition = "DefineButton({\n";
1231 		button_definition += "\tPos = 1,\n";
1232 		button_definition += "\tAction = \"move\",\n";
1233 		button_definition += "\tPopup = \"popup-commands\",\n";
1234 		button_definition += "\tKey = \"m\",\n";
1235 		button_definition += "\tHint = _(\"~!Move\"),\n";
1236 		button_definition += "\tForUnit = {\"" + this->Ident + "\"},\n";
1237 		button_definition += "})";
1238 		CclCommand(button_definition);
1239 	}
1240 
1241 	if (this->CanMove()) {
1242 		std::string button_definition = "DefineButton({\n";
1243 		button_definition += "\tPos = 2,\n";
1244 		button_definition += "\tAction = \"stop\",\n";
1245 		button_definition += "\tPopup = \"popup-commands\",\n";
1246 		button_definition += "\tKey = \"s\",\n";
1247 		button_definition += "\tHint = _(\"~!Stop\"),\n";
1248 		button_definition += "\tForUnit = {\"" + this->Ident + "\"},\n";
1249 		button_definition += "})";
1250 		CclCommand(button_definition);
1251 	}
1252 
1253 	if (this->CanMove() && this->CanAttack) {
1254 		std::string button_definition = "DefineButton({\n";
1255 		button_definition += "\tPos = 3,\n";
1256 		button_definition += "\tAction = \"attack\",\n";
1257 		button_definition += "\tPopup = \"popup-commands\",\n";
1258 		button_definition += "\tKey = \"a\",\n";
1259 		button_definition += "\tHint = _(\"~!Attack\"),\n";
1260 		button_definition += "\tForUnit = {\"" + this->Ident + "\"},\n";
1261 		button_definition += "})";
1262 		CclCommand(button_definition);
1263 	}
1264 
1265 	if (this->CanMove() && ((!this->BoolFlag[COWARD_INDEX].value && this->CanAttack) || this->UnitType == UnitTypeFly)) {
1266 		std::string button_definition = "DefineButton({\n";
1267 		button_definition += "\tPos = 4,\n";
1268 		button_definition += "\tAction = \"patrol\",\n";
1269 		button_definition += "\tPopup = \"popup-commands\",\n";
1270 		button_definition += "\tKey = \"p\",\n";
1271 		button_definition += "\tHint = _(\"~!Patrol\"),\n";
1272 		button_definition += "\tForUnit = {\"" + this->Ident + "\"},\n";
1273 		button_definition += "})";
1274 		CclCommand(button_definition);
1275 	}
1276 
1277 	if (this->CanMove() && !this->BoolFlag[COWARD_INDEX].value && this->CanAttack && !(this->CanTransport() && this->BoolFlag[ATTACKFROMTRANSPORTER_INDEX].value)) {
1278 		std::string button_definition = "DefineButton({\n";
1279 		button_definition += "\tPos = 5,\n";
1280 		button_definition += "\tAction = \"stand-ground\",\n";
1281 		button_definition += "\tPopup = \"popup-commands\",\n";
1282 		button_definition += "\tKey = \"t\",\n";
1283 		button_definition += "\tHint = _(\"S~!tand Ground\"),\n";
1284 		button_definition += "\tForUnit = {\"" + this->Ident + "\"},\n";
1285 		button_definition += "})";
1286 		CclCommand(button_definition);
1287 	}
1288 
1289 	// make units allowed by default
1290 	for (int i = 0; i < PlayerMax; ++i) {
1291 		AllowUnitId(Players[i], this->Slot, 65536);
1292 	}
1293 
1294 	this->Initialized = true;
1295 
1296 	CclCommand("if not (GetArrayIncludes(Units, \"" + this->Ident + "\")) then table.insert(Units, \"" + this->Ident + "\") end"); //FIXME: needed at present to make unit type data files work without scripting being necessary, but it isn't optimal to interact with a scripting table like "Units" in this manner (that table should probably be replaced with getting a list of unit types from the engine)
1297 }
1298 
GetTileSize() const1299 Vec2i CUnitType::GetTileSize() const
1300 {
1301 	return this->TileSize;
1302 }
1303 
GetHalfTileSize() const1304 Vec2i CUnitType::GetHalfTileSize() const
1305 {
1306 	return this->GetTileSize() / 2;
1307 }
1308 
GetTilePixelSize(const int map_layer) const1309 PixelSize CUnitType::GetTilePixelSize(const int map_layer) const
1310 {
1311 	return PixelSize(PixelSize(this->GetTileSize()) * Map.GetMapLayerPixelTileSize(map_layer));
1312 }
1313 
GetTileCenterPosOffset() const1314 Vec2i CUnitType::GetTileCenterPosOffset() const
1315 {
1316 	return (this->GetTileSize() - 1) / 2;
1317 }
1318 
CheckUserBoolFlags(const char * BoolFlags) const1319 bool CUnitType::CheckUserBoolFlags(const char *BoolFlags) const
1320 {
1321 	for (unsigned int i = 0; i < UnitTypeVar.GetNumberBoolFlag(); ++i) { // User defined flags
1322 		if (BoolFlags[i] != CONDITION_TRUE &&
1323 			((BoolFlags[i] == CONDITION_ONLY) ^ (BoolFlag[i].value))) {
1324 			return false;
1325 		}
1326 	}
1327 	return true;
1328 }
1329 
CanMove() const1330 bool CUnitType::CanMove() const
1331 {
1332 	return Animations && Animations->Move;
1333 }
1334 
CanSelect(GroupSelectionMode mode) const1335 bool CUnitType::CanSelect(GroupSelectionMode mode) const
1336 {
1337 	if (!BoolFlag[ISNOTSELECTABLE_INDEX].value) {
1338 		switch (mode) {
1339 			case SELECTABLE_BY_RECTANGLE_ONLY:
1340 				return BoolFlag[SELECTABLEBYRECTANGLE_INDEX].value;
1341 			case NON_SELECTABLE_BY_RECTANGLE_ONLY:
1342 				return !BoolFlag[SELECTABLEBYRECTANGLE_INDEX].value;
1343 			default:
1344 				return true;
1345 		}
1346 	}
1347 	return false;
1348 }
1349 
SetParent(CUnitType * parent_type)1350 void CUnitType::SetParent(CUnitType *parent_type)
1351 {
1352 	if (!parent_type->Initialized) {
1353 		fprintf(stderr, "Unit type \"%s\" is inheriting features from a non-initialized parent (\"%s\").\n", this->Ident.c_str(), parent_type->Ident.c_str());
1354 	}
1355 
1356 	this->Parent = parent_type;
1357 
1358 	if (this->Name.empty()) {
1359 		this->Name = parent_type->Name;
1360 	}
1361 	this->Class = parent_type->Class;
1362 	if (this->Class != -1 && std::find(ClassUnitTypes[this->Class].begin(), ClassUnitTypes[this->Class].end(), this) == ClassUnitTypes[this->Class].end()) {
1363 		ClassUnitTypes[this->Class].push_back(this);
1364 	}
1365 	this->DrawLevel = parent_type->DrawLevel;
1366 	this->File = parent_type->File;
1367 	this->Width = parent_type->Width;
1368 	this->Height = parent_type->Height;
1369 	this->OffsetX = parent_type->OffsetX;
1370 	this->OffsetY = parent_type->OffsetY;
1371 	this->ShadowFile = parent_type->ShadowFile;
1372 	this->ShadowWidth = parent_type->ShadowWidth;
1373 	this->ShadowHeight = parent_type->ShadowHeight;
1374 	this->ShadowOffsetX = parent_type->ShadowOffsetX;
1375 	this->ShadowOffsetY = parent_type->ShadowOffsetY;
1376 	this->LightFile = parent_type->LightFile;
1377 	this->TileSize = parent_type->TileSize;
1378 	this->BoxWidth = parent_type->BoxWidth;
1379 	this->BoxHeight = parent_type->BoxHeight;
1380 	this->BoxOffsetX = parent_type->BoxOffsetX;
1381 	this->BoxOffsetY = parent_type->BoxOffsetY;
1382 	this->Construction = parent_type->Construction;
1383 	this->UnitType = parent_type->UnitType;
1384 	this->Missile.Name = parent_type->Missile.Name;
1385 	this->Missile.Missile = nullptr;
1386 	this->FireMissile.Name = parent_type->FireMissile.Name;
1387 	this->FireMissile.Missile = nullptr;
1388 	this->ExplodeWhenKilled = parent_type->ExplodeWhenKilled;
1389 	this->Explosion.Name = parent_type->Explosion.Name;
1390 	this->Explosion.Missile = nullptr;
1391 	this->CorpseName = parent_type->CorpseName;
1392 	this->CorpseType = nullptr;
1393 	this->MinAttackRange = parent_type->MinAttackRange;
1394 	this->DefaultStat.Variables[ATTACKRANGE_INDEX].Value = parent_type->DefaultStat.Variables[ATTACKRANGE_INDEX].Value;
1395 	this->DefaultStat.Variables[ATTACKRANGE_INDEX].Max = parent_type->DefaultStat.Variables[ATTACKRANGE_INDEX].Max;
1396 	this->DefaultStat.Variables[PRIORITY_INDEX].Value = parent_type->DefaultStat.Variables[PRIORITY_INDEX].Value;
1397 	this->DefaultStat.Variables[PRIORITY_INDEX].Max  = parent_type->DefaultStat.Variables[PRIORITY_INDEX].Max;
1398 	this->AnnoyComputerFactor = parent_type->AnnoyComputerFactor;
1399 	this->TrainQuantity = parent_type->TrainQuantity;
1400 	this->CostModifier = parent_type->CostModifier;
1401 	this->ItemClass = parent_type->ItemClass;
1402 	this->MaxOnBoard = parent_type->MaxOnBoard;
1403 	this->RepairRange = parent_type->RepairRange;
1404 	this->RepairHP = parent_type->RepairHP;
1405 	this->MouseAction = parent_type->MouseAction;
1406 	this->CanAttack = parent_type->CanAttack;
1407 	this->CanTarget = parent_type->CanTarget;
1408 	this->LandUnit = parent_type->LandUnit;
1409 	this->SeaUnit = parent_type->SeaUnit;
1410 	this->AirUnit = parent_type->AirUnit;
1411 	this->BoardSize = parent_type->BoardSize;
1412 	this->ButtonLevelForTransporter = parent_type->ButtonLevelForTransporter;
1413 	this->ButtonPos = parent_type->ButtonPos;
1414 	this->ButtonLevel = parent_type->ButtonLevel;
1415 	this->ButtonPopup = parent_type->ButtonPopup;
1416 	this->ButtonHint = parent_type->ButtonHint;
1417 	this->ButtonKey = parent_type->ButtonKey;
1418 	this->BurnPercent = parent_type->BurnPercent;
1419 	this->BurnDamageRate = parent_type->BurnDamageRate;
1420 	this->PoisonDrain = parent_type->PoisonDrain;
1421 	this->AutoBuildRate = parent_type->AutoBuildRate;
1422 	this->Animations = parent_type->Animations;
1423 	this->Sound = parent_type->Sound;
1424 	this->NumDirections = parent_type->NumDirections;
1425 	this->NeutralMinimapColorRGB = parent_type->NeutralMinimapColorRGB;
1426 	this->RandomMovementProbability = parent_type->RandomMovementProbability;
1427 	this->RandomMovementDistance = parent_type->RandomMovementDistance;
1428 	this->GivesResource = parent_type->GivesResource;
1429 	this->RequirementsString = parent_type->RequirementsString;
1430 	this->ExperienceRequirementsString = parent_type->ExperienceRequirementsString;
1431 	this->BuildingRulesString = parent_type->BuildingRulesString;
1432 	this->Elixir = parent_type->Elixir;
1433 	this->Icon.Name = parent_type->Icon.Name;
1434 	this->Icon.Icon = nullptr;
1435 	if (!this->Icon.Name.empty()) {
1436 		this->Icon.Load();
1437 	}
1438 	for (size_t i = 0; i < parent_type->Spells.size(); ++i) {
1439 		this->Spells.push_back(parent_type->Spells[i]);
1440 	}
1441 	if (parent_type->AutoCastActive) {
1442 		this->AutoCastActive = new char[CSpell::Spells.size()];
1443 		memset(this->AutoCastActive, 0, CSpell::Spells.size() * sizeof(char));
1444 		for (unsigned int i = 0; i < CSpell::Spells.size(); ++i) {
1445 			this->AutoCastActive[i] = parent_type->AutoCastActive[i];
1446 		}
1447 	}
1448 	for (unsigned int i = 0; i < MaxCosts; ++i) {
1449 		this->DefaultStat.Costs[i] = parent_type->DefaultStat.Costs[i];
1450 		this->RepairCosts[i] = parent_type->RepairCosts[i];
1451 		this->DefaultStat.ImproveIncomes[i] = parent_type->DefaultStat.ImproveIncomes[i];
1452 		this->DefaultStat.ResourceDemand[i] = parent_type->DefaultStat.ResourceDemand[i];
1453 		this->CanStore[i] = parent_type->CanStore[i];
1454 		this->GrandStrategyProductionEfficiencyModifier[i] = parent_type->GrandStrategyProductionEfficiencyModifier[i];
1455 
1456 		if (parent_type->ResInfo[i]) {
1457 			ResourceInfo *res = new ResourceInfo;
1458 			res->ResourceId = i;
1459 			this->ResInfo[i] = res;
1460 			res->ResourceStep = parent_type->ResInfo[i]->ResourceStep;
1461 			res->WaitAtResource = parent_type->ResInfo[i]->WaitAtResource;
1462 			res->WaitAtDepot = parent_type->ResInfo[i]->WaitAtDepot;
1463 			res->ResourceCapacity = parent_type->ResInfo[i]->ResourceCapacity;
1464 			res->LoseResources = parent_type->ResInfo[i]->LoseResources;
1465 			res->RefineryHarvester = parent_type->ResInfo[i]->RefineryHarvester;
1466 			res->FileWhenEmpty = parent_type->ResInfo[i]->FileWhenEmpty;
1467 			res->FileWhenLoaded = parent_type->ResInfo[i]->FileWhenLoaded;
1468 		}
1469 	}
1470 
1471 	this->DefaultStat.UnitStock = parent_type->DefaultStat.UnitStock;
1472 
1473 	for (unsigned int i = 0; i < UnitTypeVar.GetNumberVariable(); ++i) {
1474 		this->DefaultStat.Variables[i].Enable = parent_type->DefaultStat.Variables[i].Enable;
1475 		this->DefaultStat.Variables[i].Value = parent_type->DefaultStat.Variables[i].Value;
1476 		this->DefaultStat.Variables[i].Max = parent_type->DefaultStat.Variables[i].Max;
1477 		this->DefaultStat.Variables[i].Increase = parent_type->DefaultStat.Variables[i].Increase;
1478 	}
1479 	for (unsigned int i = 0; i < UnitTypeVar.GetNumberBoolFlag(); ++i) {
1480 		this->BoolFlag[i].value = parent_type->BoolFlag[i].value;
1481 		this->BoolFlag[i].CanTransport = parent_type->BoolFlag[i].CanTransport;
1482 	}
1483 	for (size_t i = 0; i < parent_type->WeaponClasses.size(); ++i) {
1484 		this->WeaponClasses.push_back(parent_type->WeaponClasses[i]);
1485 	}
1486 	for (size_t i = 0; i < parent_type->SoldUnits.size(); ++i) {
1487 		this->SoldUnits.push_back(parent_type->SoldUnits[i]);
1488 	}
1489 	for (size_t i = 0; i < parent_type->SpawnUnits.size(); ++i) {
1490 		this->SpawnUnits.push_back(parent_type->SpawnUnits[i]);
1491 	}
1492 	for (size_t i = 0; i < parent_type->Drops.size(); ++i) {
1493 		this->Drops.push_back(parent_type->Drops[i]);
1494 	}
1495 	for (size_t i = 0; i < parent_type->AiDrops.size(); ++i) {
1496 		this->AiDrops.push_back(parent_type->AiDrops[i]);
1497 	}
1498 	for (size_t i = 0; i < parent_type->DropSpells.size(); ++i) {
1499 		this->DropSpells.push_back(parent_type->DropSpells[i]);
1500 	}
1501 	for (size_t i = 0; i < parent_type->Affixes.size(); ++i) {
1502 		this->Affixes.push_back(parent_type->Affixes[i]);
1503 	}
1504 	for (size_t i = 0; i < parent_type->Traits.size(); ++i) {
1505 		this->Traits.push_back(parent_type->Traits[i]);
1506 	}
1507 	for (size_t i = 0; i < parent_type->StartingAbilities.size(); ++i) {
1508 		this->StartingAbilities.push_back(parent_type->StartingAbilities[i]);
1509 	}
1510 	for (size_t i = 0; i < parent_type->Trains.size(); ++i) {
1511 		this->Trains.push_back(parent_type->Trains[i]);
1512 		parent_type->Trains[i]->TrainedBy.push_back(this);
1513 	}
1514 	for (size_t i = 0; i < parent_type->StartingResources.size(); ++i) {
1515 		this->StartingResources.push_back(parent_type->StartingResources[i]);
1516 	}
1517 	for (std::map<int, std::vector<std::string>>::iterator iterator = parent_type->PersonalNames.begin(); iterator != parent_type->PersonalNames.end(); ++iterator) {
1518 		for (size_t i = 0; i < iterator->second.size(); ++i) {
1519 			this->PersonalNames[iterator->first].push_back(iterator->second[i]);
1520 		}
1521 	}
1522 	for (CUnitTypeVariation *parent_variation : parent_type->Variations) {
1523 		CUnitTypeVariation *variation = new CUnitTypeVariation;
1524 
1525 		variation->ID = this->Variations.size();
1526 		this->Variations.push_back(variation);
1527 
1528 		variation->VariationId = parent_variation->VariationId;
1529 		variation->TypeName = parent_variation->TypeName;
1530 		variation->File = parent_variation->File;
1531 		for (unsigned int i = 0; i < MaxCosts; ++i) {
1532 			variation->FileWhenLoaded[i] = parent_variation->FileWhenLoaded[i];
1533 			variation->FileWhenEmpty[i] = parent_variation->FileWhenEmpty[i];
1534 		}
1535 		variation->ShadowFile = parent_variation->ShadowFile;
1536 		variation->LightFile = parent_variation->LightFile;
1537 		variation->FrameWidth = parent_variation->FrameWidth;
1538 		variation->FrameHeight = parent_variation->FrameHeight;
1539 		variation->ResourceMin = parent_variation->ResourceMin;
1540 		variation->ResourceMax = parent_variation->ResourceMax;
1541 		variation->Weight = parent_variation->Weight;
1542 		variation->Icon.Name = parent_variation->Icon.Name;
1543 		variation->Icon.Icon = nullptr;
1544 		if (!variation->Icon.Name.empty()) {
1545 			variation->Icon.Load();
1546 		}
1547 		if (parent_variation->Animations) {
1548 			variation->Animations = parent_variation->Animations;
1549 		}
1550 		variation->Construction = parent_variation->Construction;
1551 		variation->UpgradesRequired = parent_variation->UpgradesRequired;
1552 		variation->UpgradesForbidden = parent_variation->UpgradesForbidden;
1553 		for (size_t i = 0; i < parent_variation->ItemClassesEquipped.size(); ++i) {
1554 			variation->ItemClassesEquipped.push_back(parent_variation->ItemClassesEquipped[i]);
1555 		}
1556 		for (size_t i = 0; i < parent_variation->ItemClassesNotEquipped.size(); ++i) {
1557 			variation->ItemClassesNotEquipped.push_back(parent_variation->ItemClassesNotEquipped[i]);
1558 		}
1559 		for (size_t i = 0; i < parent_variation->ItemsEquipped.size(); ++i) {
1560 			variation->ItemsEquipped.push_back(parent_variation->ItemsEquipped[i]);
1561 		}
1562 		for (size_t i = 0; i < parent_variation->ItemsNotEquipped.size(); ++i) {
1563 			variation->ItemsNotEquipped.push_back(parent_variation->ItemsNotEquipped[i]);
1564 		}
1565 		for (size_t i = 0; i < parent_variation->Terrains.size(); ++i) {
1566 			variation->Terrains.push_back(parent_variation->Terrains[i]);
1567 		}
1568 
1569 		for (int i = 0; i < MaxImageLayers; ++i) {
1570 			variation->LayerFiles[i] = parent_variation->LayerFiles[i];
1571 		}
1572 		for (std::map<int, IconConfig>::iterator iterator = parent_variation->ButtonIcons.begin(); iterator != parent_variation->ButtonIcons.end(); ++iterator) {
1573 			variation->ButtonIcons[iterator->first].Name = iterator->second.Name;
1574 			variation->ButtonIcons[iterator->first].Icon = nullptr;
1575 			variation->ButtonIcons[iterator->first].Load();
1576 			variation->ButtonIcons[iterator->first].Icon->Load();
1577 		}
1578 	}
1579 
1580 	for (int i = 0; i < MaxImageLayers; ++i) {
1581 		this->LayerFiles[i] = parent_type->LayerFiles[i];
1582 
1583 		//inherit layer variations
1584 		for (CUnitTypeVariation *parent_variation : parent_type->LayerVariations[i]) {
1585 			CUnitTypeVariation *variation = new CUnitTypeVariation;
1586 
1587 			variation->ID = this->LayerVariations[i].size();
1588 			this->LayerVariations[i].push_back(variation);
1589 
1590 			variation->VariationId = parent_variation->VariationId;
1591 			variation->File = parent_variation->File;
1592 			variation->UpgradesRequired = parent_variation->UpgradesRequired;
1593 			variation->UpgradesForbidden = parent_variation->UpgradesForbidden;
1594 			for (size_t u = 0; u < parent_variation->ItemClassesEquipped.size(); ++u) {
1595 				variation->ItemClassesEquipped.push_back(parent_variation->ItemClassesEquipped[u]);
1596 			}
1597 			for (size_t u = 0; u < parent_variation->ItemClassesNotEquipped.size(); ++u) {
1598 				variation->ItemClassesNotEquipped.push_back(parent_variation->ItemClassesNotEquipped[u]);
1599 			}
1600 			for (size_t u = 0; u < parent_variation->ItemsEquipped.size(); ++u) {
1601 				variation->ItemsEquipped.push_back(parent_variation->ItemsEquipped[u]);
1602 			}
1603 			for (size_t u = 0; u < parent_variation->ItemsNotEquipped.size(); ++u) {
1604 				variation->ItemsNotEquipped.push_back(parent_variation->ItemsNotEquipped[u]);
1605 			}
1606 			for (size_t u = 0; u < parent_variation->Terrains.size(); ++u) {
1607 				variation->Terrains.push_back(parent_variation->Terrains[u]);
1608 			}
1609 		}
1610 	}
1611 	for (std::map<int, IconConfig>::iterator iterator = parent_type->ButtonIcons.begin(); iterator != parent_type->ButtonIcons.end(); ++iterator) {
1612 		this->ButtonIcons[iterator->first].Name = iterator->second.Name;
1613 		this->ButtonIcons[iterator->first].Icon = nullptr;
1614 		this->ButtonIcons[iterator->first].Load();
1615 		this->ButtonIcons[iterator->first].Icon->Load();
1616 	}
1617 	for (std::map<int, CUnitType *>::iterator iterator = parent_type->DefaultEquipment.begin(); iterator != parent_type->DefaultEquipment.end(); ++iterator) {
1618 		this->DefaultEquipment[iterator->first] = iterator->second;
1619 	}
1620 	this->DefaultStat.Variables[PRIORITY_INDEX].Value = parent_type->DefaultStat.Variables[PRIORITY_INDEX].Value + 1; //increase priority by 1 to make it be chosen by the AI when building over the previous unit
1621 	this->DefaultStat.Variables[PRIORITY_INDEX].Max = parent_type->DefaultStat.Variables[PRIORITY_INDEX].Max + 1;
1622 }
1623 
UpdateDefaultBoolFlags()1624 void CUnitType::UpdateDefaultBoolFlags()
1625 {
1626 	// BoolFlag
1627 	this->BoolFlag[FLIP_INDEX].value = this->Flip;
1628 	this->BoolFlag[LANDUNIT_INDEX].value = this->LandUnit;
1629 	this->BoolFlag[AIRUNIT_INDEX].value = this->AirUnit;
1630 	this->BoolFlag[SEAUNIT_INDEX].value = this->SeaUnit;
1631 	this->BoolFlag[EXPLODEWHENKILLED_INDEX].value = this->ExplodeWhenKilled;
1632 	this->BoolFlag[CANATTACK_INDEX].value = this->CanAttack;
1633 }
1634 
1635 //Wyrmgus start
RemoveButtons(int button_action,std::string mod_file)1636 void CUnitType::RemoveButtons(int button_action, std::string mod_file)
1637 {
1638 	int buttons_size = UnitButtonTable.size();
1639 	for (int i = (buttons_size - 1); i >= 0; --i) {
1640 		if (button_action != -1 && UnitButtonTable[i]->Action != button_action) {
1641 			continue;
1642 		}
1643 		if (!mod_file.empty() && UnitButtonTable[i]->Mod != mod_file) {
1644 			continue;
1645 		}
1646 
1647 		if (UnitButtonTable[i]->UnitMask == ("," + this->Ident + ",")) { //delete the appropriate buttons
1648 			delete UnitButtonTable[i];
1649 			UnitButtonTable.erase(std::remove(UnitButtonTable.begin(), UnitButtonTable.end(), UnitButtonTable[i]), UnitButtonTable.end());
1650 		} else if (UnitButtonTable[i]->UnitMask.find(this->Ident) != std::string::npos) { //remove this unit from the "ForUnit" array of the appropriate buttons
1651 			UnitButtonTable[i]->UnitMask = FindAndReplaceString(UnitButtonTable[i]->UnitMask, this->Ident + ",", "");
1652 		}
1653 	}
1654 }
1655 
GetAvailableLevelUpUpgrades() const1656 int CUnitType::GetAvailableLevelUpUpgrades() const
1657 {
1658 	int value = 0;
1659 	int upgrade_value = 0;
1660 
1661 	if (((int) AiHelpers.ExperienceUpgrades.size()) > this->Slot) {
1662 		for (size_t i = 0; i != AiHelpers.ExperienceUpgrades[this->Slot].size(); ++i) {
1663 			int local_upgrade_value = 1;
1664 
1665 			local_upgrade_value += AiHelpers.ExperienceUpgrades[this->Slot][i]->GetAvailableLevelUpUpgrades();
1666 
1667 			if (local_upgrade_value > upgrade_value) {
1668 				upgrade_value = local_upgrade_value;
1669 			}
1670 		}
1671 	}
1672 
1673 	value += upgrade_value;
1674 
1675 	if (((int) AiHelpers.LearnableAbilities.size()) > this->Slot) {
1676 		for (size_t i = 0; i != AiHelpers.LearnableAbilities[this->Slot].size(); ++i) {
1677 			value += 1;
1678 		}
1679 	}
1680 
1681 	return value;
1682 }
1683 
GetResourceStep(const int resource,const int player) const1684 int CUnitType::GetResourceStep(const int resource, const int player) const
1685 {
1686 	if (!this->ResInfo[resource]) {
1687 		return 0;
1688 	}
1689 
1690 	int resource_step = this->ResInfo[resource]->ResourceStep;
1691 
1692 	resource_step += this->Stats[player].Variables[GATHERINGBONUS_INDEX].Value;
1693 
1694 	if (resource == CopperCost) {
1695 		resource_step += this->Stats[player].Variables[COPPERGATHERINGBONUS_INDEX].Value;
1696 	} else if (resource == SilverCost) {
1697 		resource_step += this->Stats[player].Variables[SILVERGATHERINGBONUS_INDEX].Value;
1698 	} else if (resource == GoldCost) {
1699 		resource_step += this->Stats[player].Variables[GOLDGATHERINGBONUS_INDEX].Value;
1700 	} else if (resource == IronCost) {
1701 		resource_step += this->Stats[player].Variables[IRONGATHERINGBONUS_INDEX].Value;
1702 	} else if (resource == MithrilCost) {
1703 		resource_step += this->Stats[player].Variables[MITHRILGATHERINGBONUS_INDEX].Value;
1704 	} else if (resource == WoodCost) {
1705 		resource_step += this->Stats[player].Variables[LUMBERGATHERINGBONUS_INDEX].Value;
1706 	} else if (resource == StoneCost || resource == LimestoneCost) {
1707 		resource_step += this->Stats[player].Variables[STONEGATHERINGBONUS_INDEX].Value;
1708 	} else if (resource == CoalCost) {
1709 		resource_step += this->Stats[player].Variables[COALGATHERINGBONUS_INDEX].Value;
1710 	} else if (resource == JewelryCost) {
1711 		resource_step += this->Stats[player].Variables[JEWELRYGATHERINGBONUS_INDEX].Value;
1712 	} else if (resource == FurnitureCost) {
1713 		resource_step += this->Stats[player].Variables[FURNITUREGATHERINGBONUS_INDEX].Value;
1714 	} else if (resource == LeatherCost) {
1715 		resource_step += this->Stats[player].Variables[LEATHERGATHERINGBONUS_INDEX].Value;
1716 	} else if (resource == DiamondsCost || resource == EmeraldsCost) {
1717 		resource_step += this->Stats[player].Variables[GEMSGATHERINGBONUS_INDEX].Value;
1718 	}
1719 
1720 	return resource_step;
1721 }
1722 
GetDefaultVariation(CPlayer & player,int image_layer) const1723 CUnitTypeVariation *CUnitType::GetDefaultVariation(CPlayer &player, int image_layer) const
1724 {
1725 	const std::vector<CUnitTypeVariation *> &variation_list = image_layer == -1 ? this->Variations : this->LayerVariations[image_layer];
1726 	for (CUnitTypeVariation *variation : variation_list) {
1727 		bool upgrades_check = true;
1728 		for (const CUpgrade *required_upgrade : variation->UpgradesRequired) {
1729 			if (UpgradeIdentAllowed(player, required_upgrade->Ident.c_str()) != 'R') {
1730 				upgrades_check = false;
1731 				break;
1732 			}
1733 		}
1734 
1735 		if (upgrades_check) {
1736 			for (const CUpgrade *forbidden_upgrade : variation->UpgradesForbidden) {
1737 				if (UpgradeIdentAllowed(player, forbidden_upgrade->Ident.c_str()) == 'R') {
1738 					upgrades_check = false;
1739 					break;
1740 				}
1741 			}
1742 		}
1743 
1744 		if (upgrades_check == false) {
1745 			continue;
1746 		}
1747 		return variation;
1748 	}
1749 	return nullptr;
1750 }
1751 
GetVariation(const std::string & variation_name,int image_layer) const1752 CUnitTypeVariation *CUnitType::GetVariation(const std::string &variation_name, int image_layer) const
1753 {
1754 	const std::vector<CUnitTypeVariation *> &variation_list = image_layer == -1 ? this->Variations : this->LayerVariations[image_layer];
1755 	for (CUnitTypeVariation *variation : variation_list) {
1756 		if (variation->VariationId == variation_name) {
1757 			return variation;
1758 		}
1759 	}
1760 	return nullptr;
1761 }
1762 
GetRandomVariationIdent(int image_layer) const1763 std::string CUnitType::GetRandomVariationIdent(int image_layer) const
1764 {
1765 	std::vector<std::string> variation_idents;
1766 
1767 	const std::vector<CUnitTypeVariation *> &variation_list = image_layer == -1 ? this->Variations : this->LayerVariations[image_layer];
1768 	for (const CUnitTypeVariation *variation : variation_list) {
1769 		variation_idents.push_back(variation->VariationId);
1770 	}
1771 
1772 	if (variation_idents.size() > 0) {
1773 		return variation_idents[SyncRand(variation_idents.size())];
1774 	}
1775 
1776 	return "";
1777 }
1778 
GetDefaultName(CPlayer & player) const1779 std::string CUnitType::GetDefaultName(CPlayer &player) const
1780 {
1781 	CUnitTypeVariation *variation = this->GetDefaultVariation(player);
1782 	if (variation && !variation->TypeName.empty()) {
1783 		return variation->TypeName;
1784 	} else {
1785 		return this->Name;
1786 	}
1787 }
1788 
GetDefaultLayerSprite(CPlayer & player,int image_layer) const1789 CPlayerColorGraphic *CUnitType::GetDefaultLayerSprite(CPlayer &player, int image_layer) const
1790 {
1791 	CUnitTypeVariation *variation = this->GetDefaultVariation(player);
1792 	if (this->LayerVariations[image_layer].size() > 0 && this->GetDefaultVariation(player, image_layer)->Sprite) {
1793 		return this->GetDefaultVariation(player, image_layer)->Sprite;
1794 	} else if (variation && variation->LayerSprites[image_layer]) {
1795 		return variation->LayerSprites[image_layer];
1796 	} else if (this->LayerSprites[image_layer])  {
1797 		return this->LayerSprites[image_layer];
1798 	} else {
1799 		return nullptr;
1800 	}
1801 }
1802 
CanExperienceUpgradeTo(CUnitType * type) const1803 bool CUnitType::CanExperienceUpgradeTo(CUnitType *type) const
1804 {
1805 	if (((int) AiHelpers.ExperienceUpgrades.size()) > this->Slot) {
1806 		for (size_t i = 0; i != AiHelpers.ExperienceUpgrades[this->Slot].size(); ++i) {
1807 			if (type == AiHelpers.ExperienceUpgrades[this->Slot][i] || AiHelpers.ExperienceUpgrades[this->Slot][i]->CanExperienceUpgradeTo(type)) {
1808 				return true;
1809 			}
1810 		}
1811 	}
1812 
1813 	return false;
1814 }
1815 
GetNamePlural() const1816 std::string CUnitType::GetNamePlural() const
1817 {
1818 	return GetPluralForm(this->Name);
1819 }
1820 
GeneratePersonalName(CFaction * faction,int gender) const1821 std::string CUnitType::GeneratePersonalName(CFaction *faction, int gender) const
1822 {
1823 	if (Editor.Running == EditorEditing) { // don't set the personal name if in the editor
1824 		return "";
1825 	}
1826 
1827 	std::vector<std::string> potential_names = this->GetPotentialPersonalNames(faction, gender);
1828 
1829 	if (potential_names.size() > 0) {
1830 		return potential_names[SyncRand(potential_names.size())];
1831 	}
1832 
1833 	return "";
1834 }
1835 
IsPersonalNameValid(const std::string & name,CFaction * faction,int gender) const1836 bool CUnitType::IsPersonalNameValid(const std::string &name, CFaction *faction, int gender) const
1837 {
1838 	if (name.empty()) {
1839 		return false;
1840 	}
1841 
1842 	std::vector<std::string> potential_names = this->GetPotentialPersonalNames(faction, gender);
1843 
1844 	if (std::find(potential_names.begin(), potential_names.end(), name) != potential_names.end()) {
1845 		return true;
1846 	}
1847 
1848 	return false;
1849 }
1850 
GetPotentialPersonalNames(CFaction * faction,int gender) const1851 std::vector<std::string> CUnitType::GetPotentialPersonalNames(CFaction *faction, int gender) const
1852 {
1853 	std::vector<std::string> potential_names;
1854 
1855 	if (this->PersonalNames.find(NoGender) != this->PersonalNames.end()) {
1856 		for (size_t i = 0; i < this->PersonalNames.find(NoGender)->second.size(); ++i) {
1857 			potential_names.push_back(this->PersonalNames.find(NoGender)->second[i]);
1858 		}
1859 	}
1860 	if (gender != -1 && gender != NoGender && this->PersonalNames.find(gender) != this->PersonalNames.end()) {
1861 		for (size_t i = 0; i < this->PersonalNames.find(gender)->second.size(); ++i) {
1862 			potential_names.push_back(this->PersonalNames.find(gender)->second[i]);
1863 		}
1864 	}
1865 
1866 	if (potential_names.size() == 0 && this->Civilization != -1) {
1867 		int civilization_id = this->Civilization;
1868 		if (civilization_id != -1) {
1869 			if (faction && civilization_id != faction->Civilization->ID && PlayerRaces.Species[civilization_id] == PlayerRaces.Species[faction->Civilization->ID] && this->Slot == PlayerRaces.GetFactionClassUnitType(faction->ID, this->Class)) {
1870 				civilization_id = faction->Civilization->ID;
1871 			}
1872 			CCivilization *civilization = CCivilization::Civilizations[civilization_id];
1873 			if (faction && faction->Civilization != civilization) {
1874 				faction = nullptr;
1875 			}
1876 			if (this->Faction != -1 && !faction) {
1877 				faction = PlayerRaces.Factions[this->Faction];
1878 			}
1879 
1880 			if (this->BoolFlag[ORGANIC_INDEX].value) {
1881 				if (civilization->GetPersonalNames().find(NoGender) != civilization->GetPersonalNames().end()) {
1882 					for (size_t i = 0; i < civilization->GetPersonalNames().find(NoGender)->second.size(); ++i) {
1883 						potential_names.push_back(civilization->GetPersonalNames().find(NoGender)->second[i]);
1884 					}
1885 				}
1886 				if (gender != -1 && gender != NoGender && civilization->GetPersonalNames().find(gender) != civilization->GetPersonalNames().end()) {
1887 					for (size_t i = 0; i < civilization->GetPersonalNames().find(gender)->second.size(); ++i) {
1888 						potential_names.push_back(civilization->GetPersonalNames().find(gender)->second[i]);
1889 					}
1890 				}
1891 			} else {
1892 				if (this->Class != -1 && civilization->GetUnitClassNames(this->Class).size() > 0) {
1893 					return civilization->GetUnitClassNames(this->Class);
1894 				}
1895 
1896 				if (this->UnitType == UnitTypeNaval) { // if is a ship
1897 					if (faction && faction->GetShipNames().size() > 0) {
1898 						return faction->GetShipNames();
1899 					}
1900 
1901 					if (civilization->GetShipNames().size() > 0) {
1902 						return civilization->GetShipNames();
1903 					}
1904 				}
1905 			}
1906 		}
1907 	}
1908 
1909 	return potential_names;
1910 }
1911 //Wyrmgus end
1912 
UpdateUnitStats(CUnitType & type,int reset)1913 void UpdateUnitStats(CUnitType &type, int reset)
1914 {
1915 	if (reset) {
1916 		type.MapDefaultStat = type.DefaultStat;
1917 		for (std::map<std::string, CUnitStats>::iterator iterator = type.ModDefaultStats.begin(); iterator != type.ModDefaultStats.end(); ++iterator) {
1918 			for (size_t i = 0; i < UnitTypeVar.GetNumberVariable(); ++i) {
1919 				type.MapDefaultStat.Variables[i].Value += iterator->second.Variables[i].Value;
1920 				type.MapDefaultStat.Variables[i].Max += iterator->second.Variables[i].Max;
1921 				type.MapDefaultStat.Variables[i].Increase += iterator->second.Variables[i].Increase;
1922 				if (iterator->second.Variables[i].Enable != 0) {
1923 					type.MapDefaultStat.Variables[i].Enable = iterator->second.Variables[i].Enable;
1924 				}
1925 			}
1926 			for (std::map<CUnitType *, int>::const_iterator unit_stock_iterator = iterator->second.UnitStock.begin(); unit_stock_iterator != iterator->second.UnitStock.end(); ++unit_stock_iterator) {
1927 				CUnitType *unit_type = unit_stock_iterator->first;
1928 				int unit_stock = unit_stock_iterator->second;
1929 				type.MapDefaultStat.ChangeUnitStock(unit_type, unit_stock);
1930 			}
1931 			for (int i = 0; i < MaxCosts; ++i) {
1932 				type.MapDefaultStat.Costs[i] += iterator->second.Costs[i];
1933 				type.MapDefaultStat.ImproveIncomes[i] += iterator->second.ImproveIncomes[i];
1934 				type.MapDefaultStat.ResourceDemand[i] += iterator->second.ResourceDemand[i];
1935 			}
1936 		}
1937 		for (int player = 0; player < PlayerMax; ++player) {
1938 			type.Stats[player] = type.MapDefaultStat;
1939 		}
1940 
1941 		type.MapSound = type.Sound;
1942 		for (std::map<std::string, CUnitSound>::iterator iterator = type.ModSounds.begin(); iterator != type.ModSounds.end(); ++iterator) {
1943 			if (!iterator->second.Selected.Name.empty()) {
1944 				type.MapSound.Selected = iterator->second.Selected;
1945 			}
1946 			if (!iterator->second.Acknowledgement.Name.empty()) {
1947 				type.MapSound.Acknowledgement = iterator->second.Acknowledgement;
1948 			}
1949 			if (!iterator->second.Attack.Name.empty()) {
1950 				type.MapSound.Attack = iterator->second.Attack;
1951 			}
1952 			if (!iterator->second.Idle.Name.empty()) {
1953 				type.MapSound.Idle = iterator->second.Idle;
1954 			}
1955 			if (!iterator->second.Hit.Name.empty()) {
1956 				type.MapSound.Hit = iterator->second.Hit;
1957 			}
1958 			if (!iterator->second.Miss.Name.empty()) {
1959 				type.MapSound.Miss = iterator->second.Miss;
1960 			}
1961 			if (!iterator->second.FireMissile.Name.empty()) {
1962 				type.MapSound.FireMissile = iterator->second.FireMissile;
1963 			}
1964 			if (!iterator->second.Step.Name.empty()) {
1965 				type.MapSound.Step = iterator->second.Step;
1966 			}
1967 			if (!iterator->second.StepDirt.Name.empty()) {
1968 				type.MapSound.StepDirt = iterator->second.StepDirt;
1969 			}
1970 			if (!iterator->second.StepGrass.Name.empty()) {
1971 				type.MapSound.StepGrass = iterator->second.StepGrass;
1972 			}
1973 			if (!iterator->second.StepGravel.Name.empty()) {
1974 				type.MapSound.StepGravel = iterator->second.StepGravel;
1975 			}
1976 			if (!iterator->second.StepMud.Name.empty()) {
1977 				type.MapSound.StepMud = iterator->second.StepMud;
1978 			}
1979 			if (!iterator->second.StepStone.Name.empty()) {
1980 				type.MapSound.StepStone = iterator->second.StepStone;
1981 			}
1982 			if (!iterator->second.Used.Name.empty()) {
1983 				type.MapSound.Used = iterator->second.Used;
1984 			}
1985 			if (!iterator->second.Build.Name.empty()) {
1986 				type.MapSound.Build = iterator->second.Build;
1987 			}
1988 			if (!iterator->second.Ready.Name.empty()) {
1989 				type.MapSound.Ready = iterator->second.Ready;
1990 			}
1991 			if (!iterator->second.Repair.Name.empty()) {
1992 				type.MapSound.Repair = iterator->second.Repair;
1993 			}
1994 			for (unsigned int j = 0; j < MaxCosts; ++j) {
1995 				if (!iterator->second.Harvest[j].Name.empty()) {
1996 					type.MapSound.Harvest[j] = iterator->second.Harvest[j];
1997 				}
1998 			}
1999 			if (!iterator->second.Help.Name.empty()) {
2000 				type.MapSound.Help = iterator->second.Help;
2001 			}
2002 			if (!iterator->second.Dead[ANIMATIONS_DEATHTYPES].Name.empty()) {
2003 				type.MapSound.Dead[ANIMATIONS_DEATHTYPES] = iterator->second.Dead[ANIMATIONS_DEATHTYPES];
2004 			}
2005 			int death;
2006 			for (death = 0; death < ANIMATIONS_DEATHTYPES; ++death) {
2007 				if (!iterator->second.Dead[death].Name.empty()) {
2008 					type.MapSound.Dead[death] = iterator->second.Dead[death];
2009 				}
2010 			}
2011 		}
2012 	}
2013 
2014 	// Non-solid units can always be entered and they don't block anything
2015 	if (type.BoolFlag[NONSOLID_INDEX].value) {
2016 		if (type.BoolFlag[BUILDING_INDEX].value) {
2017 			type.MovementMask = MapFieldLandUnit |
2018 								MapFieldSeaUnit |
2019 								MapFieldBuilding |
2020 								//Wyrmgus start
2021 								MapFieldItem |
2022 								MapFieldBridge |
2023 								//Wyrmgus end
2024 								MapFieldCoastAllowed |
2025 								MapFieldWaterAllowed |
2026 								MapFieldNoBuilding |
2027 								MapFieldUnpassable;
2028 			type.FieldFlags = MapFieldNoBuilding;
2029 		} else {
2030 			type.MovementMask = 0;
2031 			type.FieldFlags = 0;
2032 		}
2033 		return;
2034 	}
2035 
2036 	//  As side effect we calculate the movement flags/mask here.
2037 	switch (type.UnitType) {
2038 		case UnitTypeLand:                              // on land
2039 			//Wyrmgus start
2040 			/*
2041 			type.MovementMask =
2042 				MapFieldLandUnit |
2043 				MapFieldSeaUnit |
2044 				MapFieldBuilding | // already occuppied
2045 				MapFieldCoastAllowed |
2046 				MapFieldWaterAllowed | // can't move on this
2047 				MapFieldUnpassable;
2048 			*/
2049 			if (type.BoolFlag[DIMINUTIVE_INDEX].value) { // diminutive units can enter tiles occupied by other units and vice-versa
2050 				type.MovementMask =
2051 					MapFieldBuilding | // already occuppied
2052 					MapFieldCoastAllowed |
2053 					MapFieldWaterAllowed | // can't move on this
2054 					MapFieldUnpassable;
2055 			} else {
2056 				type.MovementMask =
2057 					MapFieldLandUnit |
2058 					MapFieldSeaUnit |
2059 					MapFieldBuilding | // already occuppied
2060 					MapFieldCoastAllowed |
2061 					MapFieldWaterAllowed | // can't move on this
2062 					MapFieldUnpassable;
2063 			}
2064 
2065 			if (type.BoolFlag[RAIL_INDEX].value) { //rail units can only move over railroads
2066 				type.MovementMask |= MapFieldNoRail;
2067 			}
2068 			//Wyrmgus end
2069 			break;
2070 		case UnitTypeFly:                               // in air
2071 			//Wyrmgus start
2072 			/*
2073 			type.MovementMask = MapFieldAirUnit; // already occuppied
2074 				MapFieldAirUnit | // already occuppied
2075 				MapFieldAirUnpassable;
2076 			*/
2077 			if (type.BoolFlag[DIMINUTIVE_INDEX].value) {
2078 				type.MovementMask =
2079 					MapFieldAirUnpassable;
2080 			} else {
2081 				type.MovementMask =
2082 					MapFieldAirUnit | // already occuppied
2083 					MapFieldAirUnpassable;
2084 			}
2085 			//Wyrmgus end
2086 			break;
2087 		//Wyrmgus start
2088 		case UnitTypeFlyLow:                               // in low air
2089 			if (type.BoolFlag[DIMINUTIVE_INDEX].value) {
2090 				type.MovementMask =
2091 					MapFieldBuilding |
2092 					MapFieldUnpassable |
2093 					MapFieldAirUnpassable;
2094 			} else {
2095 				type.MovementMask =
2096 					MapFieldLandUnit |
2097 					MapFieldSeaUnit |
2098 					MapFieldBuilding |
2099 					MapFieldUnpassable |
2100 					MapFieldAirUnpassable;
2101 			}
2102 			break;
2103 		case UnitTypeNaval:                             // on water
2104 			//Wyrmgus start
2105 			/*
2106 			if (type.CanTransport()) {
2107 				type.MovementMask =
2108 					MapFieldLandUnit |
2109 					MapFieldSeaUnit |
2110 					MapFieldBuilding | // already occuppied
2111 					//Wyrmgus start
2112 					MapFieldBridge |
2113 					//Wyrmgus end
2114 					MapFieldLandAllowed; // can't move on this
2115 				// Johns: MapFieldUnpassable only for land units?
2116 			*/
2117 			if (type.BoolFlag[CANDOCK_INDEX].value) {
2118 				type.MovementMask =
2119 					MapFieldLandUnit |
2120 					MapFieldSeaUnit |
2121 					MapFieldBuilding | // already occuppied
2122 					//Wyrmgus start
2123 					MapFieldBridge |
2124 					//Wyrmgus end
2125 					MapFieldLandAllowed | // can't move on this
2126 					MapFieldUnpassable;
2127 			} else if (type.BoolFlag[CANDOCK_INDEX].value && type.BoolFlag[DIMINUTIVE_INDEX].value) { //should add case for when is a transporter and is diminutive?
2128 				type.MovementMask =
2129 					MapFieldBuilding | // already occuppied
2130 					MapFieldBridge |
2131 					MapFieldLandAllowed | // can't move on this
2132 					MapFieldUnpassable;
2133 			} else if (type.BoolFlag[DIMINUTIVE_INDEX].value) { //should add case for when is a transporter and is diminutive?
2134 				type.MovementMask =
2135 					MapFieldBuilding | // already occuppied
2136 					MapFieldBridge |
2137 					MapFieldCoastAllowed |
2138 					MapFieldLandAllowed | // can't move on this
2139 					MapFieldUnpassable;
2140 			//Wyrmgus end
2141 			} else {
2142 				type.MovementMask =
2143 					MapFieldLandUnit |
2144 					MapFieldSeaUnit |
2145 					MapFieldBuilding | // already occuppied
2146 					//Wyrmgus start
2147 					MapFieldBridge |
2148 					//Wyrmgus end
2149 					MapFieldCoastAllowed |
2150 					MapFieldLandAllowed | // can't move on this
2151 					MapFieldUnpassable;
2152 			}
2153 			break;
2154 		default:
2155 			DebugPrint("Where moves this unit?\n");
2156 			type.MovementMask = 0;
2157 			break;
2158 	}
2159 	if (type.BoolFlag[BUILDING_INDEX].value || type.BoolFlag[SHOREBUILDING_INDEX].value) {
2160 		// Shore building is something special.
2161 		if (type.BoolFlag[SHOREBUILDING_INDEX].value) {
2162 			type.MovementMask =
2163 				MapFieldLandUnit |
2164 				MapFieldSeaUnit |
2165 				MapFieldBuilding | // already occuppied
2166 				//Wyrmgus start
2167 				MapFieldBridge |
2168 				//Wyrmgus end
2169 				MapFieldLandAllowed; // can't build on this
2170 		}
2171 		type.MovementMask |= MapFieldNoBuilding;
2172 		//Wyrmgus start
2173 		type.MovementMask |= MapFieldItem;
2174 		if (type.TerrainType) {
2175 			if ((type.TerrainType->Flags & MapFieldRailroad) || (type.TerrainType->Flags & MapFieldRoad)) {
2176 				type.MovementMask |= MapFieldRailroad;
2177 			}
2178 			if (type.TerrainType->Flags & MapFieldRoad) {
2179 				type.MovementMask |= MapFieldRoad;
2180 			}
2181 		}
2182 		if (type.BoolFlag[AIRUNPASSABLE_INDEX].value) { // for air unpassable units (i.e. doors)
2183 			type.FieldFlags |= MapFieldUnpassable;
2184 			type.FieldFlags |= MapFieldAirUnpassable;
2185 		}
2186 		//Wyrmgus end
2187 		//
2188 		// A little chaos, buildings without HP can be entered.
2189 		// The oil-patch is a very special case.
2190 		//
2191 		if (type.MapDefaultStat.Variables[HP_INDEX].Max) {
2192 			type.FieldFlags = MapFieldBuilding;
2193 		} else {
2194 			type.FieldFlags = MapFieldNoBuilding;
2195 		}
2196 	//Wyrmgus start
2197 	} else if (type.BoolFlag[ITEM_INDEX].value || type.BoolFlag[POWERUP_INDEX].value || type.BoolFlag[TRAP_INDEX].value) {
2198 		type.MovementMask = MapFieldLandUnit |
2199 							MapFieldSeaUnit |
2200 							MapFieldBuilding |
2201 							MapFieldCoastAllowed |
2202 							MapFieldWaterAllowed |
2203 							MapFieldUnpassable |
2204 							MapFieldItem;
2205 		type.FieldFlags = MapFieldItem;
2206 	} else if (type.BoolFlag[BRIDGE_INDEX].value) {
2207 		type.MovementMask = MapFieldSeaUnit |
2208 							MapFieldBuilding |
2209 							MapFieldLandAllowed |
2210 							MapFieldUnpassable |
2211 							MapFieldBridge;
2212 		type.FieldFlags = MapFieldBridge;
2213 	//Wyrmgus end
2214 	//Wyrmgus start
2215 //	} else {
2216 	} else if (!type.BoolFlag[DIMINUTIVE_INDEX].value) {
2217 	//Wyrmgus end
2218 		switch (type.UnitType) {
2219 			case UnitTypeLand: // on land
2220 				type.FieldFlags = MapFieldLandUnit;
2221 				//Wyrmgus start
2222 				if (type.BoolFlag[AIRUNPASSABLE_INDEX].value) { // for air unpassable units (i.e. doors)
2223 					type.FieldFlags |= MapFieldUnpassable;
2224 					type.FieldFlags |= MapFieldAirUnpassable;
2225 				}
2226 				if (type.BoolFlag[GRAVEL_INDEX].value) {
2227 					type.FieldFlags |= MapFieldGravel;
2228 				}
2229 				//Wyrmgus end
2230 				break;
2231 			case UnitTypeFly: // in air
2232 				type.FieldFlags = MapFieldAirUnit;
2233 				break;
2234 			//Wyrmgus start
2235 			case UnitTypeFlyLow: // in low air
2236 				type.FieldFlags = MapFieldLandUnit;
2237 				if (type.BoolFlag[AIRUNPASSABLE_INDEX].value) { // for air unpassable units (i.e. doors)
2238 					type.FieldFlags |= MapFieldUnpassable;
2239 					type.FieldFlags |= MapFieldAirUnpassable;
2240 				}
2241 				break;
2242 			//Wyrmgus end
2243 			case UnitTypeNaval: // on water
2244 				type.FieldFlags = MapFieldSeaUnit;
2245 				//Wyrmgus start
2246 				if (type.BoolFlag[AIRUNPASSABLE_INDEX].value) { // for air unpassable units (i.e. doors)
2247 					type.FieldFlags |= MapFieldUnpassable;
2248 					type.FieldFlags |= MapFieldAirUnpassable;
2249 				}
2250 				//Wyrmgus end
2251 				break;
2252 			default:
2253 				DebugPrint("Where moves this unit?\n");
2254 				type.FieldFlags = 0;
2255 				break;
2256 		}
2257 	}
2258 }
2259 
2260 
2261 /**
2262 **  Update the player stats for changed unit types.
2263 **  @param reset indicates whether the default value should be set to each stat (level, upgrades)
2264 */
UpdateStats(int reset)2265 void UpdateStats(int reset)
2266 {
2267 	// Update players stats
2268 	for (std::vector<CUnitType *>::size_type j = 0; j < UnitTypes.size(); ++j) {
2269 		CUnitType &type = *UnitTypes[j];
2270 		UpdateUnitStats(type, reset);
2271 	}
2272 }
2273 
2274 /**
2275 **  Save state of an unit-stats to file.
2276 **
2277 **  @param stats  Unit-stats to save.
2278 **  @param ident  Unit-type ident.
2279 **  @param plynr  Player number.
2280 **  @param file   Output file.
2281 */
SaveUnitStats(const CUnitStats & stats,const CUnitType & type,int plynr,CFile & file)2282 static bool SaveUnitStats(const CUnitStats &stats, const CUnitType &type, int plynr,
2283 						  CFile &file)
2284 {
2285 	Assert(plynr < PlayerMax);
2286 
2287 	if (stats == type.DefaultStat) {
2288 		return false;
2289 	}
2290 	file.printf("DefineUnitStats(\"%s\", %d, {\n  ", type.Ident.c_str(), plynr);
2291 	for (unsigned int i = 0; i < UnitTypeVar.GetNumberVariable(); ++i) {
2292 		file.printf("\"%s\", {Value = %d, Max = %d, Increase = %d%s},\n  ",
2293 					UnitTypeVar.VariableNameLookup[i], stats.Variables[i].Value,
2294 					stats.Variables[i].Max, stats.Variables[i].Increase,
2295 					stats.Variables[i].Enable ? ", Enable = true" : "");
2296 	}
2297 	file.printf("\"costs\", {");
2298 	for (unsigned int i = 0; i < MaxCosts; ++i) {
2299 		if (i) {
2300 			file.printf(" ");
2301 		}
2302 		file.printf("\"%s\", %d,", DefaultResourceNames[i].c_str(), stats.Costs[i]);
2303 	}
2304 	file.printf("},\n\"storing\", {");
2305 	for (unsigned int i = 0; i < MaxCosts; ++i) {
2306 		if (i) {
2307 			file.printf(" ");
2308 		}
2309 		file.printf("\"%s\", %d,", DefaultResourceNames[i].c_str(), stats.Storing[i]);
2310 	}
2311 	file.printf("},\n\"improve-production\", {");
2312 	for (unsigned int i = 0; i < MaxCosts; ++i) {
2313 		if (i) {
2314 			file.printf(" ");
2315 		}
2316 		file.printf("\"%s\", %d,", DefaultResourceNames[i].c_str(), stats.ImproveIncomes[i]);
2317 	}
2318 	//Wyrmgus start
2319 	file.printf("},\n\"resource-demand\", {");
2320 	for (unsigned int i = 0; i < MaxCosts; ++i) {
2321 		if (i) {
2322 			file.printf(" ");
2323 		}
2324 		file.printf("\"%s\", %d,", DefaultResourceNames[i].c_str(), stats.ResourceDemand[i]);
2325 	}
2326 	file.printf("},\n\"unit-stock\", {");
2327 	for (size_t i = 0; i < UnitTypes.size(); ++i) {
2328 		if (stats.GetUnitStock(UnitTypes[i]) == type.DefaultStat.GetUnitStock(UnitTypes[i])) {
2329 			continue;
2330 		}
2331 		if (i) {
2332 			file.printf(" ");
2333 		}
2334 		file.printf("\"%s\", %d,", UnitTypes[i]->Ident.c_str(), stats.GetUnitStock(UnitTypes[i]));
2335 	}
2336 	//Wyrmgus end
2337 	file.printf("}})\n");
2338 	return true;
2339 }
2340 
2341 /**
2342 **  Save state of the unit-type table to file.
2343 **
2344 **  @param file  Output file.
2345 */
SaveUnitTypes(CFile & file)2346 void SaveUnitTypes(CFile &file)
2347 {
2348 	file.printf("\n--- -----------------------------------------\n");
2349 	file.printf("--- MODULE: unittypes\n\n");
2350 
2351 	// Save all stats
2352 	for (std::vector<CUnitType *>::size_type i = 0; i < UnitTypes.size(); ++i) {
2353 		const CUnitType &type = *UnitTypes[i];
2354 		bool somethingSaved = false;
2355 
2356 		for (int j = 0; j < PlayerMax; ++j) {
2357 			if (Players[j].Type != PlayerNobody) {
2358 				somethingSaved |= SaveUnitStats(type.Stats[j], type, j, file);
2359 			}
2360 		}
2361 		if (somethingSaved) {
2362 			file.printf("\n");
2363 		}
2364 	}
2365 }
2366 
2367 /**
2368 **  Find unit-type by identifier.
2369 **
2370 **  @param ident  The unit-type identifier.
2371 **
2372 **  @return       Unit-type pointer.
2373 */
UnitTypeByIdent(const std::string & ident)2374 CUnitType *UnitTypeByIdent(const std::string &ident)
2375 {
2376 	std::map<std::string, CUnitType *>::iterator ret = UnitTypeMap.find(ident);
2377 	if (ret != UnitTypeMap.end()) {
2378 		return (*ret).second;
2379 	}
2380 	//Wyrmgus start
2381 //	fprintf(stderr, "Unit type \"%s\" does not exist.\n", ident.c_str());
2382 	//Wyrmgus end
2383 	return nullptr;
2384 }
2385 
2386 //Wyrmgus start
GetUnitTypeClassIndexByName(const std::string & class_name)2387 int GetUnitTypeClassIndexByName(const std::string &class_name)
2388 {
2389 	if (class_name.empty()) {
2390 		return -1;
2391 	}
2392 
2393 	if (UnitTypeClassStringToIndex.find(class_name) != UnitTypeClassStringToIndex.end()) {
2394 		return UnitTypeClassStringToIndex.find(class_name)->second;
2395 	}
2396 	return -1;
2397 }
2398 
GetOrAddUnitTypeClassIndexByName(const std::string & class_name)2399 int GetOrAddUnitTypeClassIndexByName(const std::string &class_name)
2400 {
2401 	int index = GetUnitTypeClassIndexByName(class_name);
2402 	if (index == -1 && !class_name.empty()) {
2403 		SetUnitTypeClassStringToIndex(class_name, UnitTypeClasses.size());
2404 		index = UnitTypeClasses.size();
2405 		UnitTypeClasses.push_back(class_name);
2406 		ClassUnitTypes.resize(UnitTypeClasses.size());
2407 	}
2408 	return index;
2409 }
2410 
SetUnitTypeClassStringToIndex(const std::string & class_name,int class_id)2411 void SetUnitTypeClassStringToIndex(const std::string &class_name, int class_id)
2412 {
2413 	UnitTypeClassStringToIndex[class_name] = class_id;
2414 }
2415 
GetUpgradeClassIndexByName(const std::string & class_name)2416 int GetUpgradeClassIndexByName(const std::string &class_name)
2417 {
2418 	if (UpgradeClassStringToIndex.find(class_name) != UpgradeClassStringToIndex.end()) {
2419 		return UpgradeClassStringToIndex.find(class_name)->second;
2420 	}
2421 	return -1;
2422 }
2423 
SetUpgradeClassStringToIndex(const std::string & class_name,int class_id)2424 void SetUpgradeClassStringToIndex(const std::string &class_name, int class_id)
2425 {
2426 	UpgradeClassStringToIndex[class_name] = class_id;
2427 }
2428 //Wyrmgus end
2429 
2430 /**
2431 **  Allocate an empty unit-type slot.
2432 **
2433 **  @param ident  Identifier to identify the slot (malloced by caller!).
2434 **
2435 **  @return       New allocated (zeroed) unit-type pointer.
2436 */
NewUnitTypeSlot(const std::string & ident)2437 CUnitType *NewUnitTypeSlot(const std::string &ident)
2438 {
2439 	size_t new_bool_size = UnitTypeVar.GetNumberBoolFlag();
2440 	CUnitType *type = new CUnitType;
2441 
2442 	if (!type) {
2443 		fprintf(stderr, "Out of memory\n");
2444 		ExitFatal(-1);
2445 	}
2446 	type->Slot = UnitTypes.size();
2447 	type->Ident = ident;
2448 	type->BoolFlag.resize(new_bool_size);
2449 
2450 	type->DefaultStat.Variables = new CVariable[UnitTypeVar.GetNumberVariable()];
2451 	for (unsigned int i = 0; i < UnitTypeVar.GetNumberVariable(); ++i) {
2452 		type->DefaultStat.Variables[i] = UnitTypeVar.Variable[i];
2453 	}
2454 	UnitTypes.push_back(type);
2455 	UnitTypeMap[type->Ident] = type;
2456 	return type;
2457 }
2458 
2459 /**
2460 **  Draw unit-type on map.
2461 **
2462 **  @param type    Unit-type pointer.
2463 **  @param sprite  Sprite to use for drawing
2464 **  @param player  Player number for color substitution.
2465 **  @param frame   Animation frame of unit-type.
2466 **  @param screenPos  Screen pixel (top left) position to draw unit-type.
2467 **
2468 **  @todo  Do screen position caculation in high level.
2469 **         Better way to handle in x mirrored sprites.
2470 */
DrawUnitType(const CUnitType & type,CPlayerColorGraphic * sprite,int player,int frame,const PixelPos & screenPos)2471 void DrawUnitType(const CUnitType &type, CPlayerColorGraphic *sprite, int player, int frame, const PixelPos &screenPos)
2472 {
2473 	//Wyrmgus start
2474 	if (sprite == nullptr) {
2475 		return;
2476 	}
2477 	//Wyrmgus end
2478 
2479 	PixelPos pos = screenPos;
2480 	// FIXME: move this calculation to high level.
2481 	//Wyrmgus start
2482 //	pos.x -= (type.Width - type.TileSize.x * Map.GetCurrentPixelTileSize().x) / 2;
2483 //	pos.y -= (type.Height - type.TileSize.y * Map.GetCurrentPixelTileSize().y) / 2;
2484 	pos.x -= (sprite->Width - type.TileSize.x * Map.GetCurrentPixelTileSize().x) / 2;
2485 	pos.y -= (sprite->Height - type.TileSize.y * Map.GetCurrentPixelTileSize().y) / 2;
2486 	//Wyrmgus end
2487 	pos.x += type.OffsetX;
2488 	pos.y += type.OffsetY;
2489 
2490 	//Wyrmgus start
2491 	/*
2492 	if (type.Flip) {
2493 		if (frame < 0) {
2494 			sprite->DrawPlayerColorFrameClipX(player, -frame - 1, pos.x, pos.y);
2495 		} else {
2496 			sprite->DrawPlayerColorFrameClip(player, frame, pos.x, pos.y);
2497 		}
2498 	} else {
2499 		const int row = type.NumDirections / 2 + 1;
2500 
2501 		if (frame < 0) {
2502 			frame = ((-frame - 1) / row) * type.NumDirections + type.NumDirections - (-frame - 1) % row;
2503 		} else {
2504 			frame = (frame / row) * type.NumDirections + frame % row;
2505 		}
2506 		sprite->DrawPlayerColorFrameClip(player, frame, pos.x, pos.y);
2507 	}
2508 	*/
2509 	if (type.Flip) {
2510 		if (frame < 0) {
2511 			if (type.Stats[player].Variables[TRANSPARENCY_INDEX].Value > 0) {
2512 				sprite->DrawPlayerColorFrameClipTransX(player, -frame - 1, pos.x, pos.y, int(256 - 2.56 * type.Stats[player].Variables[TRANSPARENCY_INDEX].Value), false);
2513 			} else {
2514 				sprite->DrawPlayerColorFrameClipX(player, -frame - 1, pos.x, pos.y, false);
2515 			}
2516 		} else {
2517 			if (type.Stats[player].Variables[TRANSPARENCY_INDEX].Value > 0) {
2518 				sprite->DrawPlayerColorFrameClipTrans(player, frame, pos.x, pos.y, int(256 - 2.56 * type.Stats[player].Variables[TRANSPARENCY_INDEX].Value), false);
2519 			} else {
2520 				sprite->DrawPlayerColorFrameClip(player, frame, pos.x, pos.y, false);
2521 			}
2522 		}
2523 	} else {
2524 		const int row = type.NumDirections / 2 + 1;
2525 
2526 		if (frame < 0) {
2527 			frame = ((-frame - 1) / row) * type.NumDirections + type.NumDirections - (-frame - 1) % row;
2528 		} else {
2529 			frame = (frame / row) * type.NumDirections + frame % row;
2530 		}
2531 		if (type.Stats[player].Variables[TRANSPARENCY_INDEX].Value > 0) {
2532 			sprite->DrawPlayerColorFrameClipTrans(player, frame, pos.x, pos.y, int(256 - 2.56 * type.Stats[player].Variables[TRANSPARENCY_INDEX].Value), false);
2533 		} else {
2534 			sprite->DrawPlayerColorFrameClip(player, frame, pos.x, pos.y, false);
2535 		}
2536 	}
2537 	//Wyrmgus end
2538 }
2539 
2540 /**
2541 **  Get the still animation frame
2542 */
GetStillFrame(const CUnitType & type)2543 static int GetStillFrame(const CUnitType &type)
2544 {
2545 	//Wyrmgus start
2546 	if (type.Animations == nullptr) {
2547 		return 0;
2548 	}
2549 	//Wyrmgus end
2550 
2551 	CAnimation *anim = type.Animations->Still;
2552 
2553 	while (anim) {
2554 		if (anim->Type == AnimationFrame) {
2555 			CAnimation_Frame &a_frame = *static_cast<CAnimation_Frame *>(anim);
2556 			// Use the frame facing down
2557 			return a_frame.ParseAnimInt(nullptr) + type.NumDirections / 2;
2558 		} else if (anim->Type == AnimationExactFrame) {
2559 			CAnimation_ExactFrame &a_frame = *static_cast<CAnimation_ExactFrame *>(anim);
2560 
2561 			return a_frame.ParseAnimInt(nullptr);
2562 		}
2563 		anim = anim->Next;
2564 	}
2565 	return type.NumDirections / 2;
2566 }
2567 
2568 /**
2569 **  Init unit types.
2570 */
InitUnitTypes(int reset_player_stats)2571 void InitUnitTypes(int reset_player_stats)
2572 {
2573 	for (size_t i = 0; i < UnitTypes.size(); ++i) {
2574 		CUnitType &type = *UnitTypes[i];
2575 		Assert(type.Slot == (int)i);
2576 
2577 		if (type.Animations == nullptr) {
2578 			DebugPrint(_("unit-type '%s' without animations, ignored.\n") _C_ type.Ident.c_str());
2579 			continue;
2580 		}
2581 		//  Add idents to hash.
2582 		UnitTypeMap[type.Ident] = UnitTypes[i];
2583 
2584 		//Wyrmgus start
2585 		/*
2586 		// Determine still frame
2587 		type.StillFrame = GetStillFrame(type);
2588 
2589 		// Lookup BuildingTypes
2590 		for (std::vector<CBuildRestriction *>::iterator b = type.BuildingRules.begin();
2591 			 b < type.BuildingRules.end(); ++b) {
2592 			(*b)->Init();
2593 		}
2594 
2595 		// Lookup AiBuildingTypes
2596 		for (std::vector<CBuildRestriction *>::iterator b = type.AiBuildingRules.begin();
2597 			 b < type.AiBuildingRules.end(); ++b) {
2598 			(*b)->Init();
2599 		}
2600 		*/
2601 		InitUnitType(type);
2602 		//Wyrmgus end
2603 	}
2604 
2605 	// LUDO : called after game is loaded -> don't reset stats !
2606 	UpdateStats(reset_player_stats); // Calculate the stats
2607 }
2608 
2609 //Wyrmgus start
InitUnitType(CUnitType & type)2610 void InitUnitType(CUnitType &type)
2611 {
2612 	// Determine still frame
2613 	type.StillFrame = GetStillFrame(type);
2614 
2615 	// Lookup BuildingTypes
2616 	for (std::vector<CBuildRestriction *>::iterator b = type.BuildingRules.begin();
2617 		 b < type.BuildingRules.end(); ++b) {
2618 		(*b)->Init();
2619 	}
2620 
2621 	// Lookup AiBuildingTypes
2622 	for (std::vector<CBuildRestriction *>::iterator b = type.AiBuildingRules.begin();
2623 		 b < type.AiBuildingRules.end(); ++b) {
2624 		(*b)->Init();
2625 	}
2626 }
2627 //Wyrmgus end
2628 
2629 /**
2630 **  Loads the Sprite for a unit type
2631 **
2632 **  @param type  type of unit to load
2633 */
LoadUnitTypeSprite(CUnitType & type)2634 void LoadUnitTypeSprite(CUnitType &type)
2635 {
2636 	if (!type.ShadowFile.empty()) {
2637 		type.ShadowSprite = CGraphic::ForceNew(type.ShadowFile, type.ShadowWidth, type.ShadowHeight);
2638 		type.ShadowSprite->Load();
2639 		if (type.Flip) {
2640 			type.ShadowSprite->Flip();
2641 		}
2642 		if (type.ShadowSprite->Surface->format->BytesPerPixel == 1) {
2643 			//Wyrmgus start
2644 //			type.ShadowSprite->MakeShadow();
2645 			//Wyrmgus end
2646 		}
2647 	}
2648 
2649 	if (type.BoolFlag[HARVESTER_INDEX].value) {
2650 		for (int i = 0; i < MaxCosts; ++i) {
2651 			ResourceInfo *resinfo = type.ResInfo[i];
2652 			if (!resinfo) {
2653 				continue;
2654 			}
2655 			if (!resinfo->FileWhenLoaded.empty()) {
2656 				resinfo->SpriteWhenLoaded = CPlayerColorGraphic::New(resinfo->FileWhenLoaded,
2657 																	 type.Width, type.Height);
2658 				resinfo->SpriteWhenLoaded->Load();
2659 				if (type.Flip) {
2660 					resinfo->SpriteWhenLoaded->Flip();
2661 				}
2662 			}
2663 			if (!resinfo->FileWhenEmpty.empty()) {
2664 				resinfo->SpriteWhenEmpty = CPlayerColorGraphic::New(resinfo->FileWhenEmpty,
2665 																	type.Width, type.Height);
2666 				resinfo->SpriteWhenEmpty->Load();
2667 				if (type.Flip) {
2668 					resinfo->SpriteWhenEmpty->Flip();
2669 				}
2670 			}
2671 		}
2672 	}
2673 
2674 	if (!type.File.empty()) {
2675 		type.Sprite = CPlayerColorGraphic::New(type.File, type.Width, type.Height);
2676 		type.Sprite->Load();
2677 		if (type.Flip) {
2678 			type.Sprite->Flip();
2679 		}
2680 	}
2681 
2682 #ifdef USE_MNG
2683 	if (type.Portrait.Num) {
2684 		for (int i = 0; i < type.Portrait.Num; ++i) {
2685 			type.Portrait.Mngs[i] = new Mng;
2686 			type.Portrait.Mngs[i]->Load(type.Portrait.Files[i]);
2687 		}
2688 		// FIXME: should be configurable
2689 		type.Portrait.CurrMng = 0;
2690 		type.Portrait.NumIterations = SyncRand() % 16 + 1;
2691 	}
2692 #endif
2693 
2694 	//Wyrmgus start
2695 	if (!type.LightFile.empty()) {
2696 		type.LightSprite = CGraphic::New(type.LightFile, type.Width, type.Height);
2697 		type.LightSprite->Load();
2698 		if (type.Flip) {
2699 			type.LightSprite->Flip();
2700 		}
2701 	}
2702 	for (int i = 0; i < MaxImageLayers; ++i) {
2703 		if (!type.LayerFiles[i].empty()) {
2704 			type.LayerSprites[i] = CPlayerColorGraphic::New(type.LayerFiles[i], type.Width, type.Height);
2705 			type.LayerSprites[i]->Load();
2706 			if (type.Flip) {
2707 				type.LayerSprites[i]->Flip();
2708 			}
2709 		}
2710 	}
2711 	//Wyrmgus end
2712 
2713 	//Wyrmgus start
2714 	for (CUnitTypeVariation *variation : type.Variations) {
2715 		int frame_width = type.Width;
2716 		int frame_height = type.Height;
2717 		if (variation->FrameWidth && variation->FrameHeight) {
2718 			frame_width = variation->FrameWidth;
2719 			frame_height = variation->FrameHeight;
2720 		}
2721 		if (!variation->File.empty()) {
2722 			variation->Sprite = CPlayerColorGraphic::New(variation->File, frame_width, frame_height);
2723 			variation->Sprite->Load();
2724 			if (type.Flip) {
2725 				variation->Sprite->Flip();
2726 			}
2727 		}
2728 		if (!variation->ShadowFile.empty()) {
2729 			variation->ShadowSprite = CGraphic::New(variation->ShadowFile, type.ShadowWidth, type.ShadowHeight);
2730 			variation->ShadowSprite->Load();
2731 			if (type.Flip) {
2732 				variation->ShadowSprite->Flip();
2733 			}
2734 			if (variation->ShadowSprite->Surface->format->BytesPerPixel == 1) {
2735 //				variation->ShadowSprite->MakeShadow();
2736 			}
2737 		}
2738 		if (!variation->LightFile.empty()) {
2739 			variation->LightSprite = CGraphic::New(variation->LightFile, frame_width, frame_height);
2740 			variation->LightSprite->Load();
2741 			if (type.Flip) {
2742 				variation->LightSprite->Flip();
2743 			}
2744 		}
2745 		for (int j = 0; j < MaxImageLayers; ++j) {
2746 			if (!variation->LayerFiles[j].empty()) {
2747 				variation->LayerSprites[j] = CPlayerColorGraphic::New(variation->LayerFiles[j], frame_width, frame_height);
2748 				variation->LayerSprites[j]->Load();
2749 				if (type.Flip) {
2750 					variation->LayerSprites[j]->Flip();
2751 				}
2752 			}
2753 		}
2754 
2755 		for (int j = 0; j < MaxCosts; ++j) {
2756 			if (!variation->FileWhenLoaded[j].empty()) {
2757 				variation->SpriteWhenLoaded[j] = CPlayerColorGraphic::New(variation->FileWhenLoaded[j], frame_width, frame_height);
2758 				variation->SpriteWhenLoaded[j]->Load();
2759 				if (type.Flip) {
2760 					variation->SpriteWhenLoaded[j]->Flip();
2761 				}
2762 			}
2763 			if (!variation->FileWhenEmpty[j].empty()) {
2764 				variation->SpriteWhenEmpty[j] = CPlayerColorGraphic::New(variation->FileWhenEmpty[j], frame_width, frame_height);
2765 				variation->SpriteWhenEmpty[j]->Load();
2766 				if (type.Flip) {
2767 					variation->SpriteWhenEmpty[j]->Flip();
2768 				}
2769 			}
2770 		}
2771 	}
2772 
2773 	for (int i = 0; i < MaxImageLayers; ++i) {
2774 		for (CUnitTypeVariation *layer_variation : type.LayerVariations[i]) {
2775 			if (!layer_variation->File.empty()) {
2776 				layer_variation->Sprite = CPlayerColorGraphic::New(layer_variation->File, type.Width, type.Height);
2777 				layer_variation->Sprite->Load();
2778 				if (type.Flip) {
2779 					layer_variation->Sprite->Flip();
2780 				}
2781 			}
2782 		}
2783 	}
2784 	//Wyrmgus end
2785 }
2786 
2787 
2788 /**
2789 ** Return the amount of unit-types.
2790 */
GetUnitTypesCount()2791 int GetUnitTypesCount()
2792 {
2793 	int count = 0;
2794 	for (std::vector<CUnitType *>::size_type i = 0; i < UnitTypes.size(); ++i) {
2795 		CUnitType &type = *UnitTypes[i];
2796 
2797 		if (type.Missile.IsEmpty() == false) count++;
2798 		if (type.FireMissile.IsEmpty() == false) count++;
2799 		if (type.Explosion.IsEmpty() == false) count++;
2800 
2801 
2802 		if (!type.Sprite) {
2803 			count++;
2804 		}
2805 	}
2806 	return count;
2807 }
2808 
2809 /**
2810 ** Load the graphics for the unit-types.
2811 */
LoadUnitTypes()2812 void LoadUnitTypes()
2813 {
2814 	for (std::vector<CUnitType *>::size_type i = 0; i < UnitTypes.size(); ++i) {
2815 		CUnitType &type = *UnitTypes[i];
2816 
2817 		ShowLoadProgress(_("Loading Unit Types (%d%%)"), (i + 1) * 100 / UnitTypes.size());
2818 		LoadUnitType(type);
2819 	}
2820 }
2821 
2822 //Wyrmgus start
LoadUnitType(CUnitType & type)2823 void LoadUnitType(CUnitType &type)
2824 {
2825 	// Lookup icons.
2826 	if (!type.Icon.Name.empty()) {
2827 		type.Icon.Load();
2828 	}
2829 
2830 	for (CUnitTypeVariation *variation : type.Variations) {
2831 		if (!variation->Icon.Name.empty()) {
2832 			variation->Icon.Load();
2833 		}
2834 	}
2835 
2836 	// Lookup missiles.
2837 	type.Missile.MapMissile();
2838 	//Wyrmgus start
2839 	type.FireMissile.MapMissile();
2840 	//Wyrmgus end
2841 	type.Explosion.MapMissile();
2842 
2843 	// Lookup impacts
2844 	for (int i = 0; i < ANIMATIONS_DEATHTYPES + 2; ++i) {
2845 		type.Impact[i].MapMissile();
2846 	}
2847 	// Lookup corpse.
2848 	if (!type.CorpseName.empty()) {
2849 		type.CorpseType = UnitTypeByIdent(type.CorpseName);
2850 	}
2851 #ifndef DYNAMIC_LOAD
2852 	// Load Sprite
2853 	if (!type.Sprite) {
2854 		LoadUnitTypeSprite(type);
2855 
2856 		IncItemsLoaded();
2857 	}
2858 #endif
2859 	// FIXME: should i copy the animations of same graphics?
2860 }
2861 //Wyrmgus end
2862 
Init()2863 void CUnitTypeVar::Init()
2864 {
2865 	// Variables.
2866 	Variable.resize(GetNumberVariable());
2867 	size_t new_size = UnitTypeVar.GetNumberBoolFlag();
2868 	for (unsigned int i = 0; i < UnitTypes.size(); ++i) { // adjust array for unit already defined
2869 		UnitTypes[i]->BoolFlag.resize(new_size);
2870 	}
2871 }
2872 
Clear()2873 void CUnitTypeVar::Clear()
2874 {
2875 	Variable.clear();
2876 
2877 	for (std::vector<CDecoVar *>::iterator it = DecoVar.begin();
2878 		 it != DecoVar.end(); ++it) {
2879 		delete(*it);
2880 	}
2881 	DecoVar.clear();
2882 }
2883 
2884 /**
2885 **  Cleanup the unit-type module.
2886 */
CleanUnitTypes()2887 void CleanUnitTypes()
2888 {
2889 	DebugPrint("FIXME: icon, sounds not freed.\n");
2890 	FreeAnimations();
2891 
2892 	// Clean all unit-types
2893 	for (size_t i = 0; i < UnitTypes.size(); ++i) {
2894 		delete UnitTypes[i];
2895 	}
2896 	UnitTypes.clear();
2897 	UnitTypeMap.clear();
2898 	UnitTypeVar.Clear();
2899 
2900 	// Clean hardcoded unit types.
2901 
2902 	//Wyrmgus start
2903 	for (size_t i = 0; i < Species.size(); ++i) {
2904 		delete Species[i];
2905 	}
2906 	Species.clear();
2907 	for (size_t i = 0; i < SpeciesGenuses.size(); ++i) {
2908 		delete SpeciesGenuses[i];
2909 	}
2910 	SpeciesGenuses.clear();
2911 	for (size_t i = 0; i < SpeciesFamilies.size(); ++i) {
2912 		delete SpeciesFamilies[i];
2913 	}
2914 	SpeciesFamilies.clear();
2915 	for (size_t i = 0; i < SpeciesOrders.size(); ++i) {
2916 		delete SpeciesOrders[i];
2917 	}
2918 	SpeciesOrders.clear();
2919 	for (size_t i = 0; i < SpeciesClasses.size(); ++i) {
2920 		delete SpeciesClasses[i];
2921 	}
2922 	SpeciesClasses.clear();
2923 	for (size_t i = 0; i < SpeciesPhylums.size(); ++i) {
2924 		delete SpeciesPhylums[i];
2925 	}
2926 	SpeciesPhylums.clear();
2927 	//Wyrmgus end
2928 }
2929 
2930 //Wyrmgus start
GetUnitTypeStatsString(const std::string & unit_type_ident)2931 std::string GetUnitTypeStatsString(const std::string &unit_type_ident)
2932 {
2933 	const CUnitType *unit_type = UnitTypeByIdent(unit_type_ident);
2934 
2935 	if (unit_type) {
2936 		std::string unit_type_stats_string;
2937 
2938 		bool first_var = true;
2939 		for (size_t var = 0; var < UnitTypeVar.GetNumberVariable(); ++var) {
2940 			if (
2941 				!(var == BASICDAMAGE_INDEX || var == PIERCINGDAMAGE_INDEX || var == THORNSDAMAGE_INDEX
2942 				|| var == FIREDAMAGE_INDEX || var == COLDDAMAGE_INDEX || var == ARCANEDAMAGE_INDEX || var == LIGHTNINGDAMAGE_INDEX
2943 				|| var == AIRDAMAGE_INDEX || var == EARTHDAMAGE_INDEX || var == WATERDAMAGE_INDEX || var == ACIDDAMAGE_INDEX
2944 				|| var == ARMOR_INDEX || var == FIRERESISTANCE_INDEX || var == COLDRESISTANCE_INDEX || var == ARCANERESISTANCE_INDEX || var == LIGHTNINGRESISTANCE_INDEX
2945 				|| var == AIRRESISTANCE_INDEX || var == EARTHRESISTANCE_INDEX || var == WATERRESISTANCE_INDEX || var == ACIDRESISTANCE_INDEX
2946 				|| var == HACKRESISTANCE_INDEX || var == PIERCERESISTANCE_INDEX || var == BLUNTRESISTANCE_INDEX
2947 				|| var == ACCURACY_INDEX || var == EVASION_INDEX || var == SPEED_INDEX || var == CHARGEBONUS_INDEX || var == BACKSTAB_INDEX
2948 				|| var == HITPOINTHEALING_INDEX || var == HITPOINTBONUS_INDEX
2949 				|| var == SIGHTRANGE_INDEX || var == DAYSIGHTRANGEBONUS_INDEX || var == NIGHTSIGHTRANGEBONUS_INDEX
2950 				|| var == HP_INDEX || var == MANA_INDEX || var == OWNERSHIPINFLUENCERANGE_INDEX || var == LEADERSHIPAURA_INDEX || var == REGENERATIONAURA_INDEX || var == HYDRATINGAURA_INDEX || var == ETHEREALVISION_INDEX || var == SPEEDBONUS_INDEX || var == SUPPLY_INDEX || var == TIMEEFFICIENCYBONUS_INDEX || var == RESEARCHSPEEDBONUS_INDEX || var == GARRISONEDRANGEBONUS_INDEX)
2951 			) {
2952 				continue;
2953 			}
2954 
2955 			if (unit_type->DefaultStat.Variables[var].Enable) {
2956 				if (!first_var) {
2957 					unit_type_stats_string += ", ";
2958 				} else {
2959 					first_var = false;
2960 				}
2961 
2962 				if (IsBooleanVariable(var) && unit_type->DefaultStat.Variables[var].Value < 0) {
2963 					unit_type_stats_string += "Lose ";
2964 				}
2965 
2966 				if (!IsBooleanVariable(var)) {
2967 					unit_type_stats_string += std::to_string((long long) unit_type->DefaultStat.Variables[var].Value);
2968 					if (IsPercentageVariable(var)) {
2969 						unit_type_stats_string += "%";
2970 					}
2971 					unit_type_stats_string += " ";
2972 				}
2973 
2974 				unit_type_stats_string += GetVariableDisplayName(var);
2975 			}
2976 		}
2977 
2978 		return unit_type_stats_string;
2979 	}
2980 
2981 	return "";
2982 }
2983 
GetSpecies(const std::string & species_ident)2984 CSpecies *GetSpecies(const std::string &species_ident)
2985 {
2986 	for (size_t i = 0; i < Species.size(); ++i) {
2987 		if (species_ident == Species[i]->Ident) {
2988 			return Species[i];
2989 		}
2990 	}
2991 
2992 	return nullptr;
2993 }
2994 
GetSpeciesGenus(const std::string & genus_ident)2995 CSpeciesGenus *GetSpeciesGenus(const std::string &genus_ident)
2996 {
2997 	for (size_t i = 0; i < SpeciesGenuses.size(); ++i) {
2998 		if (genus_ident == SpeciesGenuses[i]->Ident) {
2999 			return SpeciesGenuses[i];
3000 		}
3001 	}
3002 
3003 	return nullptr;
3004 }
3005 
GetSpeciesFamily(const std::string & family_ident)3006 CSpeciesFamily *GetSpeciesFamily(const std::string &family_ident)
3007 {
3008 	for (size_t i = 0; i < SpeciesFamilies.size(); ++i) {
3009 		if (family_ident == SpeciesFamilies[i]->Ident) {
3010 			return SpeciesFamilies[i];
3011 		}
3012 	}
3013 
3014 	return nullptr;
3015 }
3016 
GetSpeciesOrder(const std::string & order_ident)3017 CSpeciesOrder *GetSpeciesOrder(const std::string &order_ident)
3018 {
3019 	for (size_t i = 0; i < SpeciesOrders.size(); ++i) {
3020 		if (order_ident == SpeciesOrders[i]->Ident) {
3021 			return SpeciesOrders[i];
3022 		}
3023 	}
3024 
3025 	return nullptr;
3026 }
3027 
GetSpeciesClass(const std::string & class_ident)3028 CSpeciesClass *GetSpeciesClass(const std::string &class_ident)
3029 {
3030 	for (size_t i = 0; i < SpeciesClasses.size(); ++i) {
3031 		if (class_ident == SpeciesClasses[i]->Ident) {
3032 			return SpeciesClasses[i];
3033 		}
3034 	}
3035 
3036 	return nullptr;
3037 }
3038 
GetSpeciesPhylum(const std::string & phylum_ident)3039 CSpeciesPhylum *GetSpeciesPhylum(const std::string &phylum_ident)
3040 {
3041 	for (size_t i = 0; i < SpeciesPhylums.size(); ++i) {
3042 		if (phylum_ident == SpeciesPhylums[i]->Ident) {
3043 			return SpeciesPhylums[i];
3044 		}
3045 	}
3046 
3047 	return nullptr;
3048 }
3049 
CanEvolveToAUnitType(CTerrainType * terrain,bool sapient_only)3050 bool CSpecies::CanEvolveToAUnitType(CTerrainType *terrain, bool sapient_only)
3051 {
3052 	for (size_t i = 0; i < this->EvolvesTo.size(); ++i) {
3053 		if (
3054 			(this->EvolvesTo[i]->Type != nullptr && (!terrain || std::find(this->EvolvesTo[i]->Terrains.begin(), this->EvolvesTo[i]->Terrains.end(), terrain) != this->EvolvesTo[i]->Terrains.end()) && (!sapient_only || this->EvolvesTo[i]->Sapient))
3055 			|| this->EvolvesTo[i]->CanEvolveToAUnitType(terrain, sapient_only)
3056 		) {
3057 			return true;
3058 		}
3059 	}
3060 	return false;
3061 }
3062 
GetRandomEvolution(CTerrainType * terrain)3063 CSpecies *CSpecies::GetRandomEvolution(CTerrainType *terrain)
3064 {
3065 	std::vector<CSpecies *> potential_evolutions;
3066 
3067 	for (size_t i = 0; i < this->EvolvesTo.size(); ++i) {
3068 		if (
3069 			(this->EvolvesTo[i]->Type != nullptr && std::find(this->EvolvesTo[i]->Terrains.begin(), this->EvolvesTo[i]->Terrains.end(), terrain) != this->EvolvesTo[i]->Terrains.end())
3070 			|| this->EvolvesTo[i]->CanEvolveToAUnitType(terrain)
3071 		) { //give preference to evolutions that are native to the current terrain
3072 			potential_evolutions.push_back(this->EvolvesTo[i]);
3073 		}
3074 	}
3075 
3076 	if (potential_evolutions.size() == 0) {
3077 		for (size_t i = 0; i < this->EvolvesTo.size(); ++i) {
3078 			if (this->EvolvesTo[i]->Type != nullptr || this->EvolvesTo[i]->CanEvolveToAUnitType()) {
3079 				potential_evolutions.push_back(this->EvolvesTo[i]);
3080 			}
3081 		}
3082 	}
3083 
3084 	if (potential_evolutions.size() > 0) {
3085 		return potential_evolutions[SyncRand(potential_evolutions.size())];
3086 	}
3087 
3088 	return nullptr;
3089 }
3090 
GetImageLayerNameById(int image_layer)3091 std::string GetImageLayerNameById(int image_layer)
3092 {
3093 	if (image_layer == LeftArmImageLayer) {
3094 		return "left-arm";
3095 	} else if (image_layer == RightArmImageLayer) {
3096 		return "right-arm";
3097 	} else if (image_layer == RightHandImageLayer) {
3098 		return "right-hand";
3099 	} else if (image_layer == HairImageLayer) {
3100 		return "hair";
3101 	} else if (image_layer == ClothingImageLayer) {
3102 		return "clothing";
3103 	} else if (image_layer == ClothingLeftArmImageLayer) {
3104 		return "clothing-left-arm";
3105 	} else if (image_layer == ClothingRightArmImageLayer) {
3106 		return "clothing-right-arm";
3107 	} else if (image_layer == PantsImageLayer) {
3108 		return "pants";
3109 	} else if (image_layer == BootsImageLayer) {
3110 		return "boots";
3111 	} else if (image_layer == WeaponImageLayer) {
3112 		return "weapon";
3113 	} else if (image_layer == ShieldImageLayer) {
3114 		return "shield";
3115 	} else if (image_layer == HelmetImageLayer) {
3116 		return "helmet";
3117 	} else if (image_layer == BackpackImageLayer) {
3118 		return "backpack";
3119 	} else if (image_layer == MountImageLayer) {
3120 		return "mount";
3121 	}
3122 
3123 	return "";
3124 }
3125 
GetImageLayerIdByName(const std::string & image_layer)3126 int GetImageLayerIdByName(const std::string &image_layer)
3127 {
3128 	if (image_layer == "left-arm") {
3129 		return LeftArmImageLayer;
3130 	} else if (image_layer == "right-arm") {
3131 		return RightArmImageLayer;
3132 	} else if (image_layer == "right-hand") {
3133 		return RightHandImageLayer;
3134 	} else if (image_layer == "hair") {
3135 		return HairImageLayer;
3136 	} else if (image_layer == "clothing") {
3137 		return ClothingImageLayer;
3138 	} else if (image_layer == "clothing-left-arm") {
3139 		return ClothingLeftArmImageLayer;
3140 	} else if (image_layer == "clothing-right-arm") {
3141 		return ClothingRightArmImageLayer;
3142 	} else if (image_layer == "pants") {
3143 		return PantsImageLayer;
3144 	} else if (image_layer == "boots") {
3145 		return BootsImageLayer;
3146 	} else if (image_layer == "weapon") {
3147 		return WeaponImageLayer;
3148 	} else if (image_layer == "shield") {
3149 		return ShieldImageLayer;
3150 	} else if (image_layer == "helmet") {
3151 		return HelmetImageLayer;
3152 	} else if (image_layer == "backpack") {
3153 		return BackpackImageLayer;
3154 	} else if (image_layer == "mount") {
3155 		return MountImageLayer;
3156 	}
3157 
3158 	return -1;
3159 }
3160 //Wyrmgus end
3161