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