diff --git a/src/action/action_attack.cpp b/src/action/action_attack.cpp index 7bda9c0b8..801931ad0 100644 --- a/src/action/action_attack.cpp +++ b/src/action/action_attack.cpp @@ -56,6 +56,7 @@ #include "ui.h" #include "unit.h" #include "unittype.h" +#include "spells.h" /*---------------------------------------------------------------------------- -- Defines @@ -189,7 +190,7 @@ void AnimateActionAttack(CUnit &unit, COrder &order) { if (Action == UnitActionAttack) { if (this->HasGoal()) { - return this->GetGoal()->IsAlive(); + return this->GetGoal()->IsAliveOnMap(); } else { return Map.Info.IsPointOnMap(this->goalPos); } @@ -336,23 +337,24 @@ bool COrder_Attack::CheckForTargetInRange(CUnit &unit) this->SetGoal(goal); this->MinRange = unit.Type->MinAttackRange; this->Range = unit.Stats->Variables[ATTACKRANGE_INDEX].Max; - this->goalPos.x = this->goalPos.y = -1; + this->goalPos = goal->tilePos; this->State |= WEAK_TARGET; // weak target } // Have a weak target, try a better target. - } else if (this->HasGoal() && (this->State & WEAK_TARGET)) { + } else if (this->HasGoal() && (this->State & WEAK_TARGET || unit.Player->AiEnabled)) { CUnit *goal = this->GetGoal(); CUnit *newTarget = AttackUnitsInReactRange(unit); - if (newTarget && newTarget->Type->Priority > goal->Type->Priority) { - COrder *savedOrder = this->Clone(); + if (newTarget && newTarget->IsAgressive() + && ThreatCalculate(unit, newTarget) < ThreatCalculate(unit, goal)) { + COrder *savedOrder = this->Clone(); - if (unit.StoreOrder(savedOrder) == false) { - delete savedOrder; - savedOrder = NULL; - } - this->SetGoal(newTarget); - this->goalPos.x = this->goalPos.y = -1; + if (unit.StoreOrder(savedOrder) == false) { + delete savedOrder; + savedOrder = NULL; + } + this->SetGoal(newTarget); + this->goalPos = newTarget->tilePos; } } @@ -482,7 +484,7 @@ void COrder_Attack::AttackTarget(CUnit &unit) savedOrder = NULL; } this->SetGoal(goal); - this->goalPos.x = this->goalPos.y = -1; + this->goalPos = goal->tilePos; this->MinRange = unit.Type->MinAttackRange; this->Range = unit.Stats->Variables[ATTACKRANGE_INDEX].Max; this->State |= WEAK_TARGET; @@ -492,18 +494,19 @@ void COrder_Attack::AttackTarget(CUnit &unit) } else { if ((this->State & WEAK_TARGET)) { CUnit *newTarget = AttackUnitsInReactRange(unit); - if (newTarget && newTarget->Type->Priority > goal->Type->Priority) { - COrder *savedOrder = this->Clone(); + if (newTarget && newTarget->IsAgressive() + && ThreatCalculate(unit, newTarget) < ThreatCalculate(unit, goal)) { + COrder *savedOrder = this->Clone(); - if (unit.StoreOrder(savedOrder) == false) { - delete savedOrder; - savedOrder = NULL; - } - goal = newTarget; - this->SetGoal(newTarget); - this->goalPos.x = this->goalPos.y = -1; - this->MinRange = unit.Type->MinAttackRange; - this->State = MOVE_TO_TARGET; + if (unit.StoreOrder(savedOrder) == false) { + delete savedOrder; + savedOrder = NULL; + } + goal = newTarget; + this->SetGoal(newTarget); + this->goalPos = newTarget->tilePos; + this->MinRange = unit.Type->MinAttackRange; + this->State = MOVE_TO_TARGET; } } } diff --git a/src/action/actions.cpp b/src/action/actions.cpp index 404398009..93565cf24 100644 --- a/src/action/actions.cpp +++ b/src/action/actions.cpp @@ -274,7 +274,7 @@ static void HandleBuffs(CUnit &unit, int amount) { // Look if the time to live is over. - if (unit.TTL && (int)unit.TTL < ((int)GameCycle - unit.Variable[HP_INDEX].Value)) { + if (unit.TTL && unit.TTL < GameCycle) { DebugPrint("Unit must die %lu %lu!\n" _C_ unit.TTL _C_ GameCycle); // Hit unit does some funky stuff... @@ -284,6 +284,11 @@ static void HandleBuffs(CUnit &unit, int amount) } } + unit.Threshold -= amount; + if (unit.Threshold < 0) { + unit.Threshold = 0; + } + // decrease spells effects time. unit.Variable[BLOODLUST_INDEX].Increase = -amount; unit.Variable[HASTE_INDEX].Increase = -amount; diff --git a/src/ai/ai.cpp b/src/ai/ai.cpp index bdb160a06..014e27a05 100644 --- a/src/ai/ai.cpp +++ b/src/ai/ai.cpp @@ -703,20 +703,19 @@ void AiHelpMe(const CUnit *attacker, CUnit &defender) if (aiunit.CurrentAction() == UnitActionAttack) { COrder_Attack &orderAttack = *static_cast<COrder_Attack *>(aiunit.CurrentOrder()); - if (orderAttack.GetGoal() == NULL || orderAttack.GetGoal()->IsAgressive() == false) { - shouldAttack = true; + if (orderAttack.GetGoal() == NULL || orderAttack.GetGoal()->IsAgressive() == false + || ThreatCalculate(defender, attacker) < ThreatCalculate(defender, orderAttack.GetGoal())) { + shouldAttack = true; } } if (shouldAttack) { CommandAttack(aiunit, attacker->tilePos, const_cast<CUnit *>(attacker), FlushCommands); - if (aiunit.SavedOrder == NULL) { - COrder *savedOrder = COrder::NewActionAttack(aiunit, aiunit.tilePos); + COrder *savedOrder = COrder::NewActionAttack(aiunit, attacker->tilePos); - if (aiunit.StoreOrder(savedOrder) == false) { - delete savedOrder; - savedOrder = NULL; - } + if (aiunit.StoreOrder(savedOrder) == false) { + delete savedOrder; + savedOrder = NULL; } } } diff --git a/src/ai/ai_force.cpp b/src/ai/ai_force.cpp index 675357e1d..999ac3f92 100644 --- a/src/ai/ai_force.cpp +++ b/src/ai/ai_force.cpp @@ -52,8 +52,11 @@ /*---------------------------------------------------------------------------- -- Types ----------------------------------------------------------------------------*/ +#define AIATTACK_RANGE 0 +#define AIATTACK_ALLMAP 1 +#define AIATTACK_BUILDING 2 -template <const bool IN_REACT_RANGE> +template <const int FIND_TYPE> class AiForceEnemyFinder { public: @@ -75,10 +78,16 @@ public: if (unit->Type->CanAttack == false) { return *enemy == NULL; } - if (IN_REACT_RANGE) { + if (FIND_TYPE == AIATTACK_RANGE) { *enemy = AttackUnitsInReactRange(*unit); - } else { + } else if (FIND_TYPE == AIATTACK_ALLMAP) { *enemy = AttackUnitsInDistance(*unit, MaxMapWidth); + } else if (FIND_TYPE == AIATTACK_BUILDING) { + *enemy = AttackUnitsInDistance(*unit, MaxMapWidth, true); + Assert(!*enemy || (*enemy)->Type->Building); + if (*enemy == NULL) { + *enemy = AttackUnitsInDistance(*unit, MaxMapWidth); + } } return *enemy == NULL; } @@ -285,7 +294,7 @@ void AiForce::Attack(const Vec2i &pos) if (Map.Info.IsPointOnMap(goalPos) == false) { /* Search in entire map */ const CUnit *enemy = NULL; - AiForceEnemyFinder<false>(*this, &enemy); + AiForceEnemyFinder<AIATTACK_BUILDING>(*this, &enemy); if (enemy) { goalPos = enemy->tilePos; } @@ -639,6 +648,8 @@ static void AiGroupAttackerForTransport(AiForce &aiForce) */ void AiForce::Update() { + bool isTransporterAttack = false; + if (Size() == 0) { Attacking = false; if (!Defending && State > AiForceAttackingState_Waiting) { @@ -651,6 +662,9 @@ void AiForce::Update() Attacking = false; for (unsigned int i = 0; i < Size(); ++i) { CUnit *aiunit = Units[i]; + if (!isTransporterAttack && aiunit->Type->CanTransport()) { + isTransporterAttack = true; + } if (aiunit->Type->CanAttack) { Attacking = true; break; @@ -681,39 +695,91 @@ void AiForce::Update() return ; } - std::vector<CUnit *> idleUnits; - const CUnit *leader = NULL; - for (unsigned int i = 0; i != Size(); ++i) { - CUnit &aiunit = *Units[i]; + Assert(Map.Info.IsPointOnMap(GoalPos)); + std::vector<CUnit *> attackUnits; + CUnit *leader = NULL; + int distance = 0; + Vec2i pos = GoalPos; - if (aiunit.IsIdle()) { - if (aiunit.IsAliveOnMap()) { - idleUnits.push_back(&aiunit); + if (isTransporterAttack) { + for (unsigned int i = 0; i != Size(); ++i) { + CUnit &aiunit = *Units[i]; + + if (aiunit.IsIdle()) { + if (aiunit.IsAliveOnMap()) { + attackUnits.push_back(&aiunit); + } + } else if (aiunit.CurrentAction() == UnitActionAttack) { + leader = &aiunit; + } + } + } else { + attackUnits = Units.Units; + for (unsigned int i = 0; i < Size(); ++i) { + CUnit &aiunit = *Units[i]; + if (aiunit.CurrentAction() == UnitActionAttack) { + leader = &aiunit; + if (leader->CurrentOrder()->HasGoal() && leader->CurrentOrder()->GetGoal()->IsAliveOnMap()) { + pos = leader->CurrentOrder()->GetGoal()->tilePos; + } + break; } - } else if (aiunit.CurrentAction() == UnitActionAttack) { - leader = &aiunit; - } else if (leader == NULL) { - leader = &aiunit; } } - const Vec2i pos = leader != NULL ? leader->tilePos : this->GoalPos; - for (size_t i = 0; i != idleUnits.size(); ++i) { - CUnit &aiunit = *idleUnits[i]; - const int delay = i / 5; // To avoid lot of CPU consuption, send them with a small time difference. - aiunit.Wait = delay; - if (aiunit.Type->CanAttack) { - CommandAttack(aiunit, pos, NULL, FlushCommands); - } else if (aiunit.Type->CanTransport()) { - if (aiunit.BoardCount != 0) { - CommandUnload(aiunit, pos, NULL, FlushCommands); - } else { - // FIXME : Retrieve unit blocked (transport previously full) - CommandMove(aiunit, aiunit.Player->StartPos, FlushCommands); - this->Remove(aiunit); + if (leader != NULL) { + for (size_t i = 0; i != attackUnits.size(); ++i) { + CUnit &aiunit = *attackUnits[i]; + + if (aiunit.CurrentAction() == UnitActionAttack) { + continue; + } + + const int delay = i / 5; // To avoid lot of CPU consuption, send them with a small time difference. + + aiunit.Wait = delay; + if (aiunit.Type->CanAttack) { + CommandAttack(aiunit, pos, NULL, FlushCommands); + } else if (isTransporterAttack && aiunit.Type->CanTransport()) { + if (aiunit.BoardCount != 0) { + CommandUnload(aiunit, pos, NULL, FlushCommands); + } else { + // FIXME : Retrieve unit blocked (transport previously full) + CommandMove(aiunit, aiunit.Player->StartPos, FlushCommands); + this->Remove(aiunit); + } + } else { + CommandMove(aiunit, pos, FlushCommands); + } + } + } else { // Everyone is idle, find a new target + const CUnit *unit = NULL; + + AiForceEnemyFinder<AIATTACK_BUILDING>(*this, &unit); + + if (!unit) { + // No enemy found, give up + // FIXME: should the force go home or keep trying to attack? + DebugPrint("%d: Attack force #%lu can't find a target, giving up\n" + _C_ AiPlayer->Player->Index _C_(long unsigned int)(this - & (AiPlayer->Force[0]))); + Attacking = false; + return; + } + GoalPos = unit->tilePos; + this->State = AiForceAttackingState_Attacking; + for (size_t i = 0; i != this->Units.size(); ++i) { + CUnit *const unit = this->Units[i]; + + if (unit->Container == NULL) { + const int delay = i / 5; // To avoid lot of CPU consuption, send them with a small time difference. + + unit->Wait = delay; + if (unit->Type->CanAttack) { + CommandAttack(*unit, GoalPos, NULL, FlushCommands); + } else { + CommandMove(*unit, GoalPos, FlushCommands); + } } - } else { - CommandMove(aiunit, pos, FlushCommands); } } } @@ -738,7 +804,7 @@ void AiForceManager::Update() && force.Units[0]->MapDistanceTo(force.GoalPos) <= nearDist) { // Look if still enemies in attack range. const CUnit *dummy = NULL; - if (!AiForceEnemyFinder<true>(force, &dummy).found()) { + if (!AiForceEnemyFinder<AIATTACK_RANGE>(force, &dummy).found()) { if (Map.Info.IsPointOnMap(force.HomePos)) { for (size_t i = 0; i != force.Units.size(); ++i) { CUnit &unit = *force.Units[i]; @@ -751,6 +817,31 @@ void AiForceManager::Update() force.Defending = false; force.Attacking = false; } + } else { // Find idle units and order them to defend + std::vector<CUnit *> idleUnits; + for (unsigned int i = 0; i != Size(); ++i) { + CUnit &aiunit = *Units[i]; + + if (aiunit.IsIdle()) { + if (aiunit.IsAliveOnMap()) { + idleUnits.push_back(&aiunit); + } + } + } + for (unsigned int i = 0; i != idleUnits.size(); ++i) { + CUnit *const unit = idleUnits[i]; + + if (unit->Container == NULL) { + const int delay = i / 5; // To avoid lot of CPU consuption, send them with a small time difference. + + unit->Wait = delay; + if (unit->Type->CanAttack) { + CommandAttack(*unit, force.GoalPos, NULL, FlushCommands); + } else { + CommandMove(*unit, force.GoalPos, FlushCommands); + } + } + } } } else if (force.Attacking) { force.RemoveDeadUnit(); diff --git a/src/ai/ai_local.h b/src/ai/ai_local.h index ddc6db80c..93f1cde01 100644 --- a/src/ai/ai_local.h +++ b/src/ai/ai_local.h @@ -141,7 +141,7 @@ public: } Units.for_each(InternalRemoveUnit); Units.clear(); - GoalPos.x = GoalPos.y = -1; + HomePos.x = HomePos.y = GoalPos.x = GoalPos.y = -1; } inline size_t Size() const { return Units.size(); } diff --git a/src/ai/ai_resource.cpp b/src/ai/ai_resource.cpp index a6c5a222c..3abc022b0 100644 --- a/src/ai/ai_resource.cpp +++ b/src/ai/ai_resource.cpp @@ -856,8 +856,10 @@ static int AiAssignHarvesterFromTerrain(CUnit &unit, int resource) */ static int AiAssignHarvesterFromUnit(CUnit &unit, int resource) { + // Try to find the nearest depot first. + CUnit *depot = FindDeposit(unit, 1000, resource); // Find a resource to harvest from. - CUnit *mine = UnitFindResource(unit, unit.tilePos, 1000, resource, true); + CUnit *mine = UnitFindResource(unit, depot ? depot->tilePos : unit.tilePos, 1000, resource, true); if (mine) { CommandResource(unit, *mine, FlushCommands); diff --git a/src/include/map.h b/src/include/map.h index a6bf697e3..0daad4454 100644 --- a/src/include/map.h +++ b/src/include/map.h @@ -197,6 +197,15 @@ private: const CPlayer *player; }; +class HasNotSamePlayerAs +{ +public: + explicit HasNotSamePlayerAs(const CPlayer &_player) : player(&_player) {} + bool operator()(const CUnit *unit) const { return unit->Player != player; } +private: + const CPlayer *player; +}; + class IsAlliedWith { public: @@ -243,6 +252,12 @@ private: const CUnit *forbidden; }; +class IsBuildingType +{ +public: + bool operator()(const CUnit *unit) const { return unit->Type->Building; } +}; + template <typename Pred> class NotPredicate diff --git a/src/include/unit.h b/src/include/unit.h index 15517feaa..215623629 100644 --- a/src/include/unit.h +++ b/src/include/unit.h @@ -365,6 +365,17 @@ struct lua_State; typedef COrder *COrderPtr; +/* +** Configuration of the small (unit) AI. +*/ +#define PRIORITY_FACTOR 0x00001000 +#define HEALTH_FACTOR 0x00000001 +#define DISTANCE_FACTOR 0x00010000 +#define INRANGE_FACTOR 0x00001000 +#define INRANGE_BONUS 0x00100000 +#define CANATTACK_BONUS 0x00010000 +#define AIPRIORITY_BONUS 0x01000000 + /// Called whenever the selected unit was updated extern void SelectedUnitChanged(); @@ -543,8 +554,8 @@ public: signed char IY; /// seen Y image displacement to map position unsigned Constructed : 1; /// Unit seen construction unsigned State : 3; /// Unit seen build/upgrade state - unsigned Destroyed : PlayerMax; /// Unit seen destroyed or not - unsigned ByPlayer : PlayerMax; /// Track unit seen by player +unsigned Destroyed : PlayerMax; /// Unit seen destroyed or not +unsigned ByPlayer : PlayerMax; /// Track unit seen by player } Seen; CVariable *Variable; /// array of User Defined variables. @@ -555,6 +566,7 @@ public: int LastGroup; /// unit belongs to this last group unsigned int Wait; /// action counter + int Threshold; /// The counter while ai unit couldn't change target. struct _unit_anim_ { const CAnimation *Anim; /// Anim @@ -943,8 +955,10 @@ extern CUnit *UnitOnScreen(CUnit *unit, int x, int y); /// Let a unit die extern void LetUnitDie(CUnit &unit); -/// Destory all units inside another unit +/// Destroy all units inside another unit extern void DestroyAllInside(CUnit &source); +/// Calculate some value to measure the unit's priority for AI +extern int ThreatCalculate(const CUnit &unit, const CUnit *dest); /// Hit unit with damage, if destroyed give attacker the points extern void HitUnit(CUnit *attacker, CUnit &target, int damage); @@ -1020,7 +1034,7 @@ extern CUnit *ResourceOnMap(const Vec2i &pos, int resource, bool mine_on_top = t extern CUnit *ResourceDepositOnMap(const Vec2i &pos, int resource); /// Find best enemy in numeric range to attack -extern CUnit *AttackUnitsInDistance(const CUnit &unit, int range); +extern CUnit *AttackUnitsInDistance(const CUnit &unit, int range, bool onlyBuildings = false); /// Find best enemy in attack range to attack extern CUnit *AttackUnitsInRange(const CUnit &unit); /// Find best enemy in reaction range to attack diff --git a/src/include/unittype.h b/src/include/unittype.h index d4aeae9cd..acb55556e 100644 --- a/src/include/unittype.h +++ b/src/include/unittype.h @@ -1021,9 +1021,10 @@ public: CUnitStats DefaultStat; struct BoolFlags { - bool value; /// User defined flag. Used for (dis)allow target. - char CanTransport; /// Can transport units with this flag. - char CanTargetFlag; /// Flag needed to target with missile. + bool value; /// User defined flag. Used for (dis)allow target. + char CanTransport; /// Can transport units with this flag. + char CanTargetFlag; /// Flag needed to target with missile. + char AiPriorityTarget; /// Attack this units first. }; std::vector<BoolFlags> BoolFlag; diff --git a/src/ui/mouse.cpp b/src/ui/mouse.cpp index 3b8b21c69..907b57413 100644 --- a/src/ui/mouse.cpp +++ b/src/ui/mouse.cpp @@ -1479,6 +1479,9 @@ void UIHandleButtonDown(unsigned button) CUnit *uins; int i; + // Reset the ShowNameDelay counters + ShowNameDelay = ShowNameTime = GameCycle; + if (LongLeftButton) { if (!OldValid) { OldShowSightRange = Preference.ShowSightRange; diff --git a/src/unit/build.cpp b/src/unit/build.cpp index 28302c49e..ee3d9682e 100644 --- a/src/unit/build.cpp +++ b/src/unit/build.cpp @@ -305,7 +305,7 @@ CUnit *CanBuildHere(const CUnit *unit, const CUnitType &type, const Vec2i &pos) } // Check special rules for AI players - if (unit->Player->AiEnabled) { + if (unit && unit->Player->AiEnabled) { size_t count = type.AiBuildingRules.size(); if (count > 0) { ontoptarget = NULL; diff --git a/src/unit/script_unit.cpp b/src/unit/script_unit.cpp index c44735f1f..88a61ffba 100644 --- a/src/unit/script_unit.cpp +++ b/src/unit/script_unit.cpp @@ -475,6 +475,11 @@ static int CclUnit(lua_State *l) // FIXME : unsigned long should be better handled unit->TTL = LuaToNumber(l, -1); lua_pop(l, 1); + } else if (!strcmp(value, "threshold")) { + lua_rawgeti(l, 2, j + 1); + // FIXME : unsigned long should be better handled + unit->Threshold = LuaToNumber(l, -1); + lua_pop(l, 1); } else if (!strcmp(value, "group-id")) { lua_rawgeti(l, 2, j + 1); unit->GroupId = LuaToNumber(l, -1); diff --git a/src/unit/script_unittype.cpp b/src/unit/script_unittype.cpp index 65497c59a..4b86d90c8 100644 --- a/src/unit/script_unittype.cpp +++ b/src/unit/script_unittype.cpp @@ -126,6 +126,7 @@ static const char SLOT_KEY[] = "Slot"; static const char SHIELD_KEY[] = "ShieldPoints"; static const char POINTS_KEY[] = "Points"; static const char MAXHARVESTERS_KEY[] = "MaxHarvesters"; +static const char AITHRESHOLD_KEY[] = "AiThreshold"; /*---------------------------------------------------------------------------- -- Functions @@ -162,7 +163,7 @@ CUnitTypeVar::CVariableKeys::CVariableKeys() BASICDAMAGE_KEY, POSX_KEY, POSY_KEY, RADARRANGE_KEY, RADARJAMMERRANGE_KEY, AUTOREPAIRRANGE_KEY, BLOODLUST_KEY, HASTE_KEY, SLOW_KEY, INVISIBLE_KEY, UNHOLYARMOR_KEY, SLOT_KEY, SHIELD_KEY, POINTS_KEY, - MAXHARVESTERS_KEY + MAXHARVESTERS_KEY, AITHRESHOLD_KEY }; for (int i = 0; i < NVARALREADYDEFINED; ++i) { @@ -1005,6 +1006,33 @@ static int CclDefineUnitType(lua_State *l) } LuaError(l, "Unsupported flag tag for can-target-flag: %s" _C_ value); } + } else if (!strcmp(value, "PriorityTarget")) { + // + // Warning: ai-priority-target should only be used AFTER all bool flags + // have been defined. + // + if (!lua_istable(l, -1)) { + LuaError(l, "incorrect argument"); + } + if (type->BoolFlag.size() < UnitTypeVar.GetNumberBoolFlag()) { + type->BoolFlag.resize(UnitTypeVar.GetNumberBoolFlag()); + } + subargs = lua_rawlen(l, -1); + for (k = 0; k < subargs; ++k) { + lua_rawgeti(l, -1, k + 1); + value = LuaToString(l, -1); + lua_pop(l, 1); + ++k; + int index = UnitTypeVar.BoolFlagNameLookup[value]; + if (index != -1) { + lua_rawgeti(l, -1, k + 1); + value = LuaToString(l, -1); + lua_pop(l, 1); + type->BoolFlag[index].AiPriorityTarget = Ccl2Condition(l, value); + continue; + } + LuaError(l, "Unsupported flag tag for ai-priority-target: %s" _C_ value); + } } else if (!strcmp(value, "IsNotSelectable")) { type->IsNotSelectable = LuaToBoolean(l, -1); } else if (!strcmp(value, "SelectableByRectangle")) { diff --git a/src/unit/unit.cpp b/src/unit/unit.cpp index 2bdd9cb5a..339bd0a97 100644 --- a/src/unit/unit.cpp +++ b/src/unit/unit.cpp @@ -172,6 +172,7 @@ void CUnit::Init() memset(&Seen, 0, sizeof(Seen)); Variable = NULL; TTL = 0; + Threshold = 0; GroupId = 0; LastGroup = 0; ResourcesHeld = 0; @@ -369,7 +370,11 @@ bool CUnit::RestoreOrder() { COrder *savedOrder = this->SavedOrder; - if (savedOrder == NULL || savedOrder->IsValid() == false) { + if (savedOrder == NULL) { + return false; + } + + if (savedOrder->IsValid() == false) { delete this->SavedOrder; this->SavedOrder = NULL; return false; @@ -400,6 +405,10 @@ bool CUnit::StoreOrder(COrder *order) Assert(order); if (this->SavedOrder != NULL) { + if (this->SavedOrder->IsValid() == false) { + delete this->SavedOrder; + this->SavedOrder = NULL; + } return false; } if (order && order->Finished == true) { @@ -2580,6 +2589,11 @@ CUnit *UnitOnScreen(CUnit *ounit, int x, int y) continue; } } + // Check if better units are present on this location + if (unit->Type->IsNotSelectable) { + funit = unit; + continue; + } // // This could be taken. // @@ -2735,6 +2749,47 @@ void DestroyAllInside(CUnit &source) -- Unit AI ----------------------------------------------------------------------------*/ +int ThreatCalculate(const CUnit &unit, const CUnit *dest) +{ + int cost = 0; + + const CUnitType &type = *unit.Type; + const CUnitType &dtype = *dest->Type; + + // Priority 0-255 + cost -= dtype.Priority * PRIORITY_FACTOR; + // Remaining HP (Health) 0-65535 + cost += dest->Variable[HP_INDEX].Value * 100 / dest->Variable[HP_INDEX].Max * HEALTH_FACTOR; + + int d = unit.MapDistanceTo(*dest); + + if (d <= unit.Stats->Variables[ATTACKRANGE_INDEX].Max && d >= type.MinAttackRange) { + cost += d * INRANGE_FACTOR; + cost -= INRANGE_BONUS; + } else { + cost += d * DISTANCE_FACTOR; + } + + for (unsigned int i = 0; i < UnitTypeVar.GetNumberBoolFlag(); i++) { + if (type.BoolFlag[i].AiPriorityTarget != CONDITION_TRUE) { + if ((type.BoolFlag[i].AiPriorityTarget == CONDITION_ONLY) & + (dtype.BoolFlag[i].value)) { + cost -= AIPRIORITY_BONUS; + } + if ((type.BoolFlag[i].AiPriorityTarget == CONDITION_FALSE) & + (dtype.BoolFlag[i].value)) { + cost += AIPRIORITY_BONUS; + } + } + } + + // Unit can attack back. + if (CanTarget(&dtype, &type)) { + cost -= CANATTACK_BONUS; + } + return cost; +} + /** ** Unit is hit by missile or other damage. ** @@ -2807,7 +2862,7 @@ void HitUnit(CUnit *attacker, CUnit &target, int damage) } } - if (attacker && !target.Type->Wall && target.Type->Building && target.Player->AiEnabled) { + if (attacker && !target.Type->Wall && target.Player->AiEnabled) { AiHelpMe(attacker, target); } @@ -2816,6 +2871,9 @@ void HitUnit(CUnit *attacker, CUnit &target, int damage) (target.Variable[HP_INDEX].Value == 0)) { // unit is killed or destroyed // increase scores of the attacker, but not if attacking it's own units. // prevents cheating by killing your own units. + + // Setting ai threshold counter to 0 so it can target other units + attacker->Threshold = 0; if (attacker && target.IsEnemy(*attacker)) { attacker->Player->Score += target.Variable[POINTS_INDEX].Value; if (type->Building) { @@ -2909,47 +2967,14 @@ void HitUnit(CUnit *attacker, CUnit &target, int damage) } } - // Attack units in range (which or the attacker?) - if (attacker && target.IsAgressive() && target.CanMove()) { - if (target.CurrentAction() != UnitActionStill) { - return; - } - CUnit *goal; - - if (RevealAttacker && CanTarget(target.Type, attacker->Type)) { - // Reveal Unit that is attacking - goal = attacker; - } else { - if (target.CurrentAction() == UnitActionStandGround) { - goal = AttackUnitsInRange(target); - } else { - // Check for any other units in range - goal = AttackUnitsInReactRange(target); - } - } - if (goal) { - COrder *savedOrder = target.CurrentOrder()->Clone(); - - CommandAttack(target, goal->tilePos, NoUnitP, FlushCommands); - if (target.StoreOrder(savedOrder) == false) { - delete savedOrder; - savedOrder = NULL; - } - return; - } + if (!attacker) { + return; } - /* - What should we do with workers on : - case UnitActionRepair: - - Drop orders and run away or return after escape? - */ - // // Can't attack run away. // - if (attacker && target.CanMove() && target.CurrentAction() == UnitActionStill) { + if (!target.IsAgressive() && target.CanMove() && target.CurrentAction() == UnitActionStill) { Vec2i pos = target.tilePos - attacker->tilePos; int d = isqrt(pos.x * pos.x + pos.y * pos.y); @@ -2962,6 +2987,73 @@ void HitUnit(CUnit *attacker, CUnit &target, int damage) CommandStopUnit(target); CommandMove(target, pos, 0); } + + // The rest instructions is only for AI units. + if (!target.Player->AiEnabled) { + return; + } + + const int threshold = 30; + + if (target.Threshold && target.CurrentOrder()->HasGoal() && target.CurrentOrder()->GetGoal() == attacker) { + target.Threshold = threshold; + return; + } + + // If the threshold counter is not zero, ignoring + if (target.Threshold) { + return; + } + + // Attack units in range (which or the attacker?) + // Don't bother unit if it casting repeatable spell + if (target.IsAgressive() && target.CanMove() && target.CurrentAction() != UnitActionSpellCast) { + COrder *savedOrder = target.CurrentOrder()->Clone(); + CUnit *oldgoal = target.CurrentOrder()->GetGoal(); + CUnit *goal, *best = oldgoal; + + if (RevealAttacker && CanTarget(target.Type, attacker->Type)) { + // Reveal Unit that is attacking + goal = attacker; + } else { + if (target.CurrentAction() == UnitActionStandGround) { + goal = AttackUnitsInRange(target); + } else { + // Check for any other units in range + goal = AttackUnitsInReactRange(target); + } + } + + // Calculate the best target we could attack + best = oldgoal ? oldgoal : (goal ? goal : attacker); + if (best && best != attacker) { + if (goal && ((goal->IsAgressive() && best->IsAgressive() == false) + || (ThreatCalculate(target, goal) < ThreatCalculate(target, best)))) { + best = goal; + } + if (!RevealAttacker && (best->IsAgressive() == false || ThreatCalculate(target, attacker) < ThreatCalculate(target, best))) { + best = attacker; + } + } + CommandAttack(target, best->tilePos, NoUnitP, FlushCommands); + + // Set threshold value only for agressive units + if (best->IsAgressive()) { + target.Threshold = threshold; + } + if (target.StoreOrder(savedOrder) == false) { + delete savedOrder; + savedOrder = NULL; + } + return; + } + + /* + What should we do with workers on : + case UnitActionRepair: + + Drop orders and run away or return after escape? + */ } /*---------------------------------------------------------------------------- diff --git a/src/unit/unit_draw.cpp b/src/unit/unit_draw.cpp index 811e389c9..8cc7cb618 100644 --- a/src/unit/unit_draw.cpp +++ b/src/unit/unit_draw.cpp @@ -666,7 +666,7 @@ void ShowOrder(const CUnit &unit) if (unit.Destroyed) { return; } - if (unit.Player != ThisPlayer && !ThisPlayer->IsAllied(unit)) { + if (!DEBUG && unit.Player != ThisPlayer && !ThisPlayer->IsAllied(unit)) { return; } // Get current position diff --git a/src/unit/unit_find.cpp b/src/unit/unit_find.cpp index 6376ab52d..dbff76a55 100644 --- a/src/unit/unit_find.cpp +++ b/src/unit/unit_find.cpp @@ -51,21 +51,12 @@ #include "interface.h" #include "tileset.h" #include "pathfinder.h" +#include "spells.h" /*---------------------------------------------------------------------------- -- Defines ----------------------------------------------------------------------------*/ -/* -** Configuration of the small (unit) AI. -*/ -#define PRIORITY_FACTOR 0x00010000 -#define HEALTH_FACTOR 0x00000001 -#define DISTANCE_FACTOR 0x00100000 -#define INRANGE_FACTOR 0x00010000 -#define INRANGE_BONUS 0x01000000 -#define CANATTACK_BONUS 0x00100000 - /*---------------------------------------------------------------------------- -- Local Data ----------------------------------------------------------------------------*/ @@ -308,7 +299,7 @@ private: // Priority 0-255 cost -= dtype.Priority * PRIORITY_FACTOR; // Remaining HP (Health) 0-65535 - cost += dest->Variable[HP_INDEX].Value * HEALTH_FACTOR; + cost += dest->Variable[HP_INDEX].Value * 100 / dest->Variable[HP_INDEX].Max * HEALTH_FACTOR; if (d <= attackrange && d >= type.MinAttackRange) { cost += d * INRANGE_FACTOR; @@ -317,6 +308,19 @@ private: cost += d * DISTANCE_FACTOR; } + for (unsigned int i = 0; i < UnitTypeVar.GetNumberBoolFlag(); i++) { + if (type.BoolFlag[i].AiPriorityTarget != CONDITION_TRUE) { + if ((type.BoolFlag[i].AiPriorityTarget == CONDITION_ONLY) & + (dtype.BoolFlag[i].value)) { + cost -= AIPRIORITY_BONUS; + } + if ((type.BoolFlag[i].AiPriorityTarget == CONDITION_FALSE) & + (dtype.BoolFlag[i].value)) { + cost += AIPRIORITY_BONUS; + } + } + } + // Unit can attack back. if (CanTarget(&dtype, &type)) { cost -= CANATTACK_BONUS; @@ -417,6 +421,19 @@ public: } else { // Priority 0-255 cost += dtype.Priority * PRIORITY_FACTOR; + + for (unsigned int i = 0; i < UnitTypeVar.GetNumberBoolFlag(); i++) { + if (type.BoolFlag[i].AiPriorityTarget != CONDITION_TRUE) { + if ((type.BoolFlag[i].AiPriorityTarget == CONDITION_ONLY) & + (dtype.BoolFlag[i].value)) { + cost -= AIPRIORITY_BONUS; + } else if ((type.BoolFlag[i].AiPriorityTarget == CONDITION_FALSE) & + (dtype.BoolFlag[i].value)) { + cost += AIPRIORITY_BONUS; + } + } + } + // Remaining HP (Health) 0-65535 // Give a boost to unit we can kill in one shoot only @@ -612,12 +629,13 @@ struct CompareUnitDistance { ** If the unit can attack must be handled by caller. ** Choose the best target, that can be attacked. ** -** @param unit Find in distance for this unit. -** @param range Distance range to look. +** @param unit Find in distance for this unit. +** @param range Distance range to look. +** @param onlyBuildings Search only buildings (useful when attacking with AI force) ** ** @return Unit to be attacked. */ -CUnit *AttackUnitsInDistance(const CUnit &unit, int range) +CUnit *AttackUnitsInDistance(const CUnit &unit, int range, bool onlyBuildings) { // if necessary, take possible damage on allied units into account... if (unit.Type->Missile.Missile->Range > 1 @@ -632,8 +650,13 @@ CUnit *AttackUnitsInDistance(const CUnit &unit, int range) // If unit is removed, use containers x and y const CUnit *firstContainer = unit.Container ? unit.Container : &unit; std::vector<CUnit *> table; - Map.SelectAroundUnit(*firstContainer, missile_range, table, - MakeNotPredicate(HasSamePlayerAs(Players[PlayerNumNeutral]))); + if (onlyBuildings) { + Map.SelectAroundUnit(*firstContainer, missile_range, table, + MakeAndPredicate(HasNotSamePlayerAs(Players[PlayerNumNeutral]), IsBuildingType())); + } else { + Map.SelectAroundUnit(*firstContainer, missile_range, table, + MakeNotPredicate(HasSamePlayerAs(Players[PlayerNumNeutral]))); + } if (table.empty() == false) { return BestRangeTargetFinder(unit, range).Find(table); @@ -642,8 +665,13 @@ CUnit *AttackUnitsInDistance(const CUnit &unit, int range) } else { std::vector<CUnit *> table; - Map.SelectAroundUnit(unit, range, table, - MakeNotPredicate(HasSamePlayerAs(Players[PlayerNumNeutral]))); + if (onlyBuildings) { + Map.SelectAroundUnit(unit, range, table, + MakeAndPredicate(HasNotSamePlayerAs(Players[PlayerNumNeutral]), IsBuildingType())); + } else { + Map.SelectAroundUnit(unit, range, table, + MakeNotPredicate(HasSamePlayerAs(Players[PlayerNumNeutral]))); + } const int n = static_cast<int>(table.size()); if (range > 25 && table.size() > 9) { diff --git a/src/unit/unit_save.cpp b/src/unit/unit_save.cpp index 3f73095b6..08454f1c8 100644 --- a/src/unit/unit_save.cpp +++ b/src/unit/unit_save.cpp @@ -214,6 +214,7 @@ void SaveUnit(const CUnit &unit, CFile &file) file.printf(" \"active\","); } file.printf("\"ttl\", %lu,\n ", unit.TTL); + file.printf("\"threshold\", %lu,\n ", unit.Threshold); for (size_t i = 0; i < UnitTypeVar.GetNumberVariable(); ++i) { if (unit.Variable[i] != unit.Type->DefaultStat.Variables[i]) {