/** ** Called if a member of Group is Attacked ** ** @param attacker Pointer to attacker unit. ** @param defender Pointer to unit that is being attacked. */ void GroupHelpMe(CUnit *attacker, CUnit &defender) { /* Freandly Fire - typical splash */ if (!attacker || attacker->Player->Index == defender.Player->Index) { return; } DebugPrint("%d: GroupHelpMe %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; } if (defender.GroupId) { int mask = 0; CUnitGroup *group; for (int num = 0; num < NUM_GROUPS; ++num) { // Unit belongs to an group, check if brothers in arms can help if (defender.GroupId & (1<<num)) { mask |= (1<<num); group = &Groups[num]; for (int i = 0; i < group->NumUnits; ++i) { CUnit &gunit = *group->Units[i]; if (&defender == &gunit) { continue; } // if brother is idle or attack no-agressive target and // can attack our attacker then ask for help if (gunit.IsAgressive() && (gunit.IsIdle() || !(gunit.CurrentAction() == UnitActionAttack && gunit.CurrentOrder()->HasGoal() && gunit.CurrentOrder()->GetGoal()->IsAgressive())) && CanTarget(gunit.Type, attacker->Type)) { if (gunit.SavedOrder.Action == UnitActionStill) { // FIXME: should rewrite command handling CommandAttack(gunit, gunit.tilePos, NoUnitP, FlushCommands); gunit.SavedOrder = *gunit.Orders[1]; } CommandAttack(gunit, attacker->tilePos, attacker, FlushCommands); } } if (!(defender.GroupId & ~mask)) { return; } } } } }
/** ** Auto attack nearby units if possible */ bool AutoAttack(CUnit &unit) { if (unit.Type->CanAttack == false) { return false; } // Normal units react in reaction range. CUnit *goal = AttackUnitsInReactRange(unit); if (goal == NULL) { return false; } COrder *savedOrder = NULL; if (unit.CurrentAction() == UnitActionStill) { savedOrder = COrder::NewActionAttack(unit, unit.tilePos); } else if (unit.CanStoreOrder(unit.CurrentOrder())) { savedOrder = unit.CurrentOrder()->Clone(); } // Weak goal, can choose other unit, come back after attack CommandAttack(unit, goal->tilePos, NULL, FlushCommands); if (savedOrder != NULL) { unit.SavedOrder = savedOrder; } return true; }
/** ** @brief Auto attack nearby units if possible */ bool AutoAttack(CUnit &unit) { //Wyrmgus start // if (unit.Type->CanAttack == false) { if (unit.CanAttack() == false) { //Wyrmgus end return false; } // Normal units react in reaction range. CUnit *goal = AttackUnitsInReactRange(unit); if (goal == nullptr) { return false; } COrder *savedOrder = nullptr; if (unit.CurrentAction() == UnitActionStill) { //Wyrmgus start // savedOrder = COrder::NewActionAttack(unit, unit.tilePos); savedOrder = COrder::NewActionAttack(unit, unit.tilePos, unit.MapLayer->ID); //Wyrmgus end } else if (unit.CanStoreOrder(unit.CurrentOrder())) { savedOrder = unit.CurrentOrder()->Clone(); } // Weak goal, can choose other unit, come back after attack CommandAttack(unit, goal->tilePos, nullptr, FlushCommands, goal->MapLayer->ID); if (savedOrder != nullptr) { unit.SavedOrder = savedOrder; } return true; }
/** ** Send command: Unit attack unit or at position. ** ** @param unit pointer to unit. ** @param pos map tile position to attack. ** @param attack or !=NoUnitP unit to be attacked. ** @param flush Flag flush all pending commands. */ void SendCommandAttack(CUnit &unit, const Vec2i &pos, CUnit *attack, int flush) { if (!IsNetworkGame()) { CommandLog("attack", &unit, flush, pos.x, pos.y, attack, NULL, -1); CommandAttack(unit, pos, attack, flush); } else { NetworkSendCommand(MessageCommandAttack, unit, pos.x, pos.y, attack, 0, flush); } }
void AiForceManager::Update() { for (unsigned int f = 0; f < forces.size(); ++f) { AiForce &force = forces[f]; // Look if our defenders still have enemies in range. if (force.Defending) { force.RemoveDeadUnit(); if (force.Size() == 0) { force.Attacking = false; force.Defending = false; continue; } const int nearDist = 5; if (Map.Info.IsPointOnMap(force.GoalPos) == false) { force.ReturnToHome(); // Check if some unit from force reached goal point } else if (force.Units[0]->MapDistanceTo(force.GoalPos) <= nearDist) { // Look if still enemies in attack range. const CUnit *dummy = NULL; if (!AiForceEnemyFinder<AIATTACK_RANGE>(force, &dummy).found()) { force.ReturnToHome(); } } else { // Find idle units and order them to defend std::vector<CUnit *> idleUnits; for (unsigned int i = 0; i != force.Size(); ++i) { CUnit &aiunit = *force.Units[i]; if (aiunit.IsIdle() && 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(); force.Update(); } } }
void AiForce::Attack(const Vec2i &pos) { RemoveDeadUnit(); if (Units.size() == 0) { this->Attacking = false; return; } if (!this->Attacking) { // Remember the original force position so we can return there after attack if (this->Role == AiForceRoleDefend || (this->Role == AiForceRoleAttack && this->State == AiForceAttackingState_Waiting)) { this->HomePos = this->Units[this->Units.size() - 1]->tilePos; } this->Attacking = true; } Vec2i goalPos(pos); if (Map.Info.IsPointOnMap(goalPos) == false) { /* Search in entire map */ const CUnit *enemy = NULL; AiForceEnemyFinder<AIATTACK_BUILDING>(*this, &enemy); if (enemy) { goalPos = enemy->tilePos; } } this->GoalPos = goalPos; if (Map.Info.IsPointOnMap(goalPos) == false) { DebugPrint("%d: Need to plan an attack with transporter\n" _C_ AiPlayer->Player->Index); if (State == AiForceAttackingState_Waiting && !PlanAttack()) { DebugPrint("%d: Can't transport\n" _C_ AiPlayer->Player->Index); Attacking = false; } return; } // Send all units in the force to enemy. 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); } } } }
/** ** Attack opponent with force. ** ** @param force Force number to attack with. */ global void AiAttackWithForce(int force) { const AiUnit* aiunit; const Unit* enemy; int x; int y; AiCleanForce(force); AiPlayer->Force[force].Attacking=0; if( (aiunit=AiPlayer->Force[force].Units) ) { AiPlayer->Force[force].Attacking=1; enemy=NoUnitP; while( aiunit && !enemy ) { // Use an unit that can attack if( aiunit->Unit->Type->CanAttack ) { enemy = AttackUnitsInDistance(aiunit->Unit, MaxMapWidth); } aiunit=aiunit->Next; } if (!enemy) { DebugLevel0Fn("Need to plan an attack with transporter\n"); if( !AiPlayer->Force[force].State && !AiPlanAttack(&AiPlayer->Force[force]) ) { DebugLevel0Fn("Can't transport, look for walls\n"); if( !AiFindWall(&AiPlayer->Force[force]) ) { AiPlayer->Force[force].Attacking=0; } } return; } AiPlayer->Force[force].State=0; x = enemy->X; y = enemy->Y; // // Send all units in the force to enemy. // aiunit=AiPlayer->Force[force].Units; while( aiunit ) { if( aiunit->Unit->Type->CanAttack ) { CommandAttack(aiunit->Unit, x, y, NULL,FlushCommands); } else { CommandMove(aiunit->Unit, x, y, FlushCommands); } aiunit=aiunit->Next; } } }
void ArAIProperty::InitNPCBehaviorTree() { mAIRoot->addChild(DI_NEW BoolCondition([this](void*){ float sight = DiK2Pos::FromWorldScale(mEntity->GetAttribute()->GetEntityConfig()->sightrangenight); if (!mEntity->CheckDistance(ArEntityManager::GetHeroID(), sight)) { // change to idle mEntity->GetEntity<ArDynEntity>()->GetMotionProperty()->ModalityChange(MODALITY_STAND); return false; } else { return true; } }, true)); mAIRoot->addChild(DI_NEW BoolCondition([this](void*){ float range = DiK2Pos::FromWorldScale(mEntity->GetAttribute()->GetEntityConfig()->attackrange*1.3f); auto heroEntity = ArGameApp::Get()->GetEntityManager()->GetHero(); auto thisEntity = dynamic_cast<ArDynEntity*>(mEntity); if (!thisEntity) { return false; } if (!mEntity->CheckAbsDistance(ArEntityManager::GetHeroID(), range)) { // change to follow CommandFollowTo(ArEntityManager::GetHeroID(), range-0.1f); return false; } else { if (!(heroEntity->GetMoveProperty()->GetWalkMode() == ENUM_WALK_MODE_WALK)) { // attack CommandAttack(ArEntityManager::GetHeroID()); } return true; } }, true)); }
/** ** Attack at position with force. ** ** @param force Force number to attack with. ** @param x X tile map position to be attacked. ** @param y Y tile map position to be attacked. */ global void AiAttackWithForceAt(int force,int x,int y) { const AiUnit* aiunit; AiCleanForce(force); if( (aiunit=AiPlayer->Force[force].Units) ) { AiPlayer->Force[force].Attacking=1; // // Send all units in the force to enemy. // while( aiunit ) { if( aiunit->Unit->Type->CanAttack ) { CommandAttack(aiunit->Unit, x, y, NULL,FlushCommands); } else { CommandMove(aiunit->Unit, x, y, FlushCommands); } aiunit=aiunit->Next; } } }
/** ** Step 2) ** Send force awaay in transporters, to unload at target position. ** ** @param force Force pointer. ** ** @todo The transporter should avoid enemy contact and should land ** at an unfortified coast. If we send more transporters they ** should land on different positions. */ local void AiSendTransporter(AiForce* force) { AiUnit* aiunit; // // Find all transporters. // aiunit=force->Units; while( aiunit ) { // Transporter to unload units if( aiunit->Unit->Type->Transporter ) { CommandUnload(aiunit->Unit, force->GoalX, force->GoalY, NoUnitP, FlushCommands); // Ships to defend transporter } else if( aiunit->Unit->Type->UnitType==UnitTypeNaval ) { CommandAttack(aiunit->Unit, force->GoalX, force->GoalY, NoUnitP, FlushCommands); } aiunit=aiunit->Next; } ++force->State; }
/** ** Execute a command (from network). ** ** @param msgnr Network message type ** @param unum Unit number (slot) that receive the command. ** @param x optional X map position. ** @param y optional y map position. ** @param dstnr optional destination unit. */ void ExecCommand(unsigned char msgnr, UnitRef unum, unsigned short x, unsigned short y, UnitRef dstnr) { CUnit &unit = UnitManager.GetSlotUnit(unum); const Vec2i pos(x, y); const int arg1 = x; const int arg2 = y; // // Check if unit is already killed? // if (unit.Destroyed) { DebugPrint(" destroyed unit skipping %d\n" _C_ UnitNumber(unit)); return; } Assert(unit.Type); const int status = (msgnr & 0x80) >> 7; // Note: destroyed destination unit is handled by the action routines. switch (msgnr & 0x7F) { case MessageSync: return; case MessageQuit: return; case MessageChat: return; case MessageCommandStop: CommandLog("stop", &unit, FlushCommands, -1, -1, NoUnitP, NULL, -1); CommandStopUnit(unit); break; case MessageCommandStand: CommandLog("stand-ground", &unit, status, -1, -1, NoUnitP, NULL, -1); CommandStandGround(unit, status); break; case MessageCommandDefend: { if (dstnr != (unsigned short)0xFFFF) { CUnit &dest = UnitManager.GetSlotUnit(dstnr); Assert(dest.Type); CommandLog("defend", &unit, status, -1, -1, &dest, NULL, -1); CommandDefend(unit, dest, status); } break; } case MessageCommandFollow: { if (dstnr != (unsigned short)0xFFFF) { CUnit &dest = UnitManager.GetSlotUnit(dstnr); Assert(dest.Type); CommandLog("follow", &unit, status, -1, -1, &dest, NULL, -1); CommandFollow(unit, dest, status); } break; } case MessageCommandMove: //Wyrmgus start // CommandLog("move", &unit, status, pos.x, pos.y, NoUnitP, NULL, -1); // CommandMove(unit, pos, status); if (!unit.CanMove()) { //FIXME: find better way to identify whether the unit should move or set a rally point CommandLog("rally-point", &unit, status, pos.x, pos.y, NoUnitP, NULL, -1); CommandRallyPoint(unit, pos); } else { CommandLog("move", &unit, status, pos.x, pos.y, NoUnitP, NULL, -1); CommandMove(unit, pos, status); } //Wyrmgus end break; //Wyrmgus start case MessageCommandPickUp: { if (dstnr != (unsigned short)0xFFFF) { CUnit &dest = UnitManager.GetSlotUnit(dstnr); Assert(dest.Type); CommandLog("pick-up", &unit, status, -1, -1, &dest, NULL, -1); CommandPickUp(unit, dest, status); } break; } //Wyrmgus end case MessageCommandRepair: { CUnit *dest = NoUnitP; if (dstnr != (unsigned short)0xFFFF) { dest = &UnitManager.GetSlotUnit(dstnr); Assert(dest && dest->Type); } CommandLog("repair", &unit, status, pos.x, pos.y, dest, NULL, -1); CommandRepair(unit, pos, dest, status); break; } case MessageCommandAutoRepair: CommandLog("auto-repair", &unit, status, arg1, arg2, NoUnitP, NULL, 0); CommandAutoRepair(unit, arg1); break; case MessageCommandAttack: { CUnit *dest = NoUnitP; if (dstnr != (unsigned short)0xFFFF) { dest = &UnitManager.GetSlotUnit(dstnr); Assert(dest && dest->Type); } CommandLog("attack", &unit, status, pos.x, pos.y, dest, NULL, -1); CommandAttack(unit, pos, dest, status); break; } case MessageCommandGround: CommandLog("attack-ground", &unit, status, pos.x, pos.y, NoUnitP, NULL, -1); CommandAttackGround(unit, pos, status); break; //Wyrmgus start case MessageCommandUse: { if (dstnr != (unsigned short)0xFFFF) { CUnit &dest = UnitManager.GetSlotUnit(dstnr); Assert(dest.Type); CommandLog("use", &unit, status, -1, -1, &dest, NULL, -1); CommandUse(unit, dest, status); } break; } //Wyrmgus end case MessageCommandPatrol: CommandLog("patrol", &unit, status, pos.x, pos.y, NoUnitP, NULL, -1); CommandPatrolUnit(unit, pos, status); break; case MessageCommandBoard: { if (dstnr != (unsigned short)0xFFFF) { CUnit &dest = UnitManager.GetSlotUnit(dstnr); Assert(dest.Type); CommandLog("board", &unit, status, arg1, arg2, &dest, NULL, -1); CommandBoard(unit, dest, status); } break; } case MessageCommandUnload: { CUnit *dest = NULL; if (dstnr != (unsigned short)0xFFFF) { dest = &UnitManager.GetSlotUnit(dstnr); Assert(dest && dest->Type); } CommandLog("unload", &unit, status, pos.x, pos.y, dest, NULL, -1); CommandUnload(unit, pos, dest, status); break; } case MessageCommandBuild: CommandLog("build", &unit, status, pos.x, pos.y, NoUnitP, UnitTypes[dstnr]->Ident.c_str(), -1); CommandBuildBuilding(unit, pos, *UnitTypes[dstnr], status); break; case MessageCommandDismiss: CommandLog("dismiss", &unit, FlushCommands, -1, -1, NULL, NULL, -1); CommandDismiss(unit); break; case MessageCommandResourceLoc: CommandLog("resource-loc", &unit, status, pos.x, pos.y, NoUnitP, NULL, -1); CommandResourceLoc(unit, pos, status); break; case MessageCommandResource: { if (dstnr != (unsigned short)0xFFFF) { CUnit &dest = UnitManager.GetSlotUnit(dstnr); Assert(dest.Type); CommandLog("resource", &unit, status, -1, -1, &dest, NULL, -1); CommandResource(unit, dest, status); } break; } case MessageCommandReturn: { CUnit *dest = (dstnr != (unsigned short)0xFFFF) ? &UnitManager.GetSlotUnit(dstnr) : NULL; CommandLog("return", &unit, status, -1, -1, dest, NULL, -1); CommandReturnGoods(unit, dest, status); break; } case MessageCommandTrain: //Wyrmgus start // CommandLog("train", &unit, status, -1, -1, NoUnitP, UnitTypes[dstnr]->Ident.c_str(), -1); // CommandTrainUnit(unit, *UnitTypes[dstnr], status); CommandLog("train", &unit, status, -1, -1, NoUnitP, UnitTypes[dstnr]->Ident.c_str(), arg1); // use X as a way to mark the player CommandTrainUnit(unit, *UnitTypes[dstnr], arg1, status); //Wyrmgus end break; case MessageCommandCancelTrain: // We need (short)x for the last slot -1 if (dstnr != (unsigned short)0xFFFF) { CommandLog("cancel-train", &unit, FlushCommands, -1, -1, NoUnitP, UnitTypes[dstnr]->Ident.c_str(), (short)x); CommandCancelTraining(unit, (short)x, UnitTypes[dstnr]); } else { CommandLog("cancel-train", &unit, FlushCommands, -1, -1, NoUnitP, NULL, (short)x); CommandCancelTraining(unit, (short)x, NULL); } break; case MessageCommandUpgrade: //Wyrmgus start /* CommandLog("upgrade-to", &unit, status, -1, -1, NoUnitP, UnitTypes[dstnr]->Ident.c_str(), -1); CommandUpgradeTo(unit, *UnitTypes[dstnr], status); break; */ if (arg1 == 2) { //use X as a way to mark whether this is an upgrade or a transformation CommandLog("transform-into", &unit, status, -1, -1, NoUnitP, UnitTypes[dstnr]->Ident.c_str(), -1); CommandTransformIntoType(unit, *UnitTypes[dstnr]); } else { CommandLog("upgrade-to", &unit, status, -1, -1, NoUnitP, UnitTypes[dstnr]->Ident.c_str(), -1); CommandUpgradeTo(unit, *UnitTypes[dstnr], status); } break; //Wyrmgus end case MessageCommandCancelUpgrade: CommandLog("cancel-upgrade-to", &unit, FlushCommands, -1, -1, NoUnitP, NULL, -1); CommandCancelUpgradeTo(unit); break; case MessageCommandResearch: CommandLog("research", &unit, status, -1, -1, NoUnitP, AllUpgrades[arg1]->Ident.c_str(), -1); CommandResearch(unit, *AllUpgrades[arg1], status); break; case MessageCommandCancelResearch: CommandLog("cancel-research", &unit, FlushCommands, -1, -1, NoUnitP, NULL, -1); CommandCancelResearch(unit); break; //Wyrmgus start case MessageCommandQuest: { CommandLog("quest", &unit, 0, 0, 0, NoUnitP, Quests[arg1]->Ident.c_str(), -1); CommandQuest(unit, Quests[arg1]); break; } case MessageCommandBuy: { if (dstnr != (unsigned short)0xFFFF) { CUnit &dest = UnitManager.GetSlotUnit(dstnr); Assert(dest.Type); CommandLog("buy", &unit, 0, -1, -1, &dest, NULL, arg1); CommandBuy(unit, &dest, arg1); } break; } //Wyrmgus end default: { int id = (msgnr & 0x7f) - MessageCommandSpellCast; if (arg2 != (unsigned short)0xFFFF) { CUnit *dest = NULL; if (dstnr != (unsigned short)0xFFFF) { dest = &UnitManager.GetSlotUnit(dstnr); Assert(dest && dest->Type); } CommandLog("spell-cast", &unit, status, pos.x, pos.y, dest, NULL, id); CommandSpellCast(unit, pos, dest, *SpellTypeTable[id], status); } else { CommandLog("auto-spell-cast", &unit, status, arg1, -1, NoUnitP, NULL, id); CommandAutoSpellCast(unit, id, arg1); } break; } } }
void MonsterFSM::orderTheAttack(void) { ASSERT(m_Owner!=0, "Owner was NULL"); m_Owner->QueueCommand(CommandAttack(targetCreatureHANDLE, FRAND_RANGE(0.7f, 1.0f))); }
/** ** Unit Patrol: ** The unit patrols between two points. ** Any enemy unit in reaction range is attacked. ** @todo FIXME: ** Should do some tries to reach the end-points. ** Should support patrol between more points! ** Patrol between units. ** ** @param unit Patroling unit pointer. */ void HandleActionPatrol(CUnit *unit) { if (unit->Wait) { unit->Wait--; return; } if (!unit->SubAction) { // first entry. NewResetPath(unit); unit->SubAction = 1; } switch (DoActionMove(unit)) { case PF_FAILED: unit->SubAction = 1; break; case PF_UNREACHABLE: // Increase range and try again unit->SubAction = 1; if (unit->Orders[0]->Range <= Map.Info.MapWidth || unit->Orders[0]->Range <= Map.Info.MapHeight) { unit->Orders[0]->Range++; break; } // FALL THROUGH case PF_REACHED: unit->SubAction = 1; unit->Orders[0]->Range = 0; SwapPatrolPoints(unit); break; case PF_WAIT: // Wait for a while then give up unit->SubAction++; if (unit->SubAction == 5) { unit->SubAction = 1; unit->Orders[0]->Range = 0; SwapPatrolPoints(unit); } break; default: // moving unit->SubAction = 1; break; } if (!unit->Anim.Unbreakable) { // // Attack any enemy in reaction range. // If don't set the goal, the unit can then choose a // better goal if moving nearer to enemy. // if (unit->Type->CanAttack) { const CUnit *goal = AttackUnitsInReactRange(unit); if (goal) { DebugPrint("Patrol attack %d\n" _C_ UnitNumber(goal)); CommandAttack(unit, goal->X, goal->Y, NULL, FlushCommands); // Save current command to come back. unit->SavedOrder = *unit->Orders[0]; unit->Orders[0]->Action = UnitActionStill; unit->Orders[0]->Goal = NoUnitP; unit->SubAction = 0; } } } }
/** ** 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); } } }
/** ** Force on attack ride. We attack until there is no unit or enemy left. ** ** @param force Force pointer. */ void AiForce::Update() { Assert(Defending == false); if (Size() == 0) { Attacking = false; if (!Defending && State > AiForceAttackingState_Waiting) { DebugPrint("%d: Attack force #%lu was destroyed, giving up\n" _C_ AiPlayer->Player->Index _C_(long unsigned int)(this - & (AiPlayer->Force[0]))); Reset(true); } return; } Attacking = false; for (unsigned int i = 0; i < Size(); ++i) { CUnit *aiunit = Units[i]; if (aiunit->Type->CanAttack) { Attacking = true; break; } } if (Attacking == false) { if (!Defending && State > AiForceAttackingState_Waiting) { DebugPrint("%d: Attack force #%lu has lost all agresive units, giving up\n" _C_ AiPlayer->Player->Index _C_(long unsigned int)(this - & (AiPlayer->Force[0]))); Reset(true); } return ; } #if 0 if (State == AiForceAttackingState_Waiting) { if (!this->PlanAttack()) { DebugPrint("Can't transport, look for walls\n"); if (!AiFindWall(this)) { Attacking = false; return ; } } State = AiForceAttackingState_Boarding; } #endif if (State == AiForceAttackingState_Boarding) { AiGroupAttackerForTransport(*this); return ; } if (State == AiForceAttackingState_AttackingWithTransporter) { // Move transporters to goalpos std::vector<CUnit *> transporters; bool emptyTrans = true; for (unsigned int i = 0; i != Size(); ++i) { CUnit &aiunit = *Units[i]; if (aiunit.CanMove() && aiunit.Type->MaxOnBoard) { transporters.push_back(&aiunit); if (aiunit.BoardCount > 0) { emptyTrans = false; } } } if (transporters.empty()) { // Our transporters have been destroyed DebugPrint("%d: Attack force #%lu has lost all agresive units, giving up\n" _C_ AiPlayer->Player->Index _C_(long unsigned int)(this - & (AiPlayer->Force[0]))); Reset(true); } else if (emptyTrans) { // We have emptied our transporters, go go go State = AiForceAttackingState_GoingToRallyPoint; } else { for (size_t i = 0; i != transporters.size(); ++i) { CUnit &trans = *transporters[i]; const int delay = i / 5; // To avoid lot of CPU consuption, send them with a small time difference. trans.Wait = delay; CommandUnload(trans, this->GoalPos, NULL, FlushCommands); } } return; } CUnit *leader = NULL; for (unsigned int i = 0; i != Size(); ++i) { CUnit &aiunit = *Units[i]; if (aiunit.IsAgressive()) { leader = &aiunit; break; } } const int thresholdDist = 5; // Hard coded value Assert(Map.Info.IsPointOnMap(GoalPos)); if (State == AiForceAttackingState_GoingToRallyPoint) { // Check if we are near the goalpos int minDist = Units[0]->MapDistanceTo(this->GoalPos); int maxDist = minDist; for (size_t i = 0; i != Size(); ++i) { const int distance = Units[i]->MapDistanceTo(this->GoalPos); minDist = std::min(minDist, distance); maxDist = std::max(maxDist, distance); } if (WaitOnRallyPoint > 0 && minDist <= thresholdDist) { --WaitOnRallyPoint; } if (maxDist <= thresholdDist || !WaitOnRallyPoint) { const CUnit *unit = NULL; AiForceEnemyFinder<AIATTACK_BUILDING>(*this, &unit); if (!unit) { AiForceEnemyFinder<AIATTACK_ALLMAP>(*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; State = AiForceAttackingState_Waiting; return; } } this->GoalPos = unit->tilePos; State = AiForceAttackingState_Attacking; for (size_t i = 0; i != this->Size(); ++i) { CUnit &aiunit = *this->Units[i]; const int delay = i / 5; // To avoid lot of CPU consuption, send them with a small time difference. aiunit.Wait = delay; if (aiunit.IsAgressive()) { CommandAttack(aiunit, this->GoalPos, NULL, FlushCommands); } else { if (leader) { CommandDefend(aiunit, *leader, FlushCommands); } else { CommandMove(aiunit, this->GoalPos, FlushCommands); } } } } } std::vector<CUnit *> idleUnits; for (unsigned int i = 0; i != Size(); ++i) { CUnit &aiunit = *Units[i]; if (aiunit.IsIdle()) { idleUnits.push_back(&aiunit); } } if (idleUnits.empty()) { return; } if (State == AiForceAttackingState_Attacking && idleUnits.size() == this->Size()) { const CUnit *unit = NULL; bool isNaval = false; for (size_t i = 0; i != this->Units.size(); ++i) { CUnit *const unit = this->Units[i]; if (unit->Type->UnitType == UnitTypeNaval && unit->Type->CanAttack) { isNaval = true; break; } } if (isNaval) { AiForceEnemyFinder<AIATTACK_ALLMAP>(*this, &unit); } else { 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; State = AiForceAttackingState_Waiting; return; } else { Vec2i resultPos; NewRallyPoint(unit->tilePos, &resultPos); this->GoalPos = resultPos; this->State = AiForceAttackingState_GoingToRallyPoint; } } 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 (leader) { if (aiunit.IsAgressive()) { if (State == AiForceAttackingState_Attacking) { CommandAttack(aiunit, leader->tilePos, NULL, FlushCommands); } else { CommandAttack(aiunit, this->GoalPos, NULL, FlushCommands); } } else { CommandDefend(aiunit, *leader, FlushCommands); } } else { if (aiunit.IsAgressive()) { CommandAttack(aiunit, this->GoalPos, NULL, FlushCommands); } else { CommandMove(aiunit, this->GoalPos, FlushCommands); } } } }
void AiForce::Attack(const Vec2i &pos) { bool isDefenceForce = false; RemoveDeadUnit(); if (Units.size() == 0) { this->Attacking = false; this->State = AiForceAttackingState_Waiting; return; } if (!this->Attacking) { // Remember the original force position so we can return there after attack if (this->Role == AiForceRoleDefend || (this->Role == AiForceRoleAttack && this->State == AiForceAttackingState_Waiting)) { this->HomePos = this->Units[this->Units.size() - 1]->tilePos; } this->Attacking = true; } Vec2i goalPos(pos); bool isNaval = false; for (size_t i = 0; i != this->Units.size(); ++i) { CUnit *const unit = this->Units[i]; if (unit->Type->UnitType == UnitTypeNaval && unit->Type->CanAttack) { isNaval = true; break; } } bool isTransporter = false; for (size_t i = 0; i != this->Units.size(); ++i) { CUnit *const unit = this->Units[i]; if (unit->Type->CanTransport() && unit->IsAgressive() == false) { isTransporter = true; break; } } if (Map.Info.IsPointOnMap(goalPos) == false) { /* Search in entire map */ const CUnit *enemy = NULL; if (isTransporter) { AiForceEnemyFinder<AIATTACK_AGRESSIVE>(*this, &enemy); } else if (isNaval) { AiForceEnemyFinder<AIATTACK_ALLMAP>(*this, &enemy); } else { AiForceEnemyFinder<AIATTACK_BUILDING>(*this, &enemy); } if (enemy) { goalPos = enemy->tilePos; } } else { isDefenceForce = true; } if (Map.Info.IsPointOnMap(goalPos) == false || isTransporter) { DebugPrint("%d: Need to plan an attack with transporter\n" _C_ AiPlayer->Player->Index); if (State == AiForceAttackingState_Waiting && !PlanAttack()) { DebugPrint("%d: Can't transport\n" _C_ AiPlayer->Player->Index); Attacking = false; } return; } if (this->State == AiForceAttackingState_Waiting && isDefenceForce == false) { Vec2i resultPos; NewRallyPoint(goalPos, &resultPos); this->GoalPos = resultPos; this->State = AiForceAttackingState_GoingToRallyPoint; } else { this->GoalPos = goalPos; this->State = AiForceAttackingState_Attacking; } // Send all units in the force to enemy. CUnit *leader = NULL; for (size_t i = 0; i != this->Units.size(); ++i) { CUnit *const unit = this->Units[i]; if (unit->IsAgressive()) { leader = unit; break; } } 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->IsAgressive()) { CommandAttack(*unit, this->GoalPos, NULL, FlushCommands); } else { if (leader) { CommandDefend(*unit, *leader, FlushCommands); } else { CommandMove(*unit, this->GoalPos, FlushCommands); } } } } }
/** ** Force on attack ride. We attack until there is no unit or enemy left. ** ** @param force Force pointer. */ void AiForce::Update() { if (Size() == 0) { Attacking = false; if (!Defending && State > AiForceAttackingState_Waiting) { DebugPrint("%d: Attack force #%lu was destroyed, giving up\n" _C_ AiPlayer->Player->Index _C_(long unsigned int)(this - & (AiPlayer->Force[0]))); Reset(true); } return; } Attacking = false; for (unsigned int i = 0; i < Size(); ++i) { CUnit *aiunit = Units[i]; if (aiunit->Type->CanAttack) { Attacking = true; break; } } if (Attacking == false) { if (!Defending && State > AiForceAttackingState_Waiting) { DebugPrint("%d: Attack force #%lu has lost all agresive units, giving up\n" _C_ AiPlayer->Player->Index _C_(long unsigned int)(this - & (AiPlayer->Force[0]))); Reset(true); } return ; } #if 0 if (State == AiForceAttackingState_Waiting) { if (!this->PlanAttack()) { DebugPrint("Can't transport, look for walls\n"); if (!AiFindWall(this)) { Attacking = false; return ; } } State = AiForceAttackingState_Boarding; } #endif if (State == AiForceAttackingState_Boarding) { AiGroupAttackerForTransport(*this); return ; } Assert(Map.Info.IsPointOnMap(GoalPos)); std::vector<CUnit *> idleUnits; const CUnit *leader = NULL; for (unsigned int i = 0; i != Size(); ++i) { CUnit &aiunit = *Units[i]; if (aiunit.IsIdle()) { if (aiunit.IsAliveOnMap()) { idleUnits.push_back(&aiunit); } } else if (leader == NULL && aiunit.CurrentAction() == UnitActionAttack) { const COrder_Attack &order = *static_cast<COrder_Attack *>(aiunit.CurrentOrder()); if (order.HasGoal() && order.IsValid()) { leader = &aiunit; } } } if (idleUnits.empty()) { return ; } if (leader == NULL) { const int thresholdDist = 5; // Hard coded value int maxDist = 0; for (size_t i = 0; i != idleUnits.size(); ++i) { maxDist = std::max(maxDist, idleUnits[i]->MapDistanceTo(this->GoalPos)); } if (maxDist < thresholdDist) { 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; } } 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); } } else { CommandMove(aiunit, pos, FlushCommands); } } }
/** ** Order a unit ** ** @param l Lua state. ** ** OrderUnit(player, unit-type, sloc, dloc, order) */ static int CclOrderUnit(lua_State *l) { LuaCheckArgs(l, 5); lua_pushvalue(l, 1); const int plynr = TriggerGetPlayer(l); lua_pop(l, 1); lua_pushvalue(l, 2); const CUnitType *unittype = TriggerGetUnitType(l); lua_pop(l, 1); if (!lua_istable(l, 3)) { LuaError(l, "incorrect argument"); } Vec2i pos1; pos1.x = LuaToNumber(l, 3, 1); pos1.y = LuaToNumber(l, 3, 2); Vec2i pos2; if (lua_rawlen(l, 3) == 4) { pos2.x = LuaToNumber(l, 3, 3); pos2.y = LuaToNumber(l, 3, 4); } else { pos2 = pos1; } if (!lua_istable(l, 4)) { LuaError(l, "incorrect argument"); } Vec2i dpos1; Vec2i dpos2; dpos1.x = LuaToNumber(l, 4, 1); dpos1.y = LuaToNumber(l, 4, 2); if (lua_rawlen(l, 4) == 4) { dpos2.x = LuaToNumber(l, 4, 3); dpos2.y = LuaToNumber(l, 4, 4); } else { dpos2 = dpos1; } const char *order = LuaToString(l, 5); std::vector<CUnit *> table; Select(pos1, pos2, table); for (size_t i = 0; i != table.size(); ++i) { CUnit &unit = *table[i]; if (unittype == ANY_UNIT || (unittype == ALL_FOODUNITS && !unit.Type->Building) || (unittype == ALL_BUILDINGS && unit.Type->Building) || unittype == unit.Type) { if (plynr == -1 || plynr == unit.Player->Index) { if (!strcmp(order, "move")) { CommandMove(unit, (dpos1 + dpos2) / 2, 1); } else if (!strcmp(order, "attack")) { CUnit *attack = TargetOnMap(unit, dpos1, dpos2); CommandAttack(unit, (dpos1 + dpos2) / 2, attack, 1); } else if (!strcmp(order, "patrol")) { CommandPatrolUnit(unit, (dpos1 + dpos2) / 2, 1); } else { LuaError(l, "Unsupported order: %s" _C_ order); } } } } return 0; }
/** ** Auto attack nearby units if possible */ static void AutoAttack(CUnit *unit, bool stand_ground) { CUnit *temp; CUnit *goal; if (unit->Wait) { unit->Wait--; return; } // Cowards don't attack unless ordered. if (unit->Type->CanAttack && !unit->Type->Coward) { // Normal units react in reaction range. if (CanMove(unit) && !unit->Removed && !stand_ground) { if ((goal = AttackUnitsInReactRange(unit))) { // Weak goal, can choose other unit, come back after attack CommandAttack(unit, goal->X, goal->Y, NULL, FlushCommands); Assert(unit->SavedOrder.Action == UnitActionStill); Assert(!unit->SavedOrder.Goal); unit->SavedOrder.Action = UnitActionAttack; unit->SavedOrder.Range = 0; unit->SavedOrder.X = unit->X; unit->SavedOrder.Y = unit->Y; unit->SavedOrder.Goal = NoUnitP; } else { unit->Wait = 15; } // Removed units can only attack in AttackRange, from bunker } else if ((goal = AttackUnitsInRange(unit))) { temp = unit->Orders[0]->Goal; if (temp && temp->Orders[0]->Action == UnitActionDie) { temp->RefsDecrease(); unit->Orders[0]->Goal = temp = NoUnitP; } if (!unit->SubAction || temp != goal) { // New target. if (temp) { temp->RefsDecrease(); } unit->Orders[0]->Goal = goal; goal->RefsIncrease(); unit->State = 0; unit->SubAction = 1; // Mark attacking. UnitHeadingFromDeltaXY(unit, goal->X + (goal->Type->TileWidth - 1) / 2 - unit->X, goal->Y + (goal->Type->TileHeight - 1) / 2 - unit->Y); } return; } } else { unit->Wait = 15; } if (unit->SubAction) { // was attacking. if ((temp = unit->Orders[0]->Goal)) { temp->RefsDecrease(); unit->Orders[0]->Goal = NoUnitP; } unit->SubAction = unit->State = 0; // No attacking, restart } Assert(!unit->Orders[0]->Goal); }