void CCommandAI::ExecuteAttack(Command& c) { assert(owner->unitDef->canAttack); if (inCommand) { if (targetDied || (c.params.size() == 1 && UpdateTargetLostTimer(int(c.params[0])) == 0)) { FinishCommand(); return; } if (!(c.options & ALT_KEY) && SkipParalyzeTarget(orderTarget)) { FinishCommand(); return; } } else { if (c.params.size() == 1) { CUnit* targetUnit = unitHandler->GetUnit(c.params[0]); if (targetUnit == NULL) { FinishCommand(); return; } if (targetUnit == owner) { FinishCommand(); return; } if (targetUnit->GetTransporter() != NULL && !modInfo.targetableTransportedUnits) { FinishCommand(); return; } SetOrderTarget(targetUnit); owner->AttackUnit(targetUnit, (c.options & INTERNAL_ORDER) == 0, c.GetID() == CMD_MANUALFIRE); inCommand = true; } else { owner->AttackGround(c.GetPos(0), (c.options & INTERNAL_ORDER) == 0, c.GetID() == CMD_MANUALFIRE); inCommand = true; } } }
bool CAirCAI::SelectNewAreaAttackTargetOrPos(const Command& ac) { assert(ac.GetID() == CMD_AREA_ATTACK || (ac.GetID() == CMD_ATTACK && ac.GetParamsCount() >= 3)); if (ac.GetID() == CMD_ATTACK) { FinishCommand(); return false; } const float3& pos = ac.GetPos(0); const float radius = ac.params[3]; std::vector<int> enemyUnitIDs; CGameHelper::GetEnemyUnits(pos, radius, owner->allyteam, enemyUnitIDs); if (enemyUnitIDs.empty()) { float3 attackPos = pos + (gs->randVector() * radius); attackPos.y = CGround::GetHeightAboveWater(attackPos.x, attackPos.z); owner->AttackGround(attackPos, (ac.options & INTERNAL_ORDER) == 0, false); SetGoal(attackPos, owner->pos); } else { // note: the range of randFloat() is inclusive of 1.0f const unsigned int unitIdx = std::min<int>(gs->randFloat() * enemyUnitIDs.size(), enemyUnitIDs.size() - 1); const unsigned int unitID = enemyUnitIDs[unitIdx]; CUnit* targetUnit = unitHandler->GetUnitUnsafe(unitID); SetOrderTarget(targetUnit); owner->AttackUnit(targetUnit, (ac.options & INTERNAL_ORDER) == 0, false); SetGoal(targetUnit->pos, owner->pos); } return true; }
void CAirCAI::ExecuteAreaAttack(Command& c) { assert(owner->unitDef->canAttack); // FIXME: check owner->UsingScriptMoveType() and skip rest if true? AAirMoveType* myPlane = GetStrafeAirMoveType(owner); if (targetDied) { targetDied = false; inCommand = false; } const float3& pos = c.GetPos(0); const float radius = c.params[3]; if (inCommand) { if (myPlane->aircraftState == AAirMoveType::AIRCRAFT_LANDED) inCommand = false; if (orderTarget && orderTarget->pos.SqDistance2D(pos) > Square(radius)) { inCommand = false; // target wandered out of the attack-area SetOrderTarget(NULL); SelectNewAreaAttackTargetOrPos(c); } } else { if (myPlane->aircraftState != AAirMoveType::AIRCRAFT_LANDED) { inCommand = true; SelectNewAreaAttackTargetOrPos(c); } } }
void CAirCAI::ExecuteAttack(Command& c) { assert(owner->unitDef->canAttack); targetAge++; if (tempOrder && owner->moveState == MOVESTATE_MANEUVER) { // limit how far away we fly if (orderTarget && LinePointDist(commandPos1, commandPos2, orderTarget->pos) > 1500) { owner->AttackUnit(NULL, false, false); FinishCommand(); return; } } if (inCommand) { if (targetDied || (c.params.size() == 1 && UpdateTargetLostTimer(int(c.params[0])) == 0)) { FinishCommand(); return; } if (orderTarget != NULL) { if (orderTarget->unitDef->canfly && orderTarget->IsCrashing()) { owner->AttackUnit(NULL, false, false); FinishCommand(); return; } if (!(c.options & ALT_KEY) && SkipParalyzeTarget(orderTarget)) { owner->AttackUnit(NULL, false, false); FinishCommand(); return; } } } else { targetAge = 0; if (c.params.size() == 1) { CUnit* targetUnit = unitHandler->GetUnit(c.params[0]); if (targetUnit == NULL) { FinishCommand(); return; } if (targetUnit == owner) { FinishCommand(); return; } if (targetUnit->GetTransporter() != NULL && !modInfo.targetableTransportedUnits) { FinishCommand(); return; } SetGoal(targetUnit->pos, owner->pos, cancelDistance); SetOrderTarget(targetUnit); owner->AttackUnit(targetUnit, (c.options & INTERNAL_ORDER) == 0, false); inCommand = true; } else { SetGoal(c.GetPos(0), owner->pos, cancelDistance); owner->AttackGround(c.GetPos(0), (c.options & INTERNAL_ORDER) == 0, false); inCommand = true; } } }
void CCommandAI::FinishCommand() { assert(!commandQue.empty()); const Command cmd = commandQue.front(); const int cmdID = cmd.GetID(); const int cmdTag = cmd.tag; const bool dontRepeat = (cmd.options & INTERNAL_ORDER); if (repeatOrders && !dontRepeat && (cmdID != CMD_STOP) && (cmdID != CMD_PATROL) && (cmdID != CMD_SET_WANTED_MAX_SPEED)){ commandQue.push_back(commandQue.front()); } commandQue.pop_front(); inCommand = false; targetDied = false; unimportantMove = false; SetOrderTarget(NULL); eoh->CommandFinished(*owner, cmd); eventHandler.UnitCmdDone(owner, cmdID, cmdTag); ClearTargetLock(cmd); if (commandQue.empty()) { if (!owner->group) { eoh->UnitIdle(*owner); } eventHandler.UnitIdle(owner); } // avoid infinite loops if (lastFinishCommand != gs->frameNum) { lastFinishCommand = gs->frameNum; if (!owner->IsStunned()) { SlowUpdate(); } } }
void CCommandAI::ExecuteAttack(Command& c) { assert(owner->unitDef->canAttack); if (inCommand) { if (targetDied || (c.params.size() == 1 && UpdateTargetLostTimer(int(c.params[0])) == 0)) { FinishCommand(); return; } if ((c.params.size() == 3) && (owner->commandShotCount > 0) && (commandQue.size() > 1)) { FinishCommand(); return; } if (!(c.options & ALT_KEY) && SkipParalyzeTarget(orderTarget)) { FinishCommand(); return; } } else { owner->commandShotCount = -1; if (c.params.size() == 1) { CUnit* targetUnit = uh->GetUnit(c.params[0]); if (targetUnit != NULL && targetUnit != owner) { owner->AttackUnit(targetUnit, c.GetID() == CMD_MANUALFIRE); SetOrderTarget(targetUnit); inCommand = true; } else { FinishCommand(); return; } } else { float3 pos(c.params[0], c.params[1], c.params[2]); owner->AttackGround(pos, c.GetID() == CMD_MANUALFIRE); inCommand = true; } } }
void CCommandAI::ExecuteInsert(const Command& c, bool fromSynced) { if (c.params.size() < 3) { return; } // make the command Command newCmd((int)c.params[1], (unsigned char)c.params[2]); for (int p = 3; p < (int)c.params.size(); p++) { newCmd.params.push_back(c.params[p]); } // validate the command if (!AllowedCommand(newCmd, fromSynced)) { return; } CCommandQueue* queue = &commandQue; bool facBuildQueue = false; CFactoryCAI* facCAI = dynamic_cast<CFactoryCAI*>(this); if (facCAI) { if (c.options & CONTROL_KEY) { // check the build order const map<int, CFactoryCAI::BuildOption>& bOpts = facCAI->buildOptions; if ((newCmd.GetID() != CMD_STOP) && (newCmd.GetID() != CMD_WAIT) && ((newCmd.GetID() >= 0) || (bOpts.find(newCmd.GetID()) == bOpts.end()))) { return; } facBuildQueue = true; } else { // use the new commands queue = &facCAI->newUnitCommands; } } // FIXME: handle CMD_LOOPBACKATTACK, etc... CCommandQueue::iterator insertIt = queue->begin(); if (c.options & ALT_KEY) { // treat param0 as a position int pos = (int)c.params[0]; const unsigned int qsize = queue->size(); if (pos < 0) { pos = qsize + pos + 1; // convert the negative index if (pos < 0) { pos = 0; } } if (pos > qsize) { pos = qsize; } std::advance(insertIt, pos); } else { // treat param0 as a command tag const unsigned int tag = (unsigned int)c.params[0]; CCommandQueue::iterator ci; bool found = false; for (ci = queue->begin(); ci != queue->end(); ++ci) { const Command& qc = *ci; if (qc.tag == tag) { insertIt = ci; found = true; break; } } if (!found) { return; } if ((c.options & RIGHT_MOUSE_KEY) && (insertIt != queue->end())) { ++insertIt; // insert after the tagged command } } if (facBuildQueue) { facCAI->InsertBuildCommand(insertIt, newCmd); if (!owner->stunned) { SlowUpdate(); } return; } // shutdown the current order if the insertion is at the beginning if (!queue->empty() && (insertIt == queue->begin())) { inCommand = false; targetDied = false; unimportantMove = false; SetOrderTarget(NULL); const Command& cmd = queue->front(); eoh->CommandFinished(*owner, cmd); eventHandler.UnitCmdDone(owner, cmd.GetID(), cmd.tag); } queue->insert(insertIt, newCmd); if (!owner->stunned) { SlowUpdate(); } }
void CCommandAI::GiveAllowedCommand(const Command& c, bool fromSynced) { if (ExecuteStateCommand(c)) { return; } switch (c.GetID()) { case CMD_SELFD: { if (owner->unitDef->canSelfD) { if (!(c.options & SHIFT_KEY) || commandQue.empty()) { if (owner->selfDCountdown != 0) { owner->selfDCountdown = 0; } else { owner->selfDCountdown = owner->unitDef->selfDCountdown*2+1; } } else if (commandQue.back().GetID() == CMD_SELFD) { commandQue.pop_back(); } else { commandQue.push_back(c); } } return; } case CMD_SET_WANTED_MAX_SPEED: { if (CanSetMaxSpeed() && (commandQue.empty() || (commandQue.back().GetID() != CMD_SET_WANTED_MAX_SPEED))) { // bail early, do not check for overlaps or queue cancelling commandQue.push_back(c); if (commandQue.size()==1 && !owner->beingBuilt) { SlowUpdate(); } } return; } case CMD_WAIT: { GiveWaitCommand(c); return; } case CMD_INSERT: { ExecuteInsert(c, fromSynced); return; } case CMD_REMOVE: { ExecuteRemove(c); return; } } // flush the queue for immediate commands if (!(c.options & SHIFT_KEY)) { if (!commandQue.empty()) { const int& cmd_id = commandQue.front().GetID(); if ((cmd_id == CMD_MANUALFIRE) || (cmd_id == CMD_ATTACK) || (cmd_id == CMD_AREA_ATTACK)) { owner->AttackUnit(0,true); } waitCommandsAI.ClearUnitQueue(owner, commandQue); ClearCommandDependencies(); commandQue.clear(); } inCommand=false; SetOrderTarget(NULL); } AddCommandDependency(c); if (c.GetID() == CMD_PATROL) { CCommandQueue::iterator ci = commandQue.begin(); for (; ci != commandQue.end() && ci->GetID() != CMD_PATROL; ++ci) { // just increment } if (ci == commandQue.end()) { if (commandQue.empty()) { Command c2(CMD_PATROL, c.options); c2.params.push_back(owner->pos.x); c2.params.push_back(owner->pos.y); c2.params.push_back(owner->pos.z); commandQue.push_back(c2); } else { do { --ci; if (ci->params.size() >= 3) { Command c2(CMD_PATROL, c.options); c2.params = ci->params; commandQue.push_back(c2); break; } else if (ci == commandQue.begin()) { Command c2(CMD_PATROL, c.options); c2.params.push_back(owner->pos.x); c2.params.push_back(owner->pos.y); c2.params.push_back(owner->pos.z); commandQue.push_back(c2); break; } } while (ci != commandQue.begin()); } } } // cancel duplicated commands bool first; if (CancelCommands(c, commandQue, first) > 0) { if (first) { Command stopCommand(CMD_STOP); commandQue.push_front(stopCommand); SlowUpdate(); } return; } // do not allow overlapping commands if (!GetOverlapQueued(c).empty()) { return; } if (c.GetID() == CMD_ATTACK) { // avoid weaponless units moving to 0 distance when given attack order if (owner->weapons.empty() && (owner->unitDef->canKamikaze == false)) { Command c2(CMD_STOP); commandQue.push_back(c2); return; } } commandQue.push_back(c); if (commandQue.size() == 1 && !owner->beingBuilt && !owner->stunned) { SlowUpdate(); } }
CCommandAI::~CCommandAI() { SetOrderTarget(NULL); ClearCommandDependencies(); }
void CMobileCAI::ExecuteAttack(Command &c) { assert(owner->unitDef->canAttack); // limit how far away we fly based on our movestate if (tempOrder && orderTarget) { const float3& closestPos = ClosestPointOnLine(commandPos1, commandPos2, owner->pos); const float curTargetDist = LinePointDist(closestPos, commandPos2, orderTarget->pos); const float maxTargetDist = (500 * owner->moveState + owner->maxRange); if (owner->moveState < MOVESTATE_ROAM && curTargetDist > maxTargetDist) { StopMove(); FinishCommand(); return; } } // check if we are in direct command of attacker if (!inCommand) { if (c.params.size() == 1) { CUnit* targetUnit = unitHandler->GetUnit(c.params[0]); // check if we have valid target parameter and that we aren't attacking ourselves if (targetUnit == NULL) { StopMove(); FinishCommand(); return; } if (targetUnit == owner) { StopMove(); FinishCommand(); return; } if (targetUnit->GetTransporter() != NULL && !modInfo.targetableTransportedUnits) { StopMove(); FinishCommand(); return; } const float3 tgtErrPos = targetUnit->pos + owner->posErrorVector * 128; const float3 tgtPosDir = (tgtErrPos - owner->pos).Normalize(); SetGoal(tgtErrPos - tgtPosDir * targetUnit->radius, owner->pos); SetOrderTarget(targetUnit); owner->AttackUnit(targetUnit, (c.options & INTERNAL_ORDER) == 0, c.GetID() == CMD_MANUALFIRE); inCommand = true; } else if (c.params.size() >= 3) { // user gave force-fire attack command SetGoal(c.GetPos(0), owner->pos); inCommand = true; } } // if our target is dead or we lost it then stop attacking // NOTE: unit should actually just continue to target area! if (targetDied || (c.params.size() == 1 && UpdateTargetLostTimer(int(c.params[0])) == 0)) { // cancel keeppointingto StopMove(); FinishCommand(); return; } // user clicked on enemy unit (note that we handle aircrafts slightly differently) if (orderTarget != NULL) { bool tryTargetRotate = false; bool tryTargetHeading = false; float edgeFactor = 0.0f; // percent offset to target center const float3 targetMidPosVec = owner->midPos - orderTarget->midPos; const float targetGoalDist = (orderTarget->pos + owner->posErrorVector * 128.0f).SqDistance2D(goalPos); const float targetPosDist = Square(10.0f + orderTarget->pos.distance2D(owner->pos) * 0.2f); const float minPointingDist = std::min(1.0f * owner->losRadius * loshandler->losDiv, owner->maxRange * 0.9f); // FIXME? targetMidPosMaxDist is 3D, but compared with a 2D value const float targetMidPosDist2D = targetMidPosVec.Length2D(); //const float targetMidPosMaxDist = owner->maxRange - (orderTarget->speed.SqLength() / owner->unitDef->maxAcc); if (!owner->weapons.empty()) { if (!(c.options & ALT_KEY) && SkipParalyzeTarget(orderTarget)) { StopMove(); FinishCommand(); return; } } for (unsigned int wNum = 0; wNum < owner->weapons.size(); wNum++) { CWeapon* w = owner->weapons[wNum]; if (c.GetID() == CMD_MANUALFIRE) { assert(owner->unitDef->canManualFire); if (!w->weaponDef->manualfire) { continue; } } tryTargetRotate = w->TryTargetRotate(orderTarget, (c.options & INTERNAL_ORDER) == 0); tryTargetHeading = w->TryTargetHeading(GetHeadingFromVector(-targetMidPosVec.x, -targetMidPosVec.z), orderTarget->pos, orderTarget != NULL, orderTarget); if (tryTargetRotate || tryTargetHeading) break; edgeFactor = math::fabs(w->targetBorder); } // if w->AttackUnit() returned true then we are already // in range with our biggest (?) weapon, so stop moving // also make sure that we're not locked in close-in/in-range state loop // due to rotates invoked by in-range or out-of-range states if (tryTargetRotate) { const bool canChaseTarget = (!tempOrder || owner->moveState != MOVESTATE_HOLDPOS); const bool targetBehind = (targetMidPosVec.dot(orderTarget->speed) < 0.0f); if (canChaseTarget && tryTargetHeading && targetBehind) { SetGoal(owner->pos + (orderTarget->speed * 80), owner->pos, SQUARE_SIZE, orderTarget->speed.Length() * 1.1f); } else { StopMove(); if (gs->frameNum > lastCloseInTry + MAX_CLOSE_IN_RETRY_TICKS) { owner->moveType->KeepPointingTo(orderTarget->midPos, minPointingDist, true); } } owner->AttackUnit(orderTarget, (c.options & INTERNAL_ORDER) == 0, c.GetID() == CMD_MANUALFIRE); } // if we're on hold pos in a temporary order, then none of the close-in // code below should run, and the attack command is cancelled. else if (tempOrder && owner->moveState == MOVESTATE_HOLDPOS) { StopMove(); FinishCommand(); return; } // if ((our movetype has type HoverAirMoveType and length of 2D vector from us to target // less than 90% of our maximum range) OR squared length of 2D vector from us to target // less than 1024) then we are close enough else if (targetMidPosDist2D < (owner->maxRange * 0.9f)) { if (dynamic_cast<CHoverAirMoveType*>(owner->moveType) != NULL || (targetMidPosVec.SqLength2D() < 1024)) { StopMove(); owner->moveType->KeepPointingTo(orderTarget->midPos, minPointingDist, true); } // if (((first weapon range minus first weapon length greater than distance to target) // and length of 2D vector from us to target less than 90% of our maximum range) // then we are close enough, but need to move sideways to get a shot. //assumption is flawed: The unit may be aiming or otherwise unable to shoot else if (owner->unitDef->strafeToAttack && targetMidPosDist2D < (owner->maxRange * 0.9f)) { moveDir ^= (owner->moveType->progressState == AMoveType::Failed); const float sin = moveDir ? 3.0/5 : -3.0/5; const float cos = 4.0 / 5; float3 goalDiff; goalDiff.x = targetMidPosVec.dot(float3(cos, 0, -sin)); goalDiff.z = targetMidPosVec.dot(float3(sin, 0, cos)); goalDiff *= (targetMidPosDist2D < (owner->maxRange * 0.3f)) ? 1/cos : cos; goalDiff += orderTarget->pos; SetGoal(goalDiff, owner->pos); } } // if 2D distance of (target position plus attacker error vector times 128) // to goal position greater than // (10 plus 20% of 2D distance between attacker and target) then we need to close // in on target more else if (targetGoalDist > targetPosDist) { // if the target isn't in LOS, go to its approximate position // otherwise try to go precisely to the target // this should fix issues with low range weapons (mainly melee) const float3 errPos = ((orderTarget->losStatus[owner->allyteam] & LOS_INLOS)? ZeroVector: owner->posErrorVector * 128.0f); const float3 tgtPos = orderTarget->pos + errPos; const float3 norm = (tgtPos - owner->pos).Normalize(); const float3 goal = tgtPos - norm * (orderTarget->radius * edgeFactor * 0.8f); SetGoal(goal, owner->pos); if (lastCloseInTry < gs->frameNum + MAX_CLOSE_IN_RETRY_TICKS) lastCloseInTry = gs->frameNum; } } // user wants to attack the ground; cycle through our // weapons until we find one that can accomodate him else if (c.params.size() >= 3) { const float3 attackPos = c.GetPos(0); const float3 attackVec = attackPos - owner->pos; bool foundWeapon = false; for (unsigned int wNum = 0; wNum < owner->weapons.size(); wNum++) { CWeapon* w = owner->weapons[wNum]; if (foundWeapon) break; // XXX HACK - special weapon overrides any checks if (c.GetID() == CMD_MANUALFIRE) { assert(owner->unitDef->canManualFire); if (!w->weaponDef->manualfire) continue; if (attackVec.SqLength() >= (w->range * w->range)) continue; StopMove(); owner->AttackGround(attackPos, (c.options & INTERNAL_ORDER) == 0, c.GetID() == CMD_MANUALFIRE); owner->moveType->KeepPointingTo(attackPos, owner->maxRange * 0.9f, true); foundWeapon = true; } else { // NOTE: // we call TryTargetHeading which is less restrictive than TryTarget // (eg. the former succeeds even if the unit has not already aligned // itself with <attackVec>) if (w->TryTargetHeading(GetHeadingFromVector(attackVec.x, attackVec.z), attackPos, (c.options & INTERNAL_ORDER) == 0, NULL)) { if (w->TryTargetRotate(attackPos, (c.options & INTERNAL_ORDER) == 0)) { StopMove(); owner->AttackGround(attackPos, (c.options & INTERNAL_ORDER) == 0, c.GetID() == CMD_MANUALFIRE); foundWeapon = true; } // for gunships, this pitches the nose down such that // TryTargetRotate (which also checks range for itself) // has a bigger chance of succeeding // // hence it must be called as soon as we get in range // and may not depend on what TryTargetRotate returns // (otherwise we might never get a firing solution) owner->moveType->KeepPointingTo(attackPos, owner->maxRange * 0.9f, true); } } } #if 0 // no weapons --> no need to stop at an arbitrary distance? else if (diff.SqLength2D() < 1024) { StopMove(); owner->moveType->KeepPointingTo(attackPos, owner->maxRange * 0.9f, true); } #endif // if we are unarmed and more than 10 elmos distant // from target position, then keeping moving closer if (owner->weapons.empty() && attackPos.SqDistance2D(goalPos) > 100) { SetGoal(attackPos, owner->pos); } }