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 unit.cpp - The unit 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
32 /*----------------------------------------------------------------------------
33 -- Includes
34 ----------------------------------------------------------------------------*/
35
36 #include "stratagus.h"
37
38 #include "unit/unit.h"
39
40 #include "action/action_attack.h"
41 //Wyrmgus start
42 #include "action/action_resource.h"
43 #include "action/action_upgradeto.h"
44 //Wyrmgus end
45
46 #include "actions.h"
47 #include "ai.h"
48 //Wyrmgus start
49 #include "../ai/ai_local.h" //for using AiHelpers
50 //Wyrmgus end
51 #include "animation.h"
52 #include "character.h"
53 #include "civilization.h"
54 #include "commands.h"
55 #include "construct.h"
56 #include "game.h"
57 #include "editor.h"
58 //Wyrmgus start
59 #include "grand_strategy.h"
60 //Wyrmgus end
61 //Wyrmgus start
62 #include "item.h"
63 //Wyrmgus end
64 #include "luacallback.h"
65 #include "map/map.h"
66 #include "map/map_layer.h"
67 #include "map/site.h"
68 #include "map/tileset.h"
69 #include "missile.h"
70 #include "network.h"
71 #include "pathfinder.h"
72 #include "plane.h"
73 #include "player.h"
74 //Wyrmgus start
75 #include "quest.h"
76 //Wyrmgus end
77 #include "religion/deity.h"
78 #include "script.h"
79 #include "sound.h"
80 #include "sound_server.h"
81 #include "spells.h"
82 #include "time/time_of_day.h"
83 #include "translate.h"
84 #include "ui/button_action.h"
85 #include "ui/interface.h"
86 #include "ui/ui.h"
87 #include "unit/unit_find.h"
88 #include "unit/unit_manager.h"
89 #include "unit/unittype.h"
90 #include "unit/unit_type_variation.h"
91 #include "unitsound.h"
92 #include "upgrade/dependency.h"
93 #include "upgrade/upgrade.h"
94 #include "upgrade/upgrade_modifier.h"
95 //Wyrmgus start
96 #include "util.h"
97 //Wyrmgus end
98 #include "video.h"
99
100 #include <math.h>
101
102
103 /*----------------------------------------------------------------------------
104 -- Documentation
105 ----------------------------------------------------------------------------*/
106
107 /**
108 ** @class CUnit unit.h
109 **
110 ** \#include "unit/unit.h"
111 **
112 ** Everything belonging to a unit. FIXME: rearrange for less memory.
113 **
114 ** This class contains all information about a unit in game.
115 ** A unit could be anything: a man, a vehicle, a ship, or a building.
116 ** Currently only a tile, a unit, or a missile could be placed on the map.
117 **
118 ** The unit structure members:
119 **
120 ** CUnit::Refs
121 **
122 ** The reference counter of the unit. If the pointer to the unit
123 ** is stored the counter must be incremented and if this reference
124 ** is destroyed the counter must be decremented. Alternative it
125 ** would be possible to implement a garbage collector for this.
126 **
127 ** CUnit::Slot
128 **
129 ** This is the unique slot number. It is not possible that two
130 ** units have the same slot number at the same time. The slot
131 ** numbers are reused.
132 ** This field could be accessed by the macro UnitNumber(Unit *).
133 **
134 ** CUnit::UnitSlot
135 **
136 ** This is the index into #Units[], where the unit pointer is
137 ** stored. #Units[] is a table of all units currently active in
138 ** game. This pointer is only needed to speed up, the remove of
139 ** the unit pointer from #Units[], it didn't must be searched in
140 ** the table.
141 **
142 ** CUnit::PlayerSlot
143 **
144 ** The index into Player::Units[], where the unit pointer is
145 ** stored. Player::Units[] is a table of all units currently
146 ** belonging to a player. This pointer is only needed to speed
147 ** up, the remove of the unit pointer from Player::Units[].
148 **
149 ** CUnit::Container
150 **
151 ** Pointer to the unit containing it, or null if the unit is
152 ** free. This points to the transporter for units on board, or to
153 ** the building for peasants inside(when they are mining).
154 **
155 ** CUnit::UnitInside
156 **
157 ** Pointer to the last unit added inside. Order doesn't really
158 ** matter. All units inside are kept in a circular linked list.
159 ** This is null if there are no units inside. Multiple levels
160 ** of inclusion are allowed, though not very useful right now
161 **
162 ** CUnit::NextContained, CUnit::PrevContained
163 **
164 ** The next and previous element in the curent container. Bogus
165 ** values allowed for units not contained.
166 **
167 ** CUnit::InsideCount
168 **
169 ** The number of units inside the container.
170 **
171 ** CUnit::BoardCount
172 **
173 ** The number of units transported inside the container. This
174 ** does not include for instance stuff like harvesters returning
175 ** cargo.
176 **
177 ** CUnit::tilePos
178 **
179 ** The tile map coordinates of the unit.
180 ** 0,0 is the upper left on the map.
181 **
182 ** CUnit::Type
183 **
184 ** Pointer to the unit-type (::UnitType). The unit-type contains
185 ** all information that all units of the same type shares.
186 ** (Animations, Name, Stats, ...)
187 **
188 ** CUnit::SeenType
189 ** Pointer to the unit-type that this unit was, when last seen.
190 ** Currently only used by buildings.
191 **
192 ** CUnit::Player
193 **
194 ** Pointer to the owner of this unit (::Player). An unit could
195 ** only be owned by one player.
196 **
197 ** CUnit::Stats
198 **
199 ** Pointer to the current status (::UnitStats) of a unit. The
200 ** units of the same player and the same type could share the same
201 ** stats. The status contains all values which could be different
202 ** for each player. This f.e. the upgradeable abilities of an
203 ** unit. (CUnit::Stats::SightRange, CUnit::Stats::Armor,
204 ** CUnit::Stats::HitPoints, ...)
205 **
206 ** CUnit::CurrentSightRange
207 **
208 ** Current sight range of a unit, this changes when a unit enters
209 ** a transporter or building or exits one of these.
210 **
211 ** CUnit::Colors
212 **
213 ** Player colors of the unit. Contains the hardware dependent
214 ** pixel values for the player colors (palette index #208-#211).
215 ** Setup from the global palette. This is a pointer.
216 ** @note Index #208-#211 are various SHADES of the team color
217 ** (#208 is brightest shade, #211 is darkest shade) .... these
218 ** numbers are NOT red=#208, blue=#209, etc
219 **
220 ** CUnit::IX CUnit::IY
221 **
222 ** Coordinate displacement in pixels or coordinates inside a tile.
223 ** Currently only !=0, if the unit is moving from one tile to
224 ** another (0-32 and for ships/flyers 0-64).
225 **
226 ** CUnit::Frame
227 **
228 ** Current graphic image of the animation sequence. The high bit
229 ** (128) is used to flip this image horizontal (x direction).
230 ** This also limits the number of different frames/image to 126.
231 **
232 ** CUnit::SeenFrame
233 **
234 ** Graphic image (see CUnit::Frame) what the player on this
235 ** computer has last seen. If UnitNotSeen the player haven't seen
236 ** this unit yet.
237 **
238 ** CUnit::Direction
239 **
240 ** Contains the binary angle (0-255) in which the direction the
241 ** unit looks. 0, 32, 64, 128, 160, 192, 224, 256 corresponds to
242 ** 0?, 45?, 90?, 135?, 180?, 225?, 270?, 315?, 360? or north,
243 ** north-east, east, south-east, south, south-west, west,
244 ** north-west, north. Currently only 8 directions are used, this
245 ** is more for the future.
246 **
247 ** CUnit::Attacked
248 **
249 ** Last cycle the unit was attacked. 0 means never.
250 **
251 ** CUnit::Burning
252 **
253 ** If Burning is non-zero, the unit is burning.
254 **
255 ** CUnit::VisCount[PlayerMax]
256 **
257 ** Used to keep track of visible units on the map, it counts the
258 ** Number of seen tiles for each player. This is only modified
259 ** in UnitsMarkSeen and UnitsUnmarkSeen, from fow.
260 ** We keep track of visilibty for each player, and combine with
261 ** Shared vision ONLY when querying and such.
262 **
263 ** CUnit::SeenByPlayer
264 **
265 ** This is a bitmask of 1 and 0 values. SeenByPlayer & (1<<p) is 0
266 ** If p never saw the unit and 1 if it did. This is important for
267 ** keeping track of dead units under fog. We only keep track of units
268 ** that are visible under fog with this.
269 **
270 ** CUnit::Destroyed
271 **
272 ** @todo docu.
273 ** If you need more information, please send me an email or write it self.
274 **
275 ** CUnit::Removed
276 **
277 ** This flag means the unit is not active on map. This flag
278 ** have workers set if they are inside a building, units that are
279 ** on board of a transporter.
280 **
281 ** CUnit::Selected
282 **
283 ** Unit is selected. (So you can give it orders)
284 **
285 ** CUnit::UnderConstruction
286 ** Set when a building is under construction, and still using the
287 ** generic building animation.
288 **
289 ** CUnit::SeenUnderConstruction
290 ** Last seen state of construction. Used to draw correct building
291 ** frame. See CUnit::UnderConstruction for more information.
292 **
293 ** CUnit::SeenState
294 ** The Seen State of the building.
295 ** 01 The building in being built when last seen.
296 ** 10 The building was been upgraded when last seen.
297 **
298 ** CUnit::Boarded
299 **
300 ** This is 1 if the unit is on board a transporter.
301 **
302 **
303 ** CUnit::XP
304 **
305 ** Number of XP of the unit.
306 **
307 ** CUnit::Kills
308 **
309 ** How many units have been killed by the unit.
310 **
311 ** CUnit::GroupId
312 **
313 ** Number of the group to that the unit belongs. This is the main
314 ** group showed on map, a unit can belong to many groups.
315 **
316 ** CUnit::LastGroup
317 **
318 ** Automatic group number, to reselect the same units. When the
319 ** user selects more than one unit all units is given the next
320 ** same number. (Used for ALT-CLICK)
321 **
322 ** CUnit::Value
323 **
324 ** This values hold the amount of resources in a resource or in
325 ** in a harvester.
326 ** @todo continue documentation
327 **
328 ** CUnit::Wait
329 **
330 ** The unit is forced too wait for that many cycles. Be careful,
331 ** setting this to 0 will lock the unit.
332 **
333 ** CUnit::State
334 **
335 ** Animation state, currently position in the animation script.
336 ** 0 if an animation has just started, it should only be changed
337 ** inside of actions.
338 **
339 ** CUnit::Reset
340 **
341 ** @todo continue documentation
342 **
343 ** CUnit::Blink
344 **
345 **
346 ** CUnit::Moving
347 **
348 **
349 ** CUnit::RescuedFrom
350 **
351 ** Pointer to the original owner of a unit. It will be null if
352 ** the unit was not rescued.
353 **
354 ** CUnit::Orders
355 **
356 ** Contains all orders of the unit. Slot 0 is always used.
357 **
358 ** CUnit::SavedOrder
359 **
360 ** This order is executed, if the current order is finished.
361 ** This is used for attacking units, to return to the old
362 ** place or for patrolling units to return to patrol after
363 ** killing some enemies. Any new order given to the unit,
364 ** clears this saved order.
365 **
366 ** CUnit::NewOrder
367 **
368 ** This field is only used by buildings and this order is
369 ** assigned to any by this building new trained unit.
370 ** This is can be used to set the exit or gathering point of a
371 ** building.
372 **
373 ** CUnit::Goal
374 **
375 ** Generic goal pointer. Used by teleporters to point to circle of power.
376 **
377 **
378 ** @todo continue documentation
379 **
380 */
381
382 /*----------------------------------------------------------------------------
383 -- Variables
384 ----------------------------------------------------------------------------*/
385
386 bool EnableTrainingQueue; /// Config: training queues enabled
387 bool EnableBuildingCapture = false; /// Config: capture buildings enabled
388 bool RevealAttacker; /// Config: reveal attacker enabled
389 int ResourcesMultiBuildersMultiplier = 0; /// Config: spend resources for building with multiple workers
390
391 static unsigned long HelpMeLastCycle; /// Last cycle HelpMe sound played
392 static int HelpMeLastX; /// Last X coordinate HelpMe sound played
393 static int HelpMeLastY; /// Last Y coordinate HelpMe sound played
394
395 /*----------------------------------------------------------------------------
396 -- Functions
397 ----------------------------------------------------------------------------*/
398
399 static void RemoveUnitFromContainer(CUnit &unit);
400
401 extern int ExtraDeathIndex(const char *death);
402
403 /**
404 ** Increase a unit's reference count.
405 */
RefsIncrease()406 void CUnit::RefsIncrease()
407 {
408 Assert(Refs && !Destroyed);
409 if (!SaveGameLoading) {
410 ++Refs;
411 }
412 }
413
414 /**
415 ** Decrease a unit's reference count.
416 */
RefsDecrease()417 void CUnit::RefsDecrease()
418 {
419 Assert(Refs);
420 if (!SaveGameLoading) {
421 if (Destroyed) {
422 if (!--Refs) {
423 Release();
424 }
425 } else {
426 --Refs;
427 Assert(Refs);
428 }
429 }
430 }
431
Init()432 void CUnit::Init()
433 {
434 Refs = 0;
435 ReleaseCycle = 0;
436 PlayerSlot = static_cast<size_t>(-1);
437 InsideCount = 0;
438 BoardCount = 0;
439 UnitInside = nullptr;
440 Container = nullptr;
441 NextContained = nullptr;
442 PrevContained = nullptr;
443 NextWorker = nullptr;
444
445 Resource.Workers = nullptr;
446 Resource.Assigned = 0;
447 Resource.Active = 0;
448
449 //Wyrmgus start
450 for (int i = 0; i < MaxItemSlots; ++i) {
451 EquippedItems[i].clear();
452 }
453 SoldUnits.clear();
454 //Wyrmgus end
455
456 tilePos.x = 0;
457 tilePos.y = 0;
458 //Wyrmgus start
459 RallyPointPos.x = -1;
460 RallyPointPos.y = -1;
461 MapLayer = nullptr;
462 RallyPointMapLayer = nullptr;
463 //Wyrmgus end
464 Offset = 0;
465 Type = nullptr;
466 Player = nullptr;
467 Stats = nullptr;
468 //Wyrmgus start
469 Character = nullptr;
470 Settlement = nullptr;
471 Trait = nullptr;
472 Prefix = nullptr;
473 Suffix = nullptr;
474 Spell = nullptr;
475 Work = nullptr;
476 Elixir = nullptr;
477 Unique = nullptr;
478 Bound = false;
479 Identified = true;
480 ConnectingDestination = nullptr;
481 //Wyrmgus end
482 CurrentSightRange = 0;
483
484 pathFinderData = new PathFinderData;
485 pathFinderData->input.SetUnit(*this);
486
487 Colors = nullptr;
488 //Wyrmgus start
489 Name.clear();
490 ExtraName.clear();
491 FamilyName.clear();
492 Variation = 0;
493 memset(LayerVariation, -1, sizeof(LayerVariation));
494 //Wyrmgus end
495 IX = 0;
496 IY = 0;
497 Frame = 0;
498 Direction = 0;
499 DamagedType = ANIMATIONS_DEATHTYPES;
500 Attacked = 0;
501 Burning = 0;
502 Destroyed = 0;
503 Removed = 0;
504 Selected = 0;
505 TeamSelected = 0;
506 UnderConstruction = 0;
507 Active = 0;
508 Boarded = 0;
509 RescuedFrom = nullptr;
510 memset(VisCount, 0, sizeof(VisCount));
511 memset(&Seen, 0, sizeof(Seen));
512 Variable = nullptr;
513 TTL = 0;
514 Threshold = 0;
515 GroupId = 0;
516 LastGroup = 0;
517 ResourcesHeld = 0;
518 Wait = 0;
519 Blink = 0;
520 Moving = 0;
521 ReCast = 0;
522 CacheLock = 0;
523 Summoned = 0;
524 Waiting = 0;
525 MineLow = 0;
526 memset(&Anim, 0, sizeof(Anim));
527 memset(&WaitBackup, 0, sizeof(WaitBackup));
528 GivesResource = 0;
529 CurrentResource = 0;
530 StepCount = 0;
531 Orders.clear();
532 delete SavedOrder;
533 SavedOrder = nullptr;
534 delete NewOrder;
535 NewOrder = nullptr;
536 delete CriticalOrder;
537 CriticalOrder = nullptr;
538 AutoCastSpell = nullptr;
539 SpellCoolDownTimers = nullptr;
540 AutoRepair = 0;
541 Goal = nullptr;
542 IndividualUpgrades.clear();
543 }
544
545 /**
546 ** Release an unit.
547 **
548 ** The unit is only released, if all references are dropped.
549 */
Release(bool final)550 void CUnit::Release(bool final)
551 {
552 if (Type == nullptr) {
553 DebugPrint("unit already free\n");
554 return;
555 }
556 //Wyrmgus start
557 if (Orders.size() != 1) {
558 fprintf(stderr, "Unit to be released has more than 1 order; Unit Type: \"%s\", Orders: %d, First Order Type: %d.\n", this->Type->Ident.c_str(), (int)Orders.size(), this->CurrentAction());
559 }
560 //Wyrmgus end
561 Assert(Orders.size() == 1);
562 // Must be removed before here
563 Assert(Removed);
564
565 // First release, remove from lists/tables.
566 if (!Destroyed) {
567 DebugPrint("%d: First release %d\n" _C_ Player->Index _C_ UnitNumber(*this));
568
569 // Are more references remaining?
570 Destroyed = 1; // mark as destroyed
571
572 if (Container && !final) {
573 if (Boarded) {
574 Container->BoardCount--;
575 }
576 MapUnmarkUnitSight(*this);
577 RemoveUnitFromContainer(*this);
578 }
579
580 while (Resource.Workers) {
581 Resource.Workers->DeAssignWorkerFromMine(*this);
582 }
583
584 if (--Refs > 0) {
585 return;
586 }
587 }
588
589 Assert(!Refs);
590
591 //
592 // No more references remaining, but the network could have an order
593 // on the way. We must wait a little time before we could free the
594 // memory.
595 //
596
597 Type = nullptr;
598 //Wyrmgus start
599 Character = nullptr;
600 if (this->Settlement && this->Settlement->SiteUnit == this) {
601 this->Settlement->SiteUnit = nullptr;
602 }
603 Settlement = nullptr;
604 Trait = nullptr;
605 Prefix = nullptr;
606 Suffix = nullptr;
607 Spell = nullptr;
608 Work = nullptr;
609 Elixir = nullptr;
610 Unique = nullptr;
611 Bound = false;
612 Identified = true;
613 ConnectingDestination = nullptr;
614
615 for (int i = 0; i < MaxItemSlots; ++i) {
616 EquippedItems[i].clear();
617 }
618 SoldUnits.clear();
619 //Wyrmgus end
620
621 delete pathFinderData;
622 delete[] AutoCastSpell;
623 delete[] SpellCoolDownTimers;
624 delete[] Variable;
625 for (std::vector<COrder *>::iterator order = Orders.begin(); order != Orders.end(); ++order) {
626 delete *order;
627 }
628 Orders.clear();
629
630 // Remove the unit from the global units table.
631 UnitManager.ReleaseUnit(this);
632 }
633
634 //Wyrmgus start
SetResourcesHeld(int quantity)635 void CUnit::SetResourcesHeld(int quantity)
636 {
637 this->ResourcesHeld = quantity;
638
639 const CUnitTypeVariation *variation = this->GetVariation();
640 if (
641 variation
642 && (
643 (variation->ResourceMin && this->ResourcesHeld < variation->ResourceMin)
644 || (variation->ResourceMax && this->ResourcesHeld > variation->ResourceMax)
645 )
646 ) {
647 this->ChooseVariation();
648 }
649 }
650
ChangeResourcesHeld(int quantity)651 void CUnit::ChangeResourcesHeld(int quantity)
652 {
653 this->SetResourcesHeld(this->ResourcesHeld + quantity);
654 }
655
ReplaceOnTop(CUnit & replaced_unit)656 void CUnit::ReplaceOnTop(CUnit &replaced_unit)
657 {
658 if (replaced_unit.Unique != nullptr) {
659 this->SetUnique(replaced_unit.Unique);
660 } else {
661 if (replaced_unit.Prefix != nullptr) {
662 this->SetPrefix(replaced_unit.Prefix);
663 }
664 if (replaced_unit.Suffix != nullptr) {
665 this->SetSuffix(replaced_unit.Suffix);
666 }
667 if (replaced_unit.Spell != nullptr) {
668 this->SetSpell(replaced_unit.Spell);
669 }
670 }
671 if (replaced_unit.Settlement != nullptr) {
672 this->Settlement = replaced_unit.Settlement;
673 if (this->Type->BoolFlag[TOWNHALL_INDEX].value) {
674 this->Settlement->SiteUnit = this;
675 Map.SiteUnits.erase(std::remove(Map.SiteUnits.begin(), Map.SiteUnits.end(), &replaced_unit), Map.SiteUnits.end());
676 Map.SiteUnits.push_back(this);
677 }
678 }
679
680 this->SetResourcesHeld(replaced_unit.ResourcesHeld); // We capture the value of what is beneath.
681 this->Variable[GIVERESOURCE_INDEX].Value = replaced_unit.Variable[GIVERESOURCE_INDEX].Value;
682 this->Variable[GIVERESOURCE_INDEX].Max = replaced_unit.Variable[GIVERESOURCE_INDEX].Max;
683 this->Variable[GIVERESOURCE_INDEX].Enable = replaced_unit.Variable[GIVERESOURCE_INDEX].Enable;
684
685 replaced_unit.Remove(nullptr); // Destroy building beneath
686 UnitLost(replaced_unit);
687 UnitClearOrders(replaced_unit);
688 replaced_unit.Release();
689 }
690
ChangeExperience(int amount,int around_range)691 void CUnit::ChangeExperience(int amount, int around_range)
692 {
693 std::vector<CUnit *> table;
694 if (around_range > 0) {
695 SelectAroundUnit(*this, around_range, table, MakeAndPredicate(HasSamePlayerAs(*this->Player), IsNotBuildingType()));
696 }
697
698 amount /= 1 + table.size();
699
700 if (this->Type->BoolFlag[ORGANIC_INDEX].value) {
701 this->Variable[XP_INDEX].Max += amount;
702 this->Variable[XP_INDEX].Value = this->Variable[XP_INDEX].Max;
703 this->XPChanged();
704 }
705
706 if (around_range > 0) {
707 for (size_t i = 0; i != table.size(); ++i) {
708 if (table[i]->Type->BoolFlag[ORGANIC_INDEX].value) {
709 table[i]->Variable[XP_INDEX].Max += amount;
710 table[i]->Variable[XP_INDEX].Value = table[i]->Variable[XP_INDEX].Max;
711 table[i]->XPChanged();
712 }
713 }
714 }
715 }
716
IncreaseLevel(int level_quantity,bool automatic_learning)717 void CUnit::IncreaseLevel(int level_quantity, bool automatic_learning)
718 {
719 while (level_quantity > 0) {
720 this->Variable[LEVEL_INDEX].Value += 1;
721 if (this->Type->Stats[this->Player->Index].Variables[LEVEL_INDEX].Value < this->Variable[LEVEL_INDEX].Value) {
722 if (GetAvailableLevelUpUpgrades(true) == 0 || (this->Variable[LEVEL_INDEX].Value - this->Type->Stats[this->Player->Index].Variables[LEVEL_INDEX].Value) > 1) {
723 this->Variable[POINTS_INDEX].Max += 5 * (this->Variable[LEVEL_INDEX].Value + 1);
724 this->Variable[POINTS_INDEX].Value += 5 * (this->Variable[LEVEL_INDEX].Value + 1);
725 }
726
727 this->Variable[LEVELUP_INDEX].Value += 1;
728 this->Variable[LEVELUP_INDEX].Max = this->Variable[LEVELUP_INDEX].Value;
729 // if there are no level-up upgrades available for the unit, increase its HP instead
730 if (this->GetAvailableLevelUpUpgrades() < this->Variable[LEVELUP_INDEX].Value) {
731 this->Variable[HP_INDEX].Max += 10;
732 this->Variable[LEVELUP_INDEX].Value -= 1;
733 this->Variable[LEVELUP_INDEX].Max = this->Variable[LEVELUP_INDEX].Value;
734 }
735 }
736 this->Variable[HP_INDEX].Value = this->GetModifiedVariable(HP_INDEX, VariableMax);
737 level_quantity -= 1;
738 }
739
740 UpdateXPRequired();
741
742 bool upgrade_found = true;
743 while (this->Variable[LEVELUP_INDEX].Value > 0 && upgrade_found && automatic_learning) {
744 upgrade_found = false;
745
746 if (((int) AiHelpers.ExperienceUpgrades.size()) > Type->Slot) {
747 std::vector<CUnitType *> potential_upgrades;
748
749 if ((this->Player->AiEnabled || this->Character == nullptr) && this->Type->BoolFlag[HARVESTER_INDEX].value && this->CurrentResource && AiHelpers.ExperienceUpgrades[Type->Slot].size() > 1) {
750 //if is a harvester who is currently gathering, try to upgrade to a unit type which is best for harvesting the current resource
751 unsigned int best_gathering_rate = 0;
752 for (size_t i = 0; i != AiHelpers.ExperienceUpgrades[Type->Slot].size(); ++i) {
753 CUnitType *experience_upgrade_type = AiHelpers.ExperienceUpgrades[Type->Slot][i];
754 if (CheckDependencies(experience_upgrade_type, this, true)) {
755 if (this->Character == nullptr || std::find(this->Character->ForbiddenUpgrades.begin(), this->Character->ForbiddenUpgrades.end(), experience_upgrade_type) == this->Character->ForbiddenUpgrades.end()) {
756 if (!experience_upgrade_type->ResInfo[this->CurrentResource]) {
757 continue;
758 }
759 unsigned int gathering_rate = experience_upgrade_type->GetResourceStep(this->CurrentResource, this->Player->Index);
760 if (gathering_rate >= best_gathering_rate) {
761 if (gathering_rate > best_gathering_rate) {
762 best_gathering_rate = gathering_rate;
763 potential_upgrades.clear();
764 }
765 potential_upgrades.push_back(experience_upgrade_type);
766 }
767 }
768 }
769 }
770 } else if (this->Player->AiEnabled || (this->Character == nullptr && AiHelpers.ExperienceUpgrades[Type->Slot].size() == 1)) {
771 for (size_t i = 0; i != AiHelpers.ExperienceUpgrades[Type->Slot].size(); ++i) {
772 if (CheckDependencies(AiHelpers.ExperienceUpgrades[Type->Slot][i], this, true)) {
773 if (this->Character == nullptr || std::find(this->Character->ForbiddenUpgrades.begin(), this->Character->ForbiddenUpgrades.end(), AiHelpers.ExperienceUpgrades[Type->Slot][i]) == this->Character->ForbiddenUpgrades.end()) {
774 potential_upgrades.push_back(AiHelpers.ExperienceUpgrades[Type->Slot][i]);
775 }
776 }
777 }
778 }
779
780 if (potential_upgrades.size() > 0) {
781 this->Variable[LEVELUP_INDEX].Value -= 1;
782 this->Variable[LEVELUP_INDEX].Max = this->Variable[LEVELUP_INDEX].Value;
783 CUnitType *chosen_unit_type = potential_upgrades[SyncRand(potential_upgrades.size())];
784 if (this->Player == ThisPlayer) {
785 this->Player->Notify(NotifyGreen, this->tilePos, this->MapLayer->ID, _("%s has upgraded to %s!"), this->GetMessageName().c_str(), chosen_unit_type->Name.c_str());
786 }
787 TransformUnitIntoType(*this, *chosen_unit_type);
788 upgrade_found = true;
789 }
790 }
791
792 if ((this->Player->AiEnabled || this->Character == nullptr) && this->Variable[LEVELUP_INDEX].Value) {
793 if (((int) AiHelpers.LearnableAbilities.size()) > Type->Slot) {
794 std::vector<CUpgrade *> potential_abilities;
795 for (size_t i = 0; i != AiHelpers.LearnableAbilities[Type->Slot].size(); ++i) {
796 if (CanLearnAbility(AiHelpers.LearnableAbilities[Type->Slot][i])) {
797 potential_abilities.push_back(AiHelpers.LearnableAbilities[Type->Slot][i]);
798 }
799 }
800 if (potential_abilities.size() > 0) {
801 if (potential_abilities.size() == 1 || this->Player->AiEnabled) { //if can only acquire one particular ability, get it automatically
802 CUpgrade *chosen_ability = potential_abilities[SyncRand(potential_abilities.size())];
803 AbilityAcquire(*this, chosen_ability);
804 upgrade_found = true;
805 if (this->Player == ThisPlayer) {
806 this->Player->Notify(NotifyGreen, this->tilePos, this->MapLayer->ID, _("%s has acquired the %s ability!"), this->GetMessageName().c_str(), chosen_ability->Name.c_str());
807 }
808 }
809 }
810 }
811 }
812 }
813
814 this->Variable[LEVELUP_INDEX].Enable = 1;
815
816 this->Player->UpdateLevelUpUnits();
817 }
818
Retrain()819 void CUnit::Retrain()
820 {
821 //lose all abilities (the AbilityLost function also returns the level-ups to the unit)
822 for (size_t i = 0; i < AllUpgrades.size(); ++i) {
823 if (this->GetIndividualUpgrade(AllUpgrades[i])) {
824 if (AllUpgrades[i]->Ability && std::find(this->Type->StartingAbilities.begin(), this->Type->StartingAbilities.end(), AllUpgrades[i]) == this->Type->StartingAbilities.end()) {
825 AbilityLost(*this, AllUpgrades[i], true);
826 } else if (!strncmp(AllUpgrades[i]->Ident.c_str(), "upgrade-deity-", 14) && strncmp(AllUpgrades[i]->Ident.c_str(), "upgrade-deity-domain-", 21) && this->Character && this->Character->Custom) { //allow changing the deity for custom heroes
827 IndividualUpgradeLost(*this, AllUpgrades[i], true);
828 }
829 }
830 }
831
832 std::string unit_name = GetMessageName();
833
834 //now, revert the unit's type to the level 1 one
835 while (this->Type->Stats[this->Player->Index].Variables[LEVEL_INDEX].Value > 1) {
836 bool found_previous_unit_type = false;
837 for (size_t i = 0; i != UnitTypes.size(); ++i) {
838 if (this->Character != nullptr && std::find(this->Character->ForbiddenUpgrades.begin(), this->Character->ForbiddenUpgrades.end(), UnitTypes[i]) != this->Character->ForbiddenUpgrades.end()) {
839 continue;
840 }
841 if (((int) AiHelpers.ExperienceUpgrades.size()) > i) {
842 for (size_t j = 0; j != AiHelpers.ExperienceUpgrades[i].size(); ++j) {
843 if (AiHelpers.ExperienceUpgrades[i][j] == this->Type) {
844 this->Variable[LEVELUP_INDEX].Value += 1;
845 this->Variable[LEVELUP_INDEX].Max = this->Variable[LEVELUP_INDEX].Value;
846 this->Variable[LEVELUP_INDEX].Enable = 1;
847 TransformUnitIntoType(*this, *UnitTypes[i]);
848 if (!IsNetworkGame() && Character != nullptr) { //save the unit-type experience upgrade for persistent characters
849 if (Character->Type->Slot != i) {
850 if (Player->AiEnabled == false) {
851 Character->Type = UnitTypes[i];
852 SaveHero(Character);
853 CheckAchievements();
854 }
855 }
856 }
857 found_previous_unit_type = true;
858 break;
859 }
860 }
861 }
862 if (found_previous_unit_type) {
863 break;
864 }
865 }
866 if (!found_previous_unit_type) {
867 break;
868 }
869 }
870
871 if (this->Player == ThisPlayer) {
872 this->Player->Notify(NotifyGreen, this->tilePos, this->MapLayer->ID, _("%s's level-up choices have been reset."), unit_name.c_str());
873 }
874 }
875
HealingItemAutoUse()876 void CUnit::HealingItemAutoUse()
877 {
878 if (!HasInventory()) {
879 return;
880 }
881
882 CUnit *uins = this->UnitInside;
883
884 for (int i = 0; i < this->InsideCount; ++i, uins = uins->NextContained) {
885 if (!uins->Type->BoolFlag[ITEM_INDEX].value || uins->Elixir) {
886 continue;
887 }
888
889 if (!IsItemClassConsumable(uins->Type->ItemClass)) {
890 continue;
891 }
892
893 if (uins->Variable[HITPOINTHEALING_INDEX].Value > 0) {
894 if (
895 uins->Variable[HITPOINTHEALING_INDEX].Value <= (this->GetModifiedVariable(HP_INDEX, VariableMax) - this->Variable[HP_INDEX].Value)
896 || (this->Variable[HP_INDEX].Value * 100 / this->GetModifiedVariable(HP_INDEX, VariableMax)) <= 20 // use a healing item if has less than 20% health
897 ) {
898 if (!this->CriticalOrder) {
899 this->CriticalOrder = COrder::NewActionUse(*uins);
900 }
901 break;
902 }
903 }
904 }
905 }
906
SetCharacter(const std::string & character_ident,bool custom_hero)907 void CUnit::SetCharacter(const std::string &character_ident, bool custom_hero)
908 {
909 if (this->CurrentAction() == UnitActionDie) {
910 return;
911 }
912
913 if (this->Character != nullptr) {
914 this->Player->Heroes.erase(std::remove(this->Player->Heroes.begin(), this->Player->Heroes.end(), this), this->Player->Heroes.end());
915
916 this->Variable[HERO_INDEX].Max = this->Variable[HERO_INDEX].Value = this->Variable[HERO_INDEX].Enable = 0;
917 }
918
919 CCharacter *character = nullptr;
920 if (!custom_hero) {
921 character = CCharacter::GetCharacter(character_ident);
922 } else {
923 character = GetCustomHero(character_ident);
924 }
925
926 if (character) {
927 this->Character = character;
928 } else {
929 fprintf(stderr, "Character \"%s\" doesn't exist.\n", character_ident.c_str());
930 return;
931 }
932
933 int old_mana_percent = 0;
934 if (this->Variable[MANA_INDEX].Max > 0) {
935 old_mana_percent = this->Variable[MANA_INDEX].Value * 100 / this->Variable[MANA_INDEX].Max;
936 }
937
938 this->Name = this->Character->Name;
939 this->ExtraName = this->Character->ExtraName;
940 this->FamilyName = this->Character->FamilyName;
941
942 if (this->Character->Type != nullptr) {
943 if (this->Character->Type != this->Type) { //set type to that of the character
944 TransformUnitIntoType(*this, *this->Character->Type);
945 }
946
947 memcpy(Variable, this->Character->Type->Stats[this->Player->Index].Variables, UnitTypeVar.GetNumberVariable() * sizeof(*Variable));
948 } else {
949 fprintf(stderr, "Character \"%s\" has no unit type.\n", character_ident.c_str());
950 return;
951 }
952
953 this->IndividualUpgrades.clear(); //reset the individual upgrades and then apply the character's
954 this->Trait = nullptr;
955
956 if (this->Type->Civilization != -1 && !PlayerRaces.CivilizationUpgrades[this->Type->Civilization].empty()) {
957 CUpgrade *civilization_upgrade = CUpgrade::Get(PlayerRaces.CivilizationUpgrades[this->Type->Civilization]);
958 if (civilization_upgrade) {
959 this->SetIndividualUpgrade(civilization_upgrade, 1);
960 }
961 }
962 if (this->Type->Civilization != -1 && this->Type->Faction != -1 && !PlayerRaces.Factions[this->Type->Faction]->FactionUpgrade.empty()) {
963 CUpgrade *faction_upgrade = CUpgrade::Get(PlayerRaces.Factions[this->Type->Faction]->FactionUpgrade);
964 if (faction_upgrade) {
965 this->SetIndividualUpgrade(faction_upgrade, 1);
966 }
967 }
968
969 if (this->Character->Trait != nullptr) { //set trait
970 TraitAcquire(*this, this->Character->Trait);
971 } else if (Editor.Running == EditorNotRunning && this->Type->Traits.size() > 0) {
972 TraitAcquire(*this, this->Type->Traits[SyncRand(this->Type->Traits.size())]);
973 }
974
975 if (this->Character->Deity != nullptr && this->Character->Deity->CharacterUpgrade != nullptr) {
976 IndividualUpgradeAcquire(*this, this->Character->Deity->CharacterUpgrade);
977 }
978
979 //load worshipped deities
980 for (size_t i = 0; i < this->Character->Deities.size(); ++i) {
981 CUpgrade *deity_upgrade = this->Character->Deities[i]->DeityUpgrade;
982 if (deity_upgrade) {
983 IndividualUpgradeAcquire(*this, deity_upgrade);
984 }
985 }
986
987 for (const CUpgrade *ability_upgrade : this->Type->StartingAbilities) {
988 if (CheckDependencies(ability_upgrade, this)) {
989 IndividualUpgradeAcquire(*this, ability_upgrade);
990 }
991 }
992
993 this->Variable[LEVEL_INDEX].Max = 100000; // because the code above sets the max level to the unit type stats' Level variable (which is the same as its value)
994 if (this->Variable[LEVEL_INDEX].Value < this->Character->Level) {
995 this->IncreaseLevel(this->Character->Level - this->Variable[LEVEL_INDEX].Value, false);
996 }
997
998 this->Variable[XP_INDEX].Enable = 1;
999 this->Variable[XP_INDEX].Value = this->Variable[XPREQUIRED_INDEX].Value * this->Character->ExperiencePercent / 100;
1000 this->Variable[XP_INDEX].Max = this->Variable[XP_INDEX].Value;
1001
1002 if (this->Variable[MANA_INDEX].Max > 0) {
1003 this->Variable[MANA_INDEX].Value = this->Variable[MANA_INDEX].Max * old_mana_percent / 100;
1004 }
1005
1006 //load learned abilities
1007 std::vector<CUpgrade *> abilities_to_remove;
1008 for (size_t i = 0; i < this->Character->Abilities.size(); ++i) {
1009 if (CanLearnAbility(this->Character->Abilities[i])) {
1010 AbilityAcquire(*this, this->Character->Abilities[i], false);
1011 } else { //can't learn the ability? something changed in the game's code, remove it from persistent data and allow the hero to repick the ability
1012 abilities_to_remove.push_back(this->Character->Abilities[i]);
1013 }
1014 }
1015
1016 for (size_t i = 0; i < abilities_to_remove.size(); ++i) {
1017 this->Character->Abilities.erase(std::remove(this->Character->Abilities.begin(), this->Character->Abilities.end(), abilities_to_remove[i]), this->Character->Abilities.end());
1018 SaveHero(this->Character);
1019 }
1020
1021 //load read works
1022 for (size_t i = 0; i < this->Character->ReadWorks.size(); ++i) {
1023 ReadWork(this->Character->ReadWorks[i], false);
1024 }
1025
1026 //load consumed elixirs
1027 for (size_t i = 0; i < this->Character->ConsumedElixirs.size(); ++i) {
1028 ConsumeElixir(this->Character->ConsumedElixirs[i], false);
1029 }
1030
1031 //load items
1032 for (size_t i = 0; i < this->Character->Items.size(); ++i) {
1033 CUnit *item = MakeUnitAndPlace(this->tilePos, *this->Character->Items[i]->Type, &Players[PlayerNumNeutral], this->MapLayer->ID);
1034 if (this->Character->Items[i]->Prefix != nullptr) {
1035 item->SetPrefix(this->Character->Items[i]->Prefix);
1036 }
1037 if (this->Character->Items[i]->Suffix != nullptr) {
1038 item->SetSuffix(this->Character->Items[i]->Suffix);
1039 }
1040 if (this->Character->Items[i]->Spell != nullptr) {
1041 item->SetSpell(this->Character->Items[i]->Spell);
1042 }
1043 if (this->Character->Items[i]->Work != nullptr) {
1044 item->SetWork(this->Character->Items[i]->Work);
1045 }
1046 if (this->Character->Items[i]->Elixir != nullptr) {
1047 item->SetElixir(this->Character->Items[i]->Elixir);
1048 }
1049 item->Unique = this->Character->Items[i]->Unique;
1050 if (!this->Character->Items[i]->Name.empty()) {
1051 item->Name = this->Character->Items[i]->Name;
1052 }
1053 item->Bound = this->Character->Items[i]->Bound;
1054 item->Identified = this->Character->Items[i]->Identified;
1055 item->Remove(this);
1056 if (this->Character->IsItemEquipped(this->Character->Items[i])) {
1057 EquipItem(*item, false);
1058 }
1059 }
1060
1061 if (this->Character != nullptr) {
1062 this->Player->Heroes.push_back(this);
1063 }
1064
1065 this->Variable[HERO_INDEX].Max = this->Variable[HERO_INDEX].Value = this->Variable[HERO_INDEX].Enable = 1;
1066
1067 this->ChooseVariation(); //choose a new variation now
1068 for (int i = 0; i < MaxImageLayers; ++i) {
1069 ChooseVariation(nullptr, false, i);
1070 }
1071 this->UpdateButtonIcons();
1072 this->UpdateXPRequired();
1073 }
1074
CheckTerrainForVariation(const CUnitTypeVariation * variation) const1075 bool CUnit::CheckTerrainForVariation(const CUnitTypeVariation *variation) const
1076 {
1077 //if the variation has one or more terrain set as a precondition, then all tiles underneath the unit must match at least one of those terrains
1078 if (variation->Terrains.size() > 0) {
1079 if (!Map.Info.IsPointOnMap(this->tilePos, this->MapLayer)) {
1080 return false;
1081 }
1082 bool terrain_check = true;
1083 for (int x = 0; x < this->Type->TileSize.x; ++x) {
1084 for (int y = 0; y < this->Type->TileSize.y; ++y) {
1085 if (Map.Info.IsPointOnMap(this->tilePos + Vec2i(x, y), this->MapLayer)) {
1086 if (std::find(variation->Terrains.begin(), variation->Terrains.end(), Map.GetTileTopTerrain(this->tilePos + Vec2i(x, y), false, this->MapLayer->ID, true)) == variation->Terrains.end()) {
1087 terrain_check = false;
1088 break;
1089 }
1090 }
1091 }
1092 if (!terrain_check) {
1093 break;
1094 }
1095 }
1096 if (!terrain_check) {
1097 return false;
1098 }
1099 }
1100
1101 //if the variation has one or more terrains set as a forbidden precondition, then no tiles underneath the unit may match one of those terrains
1102 if (variation->TerrainsForbidden.size() > 0) {
1103 if (!Map.Info.IsPointOnMap(this->tilePos, this->MapLayer)) {
1104 return false;
1105 }
1106 bool terrain_check = true;
1107 for (int x = 0; x < this->Type->TileSize.x; ++x) {
1108 for (int y = 0; y < this->Type->TileSize.y; ++y) {
1109 if (Map.Info.IsPointOnMap(this->tilePos + Vec2i(x, y), this->MapLayer)) {
1110 if (std::find(variation->TerrainsForbidden.begin(), variation->TerrainsForbidden.end(), Map.GetTileTopTerrain(this->tilePos + Vec2i(x, y), false, this->MapLayer->ID, true)) == variation->TerrainsForbidden.end()) {
1111 terrain_check = false;
1112 break;
1113 }
1114 }
1115 }
1116 if (!terrain_check) {
1117 break;
1118 }
1119 }
1120 if (terrain_check) {
1121 return false;
1122 }
1123 }
1124
1125 return true;
1126 }
1127
CheckSeasonForVariation(const CUnitTypeVariation * variation) const1128 bool CUnit::CheckSeasonForVariation(const CUnitTypeVariation *variation) const
1129 {
1130 if (
1131 !variation->Seasons.empty()
1132 && (!this->MapLayer || std::find(variation->Seasons.begin(), variation->Seasons.end(), this->MapLayer->GetSeason()) == variation->Seasons.end())
1133 ) {
1134 return false;
1135 }
1136
1137 if (
1138 !variation->ForbiddenSeasons.empty()
1139 && this->MapLayer
1140 && std::find(variation->ForbiddenSeasons.begin(), variation->ForbiddenSeasons.end(), this->MapLayer->GetSeason()) != variation->ForbiddenSeasons.end()
1141 ) {
1142 return false;
1143 }
1144
1145 return true;
1146 }
1147
ChooseVariation(const CUnitType * new_type,bool ignore_old_variation,int image_layer)1148 void CUnit::ChooseVariation(const CUnitType *new_type, bool ignore_old_variation, int image_layer)
1149 {
1150 std::string priority_variation;
1151 if (image_layer == -1) {
1152 if (this->Character != nullptr && !this->Character->HairVariation.empty()) {
1153 priority_variation = this->Character->HairVariation;
1154 } else if (this->GetVariation() != nullptr) {
1155 priority_variation = this->GetVariation()->VariationId;
1156 }
1157 } else {
1158 if (image_layer == HairImageLayer && this->Character != nullptr && !this->Character->HairVariation.empty()) {
1159 priority_variation = this->Character->HairVariation;
1160 } else if (this->GetLayerVariation(image_layer)) {
1161 priority_variation = this->GetLayerVariation(image_layer)->VariationId;
1162 }
1163 }
1164
1165 std::vector<CUnitTypeVariation *> type_variations;
1166 const std::vector<CUnitTypeVariation *> &variation_list = image_layer == -1 ? (new_type != nullptr ? new_type->Variations : this->Type->Variations) : (new_type != nullptr ? new_type->LayerVariations[image_layer] : this->Type->LayerVariations[image_layer]);
1167
1168 bool found_similar = false;
1169 for (CUnitTypeVariation *variation : variation_list) {
1170 if (variation->ResourceMin && this->ResourcesHeld < variation->ResourceMin) {
1171 continue;
1172 }
1173 if (variation->ResourceMax && this->ResourcesHeld > variation->ResourceMax) {
1174 continue;
1175 }
1176
1177 if (!this->CheckSeasonForVariation(variation)) {
1178 continue;
1179 }
1180
1181 if (!this->CheckTerrainForVariation(variation)) {
1182 continue;
1183 }
1184
1185 bool upgrades_check = true;
1186 bool requires_weapon = false;
1187 bool found_weapon = false;
1188 bool requires_shield = false;
1189 bool found_shield = false;
1190 for (const CUpgrade *required_upgrade : variation->UpgradesRequired) {
1191 if (required_upgrade->Weapon) {
1192 requires_weapon = true;
1193 if (UpgradeIdentAllowed(*this->Player, required_upgrade->Ident.c_str()) == 'R' || this->GetIndividualUpgrade(required_upgrade)) {
1194 found_weapon = true;
1195 }
1196 } else if (required_upgrade->Shield) {
1197 requires_shield = true;
1198 if (UpgradeIdentAllowed(*this->Player, required_upgrade->Ident.c_str()) == 'R' || this->GetIndividualUpgrade(required_upgrade)) {
1199 found_shield = true;
1200 }
1201 } else if (UpgradeIdentAllowed(*this->Player, required_upgrade->Ident.c_str()) != 'R' && this->GetIndividualUpgrade(required_upgrade) == false) {
1202 upgrades_check = false;
1203 break;
1204 }
1205 }
1206
1207 if (upgrades_check) {
1208 for (const CUpgrade *forbidden_upgrade : variation->UpgradesForbidden) {
1209 if (UpgradeIdentAllowed(*this->Player, forbidden_upgrade->Ident.c_str()) == 'R' || this->GetIndividualUpgrade(forbidden_upgrade)) {
1210 upgrades_check = false;
1211 break;
1212 }
1213 }
1214 }
1215
1216 for (size_t j = 0; j < variation->ItemClassesNotEquipped.size(); ++j) {
1217 if (this->IsItemClassEquipped(variation->ItemClassesNotEquipped[j])) {
1218 upgrades_check = false;
1219 break;
1220 }
1221 }
1222 for (size_t j = 0; j < variation->ItemsNotEquipped.size(); ++j) {
1223 if (this->IsItemTypeEquipped(variation->ItemsNotEquipped[j])) {
1224 upgrades_check = false;
1225 break;
1226 }
1227 }
1228 if (upgrades_check == false) {
1229 continue;
1230 }
1231 for (size_t j = 0; j < variation->ItemClassesEquipped.size(); ++j) {
1232 if (GetItemClassSlot(variation->ItemClassesEquipped[j]) == WeaponItemSlot) {
1233 requires_weapon = true;
1234 if (IsItemClassEquipped(variation->ItemClassesEquipped[j])) {
1235 found_weapon = true;
1236 }
1237 } else if (GetItemClassSlot(variation->ItemClassesEquipped[j]) == ShieldItemSlot) {
1238 requires_shield = true;
1239 if (IsItemClassEquipped(variation->ItemClassesEquipped[j])) {
1240 found_shield = true;
1241 }
1242 }
1243 }
1244 for (size_t j = 0; j < variation->ItemsEquipped.size(); ++j) {
1245 if (GetItemClassSlot(variation->ItemsEquipped[j]->ItemClass) == WeaponItemSlot) {
1246 requires_weapon = true;
1247 if (this->IsItemTypeEquipped(variation->ItemsEquipped[j])) {
1248 found_weapon = true;
1249 }
1250 } else if (GetItemClassSlot(variation->ItemsEquipped[j]->ItemClass) == ShieldItemSlot) {
1251 requires_shield = true;
1252 if (this->IsItemTypeEquipped(variation->ItemsEquipped[j])) {
1253 found_shield = true;
1254 }
1255 }
1256 }
1257 if ((requires_weapon && !found_weapon) || (requires_shield && !found_shield)) {
1258 continue;
1259 }
1260 if (!ignore_old_variation && !priority_variation.empty() && (variation->VariationId.find(priority_variation) != std::string::npos || priority_variation.find(variation->VariationId) != std::string::npos)) { // if the priority variation's ident is included in that of a new viable variation (or vice-versa), give priority to the new variation over others
1261 if (!found_similar) {
1262 found_similar = true;
1263 type_variations.clear();
1264 }
1265 } else {
1266 if (found_similar) {
1267 continue;
1268 }
1269 }
1270 for (int j = 0; j < variation->Weight; ++j) {
1271 type_variations.push_back(variation);
1272 }
1273 }
1274 if (type_variations.size() > 0) {
1275 this->SetVariation(type_variations[SyncRand(type_variations.size())], new_type, image_layer);
1276 }
1277 }
1278
SetVariation(CUnitTypeVariation * new_variation,const CUnitType * new_type,int image_layer)1279 void CUnit::SetVariation(CUnitTypeVariation *new_variation, const CUnitType *new_type, int image_layer)
1280 {
1281 if (image_layer == -1) {
1282 if (
1283 (this->GetVariation() && this->GetVariation()->Animations)
1284 || (new_variation && new_variation->Animations)
1285 ) { //if the old (if any) or the new variation has specific animations, set the unit's frame to its type's still frame
1286 this->Frame = this->Type->StillFrame;
1287 }
1288 this->Variation = new_variation ? new_variation->ID : 0;
1289 } else {
1290 this->LayerVariation[image_layer] = new_variation ? new_variation->ID : -1;
1291 }
1292 }
1293
GetVariation() const1294 const CUnitTypeVariation *CUnit::GetVariation() const
1295 {
1296 if (this->Variation < (int) this->Type->Variations.size()) {
1297 return this->Type->Variations[this->Variation];
1298 }
1299
1300 return nullptr;
1301 }
1302
GetLayerVariation(const unsigned int image_layer) const1303 const CUnitTypeVariation *CUnit::GetLayerVariation(const unsigned int image_layer) const
1304 {
1305 if (this->LayerVariation[image_layer] >= 0 && this->LayerVariation[image_layer] < (int) this->Type->LayerVariations[image_layer].size()) {
1306 return this->Type->LayerVariations[image_layer][this->LayerVariation[image_layer]];
1307 }
1308
1309 return nullptr;
1310 }
1311
UpdateButtonIcons()1312 void CUnit::UpdateButtonIcons()
1313 {
1314 this->ChooseButtonIcon(ButtonAttack);
1315 this->ChooseButtonIcon(ButtonStop);
1316 this->ChooseButtonIcon(ButtonMove);
1317 this->ChooseButtonIcon(ButtonStandGround);
1318 this->ChooseButtonIcon(ButtonPatrol);
1319 if (this->Type->BoolFlag[HARVESTER_INDEX].value) {
1320 this->ChooseButtonIcon(ButtonReturn);
1321 }
1322 }
1323
ChooseButtonIcon(int button_action)1324 void CUnit::ChooseButtonIcon(int button_action)
1325 {
1326 if (button_action == ButtonAttack) {
1327 if (this->EquippedItems[ArrowsItemSlot].size() > 0 && this->EquippedItems[ArrowsItemSlot][0]->GetIcon().Icon != nullptr) {
1328 this->ButtonIcons[button_action] = this->EquippedItems[ArrowsItemSlot][0]->GetIcon().Icon;
1329 return;
1330 }
1331
1332 if (this->EquippedItems[WeaponItemSlot].size() > 0 && this->EquippedItems[WeaponItemSlot][0]->Type->ItemClass != BowItemClass && this->EquippedItems[WeaponItemSlot][0]->GetIcon().Icon != nullptr) {
1333 this->ButtonIcons[button_action] = this->EquippedItems[WeaponItemSlot][0]->GetIcon().Icon;
1334 return;
1335 }
1336 } else if (button_action == ButtonStop) {
1337 if (this->EquippedItems[ShieldItemSlot].size() > 0 && this->EquippedItems[ShieldItemSlot][0]->Type->ItemClass == ShieldItemClass && this->EquippedItems[ShieldItemSlot][0]->GetIcon().Icon != nullptr) {
1338 this->ButtonIcons[button_action] = this->EquippedItems[ShieldItemSlot][0]->GetIcon().Icon;
1339 return;
1340 }
1341 } else if (button_action == ButtonMove) {
1342 if (this->EquippedItems[BootsItemSlot].size() > 0 && this->EquippedItems[BootsItemSlot][0]->GetIcon().Icon != nullptr) {
1343 this->ButtonIcons[button_action] = this->EquippedItems[BootsItemSlot][0]->GetIcon().Icon;
1344 return;
1345 }
1346 } else if (button_action == ButtonStandGround) {
1347 if (this->EquippedItems[ArrowsItemSlot].size() > 0 && this->EquippedItems[ArrowsItemSlot][0]->Type->ButtonIcons.find(button_action) != this->EquippedItems[ArrowsItemSlot][0]->Type->ButtonIcons.end()) {
1348 this->ButtonIcons[button_action] = this->EquippedItems[ArrowsItemSlot][0]->Type->ButtonIcons.find(button_action)->second.Icon;
1349 return;
1350 }
1351
1352 if (this->EquippedItems[WeaponItemSlot].size() > 0 && this->EquippedItems[WeaponItemSlot][0]->Type->ButtonIcons.find(button_action) != this->EquippedItems[WeaponItemSlot][0]->Type->ButtonIcons.end()) {
1353 this->ButtonIcons[button_action] = this->EquippedItems[WeaponItemSlot][0]->Type->ButtonIcons.find(button_action)->second.Icon;
1354 return;
1355 }
1356 }
1357
1358 const CUnitTypeVariation *variation = this->GetVariation();
1359 if (variation && variation->ButtonIcons.find(button_action) != variation->ButtonIcons.end()) {
1360 this->ButtonIcons[button_action] = variation->ButtonIcons.find(button_action)->second.Icon;
1361 return;
1362 }
1363 for (int i = 0; i < MaxImageLayers; ++i) {
1364 const CUnitTypeVariation *layer_variation = this->GetLayerVariation(i);
1365 if (layer_variation && layer_variation->ButtonIcons.find(button_action) != layer_variation->ButtonIcons.end()) {
1366 this->ButtonIcons[button_action] = layer_variation->ButtonIcons.find(button_action)->second.Icon;
1367 return;
1368 }
1369 }
1370
1371 int all_upgrades_size = AllUpgrades.size();
1372
1373 for (int i = (CUpgradeModifier::UpgradeModifiers.size() - 1); i >= 0; --i) {
1374 const CUpgradeModifier *modifier = CUpgradeModifier::UpgradeModifiers[i];
1375 const CUpgrade *upgrade = AllUpgrades[modifier->UpgradeId];
1376 if (this->Player->Allow.Upgrades[upgrade->ID] == 'R' && modifier->ApplyTo[this->Type->Slot] == 'X') {
1377 if (
1378 (
1379 (button_action == ButtonAttack && ((upgrade->Weapon && upgrade->Item->ItemClass != BowItemClass) || upgrade->Arrows))
1380 || (button_action == ButtonStop && upgrade->Shield)
1381 || (button_action == ButtonMove && upgrade->Boots)
1382 )
1383 && upgrade->Item->Icon.Icon != nullptr
1384 ) {
1385 this->ButtonIcons[button_action] = upgrade->Item->Icon.Icon;
1386 return;
1387 } else if (button_action == ButtonStandGround && (upgrade->Weapon || upgrade->Arrows) && upgrade->Item->ButtonIcons.find(button_action) != upgrade->Item->ButtonIcons.end()) {
1388 this->ButtonIcons[button_action] = upgrade->Item->ButtonIcons.find(button_action)->second.Icon;
1389 return;
1390 }
1391 }
1392 }
1393
1394 if (button_action == ButtonAttack) {
1395 if (this->Type->DefaultEquipment.find(ArrowsItemSlot) != this->Type->DefaultEquipment.end() && this->Type->DefaultEquipment.find(ArrowsItemSlot)->second->Icon.Icon != nullptr) {
1396 this->ButtonIcons[button_action] = this->Type->DefaultEquipment.find(ArrowsItemSlot)->second->Icon.Icon;
1397 return;
1398 }
1399
1400 if (this->Type->DefaultEquipment.find(WeaponItemSlot) != this->Type->DefaultEquipment.end() && this->Type->DefaultEquipment.find(WeaponItemSlot)->second->Icon.Icon != nullptr) {
1401 this->ButtonIcons[button_action] = this->Type->DefaultEquipment.find(WeaponItemSlot)->second->Icon.Icon;
1402 return;
1403 }
1404 } else if (button_action == ButtonStop) {
1405 if (this->Type->DefaultEquipment.find(ShieldItemSlot) != this->Type->DefaultEquipment.end() && this->Type->DefaultEquipment.find(ShieldItemSlot)->second->ItemClass == ShieldItemClass && this->Type->DefaultEquipment.find(ShieldItemSlot)->second->Icon.Icon != nullptr) {
1406 this->ButtonIcons[button_action] = this->Type->DefaultEquipment.find(ShieldItemSlot)->second->Icon.Icon;
1407 return;
1408 }
1409 } else if (button_action == ButtonMove) {
1410 if (this->Type->DefaultEquipment.find(BootsItemSlot) != this->Type->DefaultEquipment.end() && this->Type->DefaultEquipment.find(BootsItemSlot)->second->Icon.Icon != nullptr) {
1411 this->ButtonIcons[button_action] = this->Type->DefaultEquipment.find(BootsItemSlot)->second->Icon.Icon;
1412 return;
1413 }
1414 } else if (button_action == ButtonStandGround) {
1415 if (this->Type->DefaultEquipment.find(ArrowsItemSlot) != this->Type->DefaultEquipment.end() && this->Type->DefaultEquipment.find(ArrowsItemSlot)->second->ButtonIcons.find(button_action) != this->Type->DefaultEquipment.find(ArrowsItemSlot)->second->ButtonIcons.end()) {
1416 this->ButtonIcons[button_action] = this->Type->DefaultEquipment.find(ArrowsItemSlot)->second->ButtonIcons.find(button_action)->second.Icon;
1417 return;
1418 }
1419
1420 if (this->Type->DefaultEquipment.find(WeaponItemSlot) != this->Type->DefaultEquipment.end() && this->Type->DefaultEquipment.find(WeaponItemSlot)->second->ButtonIcons.find(button_action) != this->Type->DefaultEquipment.find(WeaponItemSlot)->second->ButtonIcons.end()) {
1421 this->ButtonIcons[button_action] = this->Type->DefaultEquipment.find(WeaponItemSlot)->second->ButtonIcons.find(button_action)->second.Icon;
1422 return;
1423 }
1424 }
1425
1426 if (this->Type->ButtonIcons.find(button_action) != this->Type->ButtonIcons.end()) {
1427 this->ButtonIcons[button_action] = this->Type->ButtonIcons.find(button_action)->second.Icon;
1428 return;
1429 }
1430
1431 if (this->Type->Civilization != -1) {
1432 int civilization = this->Type->Civilization;
1433 int faction = this->Type->Faction;
1434
1435 if (faction == -1 && this->Player->Race == civilization) {
1436 faction = this->Player->Faction;
1437 }
1438
1439 if (faction != -1 && PlayerRaces.Factions[faction]->ButtonIcons.find(button_action) != PlayerRaces.Factions[faction]->ButtonIcons.end()) {
1440 this->ButtonIcons[button_action] = PlayerRaces.Factions[faction]->ButtonIcons[button_action].Icon;
1441 return;
1442 } else if (PlayerRaces.ButtonIcons[civilization].find(button_action) != PlayerRaces.ButtonIcons[civilization].end()) {
1443 this->ButtonIcons[button_action] = PlayerRaces.ButtonIcons[civilization][button_action].Icon;
1444 return;
1445 }
1446 }
1447
1448 if (this->ButtonIcons.find(button_action) != this->ButtonIcons.end()) { //if no proper button icon found, make sure any old button icon set for this button action isn't used either
1449 this->ButtonIcons.erase(button_action);
1450 }
1451 }
1452
EquipItem(CUnit & item,bool affect_character)1453 void CUnit::EquipItem(CUnit &item, bool affect_character)
1454 {
1455 int item_class = item.Type->ItemClass;
1456 int item_slot = GetItemClassSlot(item_class);
1457
1458 if (item_slot == -1) {
1459 fprintf(stderr, "Trying to equip item of type \"%s\", which has no item slot.\n", item.GetTypeName().c_str());
1460 return;
1461 }
1462
1463 if (GetItemSlotQuantity(item_slot) > 0 && EquippedItems[item_slot].size() == GetItemSlotQuantity(item_slot)) {
1464 DeequipItem(*EquippedItems[item_slot][EquippedItems[item_slot].size() - 1]);
1465 }
1466
1467 if (item_slot == WeaponItemSlot && EquippedItems[item_slot].size() == 0) {
1468 // remove the upgrade modifiers from weapon technologies or from abilities which require the base weapon class but aren't compatible with this weapon's class; and apply upgrade modifiers from abilities which require this weapon's class
1469 for (const CUpgradeModifier *modifier : CUpgradeModifier::UpgradeModifiers) {
1470 const CUpgrade *modifier_upgrade = AllUpgrades[modifier->UpgradeId];
1471 if (
1472 (modifier_upgrade->Weapon && Player->Allow.Upgrades[modifier_upgrade->ID] == 'R' && modifier->ApplyTo[Type->Slot] == 'X')
1473 || (modifier_upgrade->Ability && this->GetIndividualUpgrade(modifier_upgrade) && modifier_upgrade->WeaponClasses.size() > 0 && std::find(modifier_upgrade->WeaponClasses.begin(), modifier_upgrade->WeaponClasses.end(), this->Type->WeaponClasses[0]) != modifier_upgrade->WeaponClasses.end() && std::find(modifier_upgrade->WeaponClasses.begin(), modifier_upgrade->WeaponClasses.end(), item_class) == modifier_upgrade->WeaponClasses.end())
1474 ) {
1475 if (this->GetIndividualUpgrade(modifier_upgrade)) {
1476 for (int i = 0; i < this->GetIndividualUpgrade(modifier_upgrade); ++i) {
1477 RemoveIndividualUpgradeModifier(*this, modifier);
1478 }
1479 } else {
1480 RemoveIndividualUpgradeModifier(*this, modifier);
1481 }
1482 } else if (
1483 modifier_upgrade->Ability && this->GetIndividualUpgrade(modifier_upgrade) && modifier_upgrade->WeaponClasses.size() > 0 && std::find(modifier_upgrade->WeaponClasses.begin(), modifier_upgrade->WeaponClasses.end(), this->Type->WeaponClasses[0]) == modifier_upgrade->WeaponClasses.end() && std::find(modifier_upgrade->WeaponClasses.begin(), modifier_upgrade->WeaponClasses.end(), item_class) != modifier_upgrade->WeaponClasses.end()
1484 ) {
1485 if (this->GetIndividualUpgrade(modifier_upgrade)) {
1486 for (int i = 0; i < this->GetIndividualUpgrade(modifier_upgrade); ++i) {
1487 ApplyIndividualUpgradeModifier(*this, modifier);
1488 }
1489 } else {
1490 ApplyIndividualUpgradeModifier(*this, modifier);
1491 }
1492 }
1493 }
1494 } else if (item_slot == ShieldItemSlot && EquippedItems[item_slot].size() == 0) {
1495 // remove the upgrade modifiers from shield technologies
1496 for (const CUpgradeModifier *modifier : CUpgradeModifier::UpgradeModifiers) {
1497 const CUpgrade *modifier_upgrade = AllUpgrades[modifier->UpgradeId];
1498 if (modifier_upgrade->Shield && Player->Allow.Upgrades[modifier_upgrade->ID] == 'R' && modifier->ApplyTo[Type->Slot] == 'X') {
1499 RemoveIndividualUpgradeModifier(*this, modifier);
1500 }
1501 }
1502 } else if (item_slot == BootsItemSlot && EquippedItems[item_slot].size() == 0) {
1503 // remove the upgrade modifiers from boots technologies
1504 for (const CUpgradeModifier *modifier : CUpgradeModifier::UpgradeModifiers) {
1505 const CUpgrade *modifier_upgrade = AllUpgrades[modifier->UpgradeId];
1506 if (modifier_upgrade->Boots && Player->Allow.Upgrades[modifier_upgrade->ID] == 'R' && modifier->ApplyTo[Type->Slot] == 'X') {
1507 RemoveIndividualUpgradeModifier(*this, modifier);
1508 }
1509 }
1510 } else if (item_slot == ArrowsItemSlot && EquippedItems[item_slot].size() == 0) {
1511 // remove the upgrade modifiers from arrows technologies
1512 for (const CUpgradeModifier *modifier : CUpgradeModifier::UpgradeModifiers) {
1513 const CUpgrade *modifier_upgrade = AllUpgrades[modifier->UpgradeId];
1514 if (modifier_upgrade->Arrows && Player->Allow.Upgrades[modifier_upgrade->ID] == 'R' && modifier->ApplyTo[Type->Slot] == 'X') {
1515 RemoveIndividualUpgradeModifier(*this, modifier);
1516 }
1517 }
1518 }
1519
1520 if (item.Unique && item.Unique->Set && this->EquippingItemCompletesSet(&item)) {
1521 for (const CUpgradeModifier *modifier : item.Unique->Set->UpgradeModifiers) {
1522 ApplyIndividualUpgradeModifier(*this, modifier);
1523 }
1524 }
1525
1526 if (!IsNetworkGame() && Character && this->Player->AiEnabled == false && affect_character) {
1527 if (Character->GetItem(item) != nullptr) {
1528 if (!Character->IsItemEquipped(Character->GetItem(item))) {
1529 Character->EquippedItems[item_slot].push_back(Character->GetItem(item));
1530 SaveHero(Character);
1531 } else {
1532 fprintf(stderr, "Item is not equipped by character \"%s\"'s unit, but is equipped by the character itself.\n", Character->Ident.c_str());
1533 }
1534 } else {
1535 fprintf(stderr, "Item is present in the inventory of the character \"%s\"'s unit, but not in the character's inventory itself.\n", Character->Ident.c_str());
1536 }
1537 }
1538 EquippedItems[item_slot].push_back(&item);
1539
1540 //change variation, if the current one has become forbidden
1541 const CUnitTypeVariation *variation = this->GetVariation();
1542 if (
1543 variation
1544 && (
1545 std::find(variation->ItemClassesNotEquipped.begin(), variation->ItemClassesNotEquipped.end(), item.Type->ItemClass) != variation->ItemClassesNotEquipped.end()
1546 || std::find(variation->ItemsNotEquipped.begin(), variation->ItemsNotEquipped.end(), item.Type) != variation->ItemsNotEquipped.end()
1547 )
1548 ) {
1549 ChooseVariation(); //choose a new variation now
1550 }
1551 for (int i = 0; i < MaxImageLayers; ++i) {
1552 const CUnitTypeVariation *layer_variation = this->GetLayerVariation(i);
1553 if (
1554 layer_variation
1555 && (
1556 std::find(layer_variation->ItemClassesNotEquipped.begin(), layer_variation->ItemClassesNotEquipped.end(), item.Type->ItemClass) != layer_variation->ItemClassesNotEquipped.end()
1557 || std::find(layer_variation->ItemsNotEquipped.begin(), layer_variation->ItemsNotEquipped.end(), item.Type) != layer_variation->ItemsNotEquipped.end()
1558 )
1559 ) {
1560 ChooseVariation(nullptr, false, i);
1561 }
1562 }
1563
1564 if (item_slot == WeaponItemSlot || item_slot == ArrowsItemSlot) {
1565 this->ChooseButtonIcon(ButtonAttack);
1566 this->ChooseButtonIcon(ButtonStandGround);
1567 } else if (item_slot == ShieldItemSlot) {
1568 this->ChooseButtonIcon(ButtonStop);
1569 } else if (item_slot == BootsItemSlot) {
1570 this->ChooseButtonIcon(ButtonMove);
1571 }
1572 this->ChooseButtonIcon(ButtonPatrol);
1573
1574 //add item bonuses
1575 for (unsigned int i = 0; i < UnitTypeVar.GetNumberVariable(); i++) {
1576 if (
1577 i == BASICDAMAGE_INDEX || i == PIERCINGDAMAGE_INDEX || i == THORNSDAMAGE_INDEX
1578 || i == FIREDAMAGE_INDEX || i == COLDDAMAGE_INDEX || i == ARCANEDAMAGE_INDEX || i == LIGHTNINGDAMAGE_INDEX
1579 || i == AIRDAMAGE_INDEX || i == EARTHDAMAGE_INDEX || i == WATERDAMAGE_INDEX || i == ACIDDAMAGE_INDEX
1580 || i == ARMOR_INDEX || i == FIRERESISTANCE_INDEX || i == COLDRESISTANCE_INDEX || i == ARCANERESISTANCE_INDEX || i == LIGHTNINGRESISTANCE_INDEX
1581 || i == AIRRESISTANCE_INDEX || i == EARTHRESISTANCE_INDEX || i == WATERRESISTANCE_INDEX || i == ACIDRESISTANCE_INDEX
1582 || i == HACKRESISTANCE_INDEX || i == PIERCERESISTANCE_INDEX || i == BLUNTRESISTANCE_INDEX
1583 || i == ACCURACY_INDEX || i == EVASION_INDEX || i == SPEED_INDEX || i == CHARGEBONUS_INDEX || i == BACKSTAB_INDEX
1584 || i == ATTACKRANGE_INDEX
1585 ) {
1586 Variable[i].Value += item.Variable[i].Value;
1587 Variable[i].Max += item.Variable[i].Max;
1588 } else if (i == HITPOINTBONUS_INDEX) {
1589 Variable[HP_INDEX].Value += item.Variable[i].Value;
1590 Variable[HP_INDEX].Max += item.Variable[i].Max;
1591 Variable[HP_INDEX].Increase += item.Variable[i].Increase;
1592 } else if (i == SIGHTRANGE_INDEX || i == DAYSIGHTRANGEBONUS_INDEX || i == NIGHTSIGHTRANGEBONUS_INDEX) {
1593 if (!SaveGameLoading) {
1594 MapUnmarkUnitSight(*this);
1595 }
1596 Variable[i].Value += item.Variable[i].Value;
1597 Variable[i].Max += item.Variable[i].Max;
1598 if (!SaveGameLoading) {
1599 if (i == SIGHTRANGE_INDEX) {
1600 CurrentSightRange = Variable[i].Value;
1601 }
1602 UpdateUnitSightRange(*this);
1603 MapMarkUnitSight(*this);
1604 }
1605 }
1606 }
1607 }
1608
DeequipItem(CUnit & item,bool affect_character)1609 void CUnit::DeequipItem(CUnit &item, bool affect_character)
1610 {
1611 //remove item bonuses
1612 for (unsigned int i = 0; i < UnitTypeVar.GetNumberVariable(); i++) {
1613 if (
1614 i == BASICDAMAGE_INDEX || i == PIERCINGDAMAGE_INDEX || i == THORNSDAMAGE_INDEX
1615 || i == FIREDAMAGE_INDEX || i == COLDDAMAGE_INDEX || i == ARCANEDAMAGE_INDEX || i == LIGHTNINGDAMAGE_INDEX
1616 || i == AIRDAMAGE_INDEX || i == EARTHDAMAGE_INDEX || i == WATERDAMAGE_INDEX || i == ACIDDAMAGE_INDEX
1617 || i == ARMOR_INDEX || i == FIRERESISTANCE_INDEX || i == COLDRESISTANCE_INDEX || i == ARCANERESISTANCE_INDEX || i == LIGHTNINGRESISTANCE_INDEX
1618 || i == AIRRESISTANCE_INDEX || i == EARTHRESISTANCE_INDEX || i == WATERRESISTANCE_INDEX || i == ACIDRESISTANCE_INDEX
1619 || i == HACKRESISTANCE_INDEX || i == PIERCERESISTANCE_INDEX || i == BLUNTRESISTANCE_INDEX
1620 || i == ACCURACY_INDEX || i == EVASION_INDEX || i == SPEED_INDEX || i == CHARGEBONUS_INDEX || i == BACKSTAB_INDEX
1621 || i == ATTACKRANGE_INDEX
1622 ) {
1623 Variable[i].Value -= item.Variable[i].Value;
1624 Variable[i].Max -= item.Variable[i].Max;
1625 } else if (i == HITPOINTBONUS_INDEX) {
1626 Variable[HP_INDEX].Value -= item.Variable[i].Value;
1627 Variable[HP_INDEX].Max -= item.Variable[i].Max;
1628 Variable[HP_INDEX].Increase -= item.Variable[i].Increase;
1629 } else if (i == SIGHTRANGE_INDEX || i == DAYSIGHTRANGEBONUS_INDEX || i == NIGHTSIGHTRANGEBONUS_INDEX) {
1630 MapUnmarkUnitSight(*this);
1631 Variable[i].Value -= item.Variable[i].Value;
1632 Variable[i].Max -= item.Variable[i].Max;
1633 if (i == SIGHTRANGE_INDEX) {
1634 CurrentSightRange = Variable[i].Value;
1635 }
1636 UpdateUnitSightRange(*this);
1637 MapMarkUnitSight(*this);
1638 }
1639 }
1640
1641 if (item.Unique && item.Unique->Set && this->DeequippingItemBreaksSet(&item)) {
1642 for (const CUpgradeModifier *modifier : item.Unique->Set->UpgradeModifiers) {
1643 RemoveIndividualUpgradeModifier(*this, modifier);
1644 }
1645 }
1646
1647 int item_class = item.Type->ItemClass;
1648 int item_slot = GetItemClassSlot(item_class);
1649
1650 if (item_slot == -1) {
1651 fprintf(stderr, "Trying to de-equip item of type \"%s\", which has no item slot.\n", item.GetTypeName().c_str());
1652 return;
1653 }
1654
1655 if (!IsNetworkGame() && Character && this->Player->AiEnabled == false && affect_character) {
1656 if (Character->GetItem(item) != nullptr) {
1657 if (Character->IsItemEquipped(Character->GetItem(item))) {
1658 Character->EquippedItems[item_slot].erase(std::remove(Character->EquippedItems[item_slot].begin(), Character->EquippedItems[item_slot].end(), Character->GetItem(item)), Character->EquippedItems[item_slot].end());
1659 SaveHero(Character);
1660 } else {
1661 fprintf(stderr, "Item is equipped by character \"%s\"'s unit, but not by the character itself.\n", Character->Ident.c_str());
1662 }
1663 } else {
1664 fprintf(stderr, "Item is present in the inventory of the character \"%s\"'s unit, but not in the character's inventory itself.\n", Character->Ident.c_str());
1665 }
1666 }
1667 EquippedItems[item_slot].erase(std::remove(EquippedItems[item_slot].begin(), EquippedItems[item_slot].end(), &item), EquippedItems[item_slot].end());
1668
1669 if (item_slot == WeaponItemSlot && EquippedItems[item_slot].size() == 0) {
1670 // restore the upgrade modifiers from weapon technologies, and apply ability effects that are weapon class-specific accordingly
1671 for (const CUpgradeModifier *modifier : CUpgradeModifier::UpgradeModifiers) {
1672 const CUpgrade *modifier_upgrade = AllUpgrades[modifier->UpgradeId];
1673 if (
1674 (modifier_upgrade->Weapon && Player->Allow.Upgrades[modifier->UpgradeId] == 'R' && modifier->ApplyTo[Type->Slot] == 'X')
1675 || (modifier_upgrade->Ability && this->GetIndividualUpgrade(modifier_upgrade) && modifier_upgrade->WeaponClasses.size() > 0 && std::find(modifier_upgrade->WeaponClasses.begin(), modifier_upgrade->WeaponClasses.end(), this->Type->WeaponClasses[0]) != modifier_upgrade->WeaponClasses.end() && std::find(modifier_upgrade->WeaponClasses.begin(), modifier_upgrade->WeaponClasses.end(), item_class) == modifier_upgrade->WeaponClasses.end())
1676 ) {
1677 if (this->GetIndividualUpgrade(modifier_upgrade)) {
1678 for (int i = 0; i < this->GetIndividualUpgrade(modifier_upgrade); ++i) {
1679 ApplyIndividualUpgradeModifier(*this, modifier);
1680 }
1681 } else {
1682 ApplyIndividualUpgradeModifier(*this, modifier);
1683 }
1684 } else if (
1685 modifier_upgrade->Ability && this->GetIndividualUpgrade(modifier_upgrade) && modifier_upgrade->WeaponClasses.size() > 0 && std::find(modifier_upgrade->WeaponClasses.begin(), modifier_upgrade->WeaponClasses.end(), this->Type->WeaponClasses[0]) == modifier_upgrade->WeaponClasses.end() && std::find(modifier_upgrade->WeaponClasses.begin(), modifier_upgrade->WeaponClasses.end(), item_class) != modifier_upgrade->WeaponClasses.end()
1686 ) {
1687 if (this->GetIndividualUpgrade(modifier_upgrade)) {
1688 for (int i = 0; i < this->GetIndividualUpgrade(modifier_upgrade); ++i) {
1689 RemoveIndividualUpgradeModifier(*this, modifier);
1690 }
1691 } else {
1692 RemoveIndividualUpgradeModifier(*this, modifier);
1693 }
1694 }
1695 }
1696 } else if (item_slot == ShieldItemSlot && EquippedItems[item_slot].size() == 0) {
1697 // restore the upgrade modifiers from shield technologies
1698 for (const CUpgradeModifier *modifier : CUpgradeModifier::UpgradeModifiers) {
1699 const CUpgrade *modifier_upgrade = AllUpgrades[modifier->UpgradeId];
1700 if (modifier_upgrade->Shield && Player->Allow.Upgrades[modifier_upgrade->ID] == 'R' && modifier->ApplyTo[Type->Slot] == 'X') {
1701 ApplyIndividualUpgradeModifier(*this, modifier);
1702 }
1703 }
1704 } else if (item_slot == BootsItemSlot && EquippedItems[item_slot].size() == 0) {
1705 // restore the upgrade modifiers from boots technologies
1706 for (const CUpgradeModifier *modifier : CUpgradeModifier::UpgradeModifiers) {
1707 const CUpgrade *modifier_upgrade = AllUpgrades[modifier->UpgradeId];
1708 if (modifier_upgrade->Boots && Player->Allow.Upgrades[modifier_upgrade->ID] == 'R' && modifier->ApplyTo[Type->Slot] == 'X') {
1709 ApplyIndividualUpgradeModifier(*this, modifier);
1710 }
1711 }
1712 } else if (item_slot == ArrowsItemSlot && EquippedItems[item_slot].size() == 0) {
1713 // restore the upgrade modifiers from arrows technologies
1714 for (const CUpgradeModifier *modifier : CUpgradeModifier::UpgradeModifiers) {
1715 const CUpgrade *modifier_upgrade = AllUpgrades[modifier->UpgradeId];
1716 if (modifier_upgrade->Arrows && Player->Allow.Upgrades[modifier_upgrade->ID] == 'R' && modifier->ApplyTo[Type->Slot] == 'X') {
1717 ApplyIndividualUpgradeModifier(*this, modifier);
1718 }
1719 }
1720 }
1721
1722 //change variation, if the current one has become forbidden
1723 const CUnitTypeVariation *variation = this->GetVariation();
1724 if (
1725 variation
1726 && (
1727 std::find(variation->ItemClassesEquipped.begin(), variation->ItemClassesEquipped.end(), item.Type->ItemClass) != variation->ItemClassesEquipped.end()
1728 || std::find(variation->ItemsEquipped.begin(), variation->ItemsEquipped.end(), item.Type) != variation->ItemsEquipped.end()
1729 )
1730 ) {
1731 ChooseVariation(); //choose a new variation now
1732 }
1733 for (int i = 0; i < MaxImageLayers; ++i) {
1734 const CUnitTypeVariation *layer_variation = this->GetLayerVariation(i);
1735
1736 if (
1737 layer_variation
1738 && (
1739 std::find(layer_variation->ItemClassesEquipped.begin(), layer_variation->ItemClassesEquipped.end(), item.Type->ItemClass) != layer_variation->ItemClassesEquipped.end()
1740 || std::find(layer_variation->ItemsEquipped.begin(), layer_variation->ItemsEquipped.end(), item.Type) != layer_variation->ItemsEquipped.end()
1741 )
1742 ) {
1743 ChooseVariation(nullptr, false, i);
1744 }
1745 }
1746
1747 if (item_slot == WeaponItemSlot || item_slot == ArrowsItemSlot) {
1748 this->ChooseButtonIcon(ButtonAttack);
1749 this->ChooseButtonIcon(ButtonStandGround);
1750 } else if (item_slot == ShieldItemSlot) {
1751 this->ChooseButtonIcon(ButtonStop);
1752 } else if (item_slot == BootsItemSlot) {
1753 this->ChooseButtonIcon(ButtonMove);
1754 }
1755 this->ChooseButtonIcon(ButtonPatrol);
1756 }
1757
ReadWork(CUpgrade * work,bool affect_character)1758 void CUnit::ReadWork(CUpgrade *work, bool affect_character)
1759 {
1760 IndividualUpgradeAcquire(*this, work);
1761
1762 if (!IsNetworkGame() && Character && this->Player->AiEnabled == false && affect_character) {
1763 if (std::find(Character->ReadWorks.begin(), Character->ReadWorks.end(), work) == Character->ReadWorks.end()) {
1764 Character->ReadWorks.push_back(work);
1765 SaveHero(Character);
1766 }
1767 }
1768 }
1769
ConsumeElixir(CUpgrade * elixir,bool affect_character)1770 void CUnit::ConsumeElixir(CUpgrade *elixir, bool affect_character)
1771 {
1772 IndividualUpgradeAcquire(*this, elixir);
1773
1774 if (!IsNetworkGame() && Character && this->Player->AiEnabled == false && affect_character) {
1775 if (std::find(Character->ConsumedElixirs.begin(), Character->ConsumedElixirs.end(), elixir) == Character->ConsumedElixirs.end()) {
1776 Character->ConsumedElixirs.push_back(elixir);
1777 SaveHero(Character);
1778 }
1779 }
1780 }
1781
ApplyAura(int aura_index)1782 void CUnit::ApplyAura(int aura_index)
1783 {
1784 if (aura_index == LEADERSHIPAURA_INDEX) {
1785 if (!this->IsInCombat()) {
1786 return;
1787 }
1788 }
1789
1790 this->ApplyAuraEffect(aura_index);
1791
1792 //apply aura to all appropriate nearby units
1793 int aura_range = AuraRange - (this->Type->TileSize.x - 1);
1794 std::vector<CUnit *> table;
1795 SelectAroundUnit(*this, aura_range, table, MakeOrPredicate(HasSamePlayerAs(*this->Player), IsAlliedWith(*this->Player)), true);
1796 for (size_t i = 0; i != table.size(); ++i) {
1797 table[i]->ApplyAuraEffect(aura_index);
1798 }
1799
1800 table.clear();
1801 SelectAroundUnit(*this, aura_range, table, MakeOrPredicate(MakeOrPredicate(HasSamePlayerAs(*this->Player), IsAlliedWith(*this->Player)), HasSamePlayerAs(Players[PlayerNumNeutral])), true);
1802 for (size_t i = 0; i != table.size(); ++i) {
1803 if (table[i]->UnitInside) {
1804 CUnit *uins = table[i]->UnitInside;
1805 for (int j = 0; j < table[i]->InsideCount; ++j, uins = uins->NextContained) {
1806 if (uins->Player == this->Player || uins->IsAllied(*this->Player)) {
1807 uins->ApplyAuraEffect(aura_index);
1808 }
1809 }
1810 }
1811 }
1812 }
1813
ApplyAuraEffect(int aura_index)1814 void CUnit::ApplyAuraEffect(int aura_index)
1815 {
1816 int effect_index = -1;
1817 if (aura_index == LEADERSHIPAURA_INDEX) {
1818 if (this->Type->BoolFlag[BUILDING_INDEX].value) {
1819 return;
1820 }
1821 effect_index = LEADERSHIP_INDEX;
1822 } else if (aura_index == REGENERATIONAURA_INDEX) {
1823 if (!this->Type->BoolFlag[ORGANIC_INDEX].value || this->Variable[HP_INDEX].Value >= this->GetModifiedVariable(HP_INDEX, VariableMax)) {
1824 return;
1825 }
1826 effect_index = REGENERATION_INDEX;
1827 } else if (aura_index == HYDRATINGAURA_INDEX) {
1828 if (!this->Type->BoolFlag[ORGANIC_INDEX].value) {
1829 return;
1830 }
1831 effect_index = HYDRATING_INDEX;
1832 this->Variable[DEHYDRATION_INDEX].Max = 0;
1833 this->Variable[DEHYDRATION_INDEX].Value = 0;
1834 }
1835
1836 if (effect_index == -1) {
1837 return;
1838 }
1839
1840 this->Variable[effect_index].Enable = 1;
1841 this->Variable[effect_index].Max = std::max(CYCLES_PER_SECOND + 1, this->Variable[effect_index].Max);
1842 this->Variable[effect_index].Value = std::max(CYCLES_PER_SECOND + 1, this->Variable[effect_index].Value);
1843 }
1844
SetPrefix(CUpgrade * prefix)1845 void CUnit::SetPrefix(CUpgrade *prefix)
1846 {
1847 if (Prefix != nullptr) {
1848 for (size_t z = 0; z < Prefix->UpgradeModifiers.size(); ++z) {
1849 RemoveIndividualUpgradeModifier(*this, Prefix->UpgradeModifiers[z]);
1850 }
1851 this->Variable[MAGICLEVEL_INDEX].Value -= Prefix->MagicLevel;
1852 this->Variable[MAGICLEVEL_INDEX].Max -= Prefix->MagicLevel;
1853 }
1854 if (!IsNetworkGame() && Container && Container->Character && Container->Player->AiEnabled == false && Container->Character->GetItem(*this) != nullptr && Container->Character->GetItem(*this)->Prefix != prefix) { //update the persistent item, if applicable and if it hasn't been updated yet
1855 Container->Character->GetItem(*this)->Prefix = prefix;
1856 SaveHero(Container->Character);
1857 }
1858 Prefix = prefix;
1859 if (Prefix != nullptr) {
1860 for (size_t z = 0; z < Prefix->UpgradeModifiers.size(); ++z) {
1861 ApplyIndividualUpgradeModifier(*this, Prefix->UpgradeModifiers[z]);
1862 }
1863 this->Variable[MAGICLEVEL_INDEX].Value += Prefix->MagicLevel;
1864 this->Variable[MAGICLEVEL_INDEX].Max += Prefix->MagicLevel;
1865 }
1866
1867 this->UpdateItemName();
1868 }
1869
SetSuffix(CUpgrade * suffix)1870 void CUnit::SetSuffix(CUpgrade *suffix)
1871 {
1872 if (Suffix != nullptr) {
1873 for (size_t z = 0; z < Suffix->UpgradeModifiers.size(); ++z) {
1874 RemoveIndividualUpgradeModifier(*this, Suffix->UpgradeModifiers[z]);
1875 }
1876 this->Variable[MAGICLEVEL_INDEX].Value -= Suffix->MagicLevel;
1877 this->Variable[MAGICLEVEL_INDEX].Max -= Suffix->MagicLevel;
1878 }
1879 if (!IsNetworkGame() && Container && Container->Character && Container->Player->AiEnabled == false && Container->Character->GetItem(*this) != nullptr && Container->Character->GetItem(*this)->Suffix != suffix) { //update the persistent item, if applicable and if it hasn't been updated yet
1880 Container->Character->GetItem(*this)->Suffix = suffix;
1881 SaveHero(Container->Character);
1882 }
1883 Suffix = suffix;
1884 if (Suffix != nullptr) {
1885 for (size_t z = 0; z < Suffix->UpgradeModifiers.size(); ++z) {
1886 ApplyIndividualUpgradeModifier(*this, Suffix->UpgradeModifiers[z]);
1887 }
1888 this->Variable[MAGICLEVEL_INDEX].Value += Suffix->MagicLevel;
1889 this->Variable[MAGICLEVEL_INDEX].Max += Suffix->MagicLevel;
1890 }
1891
1892 this->UpdateItemName();
1893 }
1894
SetSpell(CSpell * spell)1895 void CUnit::SetSpell(CSpell *spell)
1896 {
1897 if (!IsNetworkGame() && Container && Container->Character && Container->Player->AiEnabled == false && Container->Character->GetItem(*this) != nullptr && Container->Character->GetItem(*this)->Spell != spell) { //update the persistent item, if applicable and if it hasn't been updated yet
1898 Container->Character->GetItem(*this)->Spell = spell;
1899 SaveHero(Container->Character);
1900 }
1901 Spell = spell;
1902
1903 this->UpdateItemName();
1904 }
1905
SetWork(CUpgrade * work)1906 void CUnit::SetWork(CUpgrade *work)
1907 {
1908 if (this->Work != nullptr) {
1909 this->Variable[MAGICLEVEL_INDEX].Value -= this->Work->MagicLevel;
1910 this->Variable[MAGICLEVEL_INDEX].Max -= this->Work->MagicLevel;
1911 }
1912
1913 if (!IsNetworkGame() && Container && Container->Character && Container->Player->AiEnabled == false && Container->Character->GetItem(*this) != nullptr && Container->Character->GetItem(*this)->Work != work) { //update the persistent item, if applicable and if it hasn't been updated yet
1914 Container->Character->GetItem(*this)->Work = work;
1915 SaveHero(Container->Character);
1916 }
1917
1918 Work = work;
1919
1920 if (this->Work != nullptr) {
1921 this->Variable[MAGICLEVEL_INDEX].Value += this->Work->MagicLevel;
1922 this->Variable[MAGICLEVEL_INDEX].Max += this->Work->MagicLevel;
1923 }
1924
1925 this->UpdateItemName();
1926 }
1927
SetElixir(CUpgrade * elixir)1928 void CUnit::SetElixir(CUpgrade *elixir)
1929 {
1930 if (this->Elixir != nullptr) {
1931 this->Variable[MAGICLEVEL_INDEX].Value -= this->Elixir->MagicLevel;
1932 this->Variable[MAGICLEVEL_INDEX].Max -= this->Elixir->MagicLevel;
1933 }
1934
1935 if (!IsNetworkGame() && Container && Container->Character && Container->Player->AiEnabled == false && Container->Character->GetItem(*this) != nullptr && Container->Character->GetItem(*this)->Elixir != elixir) { //update the persistent item, if applicable and if it hasn't been updated yet
1936 Container->Character->GetItem(*this)->Elixir = elixir;
1937 SaveHero(Container->Character);
1938 }
1939
1940 Elixir = elixir;
1941
1942 if (this->Elixir != nullptr) {
1943 this->Variable[MAGICLEVEL_INDEX].Value += this->Elixir->MagicLevel;
1944 this->Variable[MAGICLEVEL_INDEX].Max += this->Elixir->MagicLevel;
1945 }
1946
1947 this->UpdateItemName();
1948 }
1949
SetUnique(CUniqueItem * unique)1950 void CUnit::SetUnique(CUniqueItem *unique)
1951 {
1952 if (this->Unique && this->Unique->Set) {
1953 this->Variable[MAGICLEVEL_INDEX].Value -= this->Unique->Set->MagicLevel;
1954 this->Variable[MAGICLEVEL_INDEX].Max -= this->Unique->Set->MagicLevel;
1955 }
1956
1957 if (unique != nullptr) {
1958 SetPrefix(unique->Prefix);
1959 SetSuffix(unique->Suffix);
1960 SetSpell(unique->Spell);
1961 SetWork(unique->Work);
1962 SetElixir(unique->Elixir);
1963 if (unique->ResourcesHeld != 0) {
1964 this->SetResourcesHeld(unique->ResourcesHeld);
1965 this->Variable[GIVERESOURCE_INDEX].Value = unique->ResourcesHeld;
1966 this->Variable[GIVERESOURCE_INDEX].Max = unique->ResourcesHeld;
1967 this->Variable[GIVERESOURCE_INDEX].Enable = 1;
1968 }
1969 if (unique->Set) {
1970 this->Variable[MAGICLEVEL_INDEX].Value += unique->Set->MagicLevel;
1971 this->Variable[MAGICLEVEL_INDEX].Max += unique->Set->MagicLevel;
1972 }
1973 Name = unique->Name;
1974 Unique = unique;
1975 } else {
1976 Name.clear();
1977 Unique = nullptr;
1978 SetPrefix(nullptr);
1979 SetSuffix(nullptr);
1980 SetSpell(nullptr);
1981 SetWork(nullptr);
1982 SetElixir(nullptr);
1983 }
1984 }
1985
Identify()1986 void CUnit::Identify()
1987 {
1988 if (!IsNetworkGame() && Container && Container->Character && Container->Player->AiEnabled == false && Container->Character->GetItem(*this) != nullptr && Container->Character->GetItem(*this)->Identified != true) { //update the persistent item, if applicable and if it hasn't been updated yet
1989 Container->Character->GetItem(*this)->Identified = true;
1990 SaveHero(Container->Character);
1991 }
1992
1993 this->Identified = true;
1994
1995 if (this->Container != nullptr && this->Container->Player == ThisPlayer) {
1996 this->Container->Player->Notify(NotifyGreen, this->Container->tilePos, this->Container->MapLayer->ID, _("%s has identified the %s!"), this->Container->GetMessageName().c_str(), this->GetMessageName().c_str());
1997 }
1998 }
1999
CheckIdentification()2000 void CUnit::CheckIdentification()
2001 {
2002 if (!HasInventory()) {
2003 return;
2004 }
2005
2006 CUnit *uins = this->UnitInside;
2007
2008 for (int i = 0; i < this->InsideCount; ++i, uins = uins->NextContained) {
2009 if (!uins->Type->BoolFlag[ITEM_INDEX].value) {
2010 continue;
2011 }
2012
2013 if (!uins->Identified && this->Variable[KNOWLEDGEMAGIC_INDEX].Value >= uins->Variable[MAGICLEVEL_INDEX].Value) {
2014 uins->Identify();
2015 }
2016 }
2017 }
2018
CheckKnowledgeChange(int variable,int change)2019 void CUnit::CheckKnowledgeChange(int variable, int change) // this happens after the variable has already been changed
2020 {
2021 if (!change) {
2022 return;
2023 }
2024
2025 if (variable == KNOWLEDGEMAGIC_INDEX) {
2026 int mana_change = (this->Variable[variable].Value / 5) - ((this->Variable[variable].Value - change) / 5); // +1 max mana for every 5 levels in Knowledge (Magic)
2027 this->Variable[MANA_INDEX].Max += mana_change;
2028 if (mana_change < 0) {
2029 this->Variable[MANA_INDEX].Value += mana_change;
2030 }
2031
2032 this->CheckIdentification();
2033 } else if (variable == KNOWLEDGEWARFARE_INDEX) {
2034 int hp_change = (this->Variable[variable].Value / 5) - ((this->Variable[variable].Value - change) / 5); // +1 max HP for every 5 levels in Knowledge (Warfare)
2035 this->Variable[HP_INDEX].Max += hp_change;
2036 this->Variable[HP_INDEX].Value += hp_change;
2037 } else if (variable == KNOWLEDGEMINING_INDEX) {
2038 int stat_change = (this->Variable[variable].Value / 25) - ((this->Variable[variable].Value - change) / 25); // +1 mining gathering bonus for every 25 levels in Knowledge (Mining)
2039 this->Variable[COPPERGATHERINGBONUS_INDEX].Max += stat_change;
2040 this->Variable[COPPERGATHERINGBONUS_INDEX].Value += stat_change;
2041 this->Variable[SILVERGATHERINGBONUS_INDEX].Max += stat_change;
2042 this->Variable[SILVERGATHERINGBONUS_INDEX].Value += stat_change;
2043 this->Variable[GOLDGATHERINGBONUS_INDEX].Max += stat_change;
2044 this->Variable[GOLDGATHERINGBONUS_INDEX].Value += stat_change;
2045 this->Variable[IRONGATHERINGBONUS_INDEX].Max += stat_change;
2046 this->Variable[IRONGATHERINGBONUS_INDEX].Value += stat_change;
2047 this->Variable[MITHRILGATHERINGBONUS_INDEX].Max += stat_change;
2048 this->Variable[MITHRILGATHERINGBONUS_INDEX].Value += stat_change;
2049 this->Variable[COALGATHERINGBONUS_INDEX].Max += stat_change;
2050 this->Variable[COALGATHERINGBONUS_INDEX].Value += stat_change;
2051 this->Variable[GEMSGATHERINGBONUS_INDEX].Max += stat_change;
2052 this->Variable[GEMSGATHERINGBONUS_INDEX].Value += stat_change;
2053 }
2054 }
2055
UpdateItemName()2056 void CUnit::UpdateItemName()
2057 {
2058 if (this->Unique) {
2059 Name = _(this->Unique->Name.c_str());
2060 return;
2061 }
2062
2063 Name.clear();
2064 if (Prefix == nullptr && Spell == nullptr && Work == nullptr && Suffix == nullptr) { //elixirs use the name of their unit type
2065 return;
2066 }
2067
2068 if (Prefix != nullptr) {
2069 Name += _(Prefix->Name.c_str());
2070 Name += " ";
2071 }
2072 if (Work != nullptr) {
2073 Name += _(Work->Name.c_str());
2074 } else {
2075 Name += GetTypeName();
2076 }
2077 if (Suffix != nullptr) {
2078 Name += " ";
2079 Name += _(Suffix->Name.c_str());
2080 } else if (Spell != nullptr) {
2081 Name += " ";
2082 Name += _("of");
2083 Name += " ";
2084 Name += _(Spell->Name.c_str());
2085 }
2086 }
2087
GenerateDrop()2088 void CUnit::GenerateDrop()
2089 {
2090 bool base_based_mission = false;
2091 for (int p = 0; p < PlayerMax; ++p) {
2092 if (Players[p].NumTownHalls > 0 || Players[p].LostTownHallTimer) {
2093 base_based_mission = true;
2094 }
2095 }
2096
2097 if (this->Type->BoolFlag[ORGANIC_INDEX].value && !this->Character && !this->Type->BoolFlag[FAUNA_INDEX].value && base_based_mission) { //if the unit is organic and isn't a character (and isn't fauna) and this is a base-based mission, don't generate a drop
2098 return;
2099 }
2100
2101 Vec2i drop_pos = this->tilePos;
2102 drop_pos.x += SyncRand(this->Type->TileSize.x);
2103 drop_pos.y += SyncRand(this->Type->TileSize.y);
2104 CUnit *droppedUnit = nullptr;
2105 CUnitType *chosen_drop = nullptr;
2106 std::vector<CUnitType *> potential_drops;
2107 for (size_t i = 0; i < this->Type->Drops.size(); ++i) {
2108 if (CheckDependencies(this->Type->Drops[i], this)) {
2109 potential_drops.push_back(this->Type->Drops[i]);
2110 }
2111 }
2112 if (this->Player->AiEnabled) {
2113 for (size_t i = 0; i < this->Type->AiDrops.size(); ++i) {
2114 if (CheckDependencies(this->Type->AiDrops[i], this)) {
2115 potential_drops.push_back(this->Type->AiDrops[i]);
2116 }
2117 }
2118 for (std::map<std::string, std::vector<CUnitType *>>::const_iterator iterator = this->Type->ModAiDrops.begin(); iterator != this->Type->ModAiDrops.end(); ++iterator) {
2119 for (size_t i = 0; i < iterator->second.size(); ++i) {
2120 if (CheckDependencies(iterator->second[i], this)) {
2121 potential_drops.push_back(iterator->second[i]);
2122 }
2123 }
2124 }
2125 }
2126 if (potential_drops.size() > 0) {
2127 chosen_drop = potential_drops[SyncRand(potential_drops.size())];
2128 }
2129
2130 if (chosen_drop != nullptr) {
2131 CBuildRestrictionOnTop *ontop_b = OnTopDetails(*this->Type, nullptr);
2132 if (((chosen_drop->BoolFlag[ITEM_INDEX].value || chosen_drop->BoolFlag[POWERUP_INDEX].value) && (this->MapLayer->Field(drop_pos)->Flags & MapFieldItem)) || (ontop_b && ontop_b->ReplaceOnDie)) { //if the dropped unit is an item (and there's already another item there), or if this building is an ontop one (meaning another will appear under it after it is destroyed), search for another spot
2133 Vec2i resPos;
2134 FindNearestDrop(*chosen_drop, drop_pos, resPos, LookingW, this->MapLayer->ID);
2135 droppedUnit = MakeUnitAndPlace(resPos, *chosen_drop, &Players[PlayerNumNeutral], this->MapLayer->ID);
2136 } else {
2137 droppedUnit = MakeUnitAndPlace(drop_pos, *chosen_drop, &Players[PlayerNumNeutral], this->MapLayer->ID);
2138 }
2139
2140 if (droppedUnit != nullptr) {
2141 if (droppedUnit->Type->BoolFlag[FAUNA_INDEX].value) {
2142 droppedUnit->Name = droppedUnit->Type->GeneratePersonalName(nullptr, droppedUnit->Variable[GENDER_INDEX].Value);
2143 }
2144
2145 droppedUnit->GenerateSpecialProperties(this, this->Player);
2146
2147 if (droppedUnit->Type->BoolFlag[ITEM_INDEX].value && !droppedUnit->Unique) { //save the initial cycle items were placed in the ground to destroy them if they have been there for too long
2148 int ttl_cycles = (5 * 60 * CYCLES_PER_SECOND);
2149 if (droppedUnit->Prefix != nullptr || droppedUnit->Suffix != nullptr || droppedUnit->Spell != nullptr || droppedUnit->Work != nullptr || droppedUnit->Elixir != nullptr) {
2150 ttl_cycles *= 4;
2151 }
2152 droppedUnit->TTL = GameCycle + ttl_cycles;
2153 }
2154 }
2155 }
2156 }
2157
GenerateSpecialProperties(CUnit * dropper,CPlayer * dropper_player,bool allow_unique,bool sold_item,bool always_magic)2158 void CUnit::GenerateSpecialProperties(CUnit *dropper, CPlayer *dropper_player, bool allow_unique, bool sold_item, bool always_magic)
2159 {
2160 int magic_affix_chance = 10; //10% chance of the unit having a magic prefix or suffix
2161 int unique_chance = 5; //0.5% chance of the unit being unique
2162 if (dropper != nullptr) {
2163 if (dropper->Character) { //if the dropper is a character, multiply the chances of the item being magic or unique by the character's level
2164 magic_affix_chance *= dropper->Character->Level;
2165 unique_chance *= dropper->Character->Level;
2166 } else if (dropper->Type->BoolFlag[BUILDING_INDEX].value) { //if the dropper is a building, multiply the chances of the drop being magic or unique by a factor according to whether the building itself is magic/unique
2167 int chance_multiplier = 2;
2168 if (dropper->Unique) {
2169 chance_multiplier += 8;
2170 } else {
2171 if (dropper->Prefix != nullptr) {
2172 chance_multiplier += 1;
2173 }
2174 if (dropper->Suffix != nullptr) {
2175 chance_multiplier += 1;
2176 }
2177 }
2178 magic_affix_chance *= chance_multiplier;
2179 unique_chance *= chance_multiplier;
2180 }
2181 }
2182
2183 if (sold_item) {
2184 magic_affix_chance /= 4;
2185 unique_chance /= 4;
2186 }
2187
2188 if (SyncRand(100) >= (100 - magic_affix_chance)) {
2189 this->GenerateWork(dropper, dropper_player);
2190 }
2191 if (SyncRand(100) >= (100 - magic_affix_chance)) {
2192 this->GeneratePrefix(dropper, dropper_player);
2193 }
2194 if (SyncRand(100) >= (100 - magic_affix_chance)) {
2195 this->GenerateSuffix(dropper, dropper_player);
2196 }
2197 if (this->Prefix == nullptr && this->Suffix == nullptr && this->Work == nullptr && this->Elixir == nullptr && SyncRand(100) >= (100 - magic_affix_chance)) {
2198 this->GenerateSpell(dropper, dropper_player);
2199 }
2200 if (allow_unique && SyncRand(1000) >= (1000 - unique_chance)) {
2201 this->GenerateUnique(dropper, dropper_player);
2202 }
2203
2204 if (this->Type->BoolFlag[ITEM_INDEX].value && (this->Prefix != nullptr || this->Suffix != nullptr)) {
2205 this->Identified = false;
2206 }
2207
2208 if (
2209 this->Prefix == nullptr && this->Suffix == nullptr && this->Spell == nullptr && this->Work == nullptr && this->Elixir == nullptr
2210 && (this->Type->ItemClass == ScrollItemClass || this->Type->ItemClass == BookItemClass || this->Type->ItemClass == RingItemClass || this->Type->ItemClass == AmuletItemClass || this->Type->ItemClass == HornItemClass || always_magic)
2211 ) { //scrolls, books, jewelry and horns must always have a property
2212 this->GenerateSpecialProperties(dropper, dropper_player, allow_unique, sold_item, always_magic);
2213 }
2214 }
2215
GeneratePrefix(CUnit * dropper,CPlayer * dropper_player)2216 void CUnit::GeneratePrefix(CUnit *dropper, CPlayer *dropper_player)
2217 {
2218 std::vector<CUpgrade *> potential_prefixes;
2219 for (size_t i = 0; i < this->Type->Affixes.size(); ++i) {
2220 if ((this->Type->ItemClass == -1 && this->Type->Affixes[i]->MagicPrefix) || (this->Type->ItemClass != -1 && this->Type->Affixes[i]->ItemPrefix[Type->ItemClass])) {
2221 potential_prefixes.push_back(this->Type->Affixes[i]);
2222 }
2223 }
2224 if (dropper_player != nullptr) {
2225 for (size_t i = 0; i < AllUpgrades.size(); ++i) {
2226 if (this->Type->ItemClass != -1 && AllUpgrades[i]->ItemPrefix[Type->ItemClass] && CheckDependencies(AllUpgrades[i], dropper)) {
2227 potential_prefixes.push_back(AllUpgrades[i]);
2228 }
2229 }
2230 }
2231
2232 if (potential_prefixes.size() > 0) {
2233 SetPrefix(potential_prefixes[SyncRand(potential_prefixes.size())]);
2234 }
2235 }
2236
GenerateSuffix(CUnit * dropper,CPlayer * dropper_player)2237 void CUnit::GenerateSuffix(CUnit *dropper, CPlayer *dropper_player)
2238 {
2239 std::vector<CUpgrade *> potential_suffixes;
2240 for (size_t i = 0; i < this->Type->Affixes.size(); ++i) {
2241 if ((this->Type->ItemClass == -1 && this->Type->Affixes[i]->MagicSuffix) || (this->Type->ItemClass != -1 && this->Type->Affixes[i]->ItemSuffix[Type->ItemClass])) {
2242 if (Prefix == nullptr || !this->Type->Affixes[i]->IncompatibleAffixes[Prefix->ID]) { //don't allow a suffix incompatible with the prefix to appear
2243 potential_suffixes.push_back(this->Type->Affixes[i]);
2244 }
2245 }
2246 }
2247 if (dropper_player != nullptr) {
2248 for (size_t i = 0; i < AllUpgrades.size(); ++i) {
2249 if (this->Type->ItemClass != -1 && AllUpgrades[i]->ItemSuffix[Type->ItemClass] && CheckDependencies(AllUpgrades[i], dropper)) {
2250 if (Prefix == nullptr || !AllUpgrades[i]->IncompatibleAffixes[Prefix->ID]) { //don't allow a suffix incompatible with the prefix to appear
2251 potential_suffixes.push_back(AllUpgrades[i]);
2252 }
2253 }
2254 }
2255 }
2256
2257 if (potential_suffixes.size() > 0) {
2258 SetSuffix(potential_suffixes[SyncRand(potential_suffixes.size())]);
2259 }
2260 }
2261
GenerateSpell(CUnit * dropper,CPlayer * dropper_player)2262 void CUnit::GenerateSpell(CUnit *dropper, CPlayer *dropper_player)
2263 {
2264 std::vector<CSpell *> potential_spells;
2265 if (dropper != nullptr) {
2266 for (size_t i = 0; i < dropper->Type->DropSpells.size(); ++i) {
2267 if (this->Type->ItemClass != -1 && dropper->Type->DropSpells[i]->ItemSpell[Type->ItemClass]) {
2268 potential_spells.push_back(dropper->Type->DropSpells[i]);
2269 }
2270 }
2271 }
2272
2273 if (potential_spells.size() > 0) {
2274 SetSpell(potential_spells[SyncRand(potential_spells.size())]);
2275 }
2276 }
2277
GenerateWork(CUnit * dropper,CPlayer * dropper_player)2278 void CUnit::GenerateWork(CUnit *dropper, CPlayer *dropper_player)
2279 {
2280 std::vector<CUpgrade *> potential_works;
2281 for (size_t i = 0; i < this->Type->Affixes.size(); ++i) {
2282 if (this->Type->ItemClass != -1 && this->Type->Affixes[i]->Work == this->Type->ItemClass && !this->Type->Affixes[i]->UniqueOnly) {
2283 potential_works.push_back(this->Type->Affixes[i]);
2284 }
2285 }
2286 if (dropper_player != nullptr) {
2287 for (size_t i = 0; i < AllUpgrades.size(); ++i) {
2288 if (this->Type->ItemClass != -1 && AllUpgrades[i]->Work == this->Type->ItemClass && CheckDependencies(AllUpgrades[i], dropper) && !AllUpgrades[i]->UniqueOnly) {
2289 potential_works.push_back(AllUpgrades[i]);
2290 }
2291 }
2292 }
2293
2294 if (potential_works.size() > 0) {
2295 SetWork(potential_works[SyncRand(potential_works.size())]);
2296 }
2297 }
2298
GenerateUnique(CUnit * dropper,CPlayer * dropper_player)2299 void CUnit::GenerateUnique(CUnit *dropper, CPlayer *dropper_player)
2300 {
2301 std::vector<CUniqueItem *> potential_uniques;
2302 for (size_t i = 0; i < UniqueItems.size(); ++i) {
2303 if (
2304 Type == UniqueItems[i]->Type
2305 && ( //the dropper unit must be capable of generating this unique item's prefix to drop the item, or else the unit must be capable of generating it on its own
2306 UniqueItems[i]->Prefix == nullptr
2307 || (dropper_player != nullptr && CheckDependencies(UniqueItems[i]->Prefix, dropper))
2308 || std::find(this->Type->Affixes.begin(), this->Type->Affixes.end(), UniqueItems[i]->Prefix) != this->Type->Affixes.end()
2309 )
2310 && ( //the dropper unit must be capable of generating this unique item's suffix to drop the item, or else the unit must be capable of generating it on its own
2311 UniqueItems[i]->Suffix == nullptr
2312 || (dropper_player != nullptr && CheckDependencies(UniqueItems[i]->Suffix, dropper))
2313 || std::find(this->Type->Affixes.begin(), this->Type->Affixes.end(), UniqueItems[i]->Suffix) != this->Type->Affixes.end()
2314 )
2315 && ( //the dropper unit must be capable of generating this unique item's set to drop the item
2316 UniqueItems[i]->Set == nullptr
2317 || (dropper_player != nullptr && CheckDependencies(UniqueItems[i]->Set, dropper))
2318 )
2319 && ( //the dropper unit must be capable of generating this unique item's spell to drop the item
2320 UniqueItems[i]->Spell == nullptr
2321 || (dropper != nullptr && std::find(dropper->Type->DropSpells.begin(), dropper->Type->DropSpells.end(), UniqueItems[i]->Spell) != dropper->Type->DropSpells.end())
2322 )
2323 && ( //the dropper unit must be capable of generating this unique item's work to drop the item, or else the unit must be capable of generating it on its own
2324 UniqueItems[i]->Work == nullptr
2325 || std::find(this->Type->Affixes.begin(), this->Type->Affixes.end(), UniqueItems[i]->Work) != this->Type->Affixes.end()
2326 || (dropper_player != nullptr && CheckDependencies(UniqueItems[i]->Work, dropper))
2327 )
2328 && ( //the dropper unit must be capable of generating this unique item's elixir to drop the item, or else the unit must be capable of generating it on its own
2329 UniqueItems[i]->Elixir == nullptr
2330 || std::find(this->Type->Affixes.begin(), this->Type->Affixes.end(), UniqueItems[i]->Elixir) != this->Type->Affixes.end()
2331 || (dropper_player != nullptr && CheckDependencies(UniqueItems[i]->Elixir, dropper))
2332 )
2333 && UniqueItems[i]->CanDrop()
2334 ) {
2335 potential_uniques.push_back(UniqueItems[i]);
2336 }
2337 }
2338
2339 if (potential_uniques.size() > 0) {
2340 CUniqueItem *chosen_unique = potential_uniques[SyncRand(potential_uniques.size())];
2341 SetUnique(chosen_unique);
2342 }
2343 }
2344
UpdateSoldUnits()2345 void CUnit::UpdateSoldUnits()
2346 {
2347 if (!this->Type->BoolFlag[RECRUITHEROES_INDEX].value && this->Type->SoldUnits.empty() && this->SoldUnits.empty()) {
2348 return;
2349 }
2350
2351 if (this->UnderConstruction == 1 || !Map.Info.IsPointOnMap(this->tilePos, this->MapLayer) || Editor.Running != EditorNotRunning) {
2352 return;
2353 }
2354
2355 for (size_t i = 0; i < this->SoldUnits.size(); ++i) {
2356 DestroyAllInside(*this->SoldUnits[i]);
2357 LetUnitDie(*this->SoldUnits[i]);
2358 }
2359 this->SoldUnits.clear();
2360
2361 std::vector<CUnitType *> potential_items;
2362 std::vector<CCharacter *> potential_heroes;
2363 if (this->Type->BoolFlag[RECRUITHEROES_INDEX].value && !IsNetworkGame()) { // allow heroes to be recruited at town halls
2364 int civilization_id = this->Type->Civilization;
2365 if (civilization_id != -1 && civilization_id != this->Player->Race && this->Player->Race != -1 && this->Player->Faction != -1 && this->Type->Slot == PlayerRaces.GetFactionClassUnitType(this->Player->Faction, this->Type->Class)) {
2366 civilization_id = this->Player->Race;
2367 }
2368
2369 if (CurrentQuest == nullptr) {
2370 for (CCharacter *character : CCharacter::Characters) {
2371 if (this->Player->CanRecruitHero(character)) {
2372 potential_heroes.push_back(character);
2373 }
2374 }
2375 }
2376 if (this->Player == ThisPlayer) {
2377 for (std::map<std::string, CCharacter *>::iterator iterator = CustomHeroes.begin(); iterator != CustomHeroes.end(); ++iterator) {
2378 if (
2379 (iterator->second->Civilization && iterator->second->Civilization->ID == civilization_id || iterator->second->Type->Slot == PlayerRaces.GetCivilizationClassUnitType(civilization_id, iterator->second->Type->Class))
2380 && CheckDependencies(iterator->second->Type, this, true) && iterator->second->CanAppear()
2381 ) {
2382 potential_heroes.push_back(iterator->second);
2383 }
2384 }
2385 }
2386 } else {
2387 for (size_t i = 0; i < this->Type->SoldUnits.size(); ++i) {
2388 if (CheckDependencies(this->Type->SoldUnits[i], this)) {
2389 potential_items.push_back(this->Type->SoldUnits[i]);
2390 }
2391 }
2392 }
2393
2394 if (potential_items.empty() && potential_heroes.empty()) {
2395 return;
2396 }
2397
2398 int sold_unit_max = 4;
2399 if (!potential_items.empty()) {
2400 sold_unit_max = 15;
2401 }
2402
2403 for (int i = 0; i < sold_unit_max; ++i) {
2404 CUnit *new_unit = nullptr;
2405 if (!potential_heroes.empty()) {
2406 CCharacter *chosen_hero = potential_heroes[SyncRand(potential_heroes.size())];
2407 new_unit = MakeUnitAndPlace(this->tilePos, *chosen_hero->Type, &Players[PlayerNumNeutral], this->MapLayer->ID);
2408 new_unit->SetCharacter(chosen_hero->Ident, chosen_hero->Custom);
2409 potential_heroes.erase(std::remove(potential_heroes.begin(), potential_heroes.end(), chosen_hero), potential_heroes.end());
2410 } else {
2411 CUnitType *chosen_unit_type = potential_items[SyncRand(potential_items.size())];
2412 new_unit = MakeUnitAndPlace(this->tilePos, *chosen_unit_type, &Players[PlayerNumNeutral], this->MapLayer->ID);
2413 new_unit->GenerateSpecialProperties(this, this->Player, true, true);
2414 new_unit->Identified = true;
2415 if (new_unit->Unique && this->Player == ThisPlayer) { //send a notification if a unique item is being sold, we don't want the player to have to worry about missing it :)
2416 this->Player->Notify(NotifyGreen, this->tilePos, this->MapLayer->ID, "%s", _("Unique item available for sale"));
2417 }
2418 }
2419 new_unit->Remove(this);
2420 this->SoldUnits.push_back(new_unit);
2421 if (potential_heroes.empty() && potential_items.empty()) {
2422 break;
2423 }
2424 }
2425 if (IsOnlySelected(*this)) {
2426 UI.ButtonPanel.Update();
2427 }
2428 }
2429
SellUnit(CUnit * sold_unit,int player)2430 void CUnit::SellUnit(CUnit *sold_unit, int player)
2431 {
2432 this->SoldUnits.erase(std::remove(this->SoldUnits.begin(), this->SoldUnits.end(), sold_unit), this->SoldUnits.end());
2433 DropOutOnSide(*sold_unit, sold_unit->Direction, this);
2434 if (!sold_unit->Type->BoolFlag[ITEM_INDEX].value) {
2435 sold_unit->ChangeOwner(Players[player]);
2436 }
2437 Players[player].ChangeResource(CopperCost, -sold_unit->GetPrice(), true);
2438 if (Players[player].AiEnabled && !sold_unit->Type->BoolFlag[ITEM_INDEX].value && !sold_unit->Type->BoolFlag[HARVESTER_INDEX].value) { //add the hero to an AI force, if the hero isn't a harvester
2439 Players[player].Ai->Force.RemoveDeadUnit();
2440 Players[player].Ai->Force.Assign(*sold_unit, -1, true);
2441 }
2442 if (sold_unit->Character) {
2443 Players[player].HeroCooldownTimer = HeroCooldownCycles;
2444 sold_unit->Variable[MANA_INDEX].Value = 0; //start off with 0 mana
2445 }
2446 if (IsOnlySelected(*this)) {
2447 UI.ButtonPanel.Update();
2448 }
2449 }
2450
2451 /**
2452 ** Produce a resource
2453 **
2454 ** @param resource Resource to be produced.
2455 */
ProduceResource(const int resource)2456 void CUnit::ProduceResource(const int resource)
2457 {
2458 if (resource == this->GivesResource) {
2459 return;
2460 }
2461
2462 int old_resource = this->GivesResource;
2463
2464 if (resource != 0) {
2465 this->GivesResource = resource;
2466 this->ResourcesHeld = 10000;
2467 } else {
2468 this->GivesResource = 0;
2469 this->ResourcesHeld = 0;
2470 }
2471
2472 if (old_resource != 0) {
2473 if (this->Resource.Workers) {
2474 for (CUnit *uins = this->Resource.Workers; uins; uins = uins->NextWorker) {
2475 if (uins->Container == this) {
2476 uins->CurrentOrder()->Finished = true;
2477 DropOutOnSide(*uins, LookingW, this);
2478 }
2479 }
2480 }
2481 this->Resource.Active = 0;
2482 }
2483 }
2484
2485 /**
2486 ** Sells 100 of a resource for copper
2487 **
2488 ** @param resource Resource to be sold.
2489 */
SellResource(const int resource,const int player)2490 void CUnit::SellResource(const int resource, const int player)
2491 {
2492 if ((Players[player].Resources[resource] + Players[player].StoredResources[resource]) < 100) {
2493 return;
2494 }
2495
2496 Players[player].ChangeResource(resource, -100, true);
2497 Players[player].ChangeResource(CopperCost, this->Player->GetEffectiveResourceSellPrice(resource), true);
2498
2499 this->Player->DecreaseResourcePrice(resource);
2500 }
2501
2502 /**
2503 ** Buy a resource for copper
2504 **
2505 ** @param resource Resource to be bought.
2506 */
BuyResource(const int resource,const int player)2507 void CUnit::BuyResource(const int resource, const int player)
2508 {
2509 if ((Players[player].Resources[CopperCost] + Players[player].StoredResources[CopperCost]) < this->Player->GetEffectiveResourceBuyPrice(resource)) {
2510 return;
2511 }
2512
2513 Players[player].ChangeResource(resource, 100, true);
2514 Players[player].ChangeResource(CopperCost, -this->Player->GetEffectiveResourceBuyPrice(resource), true);
2515
2516 this->Player->IncreaseResourcePrice(resource);
2517 }
2518
Scout()2519 void CUnit::Scout()
2520 {
2521 int scout_range = std::max(16, this->CurrentSightRange * 2);
2522
2523 Vec2i target_pos = this->tilePos;
2524
2525 target_pos.x += SyncRand(scout_range * 2 + 1) - scout_range;
2526 target_pos.y += SyncRand(scout_range * 2 + 1) - scout_range;
2527
2528 // restrict to map
2529 Map.Clamp(target_pos, this->MapLayer->ID);
2530
2531 // move if possible
2532 if (target_pos != this->tilePos) {
2533 // if the tile the scout is moving to happens to have a layer connector, use it
2534 bool found_connector = false;
2535 CUnitCache &unitcache = Map.Field(target_pos, this->MapLayer->ID)->UnitCache;
2536 for (CUnitCache::iterator it = unitcache.begin(); it != unitcache.end(); ++it) {
2537 CUnit *connector = *it;
2538
2539 if (connector->ConnectingDestination != nullptr && this->CanUseItem(connector)) {
2540 CommandUse(*this, *connector, FlushCommands);
2541 found_connector = true;
2542 break;
2543 }
2544 }
2545 if (found_connector) {
2546 return;
2547 }
2548
2549 UnmarkUnitFieldFlags(*this);
2550 if (UnitCanBeAt(*this, target_pos, this->MapLayer->ID)) {
2551 MarkUnitFieldFlags(*this);
2552 CommandMove(*this, target_pos, FlushCommands, this->MapLayer->ID);
2553 return;
2554 }
2555 MarkUnitFieldFlags(*this);
2556 }
2557 }
2558 //Wyrmgus end
2559
CurrentAction() const2560 unsigned int CUnit::CurrentAction() const
2561 {
2562 return (CurrentOrder()->Action);
2563 }
2564
ClearAction()2565 void CUnit::ClearAction()
2566 {
2567 Orders[0]->Finished = true;
2568
2569 if (Selected) {
2570 SelectedUnitChanged();
2571 }
2572 }
2573
2574
IsIdle() const2575 bool CUnit::IsIdle() const
2576 {
2577 //Wyrmgus start
2578 // return Orders.size() == 1 && CurrentAction() == UnitActionStill;
2579 return Orders.size() == 1 && CurrentAction() == UnitActionStill && this->Variable[STUN_INDEX].Value == 0;
2580 //Wyrmgus end
2581 }
2582
IsAlive() const2583 bool CUnit::IsAlive() const
2584 {
2585 return !Destroyed && CurrentAction() != UnitActionDie;
2586 }
2587
GetDrawLevel() const2588 int CUnit::GetDrawLevel() const
2589 {
2590 return ((Type->CorpseType && CurrentAction() == UnitActionDie) ?
2591 Type->CorpseType->DrawLevel :
2592 ((CurrentAction() == UnitActionDie) ? Type->DrawLevel - 10 : Type->DrawLevel));
2593 }
2594
2595 /**
2596 ** Initialize the unit slot with default values.
2597 **
2598 ** @param type Unit-type
2599 */
Init(const CUnitType & type)2600 void CUnit::Init(const CUnitType &type)
2601 {
2602 // Set refs to 1. This is the "I am alive ref", lost in ReleaseUnit.
2603 Refs = 1;
2604
2605 // Build all unit table
2606 UnitManager.Add(this);
2607
2608 // Initialise unit structure (must be zero filled!)
2609 Type = &type;
2610
2611 Seen.Frame = UnitNotSeen; // Unit isn't yet seen
2612
2613 Frame = type.StillFrame;
2614
2615 if (UnitTypeVar.GetNumberVariable()) {
2616 Assert(!Variable);
2617 const unsigned int size = UnitTypeVar.GetNumberVariable();
2618 Variable = new CVariable[size];
2619 std::copy(type.MapDefaultStat.Variables, type.MapDefaultStat.Variables + size, Variable);
2620 } else {
2621 Variable = nullptr;
2622 }
2623
2624 IndividualUpgrades.clear();
2625
2626 // Set a heading for the unit if it Handles Directions
2627 // Don't set a building heading, as only 1 construction direction
2628 // is allowed.
2629 if (type.NumDirections > 1 && type.BoolFlag[NORANDOMPLACING_INDEX].value == false && type.Sprite && !type.BoolFlag[BUILDING_INDEX].value) {
2630 Direction = (SyncRand() >> 8) & 0xFF; // random heading
2631 UnitUpdateHeading(*this);
2632 }
2633
2634 // Create AutoCastSpell and SpellCoolDownTimers arrays for casters
2635 //Wyrmgus start
2636 // if (type.CanCastSpell) {
2637 //to avoid crashes with spell items for units who cannot ordinarily cast spells
2638 //Wyrmgus end
2639 AutoCastSpell = new char[CSpell::Spells.size()];
2640 SpellCoolDownTimers = new int[CSpell::Spells.size()];
2641 memset(SpellCoolDownTimers, 0, CSpell::Spells.size() * sizeof(int));
2642 if (Type->AutoCastActive) {
2643 memcpy(AutoCastSpell, Type->AutoCastActive, CSpell::Spells.size());
2644 } else {
2645 memset(AutoCastSpell, 0, CSpell::Spells.size());
2646 }
2647 //Wyrmgus start
2648 // }
2649 //Wyrmgus end
2650 Active = 1;
2651 Removed = 1;
2652
2653 // Has StartingResources, Use those
2654 //Wyrmgus start
2655 // this->ResourcesHeld = type.StartingResources;
2656 if (type.GivesResource) {
2657 this->GivesResource = type.GivesResource;
2658 if (type.StartingResources.size() > 0) {
2659 this->ResourcesHeld = type.StartingResources[SyncRand(type.StartingResources.size())];
2660 } else {
2661 this->ResourcesHeld = 0;
2662 }
2663 }
2664 //Wyrmgus end
2665
2666 Assert(Orders.empty());
2667
2668 Orders.push_back(COrder::NewActionStill());
2669
2670 Assert(NewOrder == nullptr);
2671 NewOrder = nullptr;
2672 Assert(SavedOrder == nullptr);
2673 SavedOrder = nullptr;
2674 Assert(CriticalOrder == nullptr);
2675 CriticalOrder = nullptr;
2676 }
2677
2678 /**
2679 ** Restore the saved order
2680 **
2681 ** @return True if the saved order was restored
2682 */
RestoreOrder()2683 bool CUnit::RestoreOrder()
2684 {
2685 COrder *savedOrder = this->SavedOrder;
2686
2687 if (savedOrder == nullptr) {
2688 return false;
2689 }
2690
2691 if (savedOrder->IsValid() == false) {
2692 delete savedOrder;
2693 this->SavedOrder = nullptr;
2694 return false;
2695 }
2696
2697 // Cannot delete this->Orders[0] since it is generally that order
2698 // which call this method.
2699 this->Orders[0]->Finished = true;
2700
2701 //copy
2702 this->Orders.insert(this->Orders.begin() + 1, savedOrder);
2703
2704 this->SavedOrder = nullptr;
2705 return true;
2706 }
2707
2708 /**
2709 ** Check if we can store this order
2710 **
2711 ** @return True if the order could be saved
2712 */
CanStoreOrder(COrder * order)2713 bool CUnit::CanStoreOrder(COrder *order)
2714 {
2715 Assert(order);
2716
2717 if ((order && order->Finished == true) || order->IsValid() == false) {
2718 return false;
2719 }
2720 if (this->SavedOrder != nullptr) {
2721 return false;
2722 }
2723 return true;
2724 }
2725
2726 /**
2727 ** Assigns a unit to a player, adjusting buildings, food and totals
2728 **
2729 ** @param player player which have the unit.
2730 */
AssignToPlayer(CPlayer & player)2731 void CUnit::AssignToPlayer(CPlayer &player)
2732 {
2733 const CUnitType &type = *Type;
2734
2735 // Build player unit table
2736 //Wyrmgus start
2737 // if (!type.BoolFlag[VANISHES_INDEX].value && CurrentAction() != UnitActionDie) {
2738 if (!type.BoolFlag[VANISHES_INDEX].value && CurrentAction() != UnitActionDie && !this->Destroyed) {
2739 //Wyrmgus end
2740 player.AddUnit(*this);
2741 if (!SaveGameLoading) {
2742 // If unit is dying, it's already been lost by all players
2743 // don't count again
2744 if (type.BoolFlag[BUILDING_INDEX].value) {
2745 // FIXME: support more races
2746 //Wyrmgus start
2747 // if (!type.BoolFlag[WALL_INDEX].value && &type != UnitTypeOrcWall && &type != UnitTypeHumanWall) {
2748 //Wyrmgus end
2749 player.TotalBuildings++;
2750 //Wyrmgus start
2751 // }
2752 //Wyrmgus end
2753 } else {
2754 player.TotalUnits++;
2755
2756 for (CPlayerQuestObjective *objective : player.QuestObjectives) {
2757 if (
2758 (objective->ObjectiveType == BuildUnitsObjectiveType && std::find(objective->UnitTypes.begin(), objective->UnitTypes.end(), &type) != objective->UnitTypes.end())
2759 || (objective->ObjectiveType == BuildUnitsOfClassObjectiveType && objective->UnitClass == type.Class)
2760 ) {
2761 objective->Counter = std::min(objective->Counter + 1, objective->Quantity);
2762 }
2763 }
2764 }
2765 }
2766
2767 player.IncreaseCountsForUnit(this);
2768
2769 player.Demand += type.Stats[player.Index].Variables[DEMAND_INDEX].Value; // food needed
2770 }
2771
2772 // Don't Add the building if it's dying, used to load a save game
2773 //Wyrmgus start
2774 // if (type.BoolFlag[BUILDING_INDEX].value && CurrentAction() != UnitActionDie) {
2775 if (type.BoolFlag[BUILDING_INDEX].value && CurrentAction() != UnitActionDie && !this->Destroyed && !type.BoolFlag[VANISHES_INDEX].value) {
2776 //Wyrmgus end
2777 //Wyrmgus start
2778 // if (!type.BoolFlag[WALL_INDEX].value && &type != UnitTypeOrcWall && &type != UnitTypeHumanWall) {
2779 //Wyrmgus end
2780 player.NumBuildings++;
2781 //Wyrmgus start
2782 if (CurrentAction() == UnitActionBuilt) {
2783 player.NumBuildingsUnderConstruction++;
2784 player.ChangeUnitTypeUnderConstructionCount(&type, 1);
2785 }
2786 //Wyrmgus end
2787 //Wyrmgus start
2788 // }
2789 //Wyrmgus end
2790 }
2791 Player = &player;
2792 Stats = &type.Stats[Player->Index];
2793 Colors = &player.UnitColors;
2794 if (!SaveGameLoading) {
2795 if (UnitTypeVar.GetNumberVariable()) {
2796 Assert(Variable);
2797 Assert(Stats->Variables);
2798 memcpy(Variable, Stats->Variables, UnitTypeVar.GetNumberVariable() * sizeof(*Variable));
2799 }
2800 }
2801
2802 //Wyrmgus start
2803 if (!SaveGameLoading) {
2804 //assign a gender to the unit
2805 if (this->Variable[GENDER_INDEX].Value == NoGender && this->Type->BoolFlag[ORGANIC_INDEX].value) { // Gender: 0 = Not Set, 1 = Male, 2 = Female, 3 = Asexual
2806 this->Variable[GENDER_INDEX].Value = SyncRand(2) + 1;
2807 this->Variable[GENDER_INDEX].Max = MaxGenders;
2808 this->Variable[GENDER_INDEX].Enable = 1;
2809 }
2810
2811 //generate a personal name for the unit, if applicable
2812 if (this->Character == nullptr) {
2813 this->UpdatePersonalName();
2814 }
2815
2816 this->UpdateSoldUnits();
2817 }
2818 //Wyrmgus end
2819 }
2820
2821 /**
2822 ** Create a new unit.
2823 **
2824 ** @param type Pointer to unit-type.
2825 ** @param player Pointer to owning player.
2826 **
2827 ** @return Pointer to created unit.
2828 */
MakeUnit(const CUnitType & type,CPlayer * player)2829 CUnit *MakeUnit(const CUnitType &type, CPlayer *player)
2830 {
2831 CUnit *unit = UnitManager.AllocUnit();
2832 if (unit == nullptr) {
2833 return nullptr;
2834 }
2835 unit->Init(type);
2836 // Only Assign if a Player was specified
2837 if (player) {
2838 unit->AssignToPlayer(*player);
2839
2840 //Wyrmgus start
2841 unit->ChooseVariation(nullptr, true);
2842 for (int i = 0; i < MaxImageLayers; ++i) {
2843 unit->ChooseVariation(nullptr, true, i);
2844 }
2845 unit->UpdateButtonIcons();
2846 unit->UpdateXPRequired();
2847 //Wyrmgus end
2848 }
2849
2850 //Wyrmgus start
2851 // grant the unit the civilization/faction upgrades of its respective civilization/faction, so that it is able to pursue its upgrade line in experience upgrades even if it changes hands
2852 if (unit->Type->Civilization != -1 && !PlayerRaces.CivilizationUpgrades[unit->Type->Civilization].empty()) {
2853 CUpgrade *civilization_upgrade = CUpgrade::Get(PlayerRaces.CivilizationUpgrades[unit->Type->Civilization]);
2854 if (civilization_upgrade) {
2855 unit->SetIndividualUpgrade(civilization_upgrade, 1);
2856 }
2857 }
2858 if (unit->Type->Civilization != -1 && unit->Type->Faction != -1 && !PlayerRaces.Factions[unit->Type->Faction]->FactionUpgrade.empty()) {
2859 CUpgrade *faction_upgrade = CUpgrade::Get(PlayerRaces.Factions[unit->Type->Faction]->FactionUpgrade);
2860 if (faction_upgrade) {
2861 unit->SetIndividualUpgrade(faction_upgrade, 1);
2862 }
2863 }
2864
2865 // generate a trait for the unit, if any are available (only if the editor isn't running)
2866 if (Editor.Running == EditorNotRunning && unit->Type->Traits.size() > 0) {
2867 TraitAcquire(*unit, unit->Type->Traits[SyncRand(unit->Type->Traits.size())]);
2868 }
2869
2870 for (size_t i = 0; i < unit->Type->StartingAbilities.size(); ++i) {
2871 if (CheckDependencies(unit->Type->StartingAbilities[i], unit)) {
2872 IndividualUpgradeAcquire(*unit, unit->Type->StartingAbilities[i]);
2873 }
2874 }
2875
2876 if (unit->Type->Elixir) { //set the unit type's elixir, if any
2877 unit->SetElixir(unit->Type->Elixir);
2878 }
2879
2880 unit->Variable[MANA_INDEX].Value = 0; //start off with 0 mana
2881 //Wyrmgus end
2882
2883 if (unit->Type->OnInit) {
2884 unit->Type->OnInit->pushPreamble();
2885 unit->Type->OnInit->pushInteger(UnitNumber(*unit));
2886 unit->Type->OnInit->run();
2887 }
2888
2889 // fancy buildings: mirror buildings (but shadows not correct)
2890 if (type.BoolFlag[BUILDING_INDEX].value && FancyBuildings
2891 && unit->Type->BoolFlag[NORANDOMPLACING_INDEX].value == false && (SyncRand() & 1) != 0) {
2892 unit->Frame = -unit->Frame - 1;
2893 }
2894
2895 return unit;
2896 }
2897
2898 /**
2899 ** (Un)Mark on vision table the Sight of the unit
2900 ** (and units inside for transporter (recursively))
2901 **
2902 ** @param unit Unit to (un)mark.
2903 ** @param pos coord of first container of unit.
2904 ** @param width Width of the first container of unit.
2905 ** @param height Height of the first container of unit.
2906 ** @param f Function to (un)mark for normal vision.
2907 ** @param f2 Function to (un)mark for cloaking vision.
2908 */
MapMarkUnitSightRec(const CUnit & unit,const Vec2i & pos,int width,int height,MapMarkerFunc * f,MapMarkerFunc * f2,MapMarkerFunc * f3)2909 static void MapMarkUnitSightRec(const CUnit &unit, const Vec2i &pos, int width, int height,
2910 //Wyrmgus start
2911 // MapMarkerFunc *f, MapMarkerFunc *f2)
2912 MapMarkerFunc *f, MapMarkerFunc *f2, MapMarkerFunc *f3)
2913 //Wyrmgus end
2914 {
2915 Assert(f);
2916 //Wyrmgus start
2917 /*
2918 MapSight(*unit.Player, pos, width, height,
2919 unit.GetFirstContainer()->CurrentSightRange, f);
2920
2921 if (unit.Type && unit.Type->BoolFlag[DETECTCLOAK_INDEX].value && f2) {
2922 MapSight(*unit.Player, pos, width, height,
2923 unit.GetFirstContainer()->CurrentSightRange, f2);
2924 }
2925 */
2926
2927 MapSight(*unit.Player, pos, width, height,
2928 unit.Container && unit.Container->CurrentSightRange >= unit.CurrentSightRange ? unit.Container->CurrentSightRange : unit.CurrentSightRange, f, unit.MapLayer->ID);
2929
2930 if (unit.Type && unit.Type->BoolFlag[DETECTCLOAK_INDEX].value && f2) {
2931 MapSight(*unit.Player, pos, width, height,
2932 unit.Container && unit.Container->CurrentSightRange >= unit.CurrentSightRange ? unit.Container->CurrentSightRange : unit.CurrentSightRange, f2, unit.MapLayer->ID);
2933 }
2934
2935 if (unit.Variable[ETHEREALVISION_INDEX].Value && f3) {
2936 MapSight(*unit.Player, pos, width, height,
2937 unit.Container && unit.Container->CurrentSightRange >= unit.CurrentSightRange ? unit.Container->CurrentSightRange : unit.CurrentSightRange, f3, unit.MapLayer->ID);
2938 }
2939 //Wyrmgus end
2940
2941 CUnit *unit_inside = unit.UnitInside;
2942 for (int i = unit.InsideCount; i--; unit_inside = unit_inside->NextContained) {
2943 //Wyrmgus start
2944 // MapMarkUnitSightRec(*unit_inside, pos, width, height, f, f2);
2945 MapMarkUnitSightRec(*unit_inside, pos, width, height, f, f2, f3);
2946 //Wyrmgus end
2947 }
2948 }
2949
2950 /**
2951 ** @brief Return the topmost container for the unit
2952 **
2953 ** @param unit The unit for which to get the topmost container
2954 **
2955 ** @return The unit's topmost container if present, or the unit itself otherwise; this function should never return null
2956 */
GetFirstContainer() const2957 CUnit *CUnit::GetFirstContainer() const
2958 {
2959 const CUnit *container = this;
2960
2961 while (container->Container) {
2962 container = container->Container;
2963 }
2964
2965 return const_cast<CUnit *>(container);
2966 }
2967
2968 /**
2969 ** Mark on vision table the Sight of the unit
2970 ** (and units inside for transporter)
2971 **
2972 ** @param unit unit to unmark its vision.
2973 ** @see MapUnmarkUnitSight.
2974 */
MapMarkUnitSight(CUnit & unit)2975 void MapMarkUnitSight(CUnit &unit)
2976 {
2977 CUnit *container = unit.GetFirstContainer();// First container of the unit.
2978 Assert(container->Type);
2979
2980 MapMarkUnitSightRec(unit, container->tilePos, container->Type->TileSize.x, container->Type->TileSize.y,
2981 //Wyrmgus start
2982 // MapMarkTileSight, MapMarkTileDetectCloak);
2983 MapMarkTileSight, MapMarkTileDetectCloak, MapMarkTileDetectEthereal);
2984 //Wyrmgus end
2985
2986 // Never mark radar, except if the top unit, and unit is usable
2987 if (&unit == container && !unit.IsUnusable()) {
2988 if (unit.Stats->Variables[RADAR_INDEX].Value) {
2989 MapMarkRadar(*unit.Player, unit.tilePos, unit.Type->TileSize.x,
2990 unit.Type->TileSize.y, unit.Stats->Variables[RADAR_INDEX].Value, unit.MapLayer->ID);
2991 }
2992 if (unit.Stats->Variables[RADARJAMMER_INDEX].Value) {
2993 MapMarkRadarJammer(*unit.Player, unit.tilePos, unit.Type->TileSize.x,
2994 unit.Type->TileSize.y, unit.Stats->Variables[RADARJAMMER_INDEX].Value, unit.MapLayer->ID);
2995 }
2996 }
2997
2998 //Wyrmgus start
2999 if (unit.Variable[OWNERSHIPINFLUENCERANGE_INDEX].Value) {
3000 MapMarkOwnership(*unit.Player, unit.tilePos, unit.Type->TileSize.x,
3001 unit.Type->TileSize.y, unit.Variable[OWNERSHIPINFLUENCERANGE_INDEX].Value, unit.MapLayer->ID);
3002 }
3003 //Wyrmgus end
3004 }
3005
3006 /**
3007 ** Unmark on vision table the Sight of the unit
3008 ** (and units inside for transporter)
3009 **
3010 ** @param unit unit to unmark its vision.
3011 ** @see MapMarkUnitSight.
3012 */
MapUnmarkUnitSight(CUnit & unit)3013 void MapUnmarkUnitSight(CUnit &unit)
3014 {
3015 Assert(unit.Type);
3016
3017 CUnit *container = unit.GetFirstContainer();
3018 Assert(container->Type);
3019 MapMarkUnitSightRec(unit,
3020 container->tilePos, container->Type->TileSize.x, container->Type->TileSize.y,
3021 //Wyrmgus start
3022 // MapUnmarkTileSight, MapUnmarkTileDetectCloak);
3023 MapUnmarkTileSight, MapUnmarkTileDetectCloak, MapUnmarkTileDetectEthereal);
3024 //Wyrmgus end
3025
3026 // Never mark radar, except if the top unit?
3027 if (&unit == container && !unit.IsUnusable()) {
3028 if (unit.Stats->Variables[RADAR_INDEX].Value) {
3029 MapUnmarkRadar(*unit.Player, unit.tilePos, unit.Type->TileSize.x,
3030 unit.Type->TileSize.y, unit.Stats->Variables[RADAR_INDEX].Value, unit.MapLayer->ID);
3031 }
3032 if (unit.Stats->Variables[RADARJAMMER_INDEX].Value) {
3033 MapUnmarkRadarJammer(*unit.Player, unit.tilePos, unit.Type->TileSize.x,
3034 unit.Type->TileSize.y, unit.Stats->Variables[RADARJAMMER_INDEX].Value, unit.MapLayer->ID);
3035 }
3036
3037 }
3038
3039 //Wyrmgus start
3040 if (unit.Variable[OWNERSHIPINFLUENCERANGE_INDEX].Value) {
3041 MapUnmarkOwnership(*unit.Player, unit.tilePos, unit.Type->TileSize.x,
3042 unit.Type->TileSize.y, unit.Variable[OWNERSHIPINFLUENCERANGE_INDEX].Value, unit.MapLayer->ID);
3043 }
3044 //Wyrmgus end
3045 }
3046
3047 /**
3048 ** Update the Unit Current sight range to good value and transported units inside.
3049 **
3050 ** @param unit unit to update SightRange
3051 **
3052 ** @internal before using it, MapUnmarkUnitSight(unit)
3053 ** and after MapMarkUnitSight(unit)
3054 ** are often necessary.
3055 **
3056 ** FIXME @todo manage differently unit inside with option.
3057 ** (no vision, min, host value, own value, bonus value, ...)
3058 */
UpdateUnitSightRange(CUnit & unit)3059 void UpdateUnitSightRange(CUnit &unit)
3060 {
3061 //Wyrmgus start
3062 /*
3063 #if 0 // which is the better ? caller check ?
3064 if (SaveGameLoading) {
3065 return ;
3066 }
3067 #else
3068 Assert(!SaveGameLoading);
3069 #endif
3070 */
3071 //Wyrmgus end
3072 // FIXME : these values must be configurable.
3073 //Wyrmgus start
3074 int unit_sight_range = unit.Variable[SIGHTRANGE_INDEX].Max;
3075 if (unit.MapLayer) {
3076 if (unit.MapLayer->GetTimeOfDay() && unit.MapLayer->GetTimeOfDay()->Day) {
3077 unit_sight_range += unit.Variable[DAYSIGHTRANGEBONUS_INDEX].Value;
3078 }
3079 else if (unit.MapLayer->GetTimeOfDay() && unit.MapLayer->GetTimeOfDay()->Night) {
3080 unit_sight_range += unit.Variable[NIGHTSIGHTRANGEBONUS_INDEX].Value;
3081 }
3082 }
3083 unit_sight_range = std::max<int>(1, unit_sight_range);
3084 //Wyrmgus end
3085 if (unit.UnderConstruction) { // Units under construction have no sight range.
3086 unit.CurrentSightRange = 1;
3087 } else if (!unit.Container) { // proper value.
3088 //Wyrmgus start
3089 // unit.CurrentSightRange = unit.Stats->Variables[SIGHTRANGE_INDEX].Max;
3090 unit.CurrentSightRange = unit_sight_range;
3091 //Wyrmgus end
3092 } else { // value of it container.
3093 //Wyrmgus start
3094 // unit.CurrentSightRange = unit.Container->CurrentSightRange;
3095 //if a unit is inside a container, then use the sight of the unit or the container, whichever is greater
3096 if (unit_sight_range <= unit.Container->CurrentSightRange) {
3097 unit.CurrentSightRange = unit.Container->CurrentSightRange;
3098 } else {
3099 unit.CurrentSightRange = unit_sight_range;
3100 }
3101 //Wyrmgus end
3102 }
3103
3104 CUnit *unit_inside = unit.UnitInside;
3105 for (int i = unit.InsideCount; i--; unit_inside = unit_inside->NextContained) {
3106 UpdateUnitSightRange(*unit_inside);
3107 }
3108 }
3109
3110 /**
3111 ** Mark the field with the FieldFlags.
3112 **
3113 ** @param unit unit to mark.
3114 */
MarkUnitFieldFlags(const CUnit & unit)3115 void MarkUnitFieldFlags(const CUnit &unit)
3116 {
3117 const unsigned int flags = unit.Type->FieldFlags;
3118 int h = unit.Type->TileSize.y; // Tile height of the unit.
3119 const int width = unit.Type->TileSize.x; // Tile width of the unit.
3120 unsigned int index = unit.Offset;
3121
3122 //Wyrmgus start
3123 // if (unit.Type->BoolFlag[VANISHES_INDEX].value) {
3124 if (unit.Type->BoolFlag[VANISHES_INDEX].value || unit.CurrentAction() == UnitActionDie) {
3125 //Wyrmgus end
3126 return ;
3127 }
3128 do {
3129 CMapField *mf = unit.MapLayer->Field(index);
3130 int w = width;
3131 do {
3132 mf->Flags |= flags;
3133 ++mf;
3134 } while (--w);
3135 index += unit.MapLayer->GetWidth();
3136 } while (--h);
3137 }
3138
3139 class _UnmarkUnitFieldFlags
3140 {
3141 public:
_UnmarkUnitFieldFlags(const CUnit & unit,CMapField * mf)3142 _UnmarkUnitFieldFlags(const CUnit &unit, CMapField *mf) : main(&unit), mf(mf)
3143 {}
3144
operator ()(CUnit * const unit) const3145 void operator()(CUnit *const unit) const
3146 {
3147 if (main != unit && unit->CurrentAction() != UnitActionDie) {
3148 mf->Flags |= unit->Type->FieldFlags;
3149 }
3150 }
3151 private:
3152 const CUnit *const main;
3153 CMapField *mf;
3154 };
3155
3156
3157 /**
3158 ** Mark the field with the FieldFlags.
3159 **
3160 ** @param unit unit to mark.
3161 */
UnmarkUnitFieldFlags(const CUnit & unit)3162 void UnmarkUnitFieldFlags(const CUnit &unit)
3163 {
3164 const unsigned int flags = ~unit.Type->FieldFlags;
3165 const int width = unit.Type->TileSize.x;
3166 int h = unit.Type->TileSize.y;
3167 unsigned int index = unit.Offset;
3168
3169 if (unit.Type->BoolFlag[VANISHES_INDEX].value) {
3170 return ;
3171 }
3172 do {
3173 CMapField *mf = unit.MapLayer->Field(index);
3174
3175 int w = width;
3176 do {
3177 mf->Flags &= flags;//clean flags
3178 _UnmarkUnitFieldFlags funct(unit, mf);
3179
3180 mf->UnitCache.for_each(funct);
3181 ++mf;
3182 } while (--w);
3183 index += unit.MapLayer->GetWidth();
3184 } while (--h);
3185 }
3186
3187 /**
3188 ** Add unit to a container. It only updates linked list stuff.
3189 **
3190 ** @param host Pointer to container.
3191 */
AddInContainer(CUnit & host)3192 void CUnit::AddInContainer(CUnit &host)
3193 {
3194 Assert(Container == nullptr);
3195 Container = &host;
3196 if (host.InsideCount == 0) {
3197 NextContained = PrevContained = this;
3198 } else {
3199 NextContained = host.UnitInside;
3200 PrevContained = host.UnitInside->PrevContained;
3201 host.UnitInside->PrevContained->NextContained = this;
3202 host.UnitInside->PrevContained = this;
3203 }
3204 host.UnitInside = this;
3205 host.InsideCount++;
3206 //Wyrmgus start
3207 if (!SaveGameLoading) { //if host has no range by itself, but the unit has range, and the unit can attack from a transporter, change the host's range to the unit's; but don't do this while loading, as it causes a crash (since one unit needs to be loaded before the other, and when this function is processed both won't already have their variables set)
3208 host.UpdateContainerAttackRange();
3209 }
3210 //Wyrmgus end
3211 }
3212
3213 /**
3214 ** Remove unit from a container. It only updates linked list stuff.
3215 **
3216 ** @param unit Pointer to unit.
3217 */
RemoveUnitFromContainer(CUnit & unit)3218 static void RemoveUnitFromContainer(CUnit &unit)
3219 {
3220 CUnit *host = unit.Container; // transporter which contain unit.
3221 Assert(unit.Container);
3222 Assert(unit.Container->InsideCount > 0);
3223
3224 host->InsideCount--;
3225 unit.NextContained->PrevContained = unit.PrevContained;
3226 unit.PrevContained->NextContained = unit.NextContained;
3227 if (host->InsideCount == 0) {
3228 host->UnitInside = nullptr;
3229 } else {
3230 if (host->UnitInside == &unit) {
3231 host->UnitInside = unit.NextContained;
3232 }
3233 }
3234 unit.Container = nullptr;
3235 //Wyrmgus start
3236 //reset host attack range
3237 host->UpdateContainerAttackRange();
3238 //Wyrmgus end
3239 }
3240
3241 //Wyrmgus start
UpdateContainerAttackRange()3242 void CUnit::UpdateContainerAttackRange()
3243 {
3244 //reset attack range, if this unit is a transporter (or garrisonable building) from which units can attack
3245 if (this->Type->CanTransport() && this->Type->BoolFlag[ATTACKFROMTRANSPORTER_INDEX].value && !this->Type->CanAttack) {
3246 this->Variable[ATTACKRANGE_INDEX].Enable = 0;
3247 this->Variable[ATTACKRANGE_INDEX].Max = 0;
3248 this->Variable[ATTACKRANGE_INDEX].Value = 0;
3249 if (this->BoardCount > 0) {
3250 CUnit *boarded_unit = this->UnitInside;
3251 for (int i = 0; i < this->InsideCount; ++i, boarded_unit = boarded_unit->NextContained) {
3252 if (boarded_unit->GetModifiedVariable(ATTACKRANGE_INDEX) > this->Variable[ATTACKRANGE_INDEX].Value && boarded_unit->Type->BoolFlag[ATTACKFROMTRANSPORTER_INDEX].value) { //if container has no range by itself, but the unit has range, and the unit can attack from a transporter, change the container's range to the unit's
3253 this->Variable[ATTACKRANGE_INDEX].Enable = 1;
3254 this->Variable[ATTACKRANGE_INDEX].Max = boarded_unit->GetModifiedVariable(ATTACKRANGE_INDEX);
3255 this->Variable[ATTACKRANGE_INDEX].Value = boarded_unit->GetModifiedVariable(ATTACKRANGE_INDEX);
3256 }
3257 }
3258 }
3259 }
3260 }
3261
UpdateXPRequired()3262 void CUnit::UpdateXPRequired()
3263 {
3264 if (!this->Type->BoolFlag[ORGANIC_INDEX].value) {
3265 return;
3266 }
3267
3268 this->Variable[XPREQUIRED_INDEX].Value = this->Type->Stats[this->Player->Index].Variables[POINTS_INDEX].Value * 4 * this->Type->Stats[this->Player->Index].Variables[LEVEL_INDEX].Value;
3269 int extra_levels = this->Variable[LEVEL_INDEX].Value - this->Type->Stats[this->Player->Index].Variables[LEVEL_INDEX].Value;
3270 for (int i = 1; i <= extra_levels; ++i) {
3271 this->Variable[XPREQUIRED_INDEX].Value += 50 * 4 * i;
3272 }
3273 this->Variable[XPREQUIRED_INDEX].Max = this->Variable[XPREQUIRED_INDEX].Value;
3274 this->Variable[XPREQUIRED_INDEX].Enable = 1;
3275 this->Variable[XP_INDEX].Enable = 1;
3276 }
3277
UpdatePersonalName(bool update_settlement_name)3278 void CUnit::UpdatePersonalName(bool update_settlement_name)
3279 {
3280 if (this->Character != nullptr) {
3281 return;
3282 } else if (this->Type->BoolFlag[ITEM_INDEX].value || this->Unique || this->Prefix || this->Suffix) {
3283 this->UpdateItemName();
3284 return;
3285 }
3286
3287 int civilization_id = this->Type->Civilization;
3288
3289 CFaction *faction = nullptr;
3290 if (this->Player->Faction != -1) {
3291 faction = PlayerRaces.Factions[this->Player->Faction];
3292
3293 if (civilization_id != -1 && civilization_id != faction->Civilization->ID && PlayerRaces.Species[civilization_id] == PlayerRaces.Species[faction->Civilization->ID] && this->Type->Slot == PlayerRaces.GetFactionClassUnitType(faction->ID, this->Type->Class)) {
3294 civilization_id = faction->Civilization->ID;
3295 }
3296 }
3297
3298 CLanguage *language = PlayerRaces.GetCivilizationLanguage(civilization_id);
3299
3300 if (this->Name.empty()) { //this is the first time the unit receives a name
3301 if (!this->Type->BoolFlag[FAUNA_INDEX].value && this->Trait != nullptr && this->Trait->Epithets.size() > 0 && SyncRand(4) == 0) { // 25% chance to give the unit an epithet based on their trait
3302 this->ExtraName = this->Trait->Epithets[SyncRand(this->Trait->Epithets.size())];
3303 }
3304 }
3305
3306 if (!this->Type->IsPersonalNameValid(this->Name, faction, this->Variable[GENDER_INDEX].Value)) {
3307 // first see if can translate the current personal name
3308 std::string new_personal_name = PlayerRaces.TranslateName(this->Name, language);
3309 if (!new_personal_name.empty()) {
3310 this->Name = new_personal_name;
3311 } else {
3312 this->Name = this->Type->GeneratePersonalName(faction, this->Variable[GENDER_INDEX].Value);
3313 }
3314 }
3315
3316 if (update_settlement_name && (this->Type->BoolFlag[TOWNHALL_INDEX].value || (this->Type->BoolFlag[BUILDING_INDEX].value && !this->Settlement))) {
3317 this->UpdateSettlement();
3318 }
3319 }
3320
UpdateExtraName()3321 void CUnit::UpdateExtraName()
3322 {
3323 if (this->Character != nullptr || !this->Type->BoolFlag[ORGANIC_INDEX].value || this->Type->BoolFlag[FAUNA_INDEX].value) {
3324 return;
3325 }
3326
3327 if (this->Trait == nullptr) {
3328 return;
3329 }
3330
3331 this->ExtraName.clear();
3332
3333 if (this->Trait->Epithets.size() > 0 && SyncRand(4) == 0) { // 25% chance to give the unit an epithet based on their trait
3334 this->ExtraName = this->Trait->Epithets[SyncRand(this->Trait->Epithets.size())];
3335 }
3336 }
3337
UpdateSettlement()3338 void CUnit::UpdateSettlement()
3339 {
3340 if (this->Removed || Editor.Running != EditorNotRunning) {
3341 return;
3342 }
3343
3344 if (!this->Type->BoolFlag[BUILDING_INDEX].value || this->Type->TerrainType) {
3345 return;
3346 }
3347
3348 if (this->Type->BoolFlag[TOWNHALL_INDEX].value || this->Type == SettlementSiteUnitType) {
3349 if (!this->Settlement) {
3350 int civilization_id = this->Type->Civilization;
3351 if (civilization_id != -1 && this->Player->Faction != -1 && (this->Player->Race == civilization_id || this->Type->Slot == PlayerRaces.GetFactionClassUnitType(this->Player->Faction, this->Type->Class))) {
3352 civilization_id = this->Player->Race;
3353 }
3354 const CCivilization *civilization = nullptr;
3355 if (civilization_id != -1) {
3356 civilization = CCivilization::Civilizations[civilization_id];
3357 }
3358
3359 int faction_id = this->Type->Faction;
3360 if (this->Player->Race == civilization_id && this->Type->Slot == PlayerRaces.GetFactionClassUnitType(this->Player->Faction, this->Type->Class)) {
3361 faction_id = this->Player->Faction;
3362 }
3363 const CFaction *faction = nullptr;
3364 if (faction_id != -1) {
3365 PlayerRaces.Factions[faction_id];
3366 }
3367
3368 std::vector<CSite *> potential_settlements;
3369 if (civilization) {
3370 for (CSite *site : civilization->Sites) {
3371 if (!site->SiteUnit) {
3372 potential_settlements.push_back(site);
3373 }
3374 }
3375 }
3376
3377 if (potential_settlements.empty() && faction) {
3378 for (CSite *site : faction->Sites) {
3379 if (!site->SiteUnit) {
3380 potential_settlements.push_back(site);
3381 }
3382 }
3383 }
3384
3385 if (potential_settlements.empty()) {
3386 for (CSite *site : CSite::Sites) {
3387 if (!site->SiteUnit) {
3388 potential_settlements.push_back(site);
3389 }
3390 }
3391 }
3392
3393 if (potential_settlements.size() > 0) {
3394 this->Settlement = potential_settlements[SyncRand(potential_settlements.size())];
3395 this->Settlement->SiteUnit = this;
3396 Map.SiteUnits.push_back(this);
3397 }
3398 }
3399 if (this->Settlement) {
3400 this->UpdateBuildingSettlementAssignment();
3401 }
3402 } else {
3403 if (this->Player->Index == PlayerNumNeutral) {
3404 return;
3405 }
3406
3407 this->Settlement = this->Player->GetNearestSettlement(this->tilePos, this->MapLayer->ID, this->Type->TileSize);
3408 }
3409 }
3410
UpdateBuildingSettlementAssignment(CSite * old_settlement)3411 void CUnit::UpdateBuildingSettlementAssignment(CSite *old_settlement)
3412 {
3413 if (Editor.Running != EditorNotRunning) {
3414 return;
3415 }
3416
3417 if (this->Player->Index == PlayerNumNeutral) {
3418 return;
3419 }
3420
3421 for (int p = 0; p < PlayerMax; ++p) {
3422 if (!Players[p].HasNeutralFactionType() && this->Player->Index != Players[p].Index) {
3423 continue;
3424 }
3425 for (int i = 0; i < Players[p].GetUnitCount(); ++i) {
3426 CUnit *settlement_unit = &Players[p].GetUnit(i);
3427 if (!settlement_unit || !settlement_unit->IsAliveOnMap() || !settlement_unit->Type->BoolFlag[BUILDING_INDEX].value || settlement_unit->Type->BoolFlag[TOWNHALL_INDEX].value || settlement_unit->Type == SettlementSiteUnitType || this->MapLayer != settlement_unit->MapLayer) {
3428 continue;
3429 }
3430 if (old_settlement && settlement_unit->Settlement != old_settlement) {
3431 continue;
3432 }
3433 settlement_unit->UpdateSettlement();
3434 }
3435 }
3436 }
3437
XPChanged()3438 void CUnit::XPChanged()
3439 {
3440 if (!this->Type->BoolFlag[ORGANIC_INDEX].value || this->Type->BoolFlag[BUILDING_INDEX].value) {
3441 return;
3442 }
3443
3444 if (this->Variable[XPREQUIRED_INDEX].Value == 0) {
3445 return;
3446 }
3447
3448 while (this->Variable[XP_INDEX].Value >= this->Variable[XPREQUIRED_INDEX].Value) {
3449 this->Variable[XP_INDEX].Max -= this->Variable[XPREQUIRED_INDEX].Max;
3450 this->Variable[XP_INDEX].Value -= this->Variable[XPREQUIRED_INDEX].Value;
3451 if (this->Player == ThisPlayer) {
3452 this->Player->Notify(NotifyGreen, this->tilePos, this->MapLayer->ID, _("%s has leveled up!"), GetMessageName().c_str());
3453 }
3454 this->IncreaseLevel(1);
3455 }
3456
3457 if (!IsNetworkGame() && this->Character != nullptr && this->Player->AiEnabled == false) {
3458 this->Character->ExperiencePercent = (this->Variable[XP_INDEX].Value * 100) / this->Variable[XPREQUIRED_INDEX].Value;
3459 SaveHero(this->Character);
3460 }
3461 }
3462 //Wyrmgus end
3463
3464 /**
3465 ** Affect Tile coord of a unit (with units inside) to tile (x, y).
3466 **
3467 ** @param unit unit to move.
3468 ** @param pos map tile position.
3469 **
3470 ** @internal before use it, Map.Remove(unit), MapUnmarkUnitSight(unit)
3471 ** and after Map.Insert(unit), MapMarkUnitSight(unit)
3472 ** are often necessary. Check Flag also for Pathfinder.
3473 */
UnitInXY(CUnit & unit,const Vec2i & pos,const int z)3474 static void UnitInXY(CUnit &unit, const Vec2i &pos, const int z)
3475 {
3476 const CMapLayer *old_map_layer = unit.MapLayer;
3477
3478 CUnit *unit_inside = unit.UnitInside;
3479
3480 unit.tilePos = pos;
3481 unit.Offset = Map.getIndex(pos, z);
3482 unit.MapLayer = Map.MapLayers[z];
3483
3484 //Wyrmgus start
3485 if (!SaveGameLoading && old_map_layer != unit.MapLayer) {
3486 UpdateUnitSightRange(unit);
3487 }
3488 //Wyrmgus end
3489
3490 for (int i = unit.InsideCount; i--; unit_inside = unit_inside->NextContained) {
3491 UnitInXY(*unit_inside, pos, z);
3492 }
3493 }
3494
3495 /**
3496 ** Move a unit (with units inside) to tile (pos).
3497 ** (Do stuff with vision, cachelist and pathfinding).
3498 **
3499 ** @param pos map tile position.
3500 **
3501 */
3502 //Wyrmgus start
3503 //void CUnit::MoveToXY(const Vec2i &pos)
MoveToXY(const Vec2i & pos,int z)3504 void CUnit::MoveToXY(const Vec2i &pos, int z)
3505 //Wyrmgus end
3506 {
3507 MapUnmarkUnitSight(*this);
3508 Map.Remove(*this);
3509 UnmarkUnitFieldFlags(*this);
3510
3511 //Wyrmgus start
3512 // Assert(UnitCanBeAt(*this, pos));
3513 Assert(UnitCanBeAt(*this, pos, z));
3514 //Wyrmgus end
3515 // Move the unit.
3516 //Wyrmgus start
3517 // UnitInXY(*this, pos);
3518 UnitInXY(*this, pos, z);
3519 //Wyrmgus end
3520
3521 Map.Insert(*this);
3522 MarkUnitFieldFlags(*this);
3523 // Recalculate the seen count.
3524 UnitCountSeen(*this);
3525 MapMarkUnitSight(*this);
3526
3527 //Wyrmgus start
3528 // if there is a trap in the new tile, trigger it
3529 if ((this->Type->UnitType != UnitTypeFly && this->Type->UnitType != UnitTypeFlyLow) || !this->Type->BoolFlag[ORGANIC_INDEX].value) {
3530 const CUnitCache &cache = Map.Field(pos, z)->UnitCache;
3531 for (size_t i = 0; i != cache.size(); ++i) {
3532 if (!cache[i]) {
3533 fprintf(stderr, "Error in CUnit::MoveToXY (pos %d, %d): a unit in the tile's unit cache is null.\n", pos.x, pos.y);
3534 }
3535 CUnit &unit = *cache[i];
3536 if (unit.IsAliveOnMap() && unit.Type->BoolFlag[TRAP_INDEX].value) {
3537 FireMissile(unit, this, this->tilePos, this->MapLayer->ID);
3538 LetUnitDie(unit);
3539 }
3540 }
3541 }
3542 //Wyrmgus end
3543 }
3544
3545 /**
3546 ** Place unit on map.
3547 **
3548 ** @param pos map tile position.
3549 */
Place(const Vec2i & pos,int z)3550 void CUnit::Place(const Vec2i &pos, int z)
3551 {
3552 Assert(Removed);
3553
3554 const CMapLayer *old_map_layer = this->MapLayer;
3555
3556 if (Container) {
3557 MapUnmarkUnitSight(*this);
3558 RemoveUnitFromContainer(*this);
3559 }
3560 if (!SaveGameLoading) {
3561 UpdateUnitSightRange(*this);
3562 }
3563 Removed = 0;
3564 UnitInXY(*this, pos, z);
3565 // Pathfinding info.
3566 MarkUnitFieldFlags(*this);
3567 // Tha cache list.
3568 Map.Insert(*this);
3569 // Calculate the seen count.
3570 UnitCountSeen(*this);
3571 // Vision
3572 MapMarkUnitSight(*this);
3573
3574 // Correct directions for wall units
3575 if (this->Type->BoolFlag[WALL_INDEX].value && this->CurrentAction() != UnitActionBuilt) {
3576 CorrectWallDirections(*this);
3577 UnitUpdateHeading(*this);
3578 CorrectWallNeighBours(*this);
3579 }
3580
3581 //Wyrmgus start
3582 if (this->IsAlive()) {
3583 if (this->Type->BoolFlag[BUILDING_INDEX].value) {
3584 this->UpdateSettlement(); // update the settlement name of a building when placing it
3585 }
3586
3587 //remove pathways, destroyed walls and decoration units under buildings
3588 if (this->Type->BoolFlag[BUILDING_INDEX].value && !this->Type->TerrainType) {
3589 for (int x = this->tilePos.x; x < this->tilePos.x + this->Type->TileSize.x; ++x) {
3590 for (int y = this->tilePos.y; y < this->tilePos.y + this->Type->TileSize.y; ++y) {
3591 if (!Map.Info.IsPointOnMap(x, y, this->MapLayer)) {
3592 continue;
3593 }
3594 Vec2i building_tile_pos(x, y);
3595 CMapField &mf = *this->MapLayer->Field(building_tile_pos);
3596 if ((mf.Flags & MapFieldRoad) || (mf.Flags & MapFieldRailroad) || (mf.Flags & MapFieldWall)) {
3597 Map.RemoveTileOverlayTerrain(building_tile_pos, this->MapLayer->ID);
3598 }
3599 //remove decorations if a building has been built here
3600 std::vector<CUnit *> table;
3601 Select(building_tile_pos, building_tile_pos, table, this->MapLayer->ID);
3602 for (size_t i = 0; i != table.size(); ++i) {
3603 if (table[i] && table[i]->IsAlive() && table[i]->Type->UnitType == UnitTypeLand && table[i]->Type->BoolFlag[DECORATION_INDEX].value) {
3604 if (Editor.Running == EditorNotRunning) {
3605 LetUnitDie(*table[i]);
3606 } else {
3607 EditorActionRemoveUnit(*table[i], false);
3608 }
3609 }
3610 }
3611 }
3612 }
3613 }
3614
3615 const CUnitTypeVariation *variation = this->GetVariation();
3616 if (variation) {
3617 // if a unit that is on the tile has a terrain-dependent or season-dependent variation that is not compatible with the new tile, or if this is the first position the unit is being placed in, repick the unit's variation
3618 if (!old_map_layer || !this->CheckTerrainForVariation(variation) || !this->CheckSeasonForVariation(variation)) {
3619 this->ChooseVariation();
3620 }
3621 }
3622 }
3623 //Wyrmgus end
3624 }
3625
3626 /**
3627 ** Create new unit and place on map.
3628 **
3629 ** @param pos map tile position.
3630 ** @param type Pointer to unit-type.
3631 ** @param player Pointer to owning player.
3632 **
3633 ** @return Pointer to created unit.
3634 */
3635 //Wyrmgus start
3636 //CUnit *MakeUnitAndPlace(const Vec2i &pos, const CUnitType &type, CPlayer *player)
MakeUnitAndPlace(const Vec2i & pos,const CUnitType & type,CPlayer * player,int z)3637 CUnit *MakeUnitAndPlace(const Vec2i &pos, const CUnitType &type, CPlayer *player, int z)
3638 //Wyrmgus end
3639 {
3640 CUnit *unit = MakeUnit(type, player);
3641
3642 if (unit != nullptr) {
3643 unit->Place(pos, z);
3644 }
3645 return unit;
3646 }
3647
3648 //Wyrmgus start
3649 /**
3650 ** Create a new unit and place it on the map, updating its player accordingly.
3651 **
3652 ** @param pos map tile position.
3653 ** @param type Pointer to unit-type.
3654 ** @param player Pointer to owning player.
3655 **
3656 ** @return Pointer to created unit.
3657 */
CreateUnit(const Vec2i & pos,const CUnitType & type,CPlayer * player,int z,bool no_bordering_building)3658 CUnit *CreateUnit(const Vec2i &pos, const CUnitType &type, CPlayer *player, int z, bool no_bordering_building)
3659 {
3660 CUnit *unit = MakeUnit(type, player);
3661
3662 if (unit != nullptr) {
3663 Vec2i res_pos;
3664 const int heading = SyncRand() % 256;
3665 FindNearestDrop(type, pos, res_pos, heading, z, no_bordering_building);
3666
3667 if (type.BoolFlag[BUILDING_INDEX].value) {
3668 CBuildRestrictionOnTop *b = OnTopDetails(type, nullptr);
3669 if (b && b->ReplaceOnBuild) {
3670 CUnitCache &unitCache = Map.Field(res_pos, z)->UnitCache;
3671 CUnitCache::iterator it = std::find_if(unitCache.begin(), unitCache.end(), HasSameTypeAs(*b->Parent));
3672
3673 if (it != unitCache.end()) {
3674 CUnit &replacedUnit = **it;
3675 unit->ReplaceOnTop(replacedUnit);
3676 }
3677 }
3678 }
3679
3680 unit->Place(res_pos, z);
3681 UpdateForNewUnit(*unit, 0);
3682 }
3683 return unit;
3684 }
3685
CreateResourceUnit(const Vec2i & pos,const CUnitType & type,int z,bool allow_unique)3686 CUnit *CreateResourceUnit(const Vec2i &pos, const CUnitType &type, int z, bool allow_unique)
3687 {
3688 CUnit *unit = CreateUnit(pos, type, &Players[PlayerNumNeutral], z, true);
3689 unit->GenerateSpecialProperties(nullptr, nullptr, allow_unique);
3690
3691 // create metal rocks near metal resources
3692 CUnitType *metal_rock_type = nullptr;
3693 if (type.Ident == "unit-gold-deposit") {
3694 metal_rock_type = UnitTypeByIdent("unit-gold-rock");
3695 } else if (type.Ident == "unit-silver-deposit") {
3696 metal_rock_type = UnitTypeByIdent("unit-silver-rock");
3697 } else if (type.Ident == "unit-copper-deposit") {
3698 metal_rock_type = UnitTypeByIdent("unit-copper-rock");
3699 } else if (type.Ident == "unit-diamond-deposit") {
3700 metal_rock_type = UnitTypeByIdent("unit-diamond-rock");
3701 } else if (type.Ident == "unit-emerald-deposit") {
3702 metal_rock_type = UnitTypeByIdent("unit-emerald-rock");
3703 }
3704 if (metal_rock_type) {
3705 Vec2i metal_rock_offset((type.TileSize - 1) / 2);
3706 for (int i = 0; i < 9; ++i) {
3707 CUnit *metal_rock_unit = CreateUnit(unit->tilePos + metal_rock_offset, *metal_rock_type, &Players[PlayerNumNeutral], z);
3708 }
3709 }
3710
3711 return unit;
3712 }
3713 //Wyrmgus end
3714
3715 /**
3716 ** Find the nearest position at which unit can be placed.
3717 **
3718 ** @param type Type of the dropped unit.
3719 ** @param goalPos Goal map tile position.
3720 ** @param resPos Holds the nearest point.
3721 ** @param heading preferense side to drop out of.
3722 */
3723 //Wyrmgus start
3724 //void FindNearestDrop(const CUnitType &type, const Vec2i &goalPos, Vec2i &resPos, int heading)
FindNearestDrop(const CUnitType & type,const Vec2i & goalPos,Vec2i & resPos,int heading,int z,bool no_bordering_building,bool ignore_construction_requirements)3725 void FindNearestDrop(const CUnitType &type, const Vec2i &goalPos, Vec2i &resPos, int heading, int z, bool no_bordering_building, bool ignore_construction_requirements)
3726 //Wyrmgus end
3727 {
3728 int addx = 0;
3729 int addy = 0;
3730 Vec2i pos = goalPos;
3731
3732 if (heading < LookingNE || heading > LookingNW) {
3733 goto starts;
3734 } else if (heading < LookingSE) {
3735 goto startw;
3736 } else if (heading < LookingSW) {
3737 goto startn;
3738 } else {
3739 goto starte;
3740 }
3741
3742 // FIXME: don't search outside of the map
3743 for (;;) {
3744 startw:
3745 for (int i = addy; i--; ++pos.y) {
3746 //Wyrmgus start
3747 // if (UnitTypeCanBeAt(type, pos)) {
3748 if (
3749 (UnitTypeCanBeAt(type, pos, z) || (type.BoolFlag[BUILDING_INDEX].value && OnTopDetails(type, nullptr) && !ignore_construction_requirements))
3750 && (!type.BoolFlag[BUILDING_INDEX].value || ignore_construction_requirements || CanBuildHere(nullptr, type, pos, z, no_bordering_building) != nullptr)
3751 ) {
3752 //Wyrmgus end
3753 goto found;
3754 }
3755 }
3756 ++addx;
3757 starts:
3758 for (int i = addx; i--; ++pos.x) {
3759 //Wyrmgus start
3760 // if (UnitTypeCanBeAt(type, pos)) {
3761 if (
3762 (UnitTypeCanBeAt(type, pos, z) || (type.BoolFlag[BUILDING_INDEX].value && OnTopDetails(type, nullptr) && !ignore_construction_requirements))
3763 && (!type.BoolFlag[BUILDING_INDEX].value || ignore_construction_requirements || CanBuildHere(nullptr, type, pos, z, no_bordering_building) != nullptr)
3764 ) {
3765 //Wyrmgus end
3766 goto found;
3767 }
3768 }
3769 ++addy;
3770 starte:
3771 for (int i = addy; i--; --pos.y) {
3772 //Wyrmgus start
3773 // if (UnitTypeCanBeAt(type, pos)) {
3774 if (
3775 (UnitTypeCanBeAt(type, pos, z) || (type.BoolFlag[BUILDING_INDEX].value && OnTopDetails(type, nullptr) && !ignore_construction_requirements))
3776 && (!type.BoolFlag[BUILDING_INDEX].value || ignore_construction_requirements || CanBuildHere(nullptr, type, pos, z, no_bordering_building) != nullptr)
3777 ) {
3778 //Wyrmgus end
3779 goto found;
3780 }
3781 }
3782 ++addx;
3783 startn:
3784 for (int i = addx; i--; --pos.x) {
3785 //Wyrmgus start
3786 // if (UnitTypeCanBeAt(type, pos)) {
3787 if (
3788 (UnitTypeCanBeAt(type, pos, z) || (type.BoolFlag[BUILDING_INDEX].value && OnTopDetails(type, nullptr) && !ignore_construction_requirements))
3789 && (!type.BoolFlag[BUILDING_INDEX].value || ignore_construction_requirements || CanBuildHere(nullptr, type, pos, z, no_bordering_building) != nullptr)
3790 ) {
3791 //Wyrmgus end
3792 goto found;
3793 }
3794 }
3795 ++addy;
3796 }
3797
3798 found:
3799 resPos = pos;
3800 }
3801
3802 /**
3803 ** Remove unit from map.
3804 **
3805 ** Update selection.
3806 ** Update panels.
3807 ** Update map.
3808 **
3809 ** @param host Pointer to housing unit.
3810 */
Remove(CUnit * host)3811 void CUnit::Remove(CUnit *host)
3812 {
3813 if (Removed) { // could happen!
3814 // If unit is removed (inside) and building is destroyed.
3815 DebugPrint("unit '%s(%d)' already removed\n" _C_ Type->Ident.c_str() _C_ UnitNumber(*this));
3816 return;
3817 }
3818 Map.Remove(*this);
3819 MapUnmarkUnitSight(*this);
3820 UnmarkUnitFieldFlags(*this);
3821 if (host) {
3822 AddInContainer(*host);
3823 UpdateUnitSightRange(*this);
3824 UnitInXY(*this, host->tilePos, host->MapLayer->ID);
3825 MapMarkUnitSight(*this);
3826 }
3827
3828 Removed = 1;
3829
3830 // Correct surrounding walls directions
3831 if (this->Type->BoolFlag[WALL_INDEX].value) {
3832 CorrectWallNeighBours(*this);
3833 }
3834
3835 // Remove unit from the current selection
3836 if (this->Selected) {
3837 if (::Selected.size() == 1) { // Remove building cursor
3838 CancelBuildingMode();
3839 }
3840 UnSelectUnit(*this);
3841 //Wyrmgus start
3842 // SelectionChanged();
3843 if (GameRunning) { // to avoid a crash when SelectionChanged() calls UI.ButtonPanel.Update()
3844 SelectionChanged();
3845 }
3846 //Wyrmgus end
3847 }
3848 // Remove unit from team selections
3849 if (!Selected && TeamSelected) {
3850 UnSelectUnit(*this);
3851 }
3852
3853 // Unit is seen as under cursor
3854 if (UnitUnderCursor == this) {
3855 UnitUnderCursor = nullptr;
3856 }
3857 }
3858
3859 /**
3860 ** Update information for lost units.
3861 **
3862 ** @param unit Pointer to unit.
3863 **
3864 ** @note Also called by ChangeUnitOwner
3865 */
UnitLost(CUnit & unit)3866 void UnitLost(CUnit &unit)
3867 {
3868 CPlayer &player = *unit.Player;
3869
3870 Assert(&player); // Next code didn't support no player!
3871
3872 // Call back to AI, for killed or lost units.
3873 if (Editor.Running == EditorNotRunning) {
3874 if (player.AiEnabled) {
3875 AiUnitKilled(unit);
3876 } else {
3877 // Remove unit from its groups
3878 if (unit.GroupId) {
3879 RemoveUnitFromGroups(unit);
3880 }
3881 }
3882 }
3883
3884 // Remove the unit from the player's units table.
3885
3886 const CUnitType &type = *unit.Type;
3887 if (!type.BoolFlag[VANISHES_INDEX].value) {
3888 player.RemoveUnit(unit);
3889
3890 if (type.BoolFlag[BUILDING_INDEX].value) {
3891 // FIXME: support more races
3892 //Wyrmgus start
3893 // if (!type.BoolFlag[WALL_INDEX].value && &type != UnitTypeOrcWall && &type != UnitTypeHumanWall) {
3894 //Wyrmgus end
3895 player.NumBuildings--;
3896 //Wyrmgus start
3897 if (unit.CurrentAction() == UnitActionBuilt) {
3898 player.NumBuildingsUnderConstruction--;
3899 player.ChangeUnitTypeUnderConstructionCount(&type, -1);
3900 }
3901 //Wyrmgus end
3902 //Wyrmgus start
3903 // }
3904 //Wyrmgus end
3905 }
3906 if (unit.CurrentAction() != UnitActionBuilt) {
3907 if (player.AiEnabled && player.Ai) {
3908 if (std::find(player.Ai->Scouts.begin(), player.Ai->Scouts.end(), &unit) != player.Ai->Scouts.end()) {
3909 if (player.Ai->Scouting) { //if an AI player's scout has been lost, unmark it as "scouting" so that the force can see if it now has a viable target
3910 player.Ai->Scouting = false;
3911 }
3912 }
3913 for (std::map<int, std::vector<CUnit *>>::iterator iterator = player.Ai->Transporters.begin(); iterator != player.Ai->Transporters.end(); ++iterator) {
3914 if (std::find(iterator->second.begin(), iterator->second.end(), &unit) != iterator->second.end()) {
3915 iterator->second.erase(std::remove(iterator->second.begin(), iterator->second.end(), &unit), iterator->second.end());
3916 }
3917 }
3918 }
3919
3920 player.DecreaseCountsForUnit(&unit);
3921
3922 if (unit.Variable[LEVELUP_INDEX].Value > 0) {
3923 player.UpdateLevelUpUnits(); //recalculate level-up units, since this unit no longer should be in that vector
3924 }
3925 //Wyrmgus end
3926 }
3927 }
3928
3929 // Handle unit demand. (Currently only food supported.)
3930 player.Demand -= type.Stats[player.Index].Variables[DEMAND_INDEX].Value;
3931
3932 // Update information.
3933 if (unit.CurrentAction() != UnitActionBuilt) {
3934 player.Supply -= unit.Variable[SUPPLY_INDEX].Value;
3935 // Decrease resource limit
3936 for (int i = 0; i < MaxCosts; ++i) {
3937 if (player.MaxResources[i] != -1 && type.Stats[player.Index].Storing[i]) {
3938 const int newMaxValue = player.MaxResources[i] - type.Stats[player.Index].Storing[i];
3939
3940 player.MaxResources[i] = std::max(0, newMaxValue);
3941 player.SetResource(i, player.StoredResources[i], STORE_BUILDING);
3942 }
3943 }
3944 // Handle income improvements, look if a player loses a building
3945 // which have given him a better income, find the next best
3946 // income.
3947 for (int i = 1; i < MaxCosts; ++i) {
3948 if (player.Incomes[i] && type.Stats[player.Index].ImproveIncomes[i] == player.Incomes[i]) {
3949 int m = CResource::Resources[i]->DefaultIncome;
3950
3951 for (int j = 0; j < player.GetUnitCount(); ++j) {
3952 m = std::max(m, player.GetUnit(j).Type->Stats[player.Index].ImproveIncomes[i]);
3953 }
3954 player.Incomes[i] = m;
3955 }
3956 }
3957
3958 if (type.Stats[player.Index].Variables[TRADECOST_INDEX].Enable) {
3959 int m = DefaultTradeCost;
3960
3961 for (int j = 0; j < player.GetUnitCount(); ++j) {
3962 if (player.GetUnit(j).Type->Stats[player.Index].Variables[TRADECOST_INDEX].Enable) {
3963 m = std::min(m, player.GetUnit(j).Type->Stats[player.Index].Variables[TRADECOST_INDEX].Value);
3964 }
3965 }
3966 player.TradeCost = m;
3967 }
3968
3969 //Wyrmgus start
3970 if (type.BoolFlag[TOWNHALL_INDEX].value) {
3971 bool lost_town_hall = true;
3972 for (int j = 0; j < player.GetUnitCount(); ++j) {
3973 if (player.GetUnit(j).Type->BoolFlag[TOWNHALL_INDEX].value) {
3974 lost_town_hall = false;
3975 }
3976 }
3977 if (lost_town_hall && ThisPlayer->HasContactWith(player)) {
3978 player.LostTownHallTimer = GameCycle + (30 * CYCLES_PER_SECOND); //30 seconds until being revealed
3979 for (int j = 0; j < NumPlayers; ++j) {
3980 if (player.Index != j && Players[j].Type != PlayerNobody) {
3981 Players[j].Notify(_("%s has lost their last town hall, and will be revealed in thirty seconds!"), player.Name.c_str());
3982 } else {
3983 Players[j].Notify("%s", _("You have lost your last town hall, and will be revealed in thirty seconds!"));
3984 }
3985 }
3986 }
3987 }
3988 //Wyrmgus end
3989 }
3990
3991 // Handle order cancels.
3992 unit.CurrentOrder()->Cancel(unit);
3993
3994 DebugPrint("%d: Lost %s(%d)\n" _C_ player.Index _C_ type.Ident.c_str() _C_ UnitNumber(unit));
3995
3996 // Destroy resource-platform, must re-make resource patch.
3997 //Wyrmgus start
3998 // CBuildRestrictionOnTop *b = OnTopDetails(unit, nullptr);
3999 CBuildRestrictionOnTop *b = OnTopDetails(*unit.Type, nullptr);
4000 //Wyrmgus end
4001 if (b != nullptr) {
4002 //Wyrmgus start
4003 // if (b->ReplaceOnDie && (type.GivesResource && unit.ResourcesHeld != 0)) {
4004 if (b->ReplaceOnDie && (!type.GivesResource || unit.ResourcesHeld != 0)) {
4005 //Wyrmgus end
4006 CUnit *temp = MakeUnitAndPlace(unit.tilePos, *b->Parent, &Players[PlayerNumNeutral], unit.MapLayer->ID);
4007 if (temp == nullptr) {
4008 DebugPrint("Unable to allocate Unit");
4009 } else {
4010 //Wyrmgus start
4011 // temp->ResourcesHeld = unit.ResourcesHeld;
4012 // temp->Variable[GIVERESOURCE_INDEX].Value = unit.Variable[GIVERESOURCE_INDEX].Value;
4013 // temp->Variable[GIVERESOURCE_INDEX].Max = unit.Variable[GIVERESOURCE_INDEX].Max;
4014 // temp->Variable[GIVERESOURCE_INDEX].Enable = unit.Variable[GIVERESOURCE_INDEX].Enable;
4015 //Wyrmgus end
4016 //Wyrmgus start
4017 if (unit.Unique != nullptr) {
4018 temp->SetUnique(unit.Unique);
4019 } else {
4020 if (unit.Prefix != nullptr) {
4021 temp->SetPrefix(unit.Prefix);
4022 }
4023 if (unit.Suffix != nullptr) {
4024 temp->SetSuffix(unit.Suffix);
4025 }
4026 if (unit.Spell != nullptr) {
4027 temp->SetSpell(unit.Spell);
4028 }
4029 }
4030 if (unit.Settlement != nullptr) {
4031 if (unit.Type->BoolFlag[TOWNHALL_INDEX].value) {
4032 temp->Settlement = unit.Settlement;
4033 temp->Settlement->SiteUnit = temp;
4034 Map.SiteUnits.erase(std::remove(Map.SiteUnits.begin(), Map.SiteUnits.end(), &unit), Map.SiteUnits.end());
4035 Map.SiteUnits.push_back(temp);
4036 }
4037 }
4038 if (type.GivesResource && unit.ResourcesHeld != 0) {
4039 temp->SetResourcesHeld(unit.ResourcesHeld);
4040 temp->Variable[GIVERESOURCE_INDEX].Value = unit.Variable[GIVERESOURCE_INDEX].Value;
4041 temp->Variable[GIVERESOURCE_INDEX].Max = unit.Variable[GIVERESOURCE_INDEX].Max;
4042 temp->Variable[GIVERESOURCE_INDEX].Enable = unit.Variable[GIVERESOURCE_INDEX].Enable;
4043 }
4044 //Wyrmgus end
4045 }
4046 //Wyrmgus start
4047 } else if (unit.Settlement && unit.Settlement->SiteUnit == &unit) {
4048 unit.Settlement->SiteUnit = nullptr;
4049 //Wyrmgus end
4050 }
4051 }
4052 }
4053
4054 /**
4055 ** Removes all orders from a unit.
4056 **
4057 ** @param unit The unit that will have all its orders cleared
4058 */
UnitClearOrders(CUnit & unit)4059 void UnitClearOrders(CUnit &unit)
4060 {
4061 for (size_t i = 0; i != unit.Orders.size(); ++i) {
4062 delete unit.Orders[i];
4063 }
4064 unit.Orders.clear();
4065 unit.Orders.push_back(COrder::NewActionStill());
4066 }
4067
4068 /**
4069 ** Update for new unit. Food and income ...
4070 **
4071 ** @param unit New unit pointer.
4072 ** @param upgrade True unit was upgraded.
4073 */
UpdateForNewUnit(const CUnit & unit,int upgrade)4074 void UpdateForNewUnit(const CUnit &unit, int upgrade)
4075 {
4076 const CUnitType &type = *unit.Type;
4077 CPlayer &player = *unit.Player;
4078
4079 // Handle unit supply and max resources.
4080 // Note an upgraded unit can't give more supply.
4081 if (!upgrade) {
4082 player.Supply += unit.Variable[SUPPLY_INDEX].Value;
4083 for (int i = 0; i < MaxCosts; ++i) {
4084 if (player.MaxResources[i] != -1 && type.Stats[player.Index].Storing[i]) {
4085 player.MaxResources[i] += type.Stats[player.Index].Storing[i];
4086 }
4087 }
4088 }
4089
4090 // Update resources
4091 for (int u = 1; u < MaxCosts; ++u) {
4092 player.Incomes[u] = std::max(player.Incomes[u], type.Stats[player.Index].ImproveIncomes[u]);
4093 }
4094
4095 if (type.Stats[player.Index].Variables[TRADECOST_INDEX].Enable) {
4096 player.TradeCost = std::min(player.TradeCost, type.Stats[player.Index].Variables[TRADECOST_INDEX].Value);
4097 }
4098
4099 //Wyrmgus start
4100 if (player.LostTownHallTimer != 0 && type.BoolFlag[TOWNHALL_INDEX].value && ThisPlayer->HasContactWith(player)) {
4101 player.LostTownHallTimer = 0;
4102 player.Revealed = false;
4103 for (int j = 0; j < NumPlayers; ++j) {
4104 if (player.Index != j && Players[j].Type != PlayerNobody) {
4105 Players[j].Notify(_("%s has rebuilt a town hall, and will no longer be revealed!"), player.Name.c_str());
4106 } else {
4107 Players[j].Notify("%s", _("You have rebuilt a town hall, and will no longer be revealed!"));
4108 }
4109 }
4110 }
4111 //Wyrmgus end
4112 }
4113
4114 /**
4115 ** Find nearest point of unit.
4116 **
4117 ** @param unit Pointer to unit.
4118 ** @param pos tile map position.
4119 ** @param dpos Out: nearest point tile map position to (tx,ty).
4120 */
NearestOfUnit(const CUnit & unit,const Vec2i & pos,Vec2i * dpos)4121 void NearestOfUnit(const CUnit &unit, const Vec2i &pos, Vec2i *dpos)
4122 {
4123 const int x = unit.tilePos.x;
4124 const int y = unit.tilePos.y;
4125
4126 *dpos = pos;
4127 clamp<short int>(&dpos->x, x, x + unit.Type->TileSize.x - 1);
4128 clamp<short int>(&dpos->y, y, y + unit.Type->TileSize.y - 1);
4129 }
4130
4131 /**
4132 ** Copy the unit look in Seen variables. This should be called when
4133 ** buildings go under fog of war for ThisPlayer.
4134 **
4135 ** @param unit The unit to work on
4136 */
UnitFillSeenValues(CUnit & unit)4137 static void UnitFillSeenValues(CUnit &unit)
4138 {
4139 // Seen values are undefined for visible units.
4140 unit.Seen.tilePos = unit.tilePos;
4141 unit.Seen.IY = unit.IY;
4142 unit.Seen.IX = unit.IX;
4143 unit.Seen.Frame = unit.Frame;
4144 unit.Seen.Type = unit.Type;
4145 unit.Seen.UnderConstruction = unit.UnderConstruction;
4146
4147 unit.CurrentOrder()->FillSeenValues(unit);
4148 }
4149
4150 // Wall unit positions
4151 enum {
4152 W_NORTH = 0x10,
4153 W_WEST = 0x20,
4154 W_SOUTH = 0x40,
4155 W_EAST = 0x80
4156 };
4157
4158 /**
4159 ** Correct direction for placed wall.
4160 **
4161 ** @param unit The wall unit.
4162 */
CorrectWallDirections(CUnit & unit)4163 void CorrectWallDirections(CUnit &unit)
4164 {
4165 Assert(unit.Type->BoolFlag[WALL_INDEX].value);
4166 Assert(unit.Type->NumDirections == 16);
4167 Assert(!unit.Type->Flip);
4168
4169 if (!Map.Info.IsPointOnMap(unit.tilePos, unit.MapLayer)) {
4170 return;
4171 }
4172 const struct {
4173 Vec2i offset;
4174 const int dirFlag;
4175 } configs[] = {{Vec2i(0, -1), W_NORTH}, {Vec2i(1, 0), W_EAST},
4176 {Vec2i(0, 1), W_SOUTH}, {Vec2i(-1, 0), W_WEST}
4177 };
4178 int flags = 0;
4179
4180 for (int i = 0; i != sizeof(configs) / sizeof(*configs); ++i) {
4181 const Vec2i pos = unit.tilePos + configs[i].offset;
4182 const int dirFlag = configs[i].dirFlag;
4183
4184 if (Map.Info.IsPointOnMap(pos, unit.MapLayer) == false) {
4185 flags |= dirFlag;
4186 } else {
4187 const CUnitCache &unitCache = Map.Field(pos, unit.MapLayer->ID)->UnitCache;
4188 const CUnit *neighbor = unitCache.find(HasSamePlayerAndTypeAs(unit));
4189
4190 if (neighbor != nullptr) {
4191 flags |= dirFlag;
4192 }
4193 }
4194 }
4195 unit.Direction = flags;
4196 }
4197
4198 /**
4199 ** Correct the surrounding walls.
4200 **
4201 ** @param unit The wall unit.
4202 */
CorrectWallNeighBours(CUnit & unit)4203 void CorrectWallNeighBours(CUnit &unit)
4204 {
4205 Assert(unit.Type->BoolFlag[WALL_INDEX].value);
4206
4207 const Vec2i offset[] = {Vec2i(1, 0), Vec2i(-1, 0), Vec2i(0, 1), Vec2i(0, -1)};
4208
4209 for (unsigned int i = 0; i < sizeof(offset) / sizeof(*offset); ++i) {
4210 const Vec2i pos = unit.tilePos + offset[i];
4211
4212 if (Map.Info.IsPointOnMap(pos, unit.MapLayer) == false) {
4213 continue;
4214 }
4215 CUnitCache &unitCache = unit.MapLayer->Field(pos)->UnitCache;
4216 CUnit *neighbor = unitCache.find(HasSamePlayerAndTypeAs(unit));
4217
4218 if (neighbor != nullptr) {
4219 CorrectWallDirections(*neighbor);
4220 UnitUpdateHeading(*neighbor);
4221 }
4222 }
4223 }
4224
4225 /**
4226 ** This function should get called when a unit goes under fog of war.
4227 **
4228 ** @param unit The unit that goes under fog.
4229 ** @param player The player the unit goes out of fog for.
4230 */
UnitGoesUnderFog(CUnit & unit,const CPlayer & player)4231 void UnitGoesUnderFog(CUnit &unit, const CPlayer &player)
4232 {
4233 if (unit.Type->BoolFlag[VISIBLEUNDERFOG_INDEX].value) {
4234 if (player.Type == PlayerPerson && !unit.Destroyed) {
4235 unit.RefsIncrease();
4236 }
4237 //
4238 // Icky yucky icky Seen.Destroyed trickery.
4239 // We track for each player if he's seen the unit as destroyed.
4240 // Remember, a unit is marked Destroyed when it's gone as in
4241 // completely gone, the corpses vanishes. In that case the unit
4242 // only survives since some players did NOT see the unit destroyed.
4243 // Keeping trackof that is hard, mostly due to complex shared vision
4244 // configurations.
4245 // A unit does NOT get a reference when it goes under fog if it's
4246 // Destroyed. Furthermore, it shouldn't lose a reference if it was
4247 // Seen destroyed. That only happened with complex shared vision, and
4248 // it's sort of the whole point of this tracking.
4249 //
4250 if (unit.Destroyed) {
4251 unit.Seen.Destroyed |= (1 << player.Index);
4252 }
4253 if (&player == ThisPlayer) {
4254 UnitFillSeenValues(unit);
4255 }
4256 }
4257 }
4258
4259 /**
4260 ** This function should get called when a unit goes out of fog of war.
4261 **
4262 ** @param unit The unit that goes out of fog.
4263 ** @param player The player the unit goes out of fog for.
4264 **
4265 ** @note For units that are visible under fog (mostly buildings)
4266 ** we use reference counts, from the players that know about
4267 ** the building. When an building goes under fog it gets a refs
4268 ** increase, and when it shows up it gets a decrease. It must
4269 ** not get an decrease the first time it's seen, so we have to
4270 ** keep track of what player saw what units, with SeenByPlayer.
4271 */
UnitGoesOutOfFog(CUnit & unit,const CPlayer & player)4272 void UnitGoesOutOfFog(CUnit &unit, const CPlayer &player)
4273 {
4274 if (!unit.Type->BoolFlag[VISIBLEUNDERFOG_INDEX].value) {
4275 return;
4276 }
4277 if (unit.Seen.ByPlayer & (1 << (player.Index))) {
4278 if ((player.Type == PlayerPerson) && (!(unit.Seen.Destroyed & (1 << player.Index)))) {
4279 unit.RefsDecrease();
4280 }
4281 } else {
4282 unit.Seen.ByPlayer |= (1 << (player.Index));
4283 }
4284 }
4285
4286 /**
4287 ** Recalculates a units visiblity count. This happens really often,
4288 ** Like every time a unit moves. It's really fast though, since we
4289 ** have per-tile counts.
4290 **
4291 ** @param unit pointer to the unit to check if seen
4292 */
UnitCountSeen(CUnit & unit)4293 void UnitCountSeen(CUnit &unit)
4294 {
4295 Assert(unit.Type);
4296
4297 // FIXME: optimize, only work on certain players?
4298 // This is for instance good for updating shared vision...
4299
4300 // Store old values in oldv[p]. This store if the player could see the
4301 // unit before this calc.
4302 int oldv[PlayerMax];
4303 for (int p = 0; p < PlayerMax; ++p) {
4304 if (Players[p].Type != PlayerNobody) {
4305 oldv[p] = unit.IsVisible(Players[p]);
4306 }
4307 }
4308
4309 // Calculate new VisCount values.
4310 const int height = unit.Type->TileSize.y;
4311 const int width = unit.Type->TileSize.x;
4312
4313 for (int p = 0; p < PlayerMax; ++p) {
4314 if (Players[p].Type != PlayerNobody) {
4315 int newv = 0;
4316 int y = height;
4317 unsigned int index = unit.Offset;
4318 do {
4319 CMapField *mf = unit.MapLayer->Field(index);
4320 int x = width;
4321 do {
4322 if (unit.Type->BoolFlag[PERMANENTCLOAK_INDEX].value && unit.Player != &Players[p]) {
4323 if (mf->playerInfo.VisCloak[p]) {
4324 newv++;
4325 }
4326 //Wyrmgus start
4327 } else if (unit.Type->BoolFlag[ETHEREAL_INDEX].value && unit.Player != &Players[p]) {
4328 if (mf->playerInfo.VisEthereal[p]) {
4329 newv++;
4330 }
4331 //Wyrmgus end
4332 } else {
4333 if (mf->playerInfo.IsVisible(Players[p])) {
4334 newv++;
4335 }
4336 }
4337 ++mf;
4338 } while (--x);
4339 index += unit.MapLayer->GetWidth();
4340 } while (--y);
4341 unit.VisCount[p] = newv;
4342 }
4343 }
4344
4345 //
4346 // Now here comes the tricky part. We have to go in and out of fog
4347 // for players. Hopefully this works with shared vision just great.
4348 //
4349 for (int p = 0; p < PlayerMax; ++p) {
4350 if (Players[p].Type != PlayerNobody) {
4351 int newv = unit.IsVisible(Players[p]);
4352 if (!oldv[p] && newv) {
4353 // Might have revealed a destroyed unit which caused it to
4354 // be released
4355 if (!unit.Type) {
4356 break;
4357 }
4358 UnitGoesOutOfFog(unit, Players[p]);
4359 }
4360 if (oldv[p] && !newv) {
4361 UnitGoesUnderFog(unit, Players[p]);
4362 }
4363 }
4364 }
4365 }
4366
4367 /**
4368 ** Returns true, if the unit is visible. It check the Viscount of
4369 ** the player and everyone who shares vision with him.
4370 **
4371 ** @note This understands shared vision, and should be used all around.
4372 **
4373 ** @param player The player to check.
4374 */
IsVisible(const CPlayer & player) const4375 bool CUnit::IsVisible(const CPlayer &player) const
4376 {
4377 if (VisCount[player.Index]) {
4378 return true;
4379 }
4380 for (int p = 0; p < PlayerMax; ++p) {
4381 //Wyrmgus start
4382 // if (p != player.Index && player.IsBothSharedVision(Players[p])) {
4383 if (p != player.Index && (player.IsBothSharedVision(Players[p]) || Players[p].Revealed)) {
4384 //Wyrmgus end
4385 if (VisCount[p]) {
4386 return true;
4387 }
4388 }
4389 }
4390 return false;
4391 }
4392
4393 /**
4394 ** Returns true, if unit is shown on minimap.
4395 **
4396 ** @warning This is for ::ThisPlayer only.
4397 ** @todo radar support
4398 **
4399 ** @return True if visible, false otherwise.
4400 */
IsVisibleOnMinimap() const4401 bool CUnit::IsVisibleOnMinimap() const
4402 {
4403 //Wyrmgus start
4404 if (this->MapLayer != UI.CurrentMapLayer) {
4405 return false;
4406 }
4407 //Wyrmgus end
4408
4409 // Invisible units.
4410 if (IsInvisibile(*ThisPlayer)) {
4411 return false;
4412 }
4413 if (IsVisible(*ThisPlayer) || ReplayRevealMap || IsVisibleOnRadar(*ThisPlayer)) {
4414 return IsAliveOnMap();
4415 } else {
4416 return Type->BoolFlag[VISIBLEUNDERFOG_INDEX].value && Seen.State != 3
4417 && (Seen.ByPlayer & (1 << ThisPlayer->Index))
4418 //Wyrmgus start
4419 // && !(Seen.Destroyed & (1 << ThisPlayer->Index));
4420 && !(Seen.Destroyed & (1 << ThisPlayer->Index))
4421 && !Destroyed
4422 && Map.Info.IsPointOnMap(this->tilePos, this->MapLayer)
4423 && this->MapLayer->Field(this->tilePos)->playerInfo.IsTeamExplored(*ThisPlayer);
4424 //Wyrmgus end
4425 }
4426 }
4427
4428 /**
4429 ** Returns true, if unit is visible in viewport.
4430 **
4431 ** @warning This is only true for ::ThisPlayer
4432 **
4433 ** @param vp Viewport pointer.
4434 **
4435 ** @return True if visible, false otherwise.
4436 */
IsVisibleInViewport(const CViewport & vp) const4437 bool CUnit::IsVisibleInViewport(const CViewport &vp) const
4438 {
4439 // Check if the graphic is inside the viewport.
4440 //Wyrmgus start
4441 // int x = tilePos.x * Map.GetMapLayerPixelTileSize(this->MapLayer).x + IX - (Type->Width - Type->TileSize.x * Map.GetMapLayerPixelTileSize(this->MapLayer).x) / 2 + Type->OffsetX;
4442 // int y = tilePos.y * Map.GetMapLayerPixelTileSize(this->MapLayer).y + IY - (Type->Height - Type->TileSize.y * Map.GetMapLayerPixelTileSize(this->MapLayer).y) / 2 + Type->OffsetY;
4443
4444 int frame_width = Type->Width;
4445 int frame_height = Type->Height;
4446 const CUnitTypeVariation *variation = this->GetVariation();
4447 if (variation && variation->FrameWidth && variation->FrameHeight) {
4448 frame_width = variation->FrameWidth;
4449 frame_height = variation->FrameHeight;
4450 }
4451
4452 int x = tilePos.x * Map.GetMapLayerPixelTileSize(this->MapLayer->ID).x + IX - (frame_width - Type->TileSize.x * Map.GetMapLayerPixelTileSize(this->MapLayer->ID).x) / 2 + Type->OffsetX;
4453 int y = tilePos.y * Map.GetMapLayerPixelTileSize(this->MapLayer->ID).y + IY - (frame_height - Type->TileSize.y * Map.GetMapLayerPixelTileSize(this->MapLayer->ID).y) / 2 + Type->OffsetY;
4454 //Wyrmgus end
4455 const PixelSize vpSize = vp.GetPixelSize();
4456 const PixelPos vpTopLeftMapPos = Map.TilePosToMapPixelPos_TopLeft(vp.MapPos, UI.CurrentMapLayer) + vp.Offset;
4457 const PixelPos vpBottomRightMapPos = vpTopLeftMapPos + vpSize;
4458
4459 //Wyrmgus start
4460 // if (x + Type->Width < vpTopLeftMapPos.x || x > vpBottomRightMapPos.x
4461 // || y + Type->Height < vpTopLeftMapPos.y || y > vpBottomRightMapPos.y) {
4462 if (x + frame_width < vpTopLeftMapPos.x || x > vpBottomRightMapPos.x
4463 || y + frame_height < vpTopLeftMapPos.y || y > vpBottomRightMapPos.y) {
4464 //Wyrmgus end
4465 return false;
4466 }
4467
4468 if (!ThisPlayer) {
4469 //FIXME: ARI: Added here for early game setup state by
4470 // MakeAndPlaceUnit() from LoadMap(). ThisPlayer not yet set,
4471 // so don't show anything until first real map-draw.
4472 DebugPrint("FIXME: ThisPlayer not set yet?!\n");
4473 return false;
4474 }
4475
4476 // Those are never ever visible.
4477 if (IsInvisibile(*ThisPlayer)) {
4478 return false;
4479 }
4480
4481 if (IsVisible(*ThisPlayer) || ReplayRevealMap) {
4482 return !Destroyed;
4483 } else {
4484 // Unit has to be 'discovered'
4485 // Destroyed units ARE visible under fog of war, if we haven't seen them like that.
4486 if (!Destroyed || !(Seen.Destroyed & (1 << ThisPlayer->Index))) {
4487 return (Type->BoolFlag[VISIBLEUNDERFOG_INDEX].value && (Seen.ByPlayer & (1 << ThisPlayer->Index)));
4488 } else {
4489 return false;
4490 }
4491 }
4492 }
4493
4494 /**
4495 ** Change the unit's owner
4496 **
4497 ** @param newplayer New owning player.
4498 */
4499 //Wyrmgus start
4500 //void CUnit::ChangeOwner(CPlayer &newplayer)
ChangeOwner(CPlayer & newplayer,bool show_change)4501 void CUnit::ChangeOwner(CPlayer &newplayer, bool show_change)
4502 //Wyrmgus end
4503 {
4504 CPlayer *oldplayer = Player;
4505
4506 // This shouldn't happen
4507 if (oldplayer == &newplayer) {
4508 DebugPrint("Change the unit owner to the same player???\n");
4509 return;
4510 }
4511
4512 // Can't change owner for dead units
4513 if (this->IsAlive() == false) {
4514 return;
4515 }
4516
4517 // Rescue all units in buildings/transporters.
4518 //Wyrmgus start
4519 // CUnit *uins = UnitInside;
4520 // for (int i = InsideCount; i; --i, uins = uins->NextContained) {
4521 // uins->ChangeOwner(newplayer);
4522 // }
4523
4524 //only rescue units inside if the player is actually a rescuable player (to avoid, for example, unintended worker owner changes when a depot changes hands)
4525 if (oldplayer->Type == PlayerRescueActive || oldplayer->Type == PlayerRescuePassive) {
4526 CUnit *uins = UnitInside;
4527 for (int i = InsideCount; i; --i, uins = uins->NextContained) {
4528 uins->ChangeOwner(newplayer);
4529 }
4530 }
4531 //Wyrmgus end
4532
4533 // Must change food/gold and other.
4534 UnitLost(*this);
4535
4536 // Now the new side!
4537
4538 if (Type->BoolFlag[BUILDING_INDEX].value) {
4539 //Wyrmgus start
4540 // if (!Type->BoolFlag[WALL_INDEX].value) {
4541 //Wyrmgus end
4542 newplayer.TotalBuildings++;
4543 //Wyrmgus start
4544 // }
4545 //Wyrmgus end
4546 } else {
4547 newplayer.TotalUnits++;
4548 }
4549
4550 MapUnmarkUnitSight(*this);
4551 newplayer.AddUnit(*this);
4552 Stats = &Type->Stats[newplayer.Index];
4553
4554 // Must change food/gold and other.
4555 //Wyrmgus start
4556 // if (Type->GivesResource) {
4557 if (this->GivesResource) {
4558 //Wyrmgus end
4559 DebugPrint("Resource transfer not supported\n");
4560 }
4561 newplayer.Demand += Type->Stats[newplayer.Index].Variables[DEMAND_INDEX].Value;
4562 newplayer.Supply += this->Variable[SUPPLY_INDEX].Value;
4563 // Increase resource limit
4564 for (int i = 0; i < MaxCosts; ++i) {
4565 if (newplayer.MaxResources[i] != -1 && Type->Stats[newplayer.Index].Storing[i]) {
4566 newplayer.MaxResources[i] += Type->Stats[newplayer.Index].Storing[i];
4567 }
4568 }
4569 //Wyrmgus start
4570 // if (Type->BoolFlag[BUILDING_INDEX].value && !Type->BoolFlag[WALL_INDEX].value) {
4571 if (Type->BoolFlag[BUILDING_INDEX].value) {
4572 //Wyrmgus end
4573 newplayer.NumBuildings++;
4574 }
4575 //Wyrmgus start
4576 if (CurrentAction() == UnitActionBuilt) {
4577 newplayer.NumBuildingsUnderConstruction++;
4578 newplayer.ChangeUnitTypeUnderConstructionCount(this->Type, 1);
4579 }
4580 //Wyrmgus end
4581
4582 //apply upgrades of the new player, if the old one doesn't have that upgrade
4583 for (const CUpgradeModifier *modifier : CUpgradeModifier::UpgradeModifiers) {
4584 const CUpgrade *modifier_upgrade = AllUpgrades[modifier->UpgradeId];
4585 if (oldplayer->Allow.Upgrades[modifier_upgrade->ID] != 'R' && newplayer.Allow.Upgrades[modifier_upgrade->ID] == 'R' && modifier->ApplyTo[Type->Slot] == 'X') { //if the old player doesn't have the modifier's upgrade (but the new one does), and the upgrade is applicable to the unit
4586 //Wyrmgus start
4587 // ApplyIndividualUpgradeModifier(*this, modifier);
4588 if ( // don't apply equipment-related upgrades if the unit has an item of that equipment type equipped
4589 (!modifier_upgrade->Weapon || EquippedItems[WeaponItemSlot].size() == 0)
4590 && (!modifier_upgrade->Shield || EquippedItems[ShieldItemSlot].size() == 0)
4591 && (!modifier_upgrade->Boots || EquippedItems[BootsItemSlot].size() == 0)
4592 && (!modifier_upgrade->Arrows || EquippedItems[ArrowsItemSlot].size() == 0)
4593 && !(newplayer.Race != -1 && modifier_upgrade->Ident == PlayerRaces.CivilizationUpgrades[newplayer.Race])
4594 && !(newplayer.Race != -1 && newplayer.Faction != -1 && modifier_upgrade->Ident == PlayerRaces.Factions[newplayer.Faction]->FactionUpgrade)
4595 ) {
4596 ApplyIndividualUpgradeModifier(*this, modifier);
4597 }
4598 //Wyrmgus end
4599 }
4600 }
4601
4602 newplayer.IncreaseCountsForUnit(this);
4603 UpdateForNewUnit(*this, 1);
4604
4605 UpdateUnitSightRange(*this);
4606 MapMarkUnitSight(*this);
4607
4608 //not very elegant way to make sure the tile ownership is calculated correctly
4609 MapUnmarkUnitSight(*this);
4610 MapMarkUnitSight(*this);
4611
4612 //Wyrmgus start
4613 if (newplayer.Index == ThisPlayer->Index && show_change) {
4614 this->Blink = 5;
4615 PlayGameSound(GameSounds.Rescue[newplayer.Race].Sound, MaxSampleVolume);
4616 }
4617 //Wyrmgus end
4618 }
4619
IsMineAssignedBy(const CUnit & mine,const CUnit & worker)4620 static bool IsMineAssignedBy(const CUnit &mine, const CUnit &worker)
4621 {
4622 for (CUnit *it = mine.Resource.Workers; it; it = it->NextWorker) {
4623 if (it == &worker) {
4624 return true;
4625 }
4626 }
4627 return false;
4628 }
4629
4630
AssignWorkerToMine(CUnit & mine)4631 void CUnit::AssignWorkerToMine(CUnit &mine)
4632 {
4633 if (IsMineAssignedBy(mine, *this) == true) {
4634 return;
4635 }
4636 Assert(this->NextWorker == nullptr);
4637
4638 CUnit *head = mine.Resource.Workers;
4639 #if 0
4640 DebugPrint("%d: Worker [%d] is adding into %s [%d] on %d pos\n"
4641 _C_ this->Player->Index _C_ this->Slot
4642 _C_ mine.Type->Name.c_str()
4643 _C_ mine.Slot
4644 _C_ mine.Data.Resource.Assigned);
4645 #endif
4646 this->RefsIncrease();
4647 this->NextWorker = head;
4648 mine.Resource.Workers = this;
4649 mine.Resource.Assigned++;
4650 }
4651
DeAssignWorkerFromMine(CUnit & mine)4652 void CUnit::DeAssignWorkerFromMine(CUnit &mine)
4653 {
4654 if (IsMineAssignedBy(mine, *this) == false) {
4655 return ;
4656 }
4657 CUnit *prev = nullptr, *worker = mine.Resource.Workers;
4658 #if 0
4659 DebugPrint("%d: Worker [%d] is removing from %s [%d] left %d units assigned\n"
4660 _C_ this->Player->Index _C_ this->Slot
4661 _C_ mine.Type->Name.c_str()
4662 _C_ mine.Slot
4663 _C_ mine.CurrentOrder()->Data.Resource.Assigned);
4664 #endif
4665 for (int i = 0; nullptr != worker; worker = worker->NextWorker, ++i) {
4666 if (worker == this) {
4667 CUnit *next = worker->NextWorker;
4668 worker->NextWorker = nullptr;
4669 if (prev) {
4670 prev->NextWorker = next;
4671 }
4672 if (worker == mine.Resource.Workers) {
4673 mine.Resource.Workers = next;
4674 }
4675 worker->RefsDecrease();
4676 break;
4677 }
4678 prev = worker;
4679 Assert(i <= mine.Resource.Assigned);
4680 }
4681 mine.Resource.Assigned--;
4682 Assert(mine.Resource.Assigned >= 0);
4683 }
4684
4685
4686 /**
4687 ** Change the owner of all units of a player.
4688 **
4689 ** @param oldplayer Old owning player.
4690 ** @param newplayer New owning player.
4691 */
ChangePlayerOwner(CPlayer & oldplayer,CPlayer & newplayer)4692 static void ChangePlayerOwner(CPlayer &oldplayer, CPlayer &newplayer)
4693 {
4694 if (&oldplayer == &newplayer) {
4695 return ;
4696 }
4697
4698 for (int i = 0; i != oldplayer.GetUnitCount(); ++i) {
4699 CUnit &unit = oldplayer.GetUnit(i);
4700
4701 unit.Blink = 5;
4702 unit.RescuedFrom = &oldplayer;
4703 }
4704 // ChangeOwner remove unit from the player: so change the array.
4705 while (oldplayer.GetUnitCount() != 0) {
4706 CUnit &unit = oldplayer.GetUnit(0);
4707
4708 unit.ChangeOwner(newplayer);
4709 }
4710 }
4711
4712 /**
4713 ** Rescue units.
4714 **
4715 ** Look through all rescueable players, if they could be rescued.
4716 */
RescueUnits()4717 void RescueUnits()
4718 {
4719 if (NoRescueCheck) { // all possible units are rescued
4720 return;
4721 }
4722 NoRescueCheck = true;
4723
4724 // Look if player could be rescued.
4725 for (CPlayer *p = Players; p < Players + NumPlayers; ++p) {
4726 if (p->Type != PlayerRescuePassive && p->Type != PlayerRescueActive) {
4727 continue;
4728 }
4729 if (p->GetUnitCount() != 0) {
4730 NoRescueCheck = false;
4731 // NOTE: table is changed.
4732 std::vector<CUnit *> table;
4733 table.insert(table.begin(), p->UnitBegin(), p->UnitEnd());
4734
4735 const size_t l = table.size();
4736 for (size_t j = 0; j != l; ++j) {
4737 CUnit &unit = *table[j];
4738 // Do not rescue removed units. Units inside something are
4739 // rescued by ChangeUnitOwner
4740 if (unit.Removed) {
4741 continue;
4742 }
4743 std::vector<CUnit *> around;
4744
4745 SelectAroundUnit(unit, 1, around);
4746 // Look if ally near the unit.
4747 for (size_t i = 0; i != around.size(); ++i) {
4748 //Wyrmgus start
4749 // if (around[i]->Type->CanAttack && unit.IsAllied(*around[i]) && around[i]->Player->Type != PlayerRescuePassive && around[i]->Player->Type != PlayerRescueActive) {
4750 if (around[i]->CanAttack() && unit.IsAllied(*around[i]) && around[i]->Player->Type != PlayerRescuePassive && around[i]->Player->Type != PlayerRescueActive) {
4751 //Wyrmgus end
4752 // City center converts complete race
4753 // NOTE: I use a trick here, centers could
4754 // store gold. FIXME!!!
4755 //Wyrmgus start
4756 // if (unit.Type->CanStore[GoldCost]) {
4757 if (unit.Type->BoolFlag[TOWNHALL_INDEX].value) {
4758 //Wyrmgus end
4759 ChangePlayerOwner(*p, *around[i]->Player);
4760 break;
4761 }
4762 unit.RescuedFrom = unit.Player;
4763 //Wyrmgus start
4764 // unit.ChangeOwner(*around[i]->Player);
4765 unit.ChangeOwner(*around[i]->Player, true);
4766 // unit.Blink = 5;
4767 // PlayGameSound(GameSounds.Rescue[unit.Player->Race].Sound, MaxSampleVolume);
4768 //Wyrmgus end
4769 break;
4770 }
4771 }
4772 }
4773 }
4774 }
4775 }
4776
4777 /*----------------------------------------------------------------------------
4778 -- Unit headings
4779 ----------------------------------------------------------------------------*/
4780
4781 /**
4782 ** Fast arc tangent function.
4783 **
4784 ** @param val atan argument
4785 **
4786 ** @return atan(val)
4787 */
myatan(int val)4788 static int myatan(int val)
4789 {
4790 static int init;
4791 static unsigned char atan_table[2608];
4792
4793 if (val >= 2608) {
4794 return 63;
4795 }
4796 if (!init) {
4797 for (; init < 2608; ++init) {
4798 atan_table[init] =
4799 (unsigned char)(atan((double)init / 64) * (64 * 4 / 6.2831853));
4800 }
4801 }
4802
4803 return atan_table[val];
4804 }
4805
4806 /**
4807 ** Convert direction to heading.
4808 **
4809 ** @param delta Delta.
4810 **
4811 ** @return Angle (0..255)
4812 */
DirectionToHeading(const Vec2i & delta)4813 int DirectionToHeading(const Vec2i &delta)
4814 {
4815 // Check which quadrant.
4816 if (delta.x > 0) {
4817 if (delta.y < 0) { // Quadrant 1?
4818 return myatan((delta.x * 64) / -delta.y);
4819 }
4820 // Quadrant 2?
4821 return myatan((delta.y * 64) / delta.x) + 64;
4822 }
4823 if (delta.y > 0) { // Quadrant 3?
4824 return myatan((delta.x * -64) / delta.y) + 64 * 2;
4825 }
4826 if (delta.x) { // Quadrant 4.
4827 return myatan((delta.y * -64) / -delta.x) + 64 * 3;
4828 }
4829 return 0;
4830 }
4831
4832 /**
4833 ** Convert direction to heading.
4834 **
4835 ** @param delta Delta.
4836 **
4837 ** @return Angle (0..255)
4838 */
DirectionToHeading(const PixelDiff & delta)4839 int DirectionToHeading(const PixelDiff &delta)
4840 {
4841 // code is identic for Vec2i and PixelDiff
4842 Vec2i delta2(delta.x, delta.y);
4843 return DirectionToHeading(delta2);
4844 }
4845
4846 /**
4847 ** Update sprite frame for new heading.
4848 */
UnitUpdateHeading(CUnit & unit)4849 void UnitUpdateHeading(CUnit &unit)
4850 {
4851 //Wyrmgus start
4852 //fix direction if it does not correspond to one of the defined directions
4853 int num_dir = std::max<int>(8, unit.Type->NumDirections);
4854 if (unit.Direction % (256 / num_dir) != 0) {
4855 unit.Direction = unit.Direction - (unit.Direction % (256 / num_dir));
4856 }
4857 //Wyrmgus end
4858
4859 int dir;
4860 int nextdir;
4861 bool neg;
4862
4863 if (unit.Frame < 0) {
4864 unit.Frame = -unit.Frame - 1;
4865 neg = true;
4866 } else {
4867 neg = false;
4868 }
4869 unit.Frame /= unit.Type->NumDirections / 2 + 1;
4870 unit.Frame *= unit.Type->NumDirections / 2 + 1;
4871 // Remove heading, keep animation frame
4872
4873 nextdir = 256 / unit.Type->NumDirections;
4874 dir = ((unit.Direction + nextdir / 2) & 0xFF) / nextdir;
4875 if (dir <= LookingS / nextdir) { // north->east->south
4876 unit.Frame += dir;
4877 } else {
4878 unit.Frame += 256 / nextdir - dir;
4879 unit.Frame = -unit.Frame - 1;
4880 }
4881 if (neg && !unit.Frame && unit.Type->BoolFlag[BUILDING_INDEX].value) {
4882 unit.Frame = -1;
4883 }
4884 }
4885
4886 /**
4887 ** Change unit heading/frame from delta direction x, y.
4888 **
4889 ** @param unit Unit for new direction looking.
4890 ** @param delta map tile delta direction.
4891 */
UnitHeadingFromDeltaXY(CUnit & unit,const Vec2i & delta)4892 void UnitHeadingFromDeltaXY(CUnit &unit, const Vec2i &delta)
4893 {
4894 //Wyrmgus start
4895 // unit.Direction = DirectionToHeading(delta);
4896 int num_dir = std::max<int>(8, unit.Type->NumDirections);
4897 int heading = DirectionToHeading(delta) + ((256 / num_dir) / 2);
4898 if (heading % (256 / num_dir) != 0) {
4899 heading = heading - (heading % (256 / num_dir));
4900 }
4901 unit.Direction = heading;
4902 //Wyrmgus end
4903 UnitUpdateHeading(unit);
4904 }
4905
4906 /*----------------------------------------------------------------------------
4907 -- Drop out units
4908 ----------------------------------------------------------------------------*/
4909
4910 /**
4911 ** Place a unit on the map to the side of a unit.
4912 **
4913 ** @param unit Unit to drop out.
4914 ** @param heading Direction in which the unit should appear.
4915 ** @param container Unit "containing" unit to drop (may be different of unit.Container).
4916 */
DropOutOnSide(CUnit & unit,int heading,const CUnit * container)4917 void DropOutOnSide(CUnit &unit, int heading, const CUnit *container)
4918 {
4919 Vec2i pos;
4920 int addx = 0;
4921 int addy = 0;
4922 //Wyrmgus start
4923 int z;
4924 //Wyrmgus end
4925
4926 if (container) {
4927 pos = container->tilePos;
4928 pos -= unit.Type->TileSize - 1;
4929 addx = container->Type->TileSize.x + unit.Type->TileSize.x - 1;
4930 addy = container->Type->TileSize.y + unit.Type->TileSize.y - 1;
4931 z = container->MapLayer->ID;
4932
4933 if (heading < LookingNE || heading > LookingNW) {
4934 pos.x += addx - 1;
4935 --pos.y;
4936 goto startn;
4937 } else if (heading < LookingSE) {
4938 pos.x += addx;
4939 pos.y += addy - 1;
4940 goto starte;
4941 } else if (heading < LookingSW) {
4942 pos.y += addy;
4943 goto starts;
4944 } else {
4945 --pos.x;
4946 goto startw;
4947 }
4948 } else {
4949 pos = unit.tilePos;
4950 z = unit.MapLayer->ID;
4951
4952 if (heading < LookingNE || heading > LookingNW) {
4953 goto starts;
4954 } else if (heading < LookingSE) {
4955 goto startw;
4956 } else if (heading < LookingSW) {
4957 goto startn;
4958 } else {
4959 goto starte;
4960 }
4961 }
4962 // FIXME: don't search outside of the map
4963 for (;;) {
4964 startw:
4965 for (int i = addy; i--; ++pos.y) {
4966 //Wyrmgus start
4967 // if (UnitCanBeAt(unit, pos)) {
4968 if (UnitCanBeAt(unit, pos, z)) {
4969 //Wyrmgus end
4970 goto found;
4971 }
4972 }
4973 ++addx;
4974 starts:
4975 for (int i = addx; i--; ++pos.x) {
4976 //Wyrmgus start
4977 // if (UnitCanBeAt(unit, pos)) {
4978 if (UnitCanBeAt(unit, pos, z)) {
4979 //Wyrmgus end
4980 goto found;
4981 }
4982 }
4983 ++addy;
4984 starte:
4985 for (int i = addy; i--; --pos.y) {
4986 //Wyrmgus start
4987 // if (UnitCanBeAt(unit, pos)) {
4988 if (UnitCanBeAt(unit, pos, z)) {
4989 //Wyrmgus end
4990 goto found;
4991 }
4992 }
4993 ++addx;
4994 startn:
4995 for (int i = addx; i--; --pos.x) {
4996 //Wyrmgus start
4997 // if (UnitCanBeAt(unit, pos)) {
4998 if (UnitCanBeAt(unit, pos, z)) {
4999 //Wyrmgus end
5000 goto found;
5001 }
5002 }
5003 ++addy;
5004 }
5005
5006 found:
5007 //Wyrmgus start
5008 // unit.Place(pos);
5009 unit.Place(pos, z);
5010 //Wyrmgus end
5011 }
5012
5013 /**
5014 ** Place a unit on the map nearest to goalPos.
5015 **
5016 ** @param unit Unit to drop out.
5017 ** @param goalPos Goal map tile position.
5018 ** @param addx Tile width of unit it's dropping out of.
5019 ** @param addy Tile height of unit it's dropping out of.
5020 */
DropOutNearest(CUnit & unit,const Vec2i & goalPos,const CUnit * container)5021 void DropOutNearest(CUnit &unit, const Vec2i &goalPos, const CUnit *container)
5022 {
5023 Vec2i pos;
5024 Vec2i bestPos;
5025 int bestd = 99999;
5026 int addx = 0;
5027 int addy = 0;
5028 //Wyrmgus start
5029 int z;
5030 //Wyrmgus end
5031
5032 if (container) {
5033 Assert(unit.Removed);
5034 pos = container->tilePos;
5035 pos -= unit.Type->TileSize - 1;
5036 addx = container->Type->TileSize.x + unit.Type->TileSize.x - 1;
5037 addy = container->Type->TileSize.y + unit.Type->TileSize.y - 1;
5038 --pos.x;
5039 z = container->MapLayer->ID;
5040 } else {
5041 pos = unit.tilePos;
5042 z = unit.MapLayer->ID;
5043 }
5044 // FIXME: if we reach the map borders we can go fast up, left, ...
5045
5046 for (;;) {
5047 for (int i = addy; i--; ++pos.y) { // go down
5048 //Wyrmgus start
5049 // if (UnitCanBeAt(unit, pos)) {
5050 if (UnitCanBeAt(unit, pos, z)) {
5051 //Wyrmgus end
5052 const int n = SquareDistance(goalPos, pos);
5053
5054 if (n < bestd) {
5055 bestd = n;
5056 bestPos = pos;
5057 }
5058 }
5059 }
5060 ++addx;
5061 for (int i = addx; i--; ++pos.x) { // go right
5062 //Wyrmgus start
5063 // if (UnitCanBeAt(unit, pos)) {
5064 if (UnitCanBeAt(unit, pos, z)) {
5065 //Wyrmgus end
5066 const int n = SquareDistance(goalPos, pos);
5067
5068 if (n < bestd) {
5069 bestd = n;
5070 bestPos = pos;
5071 }
5072 }
5073 }
5074 ++addy;
5075 for (int i = addy; i--; --pos.y) { // go up
5076 //Wyrmgus start
5077 // if (UnitCanBeAt(unit, pos)) {
5078 if (UnitCanBeAt(unit, pos, z)) {
5079 //Wyrmgus end
5080 const int n = SquareDistance(goalPos, pos);
5081
5082 if (n < bestd) {
5083 bestd = n;
5084 bestPos = pos;
5085 }
5086 }
5087 }
5088 ++addx;
5089 for (int i = addx; i--; --pos.x) { // go left
5090 //Wyrmgus start
5091 // if (UnitCanBeAt(unit, pos)) {
5092 if (UnitCanBeAt(unit, pos, z)) {
5093 //Wyrmgus end
5094 const int n = SquareDistance(goalPos, pos);
5095
5096 if (n < bestd) {
5097 bestd = n;
5098 bestPos = pos;
5099 }
5100 }
5101 }
5102 if (bestd != 99999) {
5103 //Wyrmgus start
5104 // unit.Place(bestPos);
5105 unit.Place(bestPos, z);
5106 //Wyrmgus end
5107 return;
5108 }
5109 ++addy;
5110 }
5111 }
5112
5113 /**
5114 ** Drop out all units inside unit.
5115 **
5116 ** @param source All units inside source are dropped out.
5117 */
DropOutAll(const CUnit & source)5118 void DropOutAll(const CUnit &source)
5119 {
5120 CUnit *unit = source.UnitInside;
5121
5122 for (int i = source.InsideCount; i; --i, unit = unit->NextContained) {
5123 DropOutOnSide(*unit, LookingW, &source);
5124 }
5125
5126 //Wyrmgus start
5127 if (unit->Type->BoolFlag[ITEM_INDEX].value && !unit->Unique) { //save the initial cycle items were placed in the ground to destroy them if they have been there for too long
5128 int ttl_cycles = (5 * 60 * CYCLES_PER_SECOND);
5129 if (unit->Prefix != nullptr || unit->Suffix != nullptr || unit->Spell != nullptr || unit->Work != nullptr || unit->Elixir != nullptr) {
5130 ttl_cycles *= 4;
5131 }
5132 unit->TTL = GameCycle + ttl_cycles;
5133 }
5134 //Wyrmgus end
5135 }
5136
5137 /*----------------------------------------------------------------------------
5138 -- Select units
5139 ----------------------------------------------------------------------------*/
5140
5141 /**
5142 ** Unit on map screen.
5143 **
5144 ** Select units on screen. (x, y are in pixels relative to map 0,0).
5145 ** Not GAMEPLAY safe, uses ReplayRevealMap
5146 **
5147 ** More units on same position.
5148 ** Cycle through units.
5149 ** First take highest unit.
5150 **
5151 ** @param x X pixel position.
5152 ** @param y Y pixel position.
5153 **
5154 ** @return An unit on x, y position.
5155 */
UnitOnScreen(int x,int y)5156 CUnit *UnitOnScreen(int x, int y)
5157 {
5158 CUnit *candidate = nullptr;
5159 for (CUnitManager::Iterator it = UnitManager.begin(); it != UnitManager.end(); ++it) {
5160 CUnit &unit = **it;
5161 if (unit.MapLayer != UI.CurrentMapLayer) {
5162 continue;
5163 }
5164 if (!ReplayRevealMap && !unit.IsVisibleAsGoal(*ThisPlayer)) {
5165 continue;
5166 }
5167 const CUnitType &type = *unit.Type;
5168 if (!type.Sprite) {
5169 continue;
5170 }
5171
5172 //
5173 // Check if mouse is over the unit.
5174 //
5175 PixelPos unitSpritePos = unit.GetMapPixelPosCenter();
5176 //Wyrmgus start
5177 // unitSpritePos.x = unitSpritePos.x - type.BoxWidth / 2 -
5178 // (type.Width - type.Sprite->Width) / 2 + type.BoxOffsetX;
5179 // unitSpritePos.y = unitSpritePos.y - type.BoxHeight / 2 -
5180 // (type.Height - type.Sprite->Height) / 2 + type.BoxOffsetY;
5181 const CUnitTypeVariation *variation = unit.GetVariation();
5182 if (variation && variation->FrameWidth && variation->FrameHeight && !variation->File.empty()) {
5183 unitSpritePos.x = unitSpritePos.x - type.BoxWidth / 2 -
5184 (variation->FrameWidth - variation->Sprite->Width) / 2 + type.BoxOffsetX;
5185 unitSpritePos.y = unitSpritePos.y - type.BoxHeight / 2 -
5186 (variation->FrameHeight - variation->Sprite->Height) / 2 + type.BoxOffsetY;
5187 } else {
5188 unitSpritePos.x = unitSpritePos.x - type.BoxWidth / 2 -
5189 (type.Width - type.Sprite->Width) / 2 + type.BoxOffsetX;
5190 unitSpritePos.y = unitSpritePos.y - type.BoxHeight / 2 -
5191 (type.Height - type.Sprite->Height) / 2 + type.BoxOffsetY;
5192 }
5193 //Wyrmgus end
5194 if (x >= unitSpritePos.x && x < unitSpritePos.x + type.BoxWidth
5195 && y >= unitSpritePos.y && y < unitSpritePos.y + type.BoxHeight) {
5196 // Check if there are other units on this place
5197 candidate = &unit;
5198 //Wyrmgus start
5199 std::vector<CUnit *> table;
5200 Select(candidate->tilePos, candidate->tilePos, table, candidate->MapLayer->ID, HasNotSamePlayerAs(Players[PlayerNumNeutral]));
5201 // if (IsOnlySelected(*candidate) || candidate->Type->BoolFlag[ISNOTSELECTABLE_INDEX].value) {
5202 if (IsOnlySelected(*candidate) || candidate->Type->BoolFlag[ISNOTSELECTABLE_INDEX].value || (candidate->Player->Type == PlayerNeutral && table.size()) || !candidate->IsAlive()) { // don't select a neutral unit if there's a player-owned unit there as well; don't selected a dead unit
5203 //Wyrmgus end
5204 continue;
5205 } else {
5206 break;
5207 }
5208 } else {
5209 continue;
5210 }
5211 }
5212 return candidate;
5213 }
5214
GetMapPixelPosTopLeft() const5215 PixelPos CUnit::GetMapPixelPosTopLeft() const
5216 {
5217 const PixelPos pos(tilePos.x * Map.GetMapLayerPixelTileSize(this->MapLayer->ID).x + IX, tilePos.y * Map.GetMapLayerPixelTileSize(this->MapLayer->ID).y + IY);
5218 return pos;
5219 }
5220
GetMapPixelPosCenter() const5221 PixelPos CUnit::GetMapPixelPosCenter() const
5222 {
5223 return GetMapPixelPosTopLeft() + this->GetHalfTilePixelSize();
5224 }
5225
5226 //Wyrmgus start
GetTileSize() const5227 Vec2i CUnit::GetTileSize() const
5228 {
5229 return this->Type->GetTileSize();
5230 }
5231
GetHalfTileSize() const5232 Vec2i CUnit::GetHalfTileSize() const
5233 {
5234 return this->GetTileSize() / 2;
5235 }
5236
GetTilePixelSize() const5237 PixelSize CUnit::GetTilePixelSize() const
5238 {
5239 return PixelSize(this->GetTileSize()) * Map.GetMapLayerPixelTileSize(this->MapLayer->ID);
5240 }
5241
GetHalfTilePixelSize() const5242 PixelSize CUnit::GetHalfTilePixelSize() const
5243 {
5244 return this->GetTilePixelSize() / 2;
5245 }
5246
GetTileCenterPos() const5247 Vec2i CUnit::GetTileCenterPos() const
5248 {
5249 return this->tilePos + this->Type->GetTileCenterPosOffset();
5250 }
5251
SetIndividualUpgrade(const CUpgrade * upgrade,int quantity)5252 void CUnit::SetIndividualUpgrade(const CUpgrade *upgrade, int quantity)
5253 {
5254 if (!upgrade) {
5255 return;
5256 }
5257
5258 if (quantity <= 0) {
5259 if (this->IndividualUpgrades.find(upgrade->ID) != this->IndividualUpgrades.end()) {
5260 this->IndividualUpgrades.erase(upgrade->ID);
5261 }
5262 } else {
5263 this->IndividualUpgrades[upgrade->ID] = quantity;
5264 }
5265 }
5266
GetIndividualUpgrade(const CUpgrade * upgrade) const5267 int CUnit::GetIndividualUpgrade(const CUpgrade *upgrade) const
5268 {
5269 if (upgrade && this->IndividualUpgrades.find(upgrade->ID) != this->IndividualUpgrades.end()) {
5270 return this->IndividualUpgrades.find(upgrade->ID)->second;
5271 } else {
5272 return 0;
5273 }
5274 }
5275
GetAvailableLevelUpUpgrades(bool only_units) const5276 int CUnit::GetAvailableLevelUpUpgrades(bool only_units) const
5277 {
5278 int value = 0;
5279 int upgrade_value = 0;
5280
5281 if (((int) AiHelpers.ExperienceUpgrades.size()) > Type->Slot) {
5282 for (size_t i = 0; i != AiHelpers.ExperienceUpgrades[Type->Slot].size(); ++i) {
5283 if (this->Character == nullptr || std::find(this->Character->ForbiddenUpgrades.begin(), this->Character->ForbiddenUpgrades.end(), AiHelpers.ExperienceUpgrades[Type->Slot][i]) == this->Character->ForbiddenUpgrades.end()) {
5284 int local_upgrade_value = 1;
5285
5286 if (!only_units) {
5287 local_upgrade_value += AiHelpers.ExperienceUpgrades[Type->Slot][i]->GetAvailableLevelUpUpgrades();
5288 }
5289
5290 if (local_upgrade_value > upgrade_value) {
5291 upgrade_value = local_upgrade_value;
5292 }
5293 }
5294 }
5295 }
5296
5297 value += upgrade_value;
5298
5299 if (!only_units && ((int) AiHelpers.LearnableAbilities.size()) > Type->Slot) {
5300 for (size_t i = 0; i != AiHelpers.LearnableAbilities[Type->Slot].size(); ++i) {
5301 value += AiHelpers.LearnableAbilities[Type->Slot][i]->MaxLimit - this->GetIndividualUpgrade(AiHelpers.LearnableAbilities[Type->Slot][i]);
5302 }
5303 }
5304
5305 return value;
5306 }
5307
GetModifiedVariable(int index,int variable_type) const5308 int CUnit::GetModifiedVariable(int index, int variable_type) const
5309 {
5310 int value = 0;
5311 if (variable_type == VariableValue) {
5312 value = this->Variable[index].Value;
5313 } else if (variable_type == VariableMax) {
5314 value = this->Variable[index].Max;
5315 } else if (variable_type == VariableIncrease) {
5316 value = this->Variable[index].Increase;
5317 }
5318
5319 if (index == ATTACKRANGE_INDEX) {
5320 if (this->Container && this->Container->Variable[GARRISONEDRANGEBONUS_INDEX].Enable) {
5321 value += this->Container->Variable[GARRISONEDRANGEBONUS_INDEX].Value; //treat the container's attack range as a bonus to the unit's attack range
5322 }
5323 std::min<int>(this->CurrentSightRange, value); // if the unit's current sight range is smaller than its attack range, use it instead
5324 } else if (index == SPEED_INDEX) {
5325 if (this->MapLayer && this->Type->UnitType != UnitTypeFly && this->Type->UnitType != UnitTypeFlyLow) {
5326 value += DefaultTileMovementCost - this->MapLayer->Field(this->Offset)->getCost();
5327 }
5328 }
5329
5330 return value;
5331 }
5332
GetReactionRange() const5333 int CUnit::GetReactionRange() const
5334 {
5335 int reaction_range = this->CurrentSightRange;
5336
5337 if (this->Player->Type != PlayerPerson) {
5338 reaction_range += 2;
5339 }
5340
5341 return reaction_range;
5342 }
5343
GetItemSlotQuantity(int item_slot) const5344 int CUnit::GetItemSlotQuantity(int item_slot) const
5345 {
5346 if (!HasInventory()) {
5347 return 0;
5348 }
5349
5350 if ( //if the item are arrows and the weapon of this unit's type is not a bow, return false
5351 item_slot == ArrowsItemSlot
5352 && Type->WeaponClasses[0] != BowItemClass
5353 ) {
5354 return 0;
5355 }
5356
5357 if (item_slot == RingItemSlot) {
5358 return 2;
5359 }
5360
5361 return 1;
5362 }
5363
GetCurrentWeaponClass() const5364 int CUnit::GetCurrentWeaponClass() const
5365 {
5366 if (HasInventory() && EquippedItems[WeaponItemSlot].size() > 0) {
5367 return EquippedItems[WeaponItemSlot][0]->Type->ItemClass;
5368 }
5369
5370 return Type->WeaponClasses[0];
5371 }
5372
GetItemVariableChange(const CUnit * item,int variable_index,bool increase) const5373 int CUnit::GetItemVariableChange(const CUnit *item, int variable_index, bool increase) const
5374 {
5375 if (item->Type->ItemClass == -1) {
5376 return 0;
5377 }
5378
5379 int item_slot = GetItemClassSlot(item->Type->ItemClass);
5380 if (item->Work == nullptr && item->Elixir == nullptr && (item_slot == -1 || this->GetItemSlotQuantity(item_slot) == 0 || !this->CanEquipItemClass(item->Type->ItemClass))) {
5381 return 0;
5382 }
5383
5384 int value = 0;
5385 if (item->Work != nullptr) {
5386 if (this->GetIndividualUpgrade(item->Work) == 0) {
5387 for (size_t z = 0; z < item->Work->UpgradeModifiers.size(); ++z) {
5388 if (!increase) {
5389 value += item->Work->UpgradeModifiers[z]->Modifier.Variables[variable_index].Value;
5390 } else {
5391 value += item->Work->UpgradeModifiers[z]->Modifier.Variables[variable_index].Increase;
5392 }
5393 }
5394 }
5395 } else if (item->Elixir != nullptr) {
5396 if (this->GetIndividualUpgrade(item->Elixir) == 0) {
5397 for (size_t z = 0; z < item->Elixir->UpgradeModifiers.size(); ++z) {
5398 if (!increase) {
5399 value += item->Elixir->UpgradeModifiers[z]->Modifier.Variables[variable_index].Value;
5400 } else {
5401 value += item->Elixir->UpgradeModifiers[z]->Modifier.Variables[variable_index].Increase;
5402 }
5403 }
5404 }
5405 } else {
5406 if (!increase) {
5407 value = item->Variable[variable_index].Value;
5408 } else {
5409 value = item->Variable[variable_index].Increase;
5410 }
5411
5412 if (!item->Identified) { //if the item is unidentified, don't show the effects of its affixes
5413 for (const CUpgradeModifier *modifier : CUpgradeModifier::UpgradeModifiers) {
5414 if (
5415 (item->Prefix != nullptr && modifier->UpgradeId == item->Prefix->ID)
5416 || (item->Suffix != nullptr && modifier->UpgradeId == item->Suffix->ID)
5417 ) {
5418 if (!increase) {
5419 value -= modifier->Modifier.Variables[variable_index].Value;
5420 } else {
5421 value -= modifier->Modifier.Variables[variable_index].Increase;
5422 }
5423 }
5424 }
5425 }
5426
5427 if (item->Unique && item->Unique->Set) {
5428 if (this->EquippingItemCompletesSet(item)) {
5429 for (size_t z = 0; z < item->Unique->Set->UpgradeModifiers.size(); ++z) {
5430 if (!increase) {
5431 value += item->Unique->Set->UpgradeModifiers[z]->Modifier.Variables[variable_index].Value;
5432 } else {
5433 value += item->Unique->Set->UpgradeModifiers[z]->Modifier.Variables[variable_index].Increase;
5434 }
5435 }
5436 }
5437 }
5438
5439 if (EquippedItems[item_slot].size() == this->GetItemSlotQuantity(item_slot)) {
5440 int item_slot_used = EquippedItems[item_slot].size() - 1;
5441 for (size_t i = 0; i < EquippedItems[item_slot].size(); ++i) {
5442 if (EquippedItems[item_slot][i] == item) {
5443 item_slot_used = i;
5444 }
5445 }
5446 if (!increase) {
5447 value -= EquippedItems[item_slot][item_slot_used]->Variable[variable_index].Value;
5448 } else {
5449 value -= EquippedItems[item_slot][item_slot_used]->Variable[variable_index].Increase;
5450 }
5451 if (EquippedItems[item_slot][item_slot_used] != item && EquippedItems[item_slot][item_slot_used]->Unique && EquippedItems[item_slot][item_slot_used]->Unique->Set) {
5452 if (this->DeequippingItemBreaksSet(EquippedItems[item_slot][item_slot_used])) {
5453 for (size_t z = 0; z < EquippedItems[item_slot][item_slot_used]->Unique->Set->UpgradeModifiers.size(); ++z) {
5454 if (!increase) {
5455 value -= EquippedItems[item_slot][item_slot_used]->Unique->Set->UpgradeModifiers[z]->Modifier.Variables[variable_index].Value;
5456 } else {
5457 value -= EquippedItems[item_slot][item_slot_used]->Unique->Set->UpgradeModifiers[z]->Modifier.Variables[variable_index].Increase;
5458 }
5459 }
5460 }
5461 }
5462 } else if (EquippedItems[item_slot].size() == 0 && (item_slot == WeaponItemSlot || item_slot == ShieldItemSlot || item_slot == BootsItemSlot || item_slot == ArrowsItemSlot)) {
5463 for (const CUpgradeModifier *modifier : CUpgradeModifier::UpgradeModifiers) {
5464 const CUpgrade *modifier_upgrade = AllUpgrades[modifier->UpgradeId];
5465 if (
5466 (
5467 (
5468 (modifier_upgrade->Weapon && item_slot == WeaponItemSlot)
5469 || (modifier_upgrade->Shield && item_slot == ShieldItemSlot)
5470 || (modifier_upgrade->Boots && item_slot == BootsItemSlot)
5471 || (modifier_upgrade->Arrows && item_slot == ArrowsItemSlot)
5472 )
5473 && Player->Allow.Upgrades[modifier_upgrade->ID] == 'R' && modifier->ApplyTo[Type->Slot] == 'X'
5474 )
5475 || (item_slot == WeaponItemSlot && modifier_upgrade->Ability && this->GetIndividualUpgrade(modifier_upgrade) && modifier_upgrade->WeaponClasses.size() > 0 && std::find(modifier_upgrade->WeaponClasses.begin(), modifier_upgrade->WeaponClasses.end(), this->GetCurrentWeaponClass()) != modifier_upgrade->WeaponClasses.end() && std::find(modifier_upgrade->WeaponClasses.begin(), modifier_upgrade->WeaponClasses.end(), item->Type->ItemClass) == modifier_upgrade->WeaponClasses.end())
5476 ) {
5477 if (this->GetIndividualUpgrade(modifier_upgrade)) {
5478 for (int i = 0; i < this->GetIndividualUpgrade(modifier_upgrade); ++i) {
5479 if (!increase) {
5480 value -= modifier->Modifier.Variables[variable_index].Value;
5481 } else {
5482 value -= modifier->Modifier.Variables[variable_index].Increase;
5483 }
5484 }
5485 } else {
5486 if (!increase) {
5487 value -= modifier->Modifier.Variables[variable_index].Value;
5488 } else {
5489 value -= modifier->Modifier.Variables[variable_index].Increase;
5490 }
5491 }
5492 } else if (
5493 modifier_upgrade->Ability && this->GetIndividualUpgrade(modifier_upgrade) && modifier_upgrade->WeaponClasses.size() > 0 && std::find(modifier_upgrade->WeaponClasses.begin(), modifier_upgrade->WeaponClasses.end(), this->GetCurrentWeaponClass()) == modifier_upgrade->WeaponClasses.end() && std::find(modifier_upgrade->WeaponClasses.begin(), modifier_upgrade->WeaponClasses.end(), item->Type->ItemClass) != modifier_upgrade->WeaponClasses.end()
5494 ) {
5495 if (this->GetIndividualUpgrade(modifier_upgrade)) {
5496 for (int i = 0; i < this->GetIndividualUpgrade(modifier_upgrade); ++i) {
5497 if (!increase) {
5498 value += modifier->Modifier.Variables[variable_index].Value;
5499 } else {
5500 value += modifier->Modifier.Variables[variable_index].Increase;
5501 }
5502 }
5503 } else {
5504 if (!increase) {
5505 value += modifier->Modifier.Variables[variable_index].Value;
5506 } else {
5507 value += modifier->Modifier.Variables[variable_index].Increase;
5508 }
5509 }
5510 }
5511 }
5512 }
5513 }
5514
5515 return value;
5516 }
5517
GetDisplayPlayer() const5518 int CUnit::GetDisplayPlayer() const
5519 {
5520 if (this->Type->BoolFlag[HIDDENOWNERSHIP_INDEX].value && this->Player != ThisPlayer) {
5521 return PlayerNumNeutral;
5522 } else {
5523 return this->RescuedFrom ? this->RescuedFrom->Index : this->Player->Index;
5524 }
5525 }
5526
GetPrice() const5527 int CUnit::GetPrice() const
5528 {
5529 int cost = this->Type->Stats[this->Player->Index].GetPrice();
5530
5531 if (this->Prefix != nullptr) {
5532 cost += this->Prefix->MagicLevel * 1000;
5533 }
5534 if (this->Suffix != nullptr) {
5535 cost += this->Suffix->MagicLevel * 1000;
5536 }
5537 if (this->Spell != nullptr) {
5538 cost += 1000;
5539 }
5540 if (this->Work != nullptr) {
5541 if (this->Type->ItemClass == BookItemClass) {
5542 cost += 5000;
5543 } else {
5544 cost += 1000;
5545 }
5546 }
5547 if (this->Elixir != nullptr) {
5548 cost += this->Elixir->MagicLevel * 1000;
5549 }
5550 if (this->Character) {
5551 cost += (this->Variable[LEVEL_INDEX].Value - this->Type->Stats[this->Player->Index].Variables[LEVEL_INDEX].Value) * 250;
5552 }
5553
5554 return cost;
5555 }
5556
GetUnitStock(CUnitType * unit_type) const5557 int CUnit::GetUnitStock(CUnitType *unit_type) const
5558 {
5559 if (unit_type && this->UnitStock.find(unit_type) != this->UnitStock.end()) {
5560 return this->UnitStock.find(unit_type)->second;
5561 } else {
5562 return 0;
5563 }
5564 }
5565
SetUnitStock(CUnitType * unit_type,int quantity)5566 void CUnit::SetUnitStock(CUnitType *unit_type, int quantity)
5567 {
5568 if (!unit_type) {
5569 return;
5570 }
5571
5572 if (quantity <= 0) {
5573 if (this->UnitStock.find(unit_type) != this->UnitStock.end()) {
5574 this->UnitStock.erase(unit_type);
5575 }
5576 } else {
5577 this->UnitStock[unit_type] = quantity;
5578 }
5579 }
5580
ChangeUnitStock(CUnitType * unit_type,int quantity)5581 void CUnit::ChangeUnitStock(CUnitType *unit_type, int quantity)
5582 {
5583 this->SetUnitStock(unit_type, this->GetUnitStock(unit_type) + quantity);
5584 }
5585
GetUnitStockReplenishmentTimer(CUnitType * unit_type) const5586 int CUnit::GetUnitStockReplenishmentTimer(CUnitType *unit_type) const
5587 {
5588 if (this->UnitStockReplenishmentTimers.find(unit_type) != this->UnitStockReplenishmentTimers.end()) {
5589 return this->UnitStockReplenishmentTimers.find(unit_type)->second;
5590 } else {
5591 return 0;
5592 }
5593 }
5594
SetUnitStockReplenishmentTimer(CUnitType * unit_type,int quantity)5595 void CUnit::SetUnitStockReplenishmentTimer(CUnitType *unit_type, int quantity)
5596 {
5597 if (!unit_type) {
5598 return;
5599 }
5600
5601 if (quantity <= 0) {
5602 if (this->UnitStockReplenishmentTimers.find(unit_type) != this->UnitStockReplenishmentTimers.end()) {
5603 this->UnitStockReplenishmentTimers.erase(unit_type);
5604 }
5605 } else {
5606 this->UnitStockReplenishmentTimers[unit_type] = quantity;
5607 }
5608 }
5609
ChangeUnitStockReplenishmentTimer(CUnitType * unit_type,int quantity)5610 void CUnit::ChangeUnitStockReplenishmentTimer(CUnitType *unit_type, int quantity)
5611 {
5612 this->SetUnitStockReplenishmentTimer(unit_type, this->GetUnitStockReplenishmentTimer(unit_type) + quantity);
5613 }
5614
GetResourceStep(const int resource) const5615 int CUnit::GetResourceStep(const int resource) const
5616 {
5617 if (!this->Type->ResInfo[resource]) {
5618 return 0;
5619 }
5620
5621 int resource_step = this->Type->ResInfo[resource]->ResourceStep;
5622
5623 resource_step += this->Variable[GATHERINGBONUS_INDEX].Value;
5624
5625 if (resource == CopperCost) {
5626 resource_step += this->Variable[COPPERGATHERINGBONUS_INDEX].Value;
5627 } else if (resource == SilverCost) {
5628 resource_step += this->Variable[SILVERGATHERINGBONUS_INDEX].Value;
5629 } else if (resource == GoldCost) {
5630 resource_step += this->Variable[GOLDGATHERINGBONUS_INDEX].Value;
5631 } else if (resource == IronCost) {
5632 resource_step += this->Variable[IRONGATHERINGBONUS_INDEX].Value;
5633 } else if (resource == MithrilCost) {
5634 resource_step += this->Variable[MITHRILGATHERINGBONUS_INDEX].Value;
5635 } else if (resource == WoodCost) {
5636 resource_step += this->Variable[LUMBERGATHERINGBONUS_INDEX].Value;
5637 } else if (resource == StoneCost || resource == LimestoneCost) {
5638 resource_step += this->Variable[STONEGATHERINGBONUS_INDEX].Value;
5639 } else if (resource == CoalCost) {
5640 resource_step += this->Variable[COALGATHERINGBONUS_INDEX].Value;
5641 } else if (resource == JewelryCost) {
5642 resource_step += this->Variable[JEWELRYGATHERINGBONUS_INDEX].Value;
5643 } else if (resource == FurnitureCost) {
5644 resource_step += this->Variable[FURNITUREGATHERINGBONUS_INDEX].Value;
5645 } else if (resource == LeatherCost) {
5646 resource_step += this->Variable[LEATHERGATHERINGBONUS_INDEX].Value;
5647 } else if (resource == DiamondsCost || resource == EmeraldsCost) {
5648 resource_step += this->Variable[GEMSGATHERINGBONUS_INDEX].Value;
5649 }
5650
5651 return resource_step;
5652 }
5653
GetTotalInsideCount(const CPlayer * player,const bool ignore_items,const bool ignore_saved_cargo,const CUnitType * type) const5654 int CUnit::GetTotalInsideCount(const CPlayer *player, const bool ignore_items, const bool ignore_saved_cargo, const CUnitType *type) const
5655 {
5656 if (!this->UnitInside) {
5657 return 0;
5658 }
5659
5660 if (this->Type->BoolFlag[SAVECARGO_INDEX].value && ignore_saved_cargo) {
5661 return 0;
5662 }
5663
5664 int inside_count = 0;
5665
5666 CUnit *inside_unit = this->UnitInside;
5667 for (int j = 0; j < this->InsideCount; ++j, inside_unit = inside_unit->NextContained) {
5668 if ( //only count units of the faction, ignore items
5669 (!player || inside_unit->Player == player)
5670 && (!ignore_items || !inside_unit->Type->BoolFlag[ITEM_INDEX].value)
5671 && (!type || inside_unit->Type == type)
5672 ) {
5673 inside_count++;
5674 }
5675 inside_count += inside_unit->GetTotalInsideCount(player, ignore_items, ignore_saved_cargo);
5676 }
5677
5678 return inside_count;
5679 }
5680
CanAttack(bool count_inside) const5681 bool CUnit::CanAttack(bool count_inside) const
5682 {
5683 if (this->Type->CanTransport() && this->Type->BoolFlag[ATTACKFROMTRANSPORTER_INDEX].value && !this->Type->BoolFlag[CANATTACK_INDEX].value) { //transporters without an attack can only attack through a unit within them
5684 if (count_inside && this->BoardCount > 0) {
5685 CUnit *boarded_unit = this->UnitInside;
5686 for (int i = 0; i < this->InsideCount; ++i, boarded_unit = boarded_unit->NextContained) {
5687 if (boarded_unit->GetModifiedVariable(ATTACKRANGE_INDEX) > 1 && boarded_unit->Type->BoolFlag[ATTACKFROMTRANSPORTER_INDEX].value) {
5688 return true;
5689 }
5690 }
5691 }
5692 return false;
5693 }
5694
5695 if (this->Container && (!this->Type->BoolFlag[ATTACKFROMTRANSPORTER_INDEX].value || !this->Container->Type->BoolFlag[ATTACKFROMTRANSPORTER_INDEX].value)) {
5696 return false;
5697 }
5698
5699 return this->Type->BoolFlag[CANATTACK_INDEX].value;
5700 }
5701
IsInCombat() const5702 bool CUnit::IsInCombat() const
5703 {
5704 // Select all units around the unit
5705 std::vector<CUnit *> table;
5706 SelectAroundUnit(*this, this->GetReactionRange(), table, IsEnemyWith(*this->Player));
5707
5708 for (size_t i = 0; i < table.size(); ++i) {
5709 const CUnit &target = *table[i];
5710
5711 if (target.IsVisibleAsGoal(*this->Player) && (CanTarget(*this->Type, *target.Type) || CanTarget(*target.Type, *this->Type))) {
5712 return true;
5713 }
5714 }
5715
5716 return false;
5717 }
5718
CanHarvest(const CUnit * dest,bool only_harvestable) const5719 bool CUnit::CanHarvest(const CUnit *dest, bool only_harvestable) const
5720 {
5721 if (!dest) {
5722 return false;
5723 }
5724
5725 if (!dest->GivesResource) {
5726 return false;
5727 }
5728
5729 if (!this->Type->ResInfo[dest->GivesResource]) {
5730 return false;
5731 }
5732
5733 if (!dest->Type->BoolFlag[CANHARVEST_INDEX].value && only_harvestable) {
5734 return false;
5735 }
5736
5737 if (!this->Type->BoolFlag[HARVESTER_INDEX].value) {
5738 return false;
5739 }
5740
5741 if (dest->GivesResource == TradeCost) {
5742 if (dest->Player == this->Player) { //can only trade with markets owned by other players
5743 return false;
5744 }
5745
5746 if (this->Type->UnitType != UnitTypeNaval && dest->Type->BoolFlag[SHOREBUILDING_INDEX].value) { //only ships can trade with docks
5747 return false;
5748 }
5749 if (this->Type->UnitType == UnitTypeNaval && !dest->Type->BoolFlag[SHOREBUILDING_INDEX].value && dest->Type->UnitType != UnitTypeNaval) { //ships cannot trade with land markets
5750 return false;
5751 }
5752 } else {
5753 if (dest->Player != this->Player && !(dest->Player->IsAllied(*this->Player) && this->Player->IsAllied(*dest->Player)) && dest->Player->Index != PlayerNumNeutral) {
5754 return false;
5755 }
5756 }
5757
5758 if (this->BoardCount) { //cannot harvest if carrying units
5759 return false;
5760 }
5761
5762 return true;
5763 }
5764
CanReturnGoodsTo(const CUnit * dest,int resource) const5765 bool CUnit::CanReturnGoodsTo(const CUnit *dest, int resource) const
5766 {
5767 if (!dest) {
5768 return false;
5769 }
5770
5771 if (!resource) {
5772 resource = this->CurrentResource;
5773 }
5774
5775 if (!resource) {
5776 return false;
5777 }
5778
5779 if (!dest->Type->CanStore[this->CurrentResource]) {
5780 return false;
5781 }
5782
5783 if (resource == TradeCost) {
5784 if (dest->Player != this->Player) { //can only return trade to markets owned by the same player
5785 return false;
5786 }
5787
5788 if (this->Type->UnitType != UnitTypeNaval && dest->Type->BoolFlag[SHOREBUILDING_INDEX].value) { //only ships can return trade to docks
5789 return false;
5790 }
5791 if (this->Type->UnitType == UnitTypeNaval && !dest->Type->BoolFlag[SHOREBUILDING_INDEX].value && dest->Type->UnitType != UnitTypeNaval) { //ships cannot return trade to land markets
5792 return false;
5793 }
5794 } else {
5795 if (dest->Player != this->Player && !(dest->Player->IsAllied(*this->Player) && this->Player->IsAllied(*dest->Player))) {
5796 return false;
5797 }
5798 }
5799
5800 return true;
5801 }
5802
5803 /**
5804 ** @brief Get whether a unit can cast a given spell
5805 **
5806 ** @return True if the unit can cast the given spell, or false otherwise
5807 */
CanCastSpell(const CSpell * spell,const bool ignore_mana_and_cooldown) const5808 bool CUnit::CanCastSpell(const CSpell *spell, const bool ignore_mana_and_cooldown) const
5809 {
5810 if (spell->IsAvailableForUnit(*this)) {
5811 if (!ignore_mana_and_cooldown) {
5812 if (this->Variable[MANA_INDEX].Value < spell->ManaCost) {
5813 return false;
5814 }
5815
5816 if (this->SpellCoolDownTimers[spell->Slot]) {
5817 return false;
5818 }
5819 }
5820
5821 return true;
5822 } else {
5823 return false;
5824 }
5825 }
5826
5827 /**
5828 ** @brief Get whether a unit can cast any spell
5829 **
5830 ** @return True if the unit can cast any spell, or false otherwise
5831 */
CanCastAnySpell() const5832 bool CUnit::CanCastAnySpell() const
5833 {
5834 for (size_t i = 0; i < this->Type->Spells.size(); ++i) {
5835 if (this->CanCastSpell(this->Type->Spells[i], true)) {
5836 return true;
5837 }
5838 }
5839
5840 return false;
5841 }
5842
5843 /**
5844 ** @brief Get whether the unit can autocast a given spell
5845 **
5846 ** @param spell The spell
5847 **
5848 ** @return True if the unit can autocast the spell, false otherwise
5849 */
CanAutoCastSpell(const CSpell * spell) const5850 bool CUnit::CanAutoCastSpell(const CSpell *spell) const
5851 {
5852 if (!this->AutoCastSpell || !spell || !this->AutoCastSpell[spell->Slot] || !spell->AutoCast) {
5853 return false;
5854 }
5855
5856 if (!CanCastSpell(spell, false)) {
5857 return false;
5858 }
5859
5860 return true;
5861 }
5862
IsItemEquipped(const CUnit * item) const5863 bool CUnit::IsItemEquipped(const CUnit *item) const
5864 {
5865 int item_slot = GetItemClassSlot(item->Type->ItemClass);
5866
5867 if (item_slot == -1) {
5868 return false;
5869 }
5870
5871 if (std::find(EquippedItems[item_slot].begin(), EquippedItems[item_slot].end(), item) != EquippedItems[item_slot].end()) {
5872 return true;
5873 }
5874
5875 return false;
5876 }
5877
IsItemClassEquipped(int item_class) const5878 bool CUnit::IsItemClassEquipped(int item_class) const
5879 {
5880 int item_slot = GetItemClassSlot(item_class);
5881
5882 if (item_slot == -1) {
5883 return false;
5884 }
5885
5886 for (size_t i = 0; i < EquippedItems[item_slot].size(); ++i) {
5887 if (EquippedItems[item_slot][i]->Type->ItemClass == item_class) {
5888 return true;
5889 }
5890 }
5891
5892 return false;
5893 }
5894
IsItemTypeEquipped(const CUnitType * item_type) const5895 bool CUnit::IsItemTypeEquipped(const CUnitType *item_type) const
5896 {
5897 int item_slot = GetItemClassSlot(item_type->ItemClass);
5898
5899 if (item_slot == -1) {
5900 return false;
5901 }
5902
5903 for (size_t i = 0; i < EquippedItems[item_slot].size(); ++i) {
5904 if (EquippedItems[item_slot][i]->Type == item_type) {
5905 return true;
5906 }
5907 }
5908
5909 return false;
5910 }
5911
IsUniqueItemEquipped(const CUniqueItem * unique) const5912 bool CUnit::IsUniqueItemEquipped(const CUniqueItem *unique) const
5913 {
5914 int item_slot = GetItemClassSlot(unique->Type->ItemClass);
5915
5916 if (item_slot == -1) {
5917 return false;
5918 }
5919
5920 int item_equipped_quantity = 0;
5921 for (size_t i = 0; i < this->EquippedItems[item_slot].size(); ++i) {
5922 if (EquippedItems[item_slot][i]->Unique == unique) {
5923 return true;
5924 }
5925 }
5926
5927 return false;
5928 }
5929
CanEquipItem(CUnit * item) const5930 bool CUnit::CanEquipItem(CUnit *item) const
5931 {
5932 if (item->Container != this) {
5933 return false;
5934 }
5935
5936 if (!item->Identified) {
5937 return false;
5938 }
5939
5940 if (!CanEquipItemClass(item->Type->ItemClass)) {
5941 return false;
5942 }
5943
5944 return true;
5945 }
5946
CanEquipItemClass(int item_class) const5947 bool CUnit::CanEquipItemClass(int item_class) const
5948 {
5949 if (item_class == -1) {
5950 return false;
5951 }
5952
5953 if (GetItemClassSlot(item_class) == -1) { //can't equip items that don't correspond to an equippable slot
5954 return false;
5955 }
5956
5957 if (GetItemClassSlot(item_class) == WeaponItemSlot && std::find(this->Type->WeaponClasses.begin(), this->Type->WeaponClasses.end(), item_class) == this->Type->WeaponClasses.end()) { //if the item is a weapon and its item class isn't a weapon class used by this unit's type, return false
5958 return false;
5959 }
5960
5961 if ( //if the item uses the shield (off-hand) slot, but that slot is unavailable for the weapon (because it is two-handed), return false
5962 GetItemClassSlot(item_class) == ShieldItemSlot
5963 && this->Type->WeaponClasses.size() > 0
5964 && (
5965 this->Type->WeaponClasses[0] == BowItemClass
5966 // add other two-handed weapons here as necessary
5967 )
5968 ) {
5969 return false;
5970 }
5971
5972 if ( //if the item is a shield and the weapon of this unit's type is incompatible with shields, return false
5973 item_class == ShieldItemClass
5974 && (
5975 Type->WeaponClasses.size() == 0
5976 || Type->WeaponClasses[0] == DaggerItemClass
5977 || Type->WeaponClasses[0] == ThrowingAxeItemClass
5978 || Type->WeaponClasses[0] == JavelinItemClass
5979 || Type->WeaponClasses[0] == GunItemClass
5980 || Type->BoolFlag[HARVESTER_INDEX].value //workers can't use shields
5981 )
5982 ) {
5983 return false;
5984 }
5985
5986 if (this->GetItemSlotQuantity(GetItemClassSlot(item_class)) == 0) {
5987 return false;
5988 }
5989
5990 return true;
5991 }
5992
CanUseItem(CUnit * item) const5993 bool CUnit::CanUseItem(CUnit *item) const
5994 {
5995 if (item->ConnectingDestination != nullptr) {
5996 if (item->Type->BoolFlag[ETHEREAL_INDEX].value && !this->Variable[ETHEREALVISION_INDEX].Value) {
5997 return false;
5998 }
5999
6000 if (this->Type->BoolFlag[RAIL_INDEX].value && !item->ConnectingDestination->HasAdjacentRailForUnitType(this->Type)) {
6001 return false;
6002 }
6003
6004 if (this->Player == item->Player || this->Player->IsAllied(*item->Player) || item->Player->Type == PlayerNeutral) {
6005 return true;
6006 }
6007 }
6008
6009 if (!item->Type->BoolFlag[ITEM_INDEX].value && !item->Type->BoolFlag[POWERUP_INDEX].value) {
6010 return false;
6011 }
6012
6013 if (item->Type->BoolFlag[ITEM_INDEX].value && item->Type->ItemClass != FoodItemClass && item->Type->ItemClass != PotionItemClass && item->Type->ItemClass != ScrollItemClass && item->Type->ItemClass != BookItemClass) {
6014 return false;
6015 }
6016
6017 if (item->Spell != nullptr) {
6018 if (!this->HasInventory() || !::CanCastSpell(*this, *item->Spell, this, this->tilePos, this->MapLayer)) {
6019 return false;
6020 }
6021 }
6022
6023 if (item->Work != nullptr) {
6024 if (!this->HasInventory() || this->GetIndividualUpgrade(item->Work)) {
6025 return false;
6026 }
6027 }
6028
6029 if (item->Elixir != nullptr) {
6030 if (!this->HasInventory() || this->GetIndividualUpgrade(item->Elixir)) {
6031 return false;
6032 }
6033 }
6034
6035 if (item->Elixir == nullptr && item->Variable[HITPOINTHEALING_INDEX].Value > 0 && this->Variable[HP_INDEX].Value >= this->GetModifiedVariable(HP_INDEX, VariableMax)) {
6036 return false;
6037 }
6038
6039 return true;
6040 }
6041
IsItemSetComplete(const CUnit * item) const6042 bool CUnit::IsItemSetComplete(const CUnit *item) const
6043 {
6044 for (size_t i = 0; i < item->Unique->Set->UniqueItems.size(); ++i) {
6045 if (!this->IsUniqueItemEquipped(item->Unique->Set->UniqueItems[i])) {
6046 return false;
6047 }
6048 }
6049
6050 return true;
6051 }
6052
EquippingItemCompletesSet(const CUnit * item) const6053 bool CUnit::EquippingItemCompletesSet(const CUnit *item) const
6054 {
6055 for (size_t i = 0; i < item->Unique->Set->UniqueItems.size(); ++i) {
6056 int item_slot = GetItemClassSlot(item->Unique->Set->UniqueItems[i]->Type->ItemClass);
6057
6058 if (item_slot == -1) {
6059 return false;
6060 }
6061
6062 bool has_item_equipped = false;
6063 for (size_t j = 0; j < this->EquippedItems[item_slot].size(); ++j) {
6064 if (EquippedItems[item_slot][j]->Unique == item->Unique->Set->UniqueItems[i]) {
6065 has_item_equipped = true;
6066 break;
6067 }
6068 }
6069
6070 if (has_item_equipped && item->Unique->Set->UniqueItems[i] == item->Unique) { //if the unique item is already equipped, it won't complete the set (either the set is already complete, or needs something else)
6071 return false;
6072 } else if (!has_item_equipped && item->Unique->Set->UniqueItems[i] != item->Unique) {
6073 return false;
6074 }
6075
6076 }
6077
6078 return true;
6079 }
6080
DeequippingItemBreaksSet(const CUnit * item) const6081 bool CUnit::DeequippingItemBreaksSet(const CUnit *item) const
6082 {
6083 if (!IsItemSetComplete(item)) {
6084 return false;
6085 }
6086
6087 int item_slot = GetItemClassSlot(item->Type->ItemClass);
6088
6089 if (item_slot == -1) {
6090 return false;
6091 }
6092
6093 int item_equipped_quantity = 0;
6094 for (size_t i = 0; i < this->EquippedItems[item_slot].size(); ++i) {
6095 if (EquippedItems[item_slot][i]->Unique == item->Unique) {
6096 item_equipped_quantity += 1;
6097 }
6098 }
6099
6100 if (item_equipped_quantity > 1) {
6101 return false;
6102 } else {
6103 return true;
6104 }
6105 }
6106
HasInventory() const6107 bool CUnit::HasInventory() const
6108 {
6109 if (this->Type->BoolFlag[INVENTORY_INDEX].value) {
6110 return true;
6111 }
6112
6113 if (!this->Type->BoolFlag[FAUNA_INDEX].value) {
6114 if (this->Character != nullptr) {
6115 return true;
6116 }
6117
6118 if (this->Variable[LEVEL_INDEX].Value >= 3 && this->Type->BoolFlag[ORGANIC_INDEX].value) {
6119 return true;
6120 }
6121 }
6122
6123 return false;
6124 }
6125
CanLearnAbility(CUpgrade * ability,bool pre) const6126 bool CUnit::CanLearnAbility(CUpgrade *ability, bool pre) const
6127 {
6128 if (!strncmp(ability->Ident.c_str(), "upgrade-deity-", 14)) { //if is a deity choice "ability", only allow for custom heroes (but display the icon for already-acquired deities for all heroes)
6129 if (!this->Character) {
6130 return false;
6131 }
6132 if (!this->Character->Custom && this->GetIndividualUpgrade(ability) == 0) {
6133 return false;
6134 }
6135 if (!pre && this->UpgradeRemovesExistingUpgrade(ability)) {
6136 return false;
6137 }
6138 }
6139
6140 if (!pre && this->GetIndividualUpgrade(ability) >= ability->MaxLimit) { // already learned
6141 return false;
6142 }
6143
6144 if (!pre && this->Variable[LEVELUP_INDEX].Value < 1 && ability->Ability) {
6145 return false;
6146 }
6147
6148 if (!CheckDependencies(ability, this, false, pre)) {
6149 return false;
6150 }
6151
6152 return true;
6153 }
6154
CanHireMercenary(CUnitType * type,int civilization_id) const6155 bool CUnit::CanHireMercenary(CUnitType *type, int civilization_id) const
6156 {
6157 if (civilization_id == -1) {
6158 civilization_id = type->Civilization;
6159 }
6160 for (int p = 0; p < PlayerMax; ++p) {
6161 if (Players[p].Type != PlayerNobody && Players[p].Type != PlayerNeutral && civilization_id == Players[p].Race && CheckDependencies(type, &Players[p], true) && Players[p].StartMapLayer == this->MapLayer->ID) {
6162 return true;
6163 }
6164 }
6165
6166 return false;
6167 }
6168
CanEat(const CUnit & unit) const6169 bool CUnit::CanEat(const CUnit &unit) const
6170 {
6171 if (this->Type->BoolFlag[CARNIVORE_INDEX].value && unit.Type->BoolFlag[FLESH_INDEX].value) {
6172 return true;
6173 }
6174
6175 if (this->Type->BoolFlag[INSECTIVORE_INDEX].value && unit.Type->BoolFlag[INSECT_INDEX].value) {
6176 return true;
6177 }
6178
6179 if (this->Type->BoolFlag[HERBIVORE_INDEX].value && unit.Type->BoolFlag[VEGETABLE_INDEX].value) {
6180 return true;
6181 }
6182
6183 if (
6184 this->Type->BoolFlag[DETRITIVORE_INDEX].value
6185 && (
6186 unit.Type->BoolFlag[DETRITUS_INDEX].value
6187 || (unit.CurrentAction() == UnitActionDie && (unit.Type->BoolFlag[FLESH_INDEX].value || unit.Type->BoolFlag[INSECT_INDEX].value))
6188 )
6189 ) {
6190 return true;
6191 }
6192
6193 return false;
6194 }
6195
LevelCheck(const int level) const6196 bool CUnit::LevelCheck(const int level) const
6197 {
6198 if (this->Variable[LEVEL_INDEX].Value == 0) {
6199 return false;
6200 }
6201
6202 return SyncRand((this->Variable[LEVEL_INDEX].Value * 2) + 1) >= level;
6203 }
6204
IsAbilityEmpowered(const CUpgrade * ability) const6205 bool CUnit::IsAbilityEmpowered(const CUpgrade *ability) const
6206 {
6207 const CPlane *plane = this->MapLayer->Plane;
6208 if (plane) {
6209 if (!plane->EmpoweredDeityDomains.empty()) {
6210 for (const CDeityDomain *deity_domain : ability->DeityDomains) {
6211 if (std::find(plane->EmpoweredDeityDomains.begin(), plane->EmpoweredDeityDomains.end(), deity_domain) != plane->EmpoweredDeityDomains.end()) {
6212 return true;
6213 }
6214 }
6215 }
6216
6217 if (!plane->EmpoweredSchoolsOfMagic.empty()) {
6218 for (const CSchoolOfMagic *school_of_magic : ability->SchoolsOfMagic) {
6219 if (std::find(plane->EmpoweredSchoolsOfMagic.begin(), plane->EmpoweredSchoolsOfMagic.end(), school_of_magic) != plane->EmpoweredSchoolsOfMagic.end()) {
6220 return true;
6221 }
6222 }
6223 }
6224 }
6225
6226 return false;
6227 }
6228
IsSpellEmpowered(const CSpell * spell) const6229 bool CUnit::IsSpellEmpowered(const CSpell *spell) const
6230 {
6231 if (spell->DependencyId != -1) {
6232 return this->IsAbilityEmpowered(AllUpgrades[spell->DependencyId]);
6233 } else {
6234 return false;
6235 }
6236 }
6237
6238 /**
6239 ** Check if the upgrade removes an existing individual upgrade of the unit.
6240 **
6241 ** @param upgrade Upgrade.
6242 */
UpgradeRemovesExistingUpgrade(const CUpgrade * upgrade) const6243 bool CUnit::UpgradeRemovesExistingUpgrade(const CUpgrade *upgrade) const
6244 {
6245 for (size_t z = 0; z < upgrade->UpgradeModifiers.size(); ++z) {
6246 for (size_t j = 0; j < upgrade->UpgradeModifiers[z]->RemoveUpgrades.size(); ++j) {
6247 if (this->GetIndividualUpgrade(upgrade->UpgradeModifiers[z]->RemoveUpgrades[j]) > 0) {
6248 return true;
6249 }
6250 }
6251 }
6252
6253 return false;
6254 }
6255
HasAdjacentRailForUnitType(const CUnitType * type) const6256 bool CUnit::HasAdjacentRailForUnitType(const CUnitType *type) const
6257 {
6258 bool has_adjacent_rail = false;
6259 Vec2i top_left_pos(this->tilePos - Vec2i(1, 1));
6260 Vec2i bottom_right_pos(this->tilePos + this->Type->TileSize);
6261
6262 for (int x = top_left_pos.x; x <= bottom_right_pos.x; ++x) {
6263 Vec2i tile_pos(x, top_left_pos.y);
6264 if (Map.Info.IsPointOnMap(tile_pos, this->MapLayer) && UnitTypeCanBeAt(*type, tile_pos, this->MapLayer->ID)) {
6265 has_adjacent_rail = true;
6266 break;
6267 }
6268
6269 tile_pos.y = bottom_right_pos.y;
6270 if (Map.Info.IsPointOnMap(tile_pos, this->MapLayer) && UnitTypeCanBeAt(*type, tile_pos, this->MapLayer->ID)) {
6271 has_adjacent_rail = true;
6272 break;
6273 }
6274 }
6275
6276 if (!has_adjacent_rail) {
6277 for (int y = top_left_pos.y; y <= bottom_right_pos.y; ++y) {
6278 Vec2i tile_pos(top_left_pos.x, y);
6279 if (Map.Info.IsPointOnMap(tile_pos, this->MapLayer) && UnitTypeCanBeAt(*type, tile_pos, this->MapLayer->ID)) {
6280 has_adjacent_rail = true;
6281 break;
6282 }
6283
6284 tile_pos.x = bottom_right_pos.x;
6285 if (Map.Info.IsPointOnMap(tile_pos, this->MapLayer) && UnitTypeCanBeAt(*type, tile_pos, this->MapLayer->ID)) {
6286 has_adjacent_rail = true;
6287 break;
6288 }
6289 }
6290 }
6291
6292 return has_adjacent_rail;
6293 }
6294
GetAnimations() const6295 CAnimations *CUnit::GetAnimations() const
6296 {
6297 const CUnitTypeVariation *variation = this->GetVariation();
6298 if (variation && variation->Animations) {
6299 return variation->Animations;
6300 } else {
6301 return this->Type->Animations;
6302 }
6303 }
6304
GetConstruction() const6305 CConstruction *CUnit::GetConstruction() const
6306 {
6307 const CUnitTypeVariation *variation = this->GetVariation();
6308 if (variation && variation->Construction) {
6309 return variation->Construction;
6310 } else {
6311 return this->Type->Construction;
6312 }
6313 }
6314
GetIcon() const6315 IconConfig CUnit::GetIcon() const
6316 {
6317 if (this->Character != nullptr && this->Character->Level >= 3 && this->Character->HeroicIcon.Icon) {
6318 return this->Character->HeroicIcon;
6319 } else if (this->Character != nullptr && this->Character->Icon.Icon) {
6320 return this->Character->Icon;
6321 } else if (this->Unique != nullptr && this->Unique->Icon.Icon) {
6322 return this->Unique->Icon;
6323 }
6324
6325 const CUnitTypeVariation *variation = this->GetVariation();
6326 if (variation && variation->Icon.Icon) {
6327 return variation->Icon;
6328 } else {
6329 return this->Type->Icon;
6330 }
6331 }
6332
GetButtonIcon(int button_action) const6333 CIcon *CUnit::GetButtonIcon(int button_action) const
6334 {
6335 if (this->ButtonIcons.find(button_action) != this->ButtonIcons.end()) {
6336 return this->ButtonIcons.find(button_action)->second;
6337 } else if (this->Player == ThisPlayer && ThisPlayer->Faction != -1 && PlayerRaces.Factions[ThisPlayer->Faction]->ButtonIcons.find(button_action) != PlayerRaces.Factions[ThisPlayer->Faction]->ButtonIcons.end()) {
6338 return PlayerRaces.Factions[ThisPlayer->Faction]->ButtonIcons[button_action].Icon;
6339 } else if (this->Player == ThisPlayer && PlayerRaces.ButtonIcons[ThisPlayer->Race].find(button_action) != PlayerRaces.ButtonIcons[ThisPlayer->Race].end()) {
6340 return PlayerRaces.ButtonIcons[ThisPlayer->Race][button_action].Icon;
6341 }
6342
6343 return nullptr;
6344 }
6345
GetMissile() const6346 MissileConfig CUnit::GetMissile() const
6347 {
6348 if (this->Variable[FIREDAMAGE_INDEX].Value > 0 && this->Type->FireMissile.Missile) {
6349 return this->Type->FireMissile;
6350 } else {
6351 return this->Type->Missile;
6352 }
6353 }
6354
GetLayerSprite(int image_layer) const6355 CPlayerColorGraphic *CUnit::GetLayerSprite(int image_layer) const
6356 {
6357 const CUnitTypeVariation *layer_variation = this->GetLayerVariation(image_layer);
6358 if (layer_variation && layer_variation->Sprite) {
6359 return layer_variation->Sprite;
6360 }
6361
6362 const CUnitTypeVariation *variation = this->GetVariation();
6363 if (variation && variation->LayerSprites[image_layer]) {
6364 return variation->LayerSprites[image_layer];
6365 } else if (this->Type->LayerSprites[image_layer]) {
6366 return this->Type->LayerSprites[image_layer];
6367 } else {
6368 return nullptr;
6369 }
6370 }
6371
GetName() const6372 std::string CUnit::GetName() const
6373 {
6374 if (GameRunning && this->Character && this->Character->Deity) {
6375 if (ThisPlayer->Race >= 0) {
6376 std::string cultural_name = this->Character->Deity->GetCulturalName(CCivilization::Civilizations[ThisPlayer->Race]);
6377
6378 if (!cultural_name.empty()) {
6379 return cultural_name;
6380 }
6381 }
6382
6383 return this->Character->Deity->Name;
6384 }
6385
6386 std::string name = this->Name;
6387
6388 if (name.empty()) {
6389 return name;
6390 }
6391
6392 if (!this->ExtraName.empty()) {
6393 name += " ";
6394 name += this->ExtraName;
6395 }
6396
6397 if (!this->FamilyName.empty()) {
6398 name += " ";
6399 name += this->FamilyName;
6400 }
6401
6402 return name;
6403 }
6404
GetTypeName() const6405 std::string CUnit::GetTypeName() const
6406 {
6407 if (this->Character && this->Character->Deity) {
6408 return _("Deity");
6409 }
6410
6411 const CUnitTypeVariation *variation = this->GetVariation();
6412 if (variation && !variation->TypeName.empty()) {
6413 return _(variation->TypeName.c_str());
6414 } else {
6415 return _(this->Type->Name.c_str());
6416 }
6417 }
6418
GetMessageName() const6419 std::string CUnit::GetMessageName() const
6420 {
6421 std::string name = GetName();
6422 if (name.empty()) {
6423 return GetTypeName();
6424 }
6425
6426 if (!this->Identified) {
6427 return GetTypeName() + " (" + _("Unidentified") + ")";
6428 }
6429
6430 if (!this->Unique && this->Work == nullptr && (this->Prefix != nullptr || this->Suffix != nullptr || this->Spell != nullptr)) {
6431 return name;
6432 }
6433
6434 return name + " (" + GetTypeName() + ")";
6435 }
6436 //Wyrmgus end
6437
6438 /**
6439 ** Let an unit die.
6440 **
6441 ** @param unit Unit to be destroyed.
6442 */
LetUnitDie(CUnit & unit,bool suicide)6443 void LetUnitDie(CUnit &unit, bool suicide)
6444 {
6445 unit.Variable[HP_INDEX].Value = std::min<int>(0, unit.Variable[HP_INDEX].Value);
6446 unit.Moving = 0;
6447 unit.TTL = 0;
6448 unit.Anim.Unbreakable = 0;
6449
6450 const CUnitType *type = unit.Type;
6451
6452 while (unit.Resource.Workers) {
6453 unit.Resource.Workers->DeAssignWorkerFromMine(unit);
6454 }
6455
6456 // removed units, just remove.
6457 if (unit.Removed) {
6458 DebugPrint("Killing a removed unit?\n");
6459 if (unit.UnitInside) {
6460 DestroyAllInside(unit);
6461 }
6462 UnitLost(unit);
6463 UnitClearOrders(unit);
6464 unit.Release();
6465 return;
6466 }
6467
6468 PlayUnitSound(unit, VoiceDying);
6469
6470 //
6471 // Catapults,... explodes.
6472 //
6473 if (type->ExplodeWhenKilled) {
6474 const PixelPos pixelPos = unit.GetMapPixelPosCenter();
6475
6476 MakeMissile(*type->Explosion.Missile, pixelPos, pixelPos, unit.MapLayer->ID);
6477 }
6478 if (type->DeathExplosion) {
6479 const PixelPos pixelPos = unit.GetMapPixelPosCenter();
6480
6481 type->DeathExplosion->pushPreamble();
6482 //Wyrmgus start
6483 type->DeathExplosion->pushInteger(UnitNumber(unit));
6484 //Wyrmgus end
6485 type->DeathExplosion->pushInteger(pixelPos.x);
6486 type->DeathExplosion->pushInteger(pixelPos.y);
6487 type->DeathExplosion->run();
6488 }
6489 if (suicide) {
6490 const PixelPos pixelPos = unit.GetMapPixelPosCenter();
6491
6492 if (unit.GetMissile().Missile) {
6493 MakeMissile(*unit.GetMissile().Missile, pixelPos, pixelPos, unit.MapLayer->ID);
6494 }
6495 }
6496 // Handle Teleporter Destination Removal
6497 if (type->BoolFlag[TELEPORTER_INDEX].value && unit.Goal) {
6498 unit.Goal->Remove(nullptr);
6499 UnitLost(*unit.Goal);
6500 UnitClearOrders(*unit.Goal);
6501 unit.Goal->Release();
6502 unit.Goal = nullptr;
6503 }
6504
6505 //Wyrmgus start
6506 for (size_t i = 0; i < unit.SoldUnits.size(); ++i) {
6507 DestroyAllInside(*unit.SoldUnits[i]);
6508 LetUnitDie(*unit.SoldUnits[i]);
6509 }
6510 unit.SoldUnits.clear();
6511 //Wyrmgus end
6512
6513 // Transporters lose or save their units and buildings their workers
6514 //Wyrmgus start
6515 // if (unit.UnitInside && unit.Type->BoolFlag[SAVECARGO_INDEX].value) {
6516 if (
6517 unit.UnitInside
6518 && (
6519 unit.Type->BoolFlag[SAVECARGO_INDEX].value
6520 || (unit.HasInventory() && unit.Character == nullptr)
6521 )
6522 ) {
6523 //Wyrmgus end
6524 DropOutAll(unit);
6525 } else if (unit.UnitInside) {
6526 DestroyAllInside(unit);
6527 }
6528
6529 //Wyrmgus start
6530 //if is a raft or bridge, destroy all land units on it
6531 if (unit.Type->BoolFlag[BRIDGE_INDEX].value) {
6532 std::vector<CUnit *> table;
6533 Select(unit.tilePos, unit.tilePos, table, unit.MapLayer->ID);
6534 for (size_t i = 0; i != table.size(); ++i) {
6535 if (table[i]->IsAliveOnMap() && !table[i]->Type->BoolFlag[BRIDGE_INDEX].value && table[i]->Type->UnitType == UnitTypeLand) {
6536 table[i]->Variable[HP_INDEX].Value = std::min<int>(0, unit.Variable[HP_INDEX].Value);
6537 table[i]->Moving = 0;
6538 table[i]->TTL = 0;
6539 table[i]->Anim.Unbreakable = 0;
6540 PlayUnitSound(*table[i], VoiceDying);
6541 table[i]->Remove(nullptr);
6542 UnitLost(*table[i]);
6543 UnitClearOrders(*table[i]);
6544 table[i]->Release();
6545 }
6546 }
6547 }
6548 //Wyrmgus end
6549
6550 //Wyrmgus start
6551 //drop items upon death
6552 if (!suicide && unit.CurrentAction() != UnitActionBuilt && (unit.Character || unit.Type->BoolFlag[BUILDING_INDEX].value || SyncRand(100) >= 66)) { //66% chance nothing will be dropped, unless the unit is a character or building, in which it case it will always drop an item
6553 unit.GenerateDrop();
6554 }
6555 //Wyrmgus end
6556
6557 //Wyrmgus start
6558 std::vector<CUnit *> seeing_table; //units seeing this unit
6559 if (type->BoolFlag[AIRUNPASSABLE_INDEX].value) {
6560 SelectAroundUnit(unit, 16, seeing_table); //a range of 16 should be safe enough; there should be no unit or building in the game with a sight range that high, let alone higher
6561 for (size_t i = 0; i != seeing_table.size(); ++i) {
6562 MapUnmarkUnitSight(*seeing_table[i]);
6563 }
6564 }
6565 //Wyrmgus end
6566
6567 unit.Remove(nullptr);
6568 UnitLost(unit);
6569 UnitClearOrders(unit);
6570
6571
6572 // Unit has death animation.
6573
6574 // Not good: UnitUpdateHeading(unit);
6575 delete unit.Orders[0];
6576 unit.Orders[0] = COrder::NewActionDie();
6577 if (type->CorpseType) {
6578 #ifdef DYNAMIC_LOAD
6579 if (!type->Sprite) {
6580 LoadUnitTypeSprite(type);
6581 }
6582 #endif
6583 unit.IX = (type->CorpseType->Width - type->CorpseType->Sprite->Width) / 2;
6584 unit.IY = (type->CorpseType->Height - type->CorpseType->Sprite->Height) / 2;
6585
6586 unit.CurrentSightRange = type->CorpseType->Stats[unit.Player->Index].Variables[SIGHTRANGE_INDEX].Max;
6587 } else {
6588 unit.CurrentSightRange = 0;
6589 }
6590
6591 // If we have a corpse, or a death animation, we are put back on the map
6592 // This enables us to be tracked. Possibly for spells (eg raise dead)
6593 //Wyrmgus start
6594 // if (type->CorpseType || (type->Animations && type->Animations->Death)) {
6595 if (type->CorpseType || (unit.GetAnimations() && unit.GetAnimations()->Death)) {
6596 //Wyrmgus end
6597 unit.Removed = 0;
6598 Map.Insert(unit);
6599
6600 // FIXME: rb: Maybe we need this here because corpse of cloaked units
6601 // may crash Sign code
6602
6603 // Recalculate the seen count.
6604 //UnitCountSeen(unit);
6605 }
6606
6607 MapMarkUnitSight(unit);
6608
6609 //Wyrmgus start
6610 if (unit.Settlement) {
6611 unit.UpdateBuildingSettlementAssignment(unit.Settlement);
6612 }
6613 //Wyrmgus end
6614
6615 //Wyrmgus start
6616 if (type->BoolFlag[AIRUNPASSABLE_INDEX].value) {
6617 for (size_t i = 0; i != seeing_table.size(); ++i) {
6618 MapMarkUnitSight(*seeing_table[i]);
6619 }
6620 }
6621 //Wyrmgus end
6622 }
6623
6624 /**
6625 ** Destroy all units inside unit.
6626 **
6627 ** @param source container.
6628 */
DestroyAllInside(CUnit & source)6629 void DestroyAllInside(CUnit &source)
6630 {
6631 CUnit *unit = source.UnitInside;
6632
6633 // No Corpses, we are inside something, and we can't be seen
6634 for (int i = source.InsideCount; i; --i, unit = unit->NextContained) {
6635 // Transporter inside a transporter?
6636 if (unit->UnitInside) {
6637 DestroyAllInside(*unit);
6638 }
6639 UnitLost(*unit);
6640 UnitClearOrders(*unit);
6641 unit->Release();
6642 }
6643 }
6644
6645 /*----------------------------------------------------------------------------
6646 -- Unit AI
6647 ----------------------------------------------------------------------------*/
6648
ThreatCalculate(const CUnit & unit,const CUnit & dest)6649 int ThreatCalculate(const CUnit &unit, const CUnit &dest)
6650 {
6651 const CUnitType &type = *unit.Type;
6652 const CUnitType &dtype = *dest.Type;
6653 int cost = 0;
6654
6655 // Buildings, non-aggressive and invincible units have the lowest priority
6656 if (dest.IsAgressive() == false || dest.Variable[UNHOLYARMOR_INDEX].Value > 0
6657 || dest.Type->BoolFlag[INDESTRUCTIBLE_INDEX].value) {
6658 if (dest.Type->CanMove() == false) {
6659 return INT_MAX;
6660 } else {
6661 return INT_MAX / 2;
6662 }
6663 }
6664
6665 // Priority 0-255
6666 cost -= dest.Variable[PRIORITY_INDEX].Value * PRIORITY_FACTOR;
6667 // Remaining HP (Health) 0-65535
6668 //Wyrmgus start
6669 // cost += dest.Variable[HP_INDEX].Value * 100 / dest.Variable[HP_INDEX].Max * HEALTH_FACTOR;
6670 cost += dest.Variable[HP_INDEX].Value * 100 / dest.GetModifiedVariable(HP_INDEX, VariableMax) * HEALTH_FACTOR;
6671 //Wyrmgus end
6672
6673 const int d = unit.MapDistanceTo(dest);
6674
6675 if (d <= unit.GetModifiedVariable(ATTACKRANGE_INDEX) && d >= type.MinAttackRange) {
6676 cost += d * INRANGE_FACTOR;
6677 cost -= INRANGE_BONUS;
6678 } else {
6679 cost += d * DISTANCE_FACTOR;
6680 }
6681
6682 for (unsigned int i = 0; i < UnitTypeVar.GetNumberBoolFlag(); i++) {
6683 if (type.BoolFlag[i].AiPriorityTarget != CONDITION_TRUE) {
6684 if ((type.BoolFlag[i].AiPriorityTarget == CONDITION_ONLY) & (dtype.BoolFlag[i].value)) {
6685 cost -= AIPRIORITY_BONUS;
6686 }
6687 if ((type.BoolFlag[i].AiPriorityTarget == CONDITION_FALSE) & (dtype.BoolFlag[i].value)) {
6688 cost += AIPRIORITY_BONUS;
6689 }
6690 }
6691 }
6692
6693 // Unit can attack back.
6694 if (CanTarget(dtype, type)) {
6695 cost -= CANATTACK_BONUS;
6696 }
6697 return cost;
6698 }
6699
HitUnit_LastAttack(const CUnit * attacker,CUnit & target)6700 static void HitUnit_LastAttack(const CUnit *attacker, CUnit &target)
6701 {
6702 const unsigned long lastattack = target.Attacked;
6703
6704 target.Attacked = GameCycle ? GameCycle : 1;
6705 if (target.Type->BoolFlag[WALL_INDEX].value || (lastattack && GameCycle <= lastattack + 2 * CYCLES_PER_SECOND)) {
6706 return;
6707 }
6708 // NOTE: perhaps this should also be moved into the notify?
6709 if (target.Player == ThisPlayer) {
6710 // FIXME: Problem with load+save.
6711
6712 //
6713 // One help cry each 2 second is enough
6714 // If on same area ignore it for 2 minutes.
6715 //
6716 if (HelpMeLastCycle < GameCycle) {
6717 if (!HelpMeLastCycle
6718 || HelpMeLastCycle + CYCLES_PER_SECOND * 120 < GameCycle
6719 || target.tilePos.x < HelpMeLastX - 14
6720 || target.tilePos.x > HelpMeLastX + 14
6721 || target.tilePos.y < HelpMeLastY - 14
6722 || target.tilePos.y > HelpMeLastY + 14) {
6723 HelpMeLastCycle = GameCycle + CYCLES_PER_SECOND * 2;
6724 HelpMeLastX = target.tilePos.x;
6725 HelpMeLastY = target.tilePos.y;
6726 PlayUnitSound(target, VoiceHelpMe);
6727 target.Player->Notify(NotifyRed, target.tilePos, target.MapLayer->ID, _("%s attacked"), target.GetMessageName().c_str());
6728 }
6729 }
6730 }
6731
6732 if (GameCycle > (lastattack + 2 * (CYCLES_PER_SECOND * 60)) && attacker && !target.Type->BoolFlag[BUILDING_INDEX].value) { //only trigger this every two minutes for the unit
6733 if (
6734 target.Player->AiEnabled
6735 && !attacker->Type->BoolFlag[INDESTRUCTIBLE_INDEX].value // don't attack indestructible units back
6736 ) {
6737 AiHelpMe(attacker->GetFirstContainer(), target);
6738 }
6739 }
6740 }
6741
HitUnit_Raid(CUnit * attacker,CUnit & target,int damage)6742 static void HitUnit_Raid(CUnit *attacker, CUnit &target, int damage)
6743 {
6744 if (!attacker) {
6745 return;
6746 }
6747
6748 if (attacker->Player == target.Player || attacker->Player->Index == PlayerNumNeutral || target.Player->Index == PlayerNumNeutral) {
6749 return;
6750 }
6751
6752 int var_index;
6753 if (target.Type->BoolFlag[BUILDING_INDEX].value) {
6754 var_index = RAIDING_INDEX;
6755 } else {
6756 var_index = MUGGING_INDEX;
6757 }
6758
6759 if (!attacker->Variable[var_index].Value) {
6760 return;
6761 }
6762
6763 if (!attacker->Variable[SHIELDPIERCING_INDEX].Value) {
6764 int shieldDamage = target.Variable[SHIELDPERMEABILITY_INDEX].Value < 100
6765 ? std::min(target.Variable[SHIELD_INDEX].Value, damage * (100 - target.Variable[SHIELDPERMEABILITY_INDEX].Value) / 100)
6766 : 0;
6767
6768 damage -= shieldDamage;
6769 }
6770
6771 damage = std::min(damage, target.Variable[HP_INDEX].Value);
6772
6773 if (damage <= 0) {
6774 return;
6775 }
6776
6777 for (int i = 0; i < MaxCosts; ++i) {
6778 if (target.Type->Stats[target.Player->Index].Costs[i] > 0) {
6779 int resource_change = target.Type->Stats[target.Player->Index].Costs[i] * damage * attacker->Variable[var_index].Value / target.GetModifiedVariable(HP_INDEX, VariableMax) / 100;
6780 resource_change = std::min(resource_change, target.Player->GetResource(i, STORE_BOTH));
6781 attacker->Player->ChangeResource(i, resource_change);
6782 attacker->Player->TotalResources[i] += resource_change;
6783 target.Player->ChangeResource(i, -resource_change);
6784 }
6785 }
6786 }
6787
HitUnit_IsUnitWillDie(const CUnit * attacker,const CUnit & target,int damage)6788 static bool HitUnit_IsUnitWillDie(const CUnit *attacker, const CUnit &target, int damage)
6789 {
6790 int shieldDamage = target.Variable[SHIELDPERMEABILITY_INDEX].Value < 100
6791 ? std::min(target.Variable[SHIELD_INDEX].Value, damage * (100 - target.Variable[SHIELDPERMEABILITY_INDEX].Value) / 100)
6792 : 0;
6793 return (target.Variable[HP_INDEX].Value <= damage && attacker && attacker->Variable[SHIELDPIERCING_INDEX].Value)
6794 || (target.Variable[HP_INDEX].Value <= damage - shieldDamage)
6795 || (target.Variable[HP_INDEX].Value == 0);
6796 }
6797
HitUnit_IncreaseScoreForKill(CUnit & attacker,CUnit & target)6798 static void HitUnit_IncreaseScoreForKill(CUnit &attacker, CUnit &target)
6799 {
6800 attacker.Player->Score += target.Variable[POINTS_INDEX].Value;
6801 if (target.Type->BoolFlag[BUILDING_INDEX].value) {
6802 attacker.Player->TotalRazings++;
6803 } else {
6804 attacker.Player->TotalKills++;
6805 }
6806
6807 //Wyrmgus start
6808 attacker.Player->UnitTypeKills[target.Type->Slot]++;
6809
6810 //distribute experience between nearby units belonging to the same player
6811 if (!target.Type->BoolFlag[BUILDING_INDEX].value) {
6812 attacker.ChangeExperience(UseHPForXp ? target.Variable[HP_INDEX].Value : target.Variable[POINTS_INDEX].Value, ExperienceRange);
6813 }
6814 //Wyrmgus end
6815
6816 attacker.Variable[KILL_INDEX].Value++;
6817 attacker.Variable[KILL_INDEX].Max++;
6818 attacker.Variable[KILL_INDEX].Enable = 1;
6819
6820 //Wyrmgus start
6821 for (size_t i = 0; i < attacker.Player->QuestObjectives.size(); ++i) {
6822 CPlayerQuestObjective *objective = attacker.Player->QuestObjectives[i];
6823 if (
6824 (objective->ObjectiveType == DestroyUnitsObjectiveType && std::find(objective->UnitTypes.begin(), objective->UnitTypes.end(), target.Type) != objective->UnitTypes.end() && (!objective->Settlement || objective->Settlement == target.Settlement))
6825 || (objective->ObjectiveType == DestroyHeroObjectiveType && target.Character && objective->Character == target.Character)
6826 || (objective->ObjectiveType == DestroyUniqueObjectiveType && target.Unique && objective->Unique == target.Unique)
6827 ) {
6828 if (!objective->Faction || objective->Faction->ID == target.Player->Faction) {
6829 objective->Counter = std::min(objective->Counter + 1, objective->Quantity);
6830 }
6831 } else if (objective->ObjectiveType == DestroyFactionObjectiveType) {
6832 const CPlayer *faction_player = GetFactionPlayer(objective->Faction);
6833
6834 if (faction_player) {
6835 int dying_faction_units = faction_player == target.Player ? 1 : 0;
6836 dying_faction_units += target.GetTotalInsideCount(faction_player, true, true);
6837
6838 if (dying_faction_units > 0 && faction_player->GetUnitCount() <= dying_faction_units) {
6839 objective->Counter = 1;
6840 }
6841 }
6842 }
6843 }
6844
6845 //also increase score for units inside the target that will be destroyed when the target dies
6846 if (
6847 target.UnitInside
6848 && !target.Type->BoolFlag[SAVECARGO_INDEX].value
6849 ) {
6850 CUnit *boarded_unit = target.UnitInside;
6851 for (int i = 0; i < target.InsideCount; ++i, boarded_unit = boarded_unit->NextContained) {
6852 if (!boarded_unit->Type->BoolFlag[ITEM_INDEX].value) { //ignore items
6853 HitUnit_IncreaseScoreForKill(attacker, *boarded_unit);
6854 }
6855 }
6856 }
6857 //Wyrmgus end
6858 }
6859
HitUnit_ApplyDamage(CUnit * attacker,CUnit & target,int damage)6860 static void HitUnit_ApplyDamage(CUnit *attacker, CUnit &target, int damage)
6861 {
6862 if (attacker && attacker->Variable[SHIELDPIERCING_INDEX].Value) {
6863 target.Variable[HP_INDEX].Value -= damage;
6864 } else {
6865 int shieldDamage = target.Variable[SHIELDPERMEABILITY_INDEX].Value < 100
6866 ? std::min(target.Variable[SHIELD_INDEX].Value, damage * (100 - target.Variable[SHIELDPERMEABILITY_INDEX].Value) / 100)
6867 : 0;
6868 if (shieldDamage) {
6869 target.Variable[SHIELD_INDEX].Value -= shieldDamage;
6870 clamp(&target.Variable[SHIELD_INDEX].Value, 0, target.Variable[SHIELD_INDEX].Max);
6871 }
6872 target.Variable[HP_INDEX].Value -= damage - shieldDamage;
6873 }
6874
6875 //Wyrmgus start
6876 //distribute experience between nearby units belonging to the same player
6877
6878 // if (UseHPForXp && attacker && target.IsEnemy(*attacker)) {
6879 if (UseHPForXp && attacker && (target.IsEnemy(*attacker) || target.Player->Type == PlayerNeutral) && !target.Type->BoolFlag[BUILDING_INDEX].value) {
6880 attacker->ChangeExperience(damage, ExperienceRange);
6881 }
6882 //Wyrmgus end
6883
6884 //Wyrmgus start
6885 //use a healing item if any are available
6886 if (target.HasInventory()) {
6887 target.HealingItemAutoUse();
6888 }
6889 //Wyrmgus end
6890 }
6891
HitUnit_BuildingCapture(CUnit * attacker,CUnit & target,int damage)6892 static void HitUnit_BuildingCapture(CUnit *attacker, CUnit &target, int damage)
6893 {
6894 // FIXME: this is dumb. I made repairers capture. crap.
6895 // david: capture enemy buildings
6896 // Only worker types can capture.
6897 // Still possible to destroy building if not careful (too many attackers)
6898 if (EnableBuildingCapture && attacker
6899 && target.Type->BoolFlag[BUILDING_INDEX].value && target.Variable[HP_INDEX].Value <= damage * 3
6900 && attacker->IsEnemy(target)
6901 && attacker->Type->RepairRange) {
6902 target.ChangeOwner(*attacker->Player);
6903 CommandStopUnit(*attacker); // Attacker shouldn't continue attack!
6904 }
6905 }
6906
HitUnit_ShowDamageMissile(const CUnit & target,int damage)6907 static void HitUnit_ShowDamageMissile(const CUnit &target, int damage)
6908 {
6909 const PixelPos targetPixelCenter = target.GetMapPixelPosCenter();
6910
6911 if ((target.IsVisibleOnMap(*ThisPlayer) || ReplayRevealMap) && !DamageMissile.empty()) {
6912 const MissileType *mtype = MissileTypeByIdent(DamageMissile);
6913 const PixelDiff offset(3, -mtype->Range);
6914
6915 MakeLocalMissile(*mtype, targetPixelCenter, targetPixelCenter + offset, target.MapLayer->ID)->Damage = -damage;
6916 }
6917 }
6918
HitUnit_ShowImpactMissile(const CUnit & target)6919 static void HitUnit_ShowImpactMissile(const CUnit &target)
6920 {
6921 const PixelPos targetPixelCenter = target.GetMapPixelPosCenter();
6922 const CUnitType &type = *target.Type;
6923
6924 if (target.Variable[SHIELD_INDEX].Value > 0
6925 && !type.Impact[ANIMATIONS_DEATHTYPES + 1].Name.empty()) { // shield impact
6926 MakeMissile(*type.Impact[ANIMATIONS_DEATHTYPES + 1].Missile, targetPixelCenter, targetPixelCenter, target.MapLayer->ID);
6927 } else if (target.DamagedType && !type.Impact[target.DamagedType].Name.empty()) { // specific to damage type impact
6928 MakeMissile(*type.Impact[target.DamagedType].Missile, targetPixelCenter, targetPixelCenter, target.MapLayer->ID);
6929 } else if (!type.Impact[ANIMATIONS_DEATHTYPES].Name.empty()) { // generic impact
6930 MakeMissile(*type.Impact[ANIMATIONS_DEATHTYPES].Missile, targetPixelCenter, targetPixelCenter, target.MapLayer->ID);
6931 }
6932 }
6933
HitUnit_ChangeVariable(CUnit & target,const Missile & missile)6934 static void HitUnit_ChangeVariable(CUnit &target, const Missile &missile)
6935 {
6936 const int var = missile.Type->ChangeVariable;
6937
6938 target.Variable[var].Enable = 1;
6939 target.Variable[var].Value += missile.Type->ChangeAmount;
6940 if (target.Variable[var].Value > target.Variable[var].Max) {
6941 if (missile.Type->ChangeMax) {
6942 target.Variable[var].Max = target.Variable[var].Value;
6943 //Wyrmgus start
6944 // } else {
6945 } else if (target.Variable[var].Value > target.GetModifiedVariable(var, VariableMax)) {
6946 //Wyrmgus end
6947 //Wyrmgus start
6948 // target.Variable[var].Value = target.Variable[var].Max;
6949 target.Variable[var].Value = target.GetModifiedVariable(var, VariableMax);
6950 //Wyrmgus end
6951 }
6952 }
6953
6954 //Wyrmgus start
6955 if (var == ATTACKRANGE_INDEX && target.Container) {
6956 target.Container->UpdateContainerAttackRange();
6957 } else if (var == LEVEL_INDEX || var == POINTS_INDEX) {
6958 target.UpdateXPRequired();
6959 } else if (var == XP_INDEX) {
6960 target.XPChanged();
6961 } else if (var == STUN_INDEX && target.Variable[var].Value > 0) { //if unit has become stunned, stop it
6962 CommandStopUnit(target);
6963 } else if (var == KNOWLEDGEMAGIC_INDEX) {
6964 target.CheckIdentification();
6965 }
6966 //Wyrmgus end
6967 }
6968
6969
HitUnit_Burning(CUnit & target)6970 static void HitUnit_Burning(CUnit &target)
6971 {
6972 //Wyrmgus start
6973 // const int f = (100 * target.Variable[HP_INDEX].Value) / target.Variable[HP_INDEX].Max;
6974 const int f = (100 * target.Variable[HP_INDEX].Value) / target.GetModifiedVariable(HP_INDEX, VariableMax);
6975 //Wyrmgus end
6976 MissileType *fire = MissileBurningBuilding(f);
6977
6978 if (fire) {
6979 const PixelPos targetPixelCenter = target.GetMapPixelPosCenter();
6980 const PixelDiff offset(0, -Map.GetMapLayerPixelTileSize(target.MapLayer->ID).y);
6981 Missile *missile = MakeMissile(*fire, targetPixelCenter + offset, targetPixelCenter + offset, target.MapLayer->ID);
6982
6983 missile->SourceUnit = ⌖
6984 target.Burning = 1;
6985 }
6986 }
6987
6988 //Wyrmgus start
HitUnit_NormalHitSpecialDamageEffects(CUnit & attacker,CUnit & target)6989 void HitUnit_NormalHitSpecialDamageEffects(CUnit &attacker, CUnit &target)
6990 {
6991 if (attacker.Variable[FIREDAMAGE_INDEX].Value > 0 && attacker.Variable[FIREDAMAGE_INDEX].Value >= attacker.Variable[COLDDAMAGE_INDEX].Value) { // apply only either the fire damage or cold damage effects, but not both at the same time; apply the one with greater value, but if both are equal fire damage takes precedence
6992 HitUnit_SpecialDamageEffect(target, FIREDAMAGE_INDEX);
6993 } else if (attacker.Variable[COLDDAMAGE_INDEX].Value > 0) {
6994 HitUnit_SpecialDamageEffect(target, COLDDAMAGE_INDEX);
6995 }
6996
6997 if (attacker.Variable[LIGHTNINGDAMAGE_INDEX].Value > 0) {
6998 HitUnit_SpecialDamageEffect(target, LIGHTNINGDAMAGE_INDEX);
6999 }
7000 }
7001
HitUnit_SpecialDamageEffect(CUnit & target,int dmg_var)7002 void HitUnit_SpecialDamageEffect(CUnit &target, int dmg_var)
7003 {
7004 if (dmg_var == COLDDAMAGE_INDEX && target.Variable[COLDRESISTANCE_INDEX].Value < 100 && target.Type->BoolFlag[ORGANIC_INDEX].value) { //if resistance to cold is 100%, the effect has no chance of being applied
7005 int rand_max = 100 * 100 / (100 - target.Variable[COLDRESISTANCE_INDEX].Value);
7006 if (SyncRand(rand_max) == 0) {
7007 target.Variable[SLOW_INDEX].Enable = 1;
7008 target.Variable[SLOW_INDEX].Value = std::max(200, target.Variable[SLOW_INDEX].Value);
7009 target.Variable[SLOW_INDEX].Max = 1000;
7010 }
7011 } else if (dmg_var == LIGHTNINGDAMAGE_INDEX && target.Variable[LIGHTNINGRESISTANCE_INDEX].Value < 100 && target.Type->BoolFlag[ORGANIC_INDEX].value) {
7012 int rand_max = 100 * 100 / (100 - target.Variable[LIGHTNINGRESISTANCE_INDEX].Value);
7013 if (SyncRand(rand_max) == 0) {
7014 target.Variable[STUN_INDEX].Enable = 1;
7015 target.Variable[STUN_INDEX].Value = std::max(50, target.Variable[STUN_INDEX].Value);
7016 target.Variable[STUN_INDEX].Max = 1000;
7017 }
7018 }
7019 }
7020 //Wyrmgus end
7021
7022 //Wyrmgus start
7023 //static void HitUnit_RunAway(CUnit &target, const CUnit &attacker)
HitUnit_RunAway(CUnit & target,const CUnit & attacker)7024 void HitUnit_RunAway(CUnit &target, const CUnit &attacker)
7025 //Wyrmgus end
7026 {
7027 Vec2i pos = target.tilePos - attacker.tilePos;
7028 int d = isqrt(pos.x * pos.x + pos.y * pos.y);
7029
7030 if (!d) {
7031 d = 1;
7032 }
7033 pos.x = target.tilePos.x + (pos.x * 5) / d + (SyncRand() & 3);
7034 pos.y = target.tilePos.y + (pos.y * 5) / d + (SyncRand() & 3);
7035 Map.Clamp(pos, target.MapLayer->ID);
7036 CommandStopUnit(target);
7037 CommandMove(target, pos, 0, target.MapLayer->ID);
7038 }
7039
HitUnit_AttackBack(CUnit & attacker,CUnit & target)7040 static void HitUnit_AttackBack(CUnit &attacker, CUnit &target)
7041 {
7042 const int threshold = 30;
7043 COrder *savedOrder = nullptr;
7044
7045 //Wyrmgus start
7046 // if (target.Player->AiEnabled == false) {
7047 if (target.Player->AiEnabled == false && target.Player->Type != PlayerNeutral) { // allow neutral units to strike back
7048 //Wyrmgus end
7049 if (target.CurrentAction() == UnitActionAttack) {
7050 COrder_Attack &order = dynamic_cast<COrder_Attack &>(*target.CurrentOrder());
7051 if (order.IsWeakTargetSelected() == false) {
7052 return;
7053 }
7054 //Wyrmgus start
7055 // } else {
7056 } else if (target.CurrentAction() != UnitActionStill) {
7057 //Wyrmgus end
7058 return;
7059 }
7060 }
7061 if (target.CanStoreOrder(target.CurrentOrder())) {
7062 savedOrder = target.CurrentOrder()->Clone();
7063 }
7064 CUnit *oldgoal = target.CurrentOrder()->GetGoal();
7065 CUnit *goal, *best = oldgoal;
7066
7067 if (RevealAttacker && CanTarget(*target.Type, *attacker.Type)) {
7068 // Reveal Unit that is attacking
7069 goal = &attacker;
7070 } else {
7071 if (target.CurrentAction() == UnitActionStandGround) {
7072 goal = AttackUnitsInRange(target);
7073 } else {
7074 // Check for any other units in range
7075 goal = AttackUnitsInReactRange(target);
7076 }
7077 }
7078
7079 // Calculate the best target we could attack
7080 if (!best || (goal && (ThreatCalculate(target, *goal) < ThreatCalculate(target, *best)))) {
7081 best = goal;
7082 }
7083 if (CanTarget(*target.Type, *attacker.Type)
7084 && (!best || (goal != &attacker
7085 && (ThreatCalculate(target, attacker) < ThreatCalculate(target, *best))))) {
7086 best = &attacker;
7087 }
7088 //Wyrmgus start
7089 // if (best && best != oldgoal && best->Player != target.Player && best->IsAllied(target) == false) {
7090 if (best && best != oldgoal && (best->Player != target.Player || target.Player->Type == PlayerNeutral) && best->IsAllied(target) == false) {
7091 //Wyrmgus end
7092 CommandAttack(target, best->tilePos, best, FlushCommands, best->MapLayer->ID);
7093 // Set threshold value only for aggressive units
7094 if (best->IsAgressive()) {
7095 target.Threshold = threshold;
7096 }
7097 if (savedOrder != nullptr) {
7098 target.SavedOrder = savedOrder;
7099 }
7100 }
7101 }
7102
7103 /**
7104 ** Unit is hit by missile or other damage.
7105 **
7106 ** @param attacker Unit that attacks.
7107 ** @param target Unit that is hit.
7108 ** @param damage How many damage to take.
7109 ** @param missile Which missile took the damage.
7110 */
7111 //Wyrmgus start
7112 //void HitUnit(CUnit *attacker, CUnit &target, int damage, const Missile *missile)
HitUnit(CUnit * attacker,CUnit & target,int damage,const Missile * missile,bool show_damage)7113 void HitUnit(CUnit *attacker, CUnit &target, int damage, const Missile *missile, bool show_damage)
7114 //Wyrmgus end
7115 {
7116 const CUnitType *type = target.Type;
7117 if (!damage) {
7118 // Can now happen by splash damage
7119 // Multiple places send x/y as damage, which may be zero
7120 return;
7121 }
7122
7123 if (target.Variable[UNHOLYARMOR_INDEX].Value > 0 || target.Type->BoolFlag[INDESTRUCTIBLE_INDEX].value) {
7124 // vladi: units with active UnholyArmour are invulnerable
7125 return;
7126 }
7127 if (target.Removed) {
7128 DebugPrint("Removed target hit\n");
7129 return;
7130 }
7131
7132 Assert(damage != 0 && target.CurrentAction() != UnitActionDie && !target.Type->BoolFlag[VANISHES_INDEX].value);
7133
7134 //Wyrmgus start
7135 if (
7136 (attacker != nullptr && attacker->Player == ThisPlayer)
7137 && target.Player != ThisPlayer
7138 ) {
7139 // If player is hitting or being hit add tension to our music
7140 AddMusicTension(1);
7141 }
7142 //Wyrmgus end
7143
7144 if (GodMode) {
7145 if (attacker && attacker->Player == ThisPlayer) {
7146 damage = target.Variable[HP_INDEX].Value;
7147 }
7148 if (target.Player == ThisPlayer) {
7149 damage = 0;
7150 }
7151 }
7152 //Wyrmgus start
7153 // HitUnit_LastAttack(attacker, target);
7154 //Wyrmgus end
7155 if (attacker) {
7156 //Wyrmgus start
7157 HitUnit_LastAttack(attacker, target); //only trigger the help me notification and AI code if there is actually an attacker
7158 //Wyrmgus end
7159 target.DamagedType = ExtraDeathIndex(attacker->Type->DamageType.c_str());
7160 }
7161
7162 // OnHit callback
7163 if (type->OnHit) {
7164 const int tarSlot = UnitNumber(target);
7165 const int atSlot = attacker && attacker->IsAlive() ? UnitNumber(*attacker) : -1;
7166
7167 type->OnHit->pushPreamble();
7168 type->OnHit->pushInteger(tarSlot);
7169 type->OnHit->pushInteger(atSlot);
7170 type->OnHit->pushInteger(damage);
7171 type->OnHit->run();
7172 }
7173
7174 // Increase variables and call OnImpact
7175 if (missile && missile->Type) {
7176 if (missile->Type->ChangeVariable != -1) {
7177 HitUnit_ChangeVariable(target, *missile);
7178 }
7179 if (missile->Type->OnImpact) {
7180 const int attackerSlot = attacker ? UnitNumber(*attacker) : -1;
7181 const int targetSlot = UnitNumber(target);
7182 missile->Type->OnImpact->pushPreamble();
7183 missile->Type->OnImpact->pushInteger(attackerSlot);
7184 missile->Type->OnImpact->pushInteger(targetSlot);
7185 missile->Type->OnImpact->pushInteger(damage);
7186 missile->Type->OnImpact->run();
7187 }
7188 }
7189
7190 HitUnit_Raid(attacker, target, damage);
7191
7192 if (HitUnit_IsUnitWillDie(attacker, target, damage)) { // unit is killed or destroyed
7193 if (attacker) {
7194 // Setting ai threshold counter to 0 so it can target other units
7195 attacker->Threshold = 0;
7196 }
7197
7198 CUnit *destroyer = attacker;
7199 if (!destroyer) {
7200 int best_distance = 0;
7201 std::vector<CUnit *> table;
7202 SelectAroundUnit(target, ExperienceRange, table, IsEnemyWith(*target.Player));
7203 for (size_t i = 0; i < table.size(); i++) {
7204 CUnit *potential_destroyer = table[i];
7205 int distance = target.MapDistanceTo(*potential_destroyer);
7206 if (!destroyer || distance < best_distance) {
7207 destroyer = potential_destroyer;
7208 best_distance = distance;
7209 }
7210 }
7211 }
7212 if (destroyer) {
7213 if (target.IsEnemy(*destroyer) || target.Player->Type == PlayerNeutral) {
7214 HitUnit_IncreaseScoreForKill(*destroyer, target);
7215 }
7216 }
7217 LetUnitDie(target);
7218 return;
7219 }
7220
7221 HitUnit_ApplyDamage(attacker, target, damage);
7222 HitUnit_BuildingCapture(attacker, target, damage);
7223 //Wyrmgus start
7224 // HitUnit_ShowDamageMissile(target, damage);
7225 if (show_damage) {
7226 HitUnit_ShowDamageMissile(target, damage);
7227 }
7228 //Wyrmgus end
7229
7230 HitUnit_ShowImpactMissile(target);
7231
7232 //Wyrmgus start
7233 // if (type->BoolFlag[BUILDING_INDEX].value && !target.Burning) {
7234 if (type->BoolFlag[BUILDING_INDEX].value && !target.Burning && !target.UnderConstruction && target.Type->TileSize.x != 1 && target.Type->TileSize.y != 1) { //the building shouldn't burn if it's still under construction, or if it's too small
7235 //Wyrmgus end
7236 HitUnit_Burning(target);
7237 }
7238
7239 /* Target Reaction on Hit */
7240 if (target.Player->AiEnabled) {
7241 if (target.CurrentOrder()->OnAiHitUnit(target, attacker, damage)) {
7242 return;
7243 }
7244 }
7245
7246 if (!attacker) {
7247 return;
7248 }
7249
7250 // Can't attack run away.
7251 //Wyrmgus start
7252 // if (!target.IsAgressive() && target.CanMove() && target.CurrentAction() == UnitActionStill && !target.BoardCount) {
7253 if (
7254 (!target.IsAgressive() || attacker->Type->BoolFlag[INDESTRUCTIBLE_INDEX].value)
7255 && target.CanMove()
7256 && (target.CurrentAction() == UnitActionStill || target.Variable[TERROR_INDEX].Value > 0)
7257 && !target.BoardCount
7258 && !target.Type->BoolFlag[BRIDGE_INDEX].value
7259 ) {
7260 //Wyrmgus end
7261 HitUnit_RunAway(target, *attacker);
7262 }
7263
7264 const int threshold = 30;
7265
7266 if (target.Threshold && target.CurrentOrder()->HasGoal() && target.CurrentOrder()->GetGoal() == attacker) {
7267 target.Threshold = threshold;
7268 return;
7269 }
7270
7271 //Wyrmgus start
7272 // if (target.Threshold == 0 && target.IsAgressive() && target.CanMove() && !target.ReCast) {
7273 if (
7274 target.Threshold == 0
7275 && (target.IsAgressive() || (target.CanAttack() && target.Type->BoolFlag[COWARD_INDEX].value && (attacker->Type->BoolFlag[COWARD_INDEX].value || attacker->Variable[HP_INDEX].Value <= 3))) // attacks back if isn't coward, or if attacker is also coward, or if attacker has 3 HP or less
7276 && target.CanMove()
7277 && !target.ReCast
7278 && !attacker->Type->BoolFlag[INDESTRUCTIBLE_INDEX].value // don't attack indestructible units back
7279 ) {
7280 //Wyrmgus end
7281 // Attack units in range (which or the attacker?)
7282 // Don't bother unit if it casting repeatable spell
7283 HitUnit_AttackBack(*attacker->GetFirstContainer(), target); //if the unit is in a container, attack it instead of the unit (which is removed and thus unreachable)
7284 }
7285
7286 // What should we do with workers on :
7287 // case UnitActionRepair:
7288 // Drop orders and run away or return after escape?
7289 }
7290
7291 /*----------------------------------------------------------------------------
7292 -- Conflicts
7293 ----------------------------------------------------------------------------*/
7294
7295 /**
7296 ** @brief Returns the map distance between this unit and another one
7297 **
7298 ** @param dst The unit the distance to which is to be obtained
7299 **
7300 ** @return The distance to the other unit, in tiles
7301 */
MapDistanceTo(const CUnit & dst) const7302 int CUnit::MapDistanceTo(const CUnit &dst) const
7303 {
7304 if (this->MapLayer != dst.MapLayer) {
7305 return 16384;
7306 }
7307
7308 return MapDistanceBetweenTypes(*this->GetFirstContainer()->Type, this->tilePos, this->MapLayer->ID, *dst.Type, dst.tilePos, dst.MapLayer->ID);
7309 }
7310
7311 /**
7312 ** Returns the map distance to unit.
7313 **
7314 ** @param pos map tile position.
7315 **
7316 ** @return The distance between in tiles.
7317 */
MapDistanceTo(const Vec2i & pos,int z) const7318 int CUnit::MapDistanceTo(const Vec2i &pos, int z) const
7319 {
7320 //Wyrmgus start
7321 if (z != this->MapLayer->ID) {
7322 return 16384;
7323 }
7324 //Wyrmgus end
7325
7326 int dx;
7327 int dy;
7328
7329 if (pos.x <= tilePos.x) {
7330 dx = tilePos.x - pos.x;
7331 //Wyrmgus start
7332 } else if (this->Container) { //if unit is within another, use the tile size of the transporter to calculate the distance
7333 dx = std::max<int>(0, pos.x - tilePos.x - this->Container->Type->TileSize.x + 1);
7334 //Wyrmgus end
7335 } else {
7336 dx = std::max<int>(0, pos.x - tilePos.x - Type->TileSize.x + 1);
7337 }
7338 if (pos.y <= tilePos.y) {
7339 dy = tilePos.y - pos.y;
7340 //Wyrmgus start
7341 } else if (this->Container) {
7342 dy = std::max<int>(0, pos.y - tilePos.y - this->Container->Type->TileSize.y + 1);
7343 //Wyrmgus end
7344 } else {
7345 dy = std::max<int>(0, pos.y - tilePos.y - Type->TileSize.y + 1);
7346 }
7347 return isqrt(dy * dy + dx * dx);
7348 }
7349
7350 /**
7351 ** @brief Returns the map distance between two points with unit type
7352 **
7353 ** @param src Source unit type
7354 ** @param pos1 Map tile position of the source (upper-left)
7355 ** @param dst Destination unit type to take into account
7356 ** @param pos2 Map tile position of the destination
7357 **
7358 ** @return The distance between the types
7359 */
MapDistanceBetweenTypes(const CUnitType & src,const Vec2i & pos1,int src_z,const CUnitType & dst,const Vec2i & pos2,int dst_z)7360 int MapDistanceBetweenTypes(const CUnitType &src, const Vec2i &pos1, int src_z, const CUnitType &dst, const Vec2i &pos2, int dst_z)
7361 {
7362 return MapDistance(src.TileSize, pos1, src_z, dst.TileSize, pos2, dst_z);
7363 }
7364
MapDistance(const Vec2i & src_size,const Vec2i & pos1,int src_z,const Vec2i & dst_size,const Vec2i & pos2,int dst_z)7365 int MapDistance(const Vec2i &src_size, const Vec2i &pos1, int src_z, const Vec2i &dst_size, const Vec2i &pos2, int dst_z)
7366 {
7367 if (src_z != dst_z) {
7368 return 16384;
7369 }
7370
7371 int dx;
7372 int dy;
7373
7374 if (pos1.x + src_size.x <= pos2.x) {
7375 dx = std::max<int>(0, pos2.x - pos1.x - src_size.x + 1);
7376 } else {
7377 dx = std::max<int>(0, pos1.x - pos2.x - dst_size.x + 1);
7378 }
7379 if (pos1.y + src_size.y <= pos2.y) {
7380 dy = pos2.y - pos1.y - src_size.y + 1;
7381 } else {
7382 dy = std::max<int>(0, pos1.y - pos2.y - dst_size.y + 1);
7383 }
7384 return isqrt(dy * dy + dx * dx);
7385 }
7386
7387 /**
7388 ** Compute the distance from the view point to a given point.
7389 **
7390 ** @param pos map tile position.
7391 **
7392 ** @todo FIXME: is it the correct place to put this function in?
7393 */
ViewPointDistance(const Vec2i & pos)7394 int ViewPointDistance(const Vec2i &pos)
7395 {
7396 const CViewport &vp = *UI.SelectedViewport;
7397 const Vec2i vpSize(vp.MapWidth, vp.MapHeight);
7398 const Vec2i middle = vp.MapPos + vpSize / 2;
7399
7400 return Distance(middle, pos);
7401 }
7402
7403 /**
7404 ** Compute the distance from the view point to a given unit.
7405 **
7406 ** @param dest Distance to this unit.
7407 **
7408 ** @todo FIXME: is it the correct place to put this function in?
7409 */
ViewPointDistanceToUnit(const CUnit & dest)7410 int ViewPointDistanceToUnit(const CUnit &dest)
7411 {
7412 const CViewport &vp = *UI.SelectedViewport;
7413 const Vec2i vpSize(vp.MapWidth, vp.MapHeight);
7414 const Vec2i midPos = vp.MapPos + vpSize / 2;
7415
7416 //Wyrmgus start
7417 // return dest.MapDistanceTo(midPos);
7418 return dest.MapDistanceTo(midPos, UI.CurrentMapLayer->ID);
7419 //Wyrmgus end
7420 }
7421
7422 /**
7423 ** Can the source unit attack the destination unit.
7424 **
7425 ** @param source Unit type pointer of the attacker.
7426 ** @param dest Unit type pointer of the target.
7427 **
7428 ** @return 0 if attacker can't target the unit, else a positive number.
7429 */
CanTarget(const CUnitType & source,const CUnitType & dest)7430 int CanTarget(const CUnitType &source, const CUnitType &dest)
7431 {
7432 for (unsigned int i = 0; i < UnitTypeVar.GetNumberBoolFlag(); i++) {
7433 if (source.BoolFlag[i].CanTargetFlag != CONDITION_TRUE) {
7434 if ((source.BoolFlag[i].CanTargetFlag == CONDITION_ONLY) ^
7435 (dest.BoolFlag[i].value)) {
7436 return 0;
7437 }
7438 }
7439 }
7440 if (dest.UnitType == UnitTypeLand) {
7441 if (dest.BoolFlag[SHOREBUILDING_INDEX].value) {
7442 return source.CanTarget & (CanTargetLand | CanTargetSea);
7443 }
7444 return source.CanTarget & CanTargetLand;
7445 }
7446 if (dest.UnitType == UnitTypeFly) {
7447 return source.CanTarget & CanTargetAir;
7448 }
7449 //Wyrmgus start
7450 if (dest.UnitType == UnitTypeFlyLow) {
7451 return (source.CanTarget & CanTargetLand) || (source.CanTarget & CanTargetAir) || (source.CanTarget & CanTargetSea);
7452 }
7453 //Wyrmgus end
7454 if (dest.UnitType == UnitTypeNaval) {
7455 return source.CanTarget & CanTargetSea;
7456 }
7457 return 0;
7458 }
7459
7460 /**
7461 ** Can the transporter transport the other unit.
7462 **
7463 ** @param transporter Unit which is the transporter.
7464 ** @param unit Unit which wants to go in the transporter.
7465 **
7466 ** @return 1 if transporter can transport unit, 0 else.
7467 */
CanTransport(const CUnit & transporter,const CUnit & unit)7468 int CanTransport(const CUnit &transporter, const CUnit &unit)
7469 {
7470 if (!transporter.Type->CanTransport()) {
7471 return 0;
7472 }
7473 if (transporter.CurrentAction() == UnitActionBuilt) { // Under construction
7474 return 0;
7475 }
7476 if (&transporter == &unit) { // Cannot transporter itself.
7477 return 0;
7478 }
7479 //Wyrmgus start
7480 /*
7481 if (transporter.BoardCount >= transporter.Type->MaxOnBoard) { // full
7482 return 0;
7483 }
7484 */
7485
7486 if (transporter.ResourcesHeld > 0 && transporter.CurrentResource) { //cannot transport units if already has cargo
7487 return 0;
7488 }
7489 //Wyrmgus end
7490
7491 if (transporter.BoardCount + unit.Type->BoardSize > transporter.Type->MaxOnBoard) { // too big unit
7492 return 0;
7493 }
7494
7495 // Can transport only allied unit.
7496 // FIXME : should be parametrable.
7497 //Wyrmgus start
7498 // if (!transporter.IsTeamed(unit)) {
7499 if (!transporter.IsTeamed(unit) && !transporter.IsAllied(unit) && transporter.Player->Type != PlayerNeutral && unit.Player->Type != PlayerNeutral) {
7500 //Wyrmgus end
7501 return 0;
7502 }
7503 for (unsigned int i = 0; i < UnitTypeVar.GetNumberBoolFlag(); i++) {
7504 if (transporter.Type->BoolFlag[i].CanTransport != CONDITION_TRUE) {
7505 if ((transporter.Type->BoolFlag[i].CanTransport == CONDITION_ONLY) ^ unit.Type->BoolFlag[i].value) {
7506 return 0;
7507 }
7508 }
7509 }
7510 return 1;
7511 }
7512
7513 //Wyrmgus start
7514 /**
7515 ** Can the unit pick up the other unit.
7516 **
7517 ** @param picker Unit which is the picker.
7518 ** @param unit Unit which wants to be picked.
7519 **
7520 ** @return true if picker can pick up unit, false else.
7521 */
CanPickUp(const CUnit & picker,const CUnit & unit)7522 bool CanPickUp(const CUnit &picker, const CUnit &unit)
7523 {
7524 if (!picker.Type->BoolFlag[ORGANIC_INDEX].value) { //only organic units can pick up power-ups and items
7525 return false;
7526 }
7527 if (!unit.Type->BoolFlag[ITEM_INDEX].value && !unit.Type->BoolFlag[POWERUP_INDEX].value) { //only item and powerup units can be picked up
7528 return false;
7529 }
7530 if (!unit.Type->BoolFlag[POWERUP_INDEX].value && !picker.HasInventory() && !IsItemClassConsumable(unit.Type->ItemClass)) { //only consumable items can be picked up as if they were power-ups for units with no inventory
7531 return false;
7532 }
7533 if (picker.CurrentAction() == UnitActionBuilt) { // Under construction
7534 return false;
7535 }
7536 if (&picker == &unit) { // Cannot pick up itself.
7537 return false;
7538 }
7539 if (picker.HasInventory() && unit.Type->BoolFlag[ITEM_INDEX].value && picker.InsideCount >= ((int) UI.InventoryButtons.size())) { // full
7540 if (picker.Player == ThisPlayer) {
7541 std::string picker_name = picker.Name + "'s (" + picker.GetTypeName() + ")";
7542 picker.Player->Notify(NotifyRed, picker.tilePos, picker.MapLayer->ID, _("%s inventory is full."), picker_name.c_str());
7543 }
7544 return false;
7545 }
7546
7547 return true;
7548 }
7549 //Wyrmgus end
7550
7551 /**
7552 ** Check if the player is an enemy
7553 **
7554 ** @param player Player to check
7555 */
IsEnemy(const CPlayer & player) const7556 bool CUnit::IsEnemy(const CPlayer &player) const
7557 {
7558 //Wyrmgus start
7559 if (this->Player->Index != player.Index && player.Type != PlayerNeutral && !this->Player->HasBuildingAccess(player) && this->Type->BoolFlag[HIDDENOWNERSHIP_INDEX].value && this->IsAgressive()) {
7560 return true;
7561 }
7562 //Wyrmgus end
7563
7564 return this->Player->IsEnemy(player);
7565 }
7566
7567 /**
7568 ** Check if the unit is an enemy
7569 **
7570 ** @param unit Unit to check
7571 */
IsEnemy(const CUnit & unit) const7572 bool CUnit::IsEnemy(const CUnit &unit) const
7573 {
7574 //Wyrmgus start
7575 if (
7576 this->Player->Type == PlayerNeutral
7577 && this->Type->BoolFlag[FAUNA_INDEX].value
7578 && this->Type->BoolFlag[ORGANIC_INDEX].value
7579 && unit.Type->BoolFlag[ORGANIC_INDEX].value
7580 && this->Type->Slot != unit.Type->Slot
7581 ) {
7582 if (
7583 this->Type->BoolFlag[PREDATOR_INDEX].value
7584 && !unit.Type->BoolFlag[PREDATOR_INDEX].value
7585 && this->CanEat(unit)
7586 ) {
7587 return true;
7588 } else if (
7589 this->Type->BoolFlag[PEOPLEAVERSION_INDEX].value
7590 && !unit.Type->BoolFlag[FAUNA_INDEX].value
7591 && unit.Player->Type != PlayerNeutral
7592 && this->MapDistanceTo(unit) <= 1
7593 ) {
7594 return true;
7595 }
7596 }
7597
7598 if (
7599 unit.Player->Type == PlayerNeutral
7600 && unit.Type->BoolFlag[PREDATOR_INDEX].value
7601 && this->Player->Type != PlayerNeutral
7602 ) {
7603 return true;
7604 }
7605
7606 if (
7607 this->Player != unit.Player
7608 && this->Player->Type != PlayerNeutral
7609 && unit.CurrentAction() == UnitActionAttack
7610 && unit.CurrentOrder()->HasGoal()
7611 && unit.CurrentOrder()->GetGoal()->Player == this->Player
7612 && !unit.CurrentOrder()->GetGoal()->Type->BoolFlag[HIDDENOWNERSHIP_INDEX].value
7613 ) {
7614 return true;
7615 }
7616
7617 if (
7618 this->Player != unit.Player && this->Player->Type != PlayerNeutral && unit.Player->Type != PlayerNeutral && !this->Player->HasBuildingAccess(*unit.Player) && !this->Player->HasNeutralFactionType()
7619 && ((this->Type->BoolFlag[HIDDENOWNERSHIP_INDEX].value && this->IsAgressive()) || (unit.Type->BoolFlag[HIDDENOWNERSHIP_INDEX].value && unit.IsAgressive()))
7620 ) {
7621 return true;
7622 }
7623
7624 return IsEnemy(*unit.Player);
7625 //Wyrmgus end
7626 }
7627
7628 /**
7629 ** Check if the player is an ally
7630 **
7631 ** @param player Player to check
7632 */
IsAllied(const CPlayer & player) const7633 bool CUnit::IsAllied(const CPlayer &player) const
7634 {
7635 return this->Player->IsAllied(player);
7636 }
7637
7638 /**
7639 ** Check if the unit is an ally
7640 **
7641 ** @param x Unit to check
7642 */
IsAllied(const CUnit & unit) const7643 bool CUnit::IsAllied(const CUnit &unit) const
7644 {
7645 return IsAllied(*unit.Player);
7646 }
7647
7648 /**
7649 ** Check if unit shares vision with the player
7650 **
7651 ** @param x Player to check
7652 */
IsSharedVision(const CPlayer & player) const7653 bool CUnit::IsSharedVision(const CPlayer &player) const
7654 {
7655 return this->Player->IsSharedVision(player);
7656 }
7657
7658 /**
7659 ** Check if the unit shares vision with the unit
7660 **
7661 ** @param x Unit to check
7662 */
IsSharedVision(const CUnit & unit) const7663 bool CUnit::IsSharedVision(const CUnit &unit) const
7664 {
7665 return IsSharedVision(*unit.Player);
7666 }
7667
7668 /**
7669 ** Check if both players share vision
7670 **
7671 ** @param x Player to check
7672 */
IsBothSharedVision(const CPlayer & player) const7673 bool CUnit::IsBothSharedVision(const CPlayer &player) const
7674 {
7675 return this->Player->IsBothSharedVision(player);
7676 }
7677
7678 /**
7679 ** Check if both units share vision
7680 **
7681 ** @param x Unit to check
7682 */
IsBothSharedVision(const CUnit & unit) const7683 bool CUnit::IsBothSharedVision(const CUnit &unit) const
7684 {
7685 return IsBothSharedVision(*unit.Player);
7686 }
7687
7688 /**
7689 ** Check if the player is on the same team
7690 **
7691 ** @param x Player to check
7692 */
IsTeamed(const CPlayer & player) const7693 bool CUnit::IsTeamed(const CPlayer &player) const
7694 {
7695 return (this->Player->Team == player.Team);
7696 }
7697
7698 /**
7699 ** Check if the unit is on the same team
7700 **
7701 ** @param x Unit to check
7702 */
IsTeamed(const CUnit & unit) const7703 bool CUnit::IsTeamed(const CUnit &unit) const
7704 {
7705 return this->IsTeamed(*unit.Player);
7706 }
7707
7708 /**
7709 ** Check if the unit is unusable (for attacking...)
7710 ** @todo look if correct used (UnitActionBuilt is no problem if attacked)?
7711 */
IsUnusable(bool ignore_built_state) const7712 bool CUnit::IsUnusable(bool ignore_built_state) const
7713 {
7714 return (!IsAliveOnMap() || (!ignore_built_state && CurrentAction() == UnitActionBuilt));
7715 }
7716
7717 /**
7718 ** Check if the unit attacking its goal will result in a ranged attack
7719 */
7720 //Wyrmgus start
7721 //bool CUnit::IsAttackRanged(CUnit *goal, const Vec2i &goalPos)
IsAttackRanged(CUnit * goal,const Vec2i & goalPos,int z)7722 bool CUnit::IsAttackRanged(CUnit *goal, const Vec2i &goalPos, int z)
7723 //Wyrmgus end
7724 {
7725 if (this->Variable[ATTACKRANGE_INDEX].Value <= 1) { //always return false if the units attack range is 1 or lower
7726 return false;
7727 }
7728
7729 if (this->Container) { //if the unit is inside a container, the attack will always be ranged
7730 return true;
7731 }
7732
7733 if (
7734 goal
7735 && goal->IsAliveOnMap()
7736 && (
7737 this->MapDistanceTo(*goal) > 1
7738 || (this->Type->UnitType != UnitTypeFly && goal->Type->UnitType == UnitTypeFly)
7739 || (this->Type->UnitType == UnitTypeFly && goal->Type->UnitType != UnitTypeFly)
7740 )
7741 ) {
7742 return true;
7743 }
7744
7745 if (!goal && Map.Info.IsPointOnMap(goalPos, z) && this->MapDistanceTo(goalPos, z) > 1) {
7746 return true;
7747 }
7748
7749 return false;
7750 }
7751
7752 /*----------------------------------------------------------------------------
7753 -- Initialize/Cleanup
7754 ----------------------------------------------------------------------------*/
7755
7756 /**
7757 ** Initialize unit module.
7758 */
InitUnits()7759 void InitUnits()
7760 {
7761 if (!SaveGameLoading) {
7762 UnitManager.Init();
7763 }
7764 }
7765
7766 /**
7767 ** Clean up unit module.
7768 */
CleanUnits()7769 void CleanUnits()
7770 {
7771 // Free memory for all units in unit table.
7772 std::vector<CUnit *> units(UnitManager.begin(), UnitManager.end());
7773
7774 for (std::vector<CUnit *>::iterator it = units.begin(); it != units.end(); ++it) {
7775 //Wyrmgus start
7776 if (*it == nullptr) {
7777 fprintf(stderr, "Error in CleanUnits: unit is null.\n");
7778 continue;
7779 }
7780 //Wyrmgus end
7781 CUnit &unit = **it;
7782
7783 //Wyrmgus start
7784 /*
7785 if (&unit == nullptr) {
7786 continue;
7787 }
7788 */
7789 //Wyrmgus end
7790 //Wyrmgus start
7791 if (unit.Type == nullptr) {
7792 fprintf(stderr, "Unit \"%d\"'s type is null.\n", UnitNumber(unit));
7793 }
7794 //Wyrmgus end
7795 if (!unit.Destroyed) {
7796 if (!unit.Removed) {
7797 unit.Remove(nullptr);
7798 }
7799 UnitClearOrders(unit);
7800 }
7801 unit.Release(true);
7802 }
7803
7804 UnitManager.Init();
7805
7806 FancyBuildings = false;
7807 HelpMeLastCycle = 0;
7808 }
7809
7810 //@}
7811