/** ** Change invalid target for new target in range. ** ** @param unit Unit to check if goal is in range ** ** @return true if order(action) have changed, false else (if goal change return false). */ bool COrder_Attack::CheckForTargetInRange(CUnit &unit) { // Target is dead? if (CheckForDeadGoal(unit)) { return true; } // No goal: if meeting enemy attack it. if (!this->HasGoal() && this->Action != UnitActionAttackGround && !Map.WallOnMap(this->goalPos)) { CUnit *goal = AttackUnitsInReactRange(unit); if (goal) { COrder *savedOrder = COrder::NewActionAttack(unit, this->goalPos); if (unit.CanStoreOrder(savedOrder) == false) { delete savedOrder; savedOrder = NULL; } else { unit.SavedOrder = savedOrder; } this->SetGoal(goal); this->MinRange = unit.Type->MinAttackRange; this->Range = unit.Stats->Variables[ATTACKRANGE_INDEX].Max; 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 || unit.Player->AiEnabled)) { CUnit *goal = this->GetGoal(); CUnit *newTarget = AttackUnitsInReactRange(unit); if (newTarget && ThreatCalculate(unit, *newTarget) < ThreatCalculate(unit, *goal)) { COrder *savedOrder = NULL; if (unit.CanStoreOrder(this)) { savedOrder = this->Clone(); } if (savedOrder != NULL) { unit.SavedOrder = savedOrder; } this->SetGoal(newTarget); this->goalPos = newTarget->tilePos; } } Assert(!unit.Type->Vanishes && !unit.Destroyed && !unit.Removed); return false; }
/** ** Called if a Unit is Attacked ** ** @param attacker Pointer to attacker unit. ** @param defender Pointer to unit that is being attacked. */ void AiHelpMe(const CUnit *attacker, CUnit &defender) { /* Friendly Fire - typical splash */ if (!attacker || attacker->Player->Index == defender.Player->Index) { //FIXME - try react somehow return; } DebugPrint("%d: %d(%s) attacked at %d,%d\n" _C_ defender.Player->Index _C_ UnitNumber(defender) _C_ defender.Type->Ident.c_str() _C_ defender.tilePos.x _C_ defender.tilePos.y); // Don't send help to scouts (zeppelin,eye of vision). if (!defender.Type->CanAttack && defender.Type->UnitType == UnitTypeFly) { return; } // Summoned unit, don't help if (defender.Summoned) { return; } PlayerAi &pai = *defender.Player->Ai; AiPlayer = &pai; // If unit belongs to an attacking force, check if force members can help. if (defender.GroupId) { AiForce &aiForce = pai.Force[defender.GroupId - 1]; // Unit belongs to an force, check if brothers in arms can help for (unsigned int i = 0; i < aiForce.Units.size(); ++i) { CUnit &aiunit = *aiForce.Units[i]; if (&defender == &aiunit) { continue; } // if brother is idle or attack no-agressive target and // can attack our attacker then ask for help // FIXME ad support for help from Coward type units if (aiunit.IsAgressive() && CanTarget(*aiunit.Type, *attacker->Type) && aiunit.CurrentOrder()->GetGoal() != attacker) { bool shouldAttack = aiunit.IsIdle() && aiunit.Threshold == 0; if (aiunit.CurrentAction() == UnitActionAttack) { const COrder_Attack &orderAttack = *static_cast<COrder_Attack *>(aiunit.CurrentOrder()); const CUnit *oldGoal = orderAttack.GetGoal(); if (oldGoal == NULL || (ThreatCalculate(defender, *attacker) < ThreatCalculate(defender, *oldGoal) && aiunit.MapDistanceTo(defender) <= aiunit.Stats->Variables[ATTACKRANGE_INDEX].Max)) { shouldAttack = true; } } if (shouldAttack) { CommandAttack(aiunit, attacker->tilePos, const_cast<CUnit *>(attacker), FlushCommands); COrder *savedOrder = COrder::NewActionAttack(aiunit, attacker->tilePos); if (aiunit.CanStoreOrder(savedOrder) == false) { delete savedOrder; savedOrder = NULL; } else { aiunit.SavedOrder = savedOrder; } } } } if (!aiForce.Defending && aiForce.State > 0) { DebugPrint("%d: %d(%s) belong to attacking force, don't defend it\n" _C_ defender.Player->Index _C_ UnitNumber(defender) _C_ defender.Type->Ident.c_str()); // unit belongs to an attacking force, // so don't send others force in such case. // FIXME: there may be other attacking the same place force who can help return; } } // Send defending forces, also send attacking forces if they are home/traning. // This is still basic model where we suspect only one base ;( const Vec2i &pos = attacker->tilePos; for (unsigned int i = 0; i < pai.Force.Size(); ++i) { AiForce &aiForce = pai.Force[i]; if (aiForce.Size() > 0 && ((aiForce.Role == AiForceRoleDefend && !aiForce.Attacking) || (aiForce.Role == AiForceRoleAttack && !aiForce.Attacking && !aiForce.State))) { // none attacking aiForce.Defending = true; aiForce.Attack(pos); } } }
/** ** Handle attacking the target. ** ** @param unit Unit, for that the attack is handled. */ void COrder_Attack::AttackTarget(CUnit &unit) { Assert(this->HasGoal() || Map.Info.IsPointOnMap(this->goalPos)); AnimateActionAttack(unit, *this); if (unit.Anim.Unbreakable) { return; } if (!this->HasGoal() && (this->Action == UnitActionAttackGround || Map.WallOnMap(this->goalPos))) { return; } // Target is dead ? Change order ? if (CheckForDeadGoal(unit)) { return; } CUnit *goal = this->GetGoal(); bool dead = !goal || goal->IsAlive() == false; // No target choose one. if (!goal) { goal = AttackUnitsInReactRange(unit); // No new goal, continue way to destination. if (!goal) { // Return to old task ? if (unit.RestoreOrder()) { return; } this->State = MOVE_TO_TARGET; return; } // Save current command to come back. COrder *savedOrder = COrder::NewActionAttack(unit, this->goalPos); if (unit.CanStoreOrder(savedOrder) == false) { delete savedOrder; savedOrder = NULL; } else { unit.SavedOrder = savedOrder; } this->SetGoal(goal); this->goalPos = goal->tilePos; this->MinRange = unit.Type->MinAttackRange; this->Range = unit.Stats->Variables[ATTACKRANGE_INDEX].Max; this->State |= WEAK_TARGET; // Have a weak target, try a better target. // FIXME: if out of range also try another target quick } else { if ((this->State & WEAK_TARGET)) { CUnit *newTarget = AttackUnitsInReactRange(unit); if (newTarget && ThreatCalculate(unit, *newTarget) < ThreatCalculate(unit, *goal)) { if (unit.CanStoreOrder(this)) { unit.SavedOrder = this->Clone(); } goal = newTarget; this->SetGoal(newTarget); this->goalPos = newTarget->tilePos; this->MinRange = unit.Type->MinAttackRange; this->State = MOVE_TO_TARGET; } } } // Still near to target, if not goto target. const int dist = unit.MapDistanceTo(*goal); if (dist > unit.Stats->Variables[ATTACKRANGE_INDEX].Max) { // towers don't chase after goal if (unit.CanMove()) { if (unit.CanStoreOrder(this)) { if (dead) { unit.SavedOrder = COrder::NewActionAttack(unit, this->goalPos); } else { unit.SavedOrder = this->Clone(); } } } unit.Frame = 0; this->State &= WEAK_TARGET; this->State |= MOVE_TO_TARGET; } if (dist < unit.Type->MinAttackRange) { this->State = MOVE_TO_TARGET; } // Turn always to target if (goal) { const Vec2i dir = goal->tilePos + goal->Type->GetHalfTileSize() - unit.tilePos; UnitHeadingFromDeltaXY(unit, dir); } }