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 CBuilderCAI::ExecuteCapture(Command& c) { // not all builders are capture-capable by default if (!owner->unitDef->canCapture) return; if (c.params.size() == 1 || c.params.size() == 5) { // capture unit CUnit* unit = unitHandler->GetUnit(c.params[0]); if (unit == NULL) { FinishCommand(); return; } if (c.params.size() == 5) { const float3& pos = c.GetPos(1); const float radius = c.params[4] + 100; // do not walk too far outside capture area if (((pos - unit->pos).SqLength2D() > (radius * radius) || (ownerBuilder->curCapture == unit && unit->IsMoving() && !IsInBuildRange(unit)))) { StopMove(); FinishCommand(); return; } } if (unit->unitDef->capturable && unit->team != owner->team && UpdateTargetLostTimer(unit->id)) { if (MoveInBuildRange(unit)) { ownerBuilder->SetCaptureTarget(unit); } } else { StopMove(); FinishCommand(); } } else if (c.params.size() == 4) { // area capture const float3 pos = c.GetPos(0); const float radius = c.params[3]; ownerBuilder->StopBuild(); if (FindCaptureTargetAndCapture(pos, radius, c.options, (c.options & META_KEY))) { inCommand = false; SlowUpdate(); return; } if (!(c.options & ALT_KEY)) { FinishCommand(); } } else { FinishCommand(); } }
void CBuilderCAI::GiveCommandReal(const Command& c, bool fromSynced) { if (!AllowedCommand(c, fromSynced)) return; // don't guard yourself if ((c.GetID() == CMD_GUARD) && (c.params.size() == 1) && ((int)c.params[0] == owner->id)) { return; } // stop building/reclaiming/... if the new command is not queued, i.e. replaces our current activity // FIXME should happen just before CMobileCAI::GiveCommandReal? (the new cmd can still be skipped!) if ((c.GetID() != CMD_WAIT && c.GetID() != CMD_SET_WANTED_MAX_SPEED) && !(c.options & SHIFT_KEY)) { if (nonQueingCommands.find(c.GetID()) == nonQueingCommands.end()) { building = false; static_cast<CBuilder*>(owner)->StopBuild(); } } if (buildOptions.find(c.GetID()) != buildOptions.end()) { if (c.params.size() < 3) return; BuildInfo bi; bi.pos = c.GetPos(0); if (c.params.size() == 4) bi.buildFacing = abs((int)c.params[3]) % NUM_FACINGS; bi.def = unitDefHandler->GetUnitDefByID(-c.GetID()); bi.pos = CGameHelper::Pos2BuildPos(bi, true); // We are a static building, check if the buildcmd is in range if (!owner->unitDef->canmove) { if (!IsInBuildRange(bi.pos, GetBuildOptionRadius(bi.def, c.GetID()))) { return; } } const CUnit* nanoFrame = NULL; // check if the buildpos is blocked if (IsBuildPosBlocked(bi, &nanoFrame)) return; // if it is a nanoframe help to finish it if (nanoFrame != NULL) { Command c2(CMD_REPAIR, c.options | INTERNAL_ORDER, nanoFrame->id); CMobileCAI::GiveCommandReal(c2, fromSynced); CMobileCAI::GiveCommandReal(c, fromSynced); return; } } else { if (c.GetID() < 0) return; } CMobileCAI::GiveCommandReal(c, fromSynced); }
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; } } }
void CBuilderCAI::ExecuteResurrect(Command& c) { // not all builders are resurrect-capable by default if (!owner->unitDef->canResurrect) return; if (c.params.size() == 1) { unsigned int id = (unsigned int) c.params[0]; if (id >= unitHandler->MaxUnits()) { // resurrect feature CFeature* feature = featureHandler->GetFeature(id - unitHandler->MaxUnits()); if (feature && feature->udef != NULL) { if (((c.options & INTERNAL_ORDER) && !(c.options & CONTROL_KEY) && IsFeatureBeingReclaimed(feature->id, owner)) || !ResurrectObject(feature)) { StopMove(); RemoveUnitFromResurrecters(owner); FinishCommand(); } else { AddUnitToResurrecters(owner); } } else { RemoveUnitFromResurrecters(owner); if (ownerBuilder->lastResurrected && unitHandler->GetUnitUnsafe(ownerBuilder->lastResurrected) != NULL && owner->unitDef->canRepair) { // resurrection finished, start repair (by overwriting the current order) c = Command(CMD_REPAIR, c.options | INTERNAL_ORDER, ownerBuilder->lastResurrected); ownerBuilder->lastResurrected = 0; inCommand = false; SlowUpdate(); return; } StopMove(); FinishCommand(); } } else { // resurrect unit RemoveUnitFromResurrecters(owner); FinishCommand(); } } else if (c.params.size() == 4) { // area resurrect const float3 pos = c.GetPos(0); const float radius = c.params[3]; if (FindResurrectableFeatureAndResurrect(pos, radius, c.options, (c.options & META_KEY))) { inCommand = false; SlowUpdate(); return; } if (!(c.options & ALT_KEY)) { FinishCommand(); } } else { // wrong number of parameters RemoveUnitFromResurrecters(owner); FinishCommand(); } }
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); } } }
bool CTransportCAI::AllowedCommand(const Command& c, bool fromSynced) { if (!CMobileCAI::AllowedCommand(c, fromSynced)) { return false; } switch (c.GetID()) { case CMD_UNLOAD_UNIT: case CMD_UNLOAD_UNITS: { const CTransportUnit* transport = static_cast<CTransportUnit*>(owner); const std::list<CTransportUnit::TransportedUnit>& transportees = transport->GetTransportedUnits(); // allow unloading empty transports for easier setup of transport bridges if (transportees.empty()) return true; if (c.GetParamsCount() == 5) { if (fromSynced) { // point transported buildings (...) in their wanted direction after unloading for (auto it = transportees.begin(); it != transportees.end(); ++it) { CBuilding* building = dynamic_cast<CBuilding*>(it->unit); if (building == NULL) continue; building->buildFacing = std::abs(int(c.GetParam(4))) % NUM_FACINGS; } } } if (c.GetParamsCount() >= 4) { // find unload positions for transportees (WHY can this run in unsynced context?) for (auto it = transportees.begin(); it != transportees.end(); ++it) { CUnit* u = it->unit; const float radius = (c.GetID() == CMD_UNLOAD_UNITS)? c.GetParam(3): 0.0f; const float spread = u->radius * transport->unitDef->unloadSpread; float3 foundPos; if (FindEmptySpot(c.GetPos(0), radius, spread, foundPos, u, fromSynced)) { return true; } // FIXME: support arbitrary unloading order for other unload types also if (unloadType != UNLOAD_LAND) { return false; } } // no empty spot found for any transported unit return false; } break; } } return true; }
static inline bool IsCommandInMap(const Command& c) { // TODO: // extend the check to commands for which // position is not stored in params[0..2] if (c.params.size() < 3) { return true; } if ((c.GetPos(0)).IsInBounds()) { return true; } const float3 pos = c.GetPos(0); LOG_L(L_DEBUG, "Dropped command %d: outside of map (x:%f y:%f z:%f)", c.GetID(), pos.x, pos.y, pos.z); return false; }
void CTransportCAI::UnloadDrop(Command& c) { // fly over and drop unit if (inCommand) { if (!owner->script->IsBusy()) { FinishCommand(); } } else { auto transport = static_cast<CTransportUnit*>(owner); const auto& transportees = transport->GetTransportedUnits(); if (transportees.empty() || dropSpots.empty()) { FinishCommand(); return; } float3 pos = c.GetPos(0); // head towards goal // note that HoverAirMoveType must be modified to allow // non-stop movement through goals for this to work well if (goalPos.SqDistance2D(pos) > Square(20.0f)) { SetGoal(lastDropPos = pos, owner->pos); } CHoverAirMoveType* am = dynamic_cast<CHoverAirMoveType*>(owner->moveType); CUnit* transportee = (transportees.front()).unit; if (am != NULL) { pos.y = CGround::GetHeightAboveWater(pos.x, pos.z); am->maxDrift = 1.0f; // if near target or passed it accidentally, drop unit if (owner->pos.SqDistance2D(pos) < Square(40.0f) || (((pos - owner->pos).Normalize()).SqDistance(owner->frontdir) > 0.25 && owner->pos.SqDistance2D(pos)< (205*205))) { am->SetAllowLanding(false); owner->script->EndTransport(); transport->DetachUnitFromAir(transportee, pos); dropSpots.pop_back(); if (dropSpots.empty()) { // move the transport away after last drop SetGoal(owner->pos + owner->frontdir * 200, owner->pos); } FinishCommand(); } } else { inCommand = true; StopMove(); owner->script->TransportDrop(transportee, pos); } } }
static inline bool IsCommandInMap(const Command& c) { // TODO: // extend the check to commands for which // position is not stored in params[0..2] if (c.params.size() >= 3) { return ((c.GetPos(0)).IsInBounds()); } return true; }
void CTransportCAI::UnloadUnits_Drop(Command& c, CTransportUnit* transport) { // called repeatedly for each unit till units are unloaded if (lastCall == gs->frameNum) { // avoid infinite loops return; } lastCall = gs->frameNum; const auto& transportees = transport->GetTransportedUnits(); if (transportees.empty()) { FinishCommand(); return; } bool canUnload = false; // at the start of each user command if (isFirstIteration) { dropSpots.clear(); startingDropPos = c.GetPos(0); approachVector = (startingDropPos - owner->pos).Normalize(); canUnload = FindEmptyDropSpots(startingDropPos, startingDropPos + approachVector * std::max(16.0f, c.params[3]), dropSpots); } else if (!dropSpots.empty() ) { // make sure we check current spot in front of us each // unload, take last landing pos as new start spot // pos = dropSpots.back(); canUnload = !dropSpots.empty(); } if (canUnload) { if (SpotIsClear(dropSpots.back(), (transportees.front()).unit)) { Command c2(CMD_UNLOAD_UNIT, c.options | INTERNAL_ORDER, dropSpots.back()); commandQue.push_front(c2); SlowUpdate(); isFirstIteration = false; return; } else { dropSpots.pop_back(); } } else { startingDropPos = -OnesVector; isFirstIteration = true; dropSpots.clear(); FinishCommand(); } }
CCommandQueue::iterator CCommandAI::GetCancelQueued(const Command& c, CCommandQueue& q) { CCommandQueue::iterator ci = q.end(); while (ci != q.begin()) { --ci; //iterate from the end and dont check the current order const Command& c2 = *ci; const int cmdID = c.GetID(); const int cmd2ID = c2.GetID(); const bool attackAndFight = (cmdID == CMD_ATTACK && cmd2ID == CMD_FIGHT && c2.params.size() == 1); if (c2.params.size() != c.params.size()) continue; if ((cmdID == cmd2ID) || (cmdID < 0 && cmd2ID < 0) || attackAndFight) { if (c.params.size() == 1) { // assume the param is a unit-ID or feature-ID if ((c2.params[0] == c.params[0]) && (cmd2ID != CMD_SET_WANTED_MAX_SPEED)) { return ci; } } else if (c.params.size() >= 3) { if (cmdID < 0) { BuildInfo bc1(c); BuildInfo bc2(c2); if (bc1.def == NULL) continue; if (bc2.def == NULL) continue; if (math::fabs(bc1.pos.x - bc2.pos.x) * 2 <= std::max(bc1.GetXSize(), bc2.GetXSize()) * SQUARE_SIZE && math::fabs(bc1.pos.z - bc2.pos.z) * 2 <= std::max(bc1.GetZSize(), bc2.GetZSize()) * SQUARE_SIZE) { return ci; } } else { // assume c and c2 are positional commands const float3& c1p = c.GetPos(0); const float3& c2p = c2.GetPos(0); if ((c1p - c2p).SqLength2D() >= (COMMAND_CANCEL_DIST * COMMAND_CANCEL_DIST)) continue; if ((c.options & SHIFT_KEY) != 0 && (c.options & INTERNAL_ORDER) != 0) continue; return ci; } } } } return q.end(); }
/** * @brief executes the move command */ void CMobileCAI::ExecuteMove(Command &c) { const float3 cmdPos = c.GetPos(0); if (cmdPos != goalPos) { SetGoal(cmdPos, owner->pos); } if ((owner->pos - goalPos).SqLength2D() < cancelDistance || owner->moveType->progressState == AMoveType::Failed) { FinishCommand(); } return; }
void CTransportCAI::UnloadUnits_Drop(Command& c, CTransportUnit* transport) { // called repeatedly for each unit till units are unloaded if (lastCall == gs->frameNum) { // avoid infinite loops return; } lastCall = gs->frameNum; if (static_cast<CTransportUnit*>(owner)->GetTransportedUnits().empty()) { FinishCommand(); return; } float3 pos = c.GetPos(0); float radius = c.params[3]; bool canUnload = false; // at the start of each user command if (isFirstIteration) { dropSpots.clear(); startingDropPos = pos; approachVector = startingDropPos - owner->pos; approachVector.Normalize(); canUnload = FindEmptyDropSpots(pos, pos + approachVector * std::max(16.0f,radius), dropSpots); } else if (!dropSpots.empty() ) { // make sure we check current spot infront of us each unload pos = dropSpots.back(); // take last landing pos as new start spot canUnload = !dropSpots.empty(); } if (canUnload) { if (SpotIsClear(dropSpots.back(), static_cast<CTransportUnit*>(owner)->GetTransportedUnits().front().unit)) { const float3 pos = dropSpots.back(); Command c2(CMD_UNLOAD_UNIT, c.options | INTERNAL_ORDER, pos); commandQue.push_front(c2); SlowUpdate(); isFirstIteration = false; return; } else { dropSpots.pop_back(); } } else { startingDropPos = float3(-1.0f, -1.0f, -1.0f); isFirstIteration = true; dropSpots.clear(); FinishCommand(); } }
void CBuilderCAI::ExecutePatrol(Command& c) { if (!owner->unitDef->canPatrol) return; if (c.params.size() < 3) { return; } Command temp(CMD_FIGHT, c.options|INTERNAL_ORDER, c.GetPos(0)); commandQue.push_back(c); commandQue.pop_front(); commandQue.push_front(temp); Command tmpC(CMD_PATROL); eoh->CommandFinished(*owner, tmpC); SlowUpdate(); }
static inline bool AdjustGroundAttackCommand(const Command& c, bool fromSynced, bool aiOrder) { if (c.params.size() < 3) return false; if (aiOrder) return false; const float3 cPos = c.GetPos(0); #if 0 // check if attack-ground is really attack-ground // // NOTE: // problematic if command contains value from UHM // but is evaluated in synced context against SHM // after roundtrip (when UHM and SHM differ a lot) // // instead just clamp the elevation, which creates // fewer issues overall (eg. artillery force-firing // at positions outside LOS where UHM and SHM do not // match will not be broken) // if (math::fabs(cPos.y - gHeight) > SQUARE_SIZE) { return false; } #else // FIXME: is fromSynced really sync-safe??? // NOTE: // uses gHeight = min(cPos.y, GetHeightAboveWater) instead // of gHeight = GetHeightReal because GuiTraceRay can stop // at water surface, so the attack-position would be moved // UNDERWATER and cause ground-attack orders to fail (this // SHOULD no longer happen, Weapon::AdjustTargetPosToWater // always forcibly adjusts positions to respect waterWeapon // now) // Command& cc = const_cast<Command&>(c); cc.params[1] = std::min(cPos.y, CGround::GetHeightAboveWater(cPos.x, cPos.z, true || fromSynced)); // cc.params[1] = CGround::GetHeightReal(cPos.x, cPos.z, true || fromSynced); return true; #endif }
/** * @brief Executes the Patrol command c */ void CMobileCAI::ExecutePatrol(Command &c) { assert(owner->unitDef->canPatrol); if (c.params.size() < 3) { LOG_L(L_ERROR, "Received a Patrol command with less than 3 params on %s in MobileCAI", owner->unitDef->humanName.c_str()); return; } Command temp(CMD_FIGHT, c.options | INTERNAL_ORDER, c.GetPos(0)); commandQue.push_back(c); commandQue.pop_front(); commandQue.push_front(temp); Command tmpC(CMD_PATROL); eoh->CommandFinished(*owner, tmpC); ExecuteFight(temp); }
void CTransportCAI::UnloadUnits_Land(Command& c, CTransportUnit* transport) { if (lastCall == gs->frameNum) { // avoid infinite loops return; } lastCall = gs->frameNum; if (static_cast<CTransportUnit*>(owner)->GetTransportedUnits().empty()) { FinishCommand(); return; } bool canUnload = false; float3 unloadPos; CUnit* u = NULL; const CTransportUnit* ownerTrans = static_cast<CTransportUnit*>(owner); const std::list<CTransportUnit::TransportedUnit>& transpunits = ownerTrans->GetTransportedUnits(); for(std::list<CTransportUnit::TransportedUnit>::const_iterator it = transpunits.begin(); it != transpunits.end(); ++it) { u = it->unit; const float3 pos = c.GetPos(0); const float radius = c.params[3]; const float spread = u->radius * ownerTrans->unitDef->unloadSpread; canUnload = FindEmptySpot(pos, radius, spread, unloadPos, u); if (canUnload) { break; } } if (canUnload) { Command c2(CMD_UNLOAD_UNIT, c.options | INTERNAL_ORDER, unloadPos); c2.PushParam(u->id); commandQue.push_front(c2); SlowUpdate(); } else { FinishCommand(); } }
void CTransportCAI::UnloadUnits_LandFlood(Command& c, CTransportUnit* transport) { if (lastCall == gs->frameNum) { // avoid infinite loops return; } lastCall = gs->frameNum; const auto& transportees = transport->GetTransportedUnits(); if (transportees.empty()) { FinishCommand(); return; } float3 pos = c.GetPos(0); float3 found; const CUnit* transportee = (transportees.front()).unit; const float radius = c.params[3]; const float spread = transportee->radius * transport->unitDef->unloadSpread; const bool canUnload = FindEmptySpot(pos, radius, spread, found, transportee); if (canUnload) { Command c2(CMD_UNLOAD_UNIT, c.options | INTERNAL_ORDER, found); commandQue.push_front(c2); if (isFirstIteration) { Command c1(CMD_MOVE, c.options | INTERNAL_ORDER, pos); commandQue.push_front(c1); startingDropPos = pos; } SlowUpdate(); return; } FinishCommand(); }
void CTransportCAI::UnloadUnits_Land(Command& c, CTransportUnit* transport) { if (lastCall == gs->frameNum) { // avoid infinite loops return; } lastCall = gs->frameNum; const std::list<CTransportUnit::TransportedUnit>& transportees = transport->GetTransportedUnits(); if (transportees.empty()) { FinishCommand(); return; } CUnit* transportee = NULL; float3 unloadPos; for (auto it = transportees.begin(); it != transportees.end(); ++it) { const float3 pos = c.GetPos(0); const float radius = c.params[3]; const float spread = (it->unit)->radius * transport->unitDef->unloadSpread; if (FindEmptySpot(pos, radius, spread, unloadPos, it->unit)) { transportee = it->unit; break; } } if (transportee != NULL) { Command c2(CMD_UNLOAD_UNIT, c.options | INTERNAL_ORDER, unloadPos); c2.PushParam(transportee->id); commandQue.push_front(c2); SlowUpdate(); } else { FinishCommand(); } }
void CBuilderCAI::ExecuteRepair(Command& c) { // not all builders are repair-capable by default if (!owner->unitDef->canRepair) return; CBuilder* builder = (CBuilder*) owner; if (c.params.size() == 1 || c.params.size() == 5) { // repair unit CUnit* unit = uh->GetUnit(c.params[0]); if (unit == NULL) { FinishCommand(); return; } if (tempOrder && owner->moveState == MOVESTATE_MANEUVER) { // limit how far away we go if (LinePointDist(commandPos1, commandPos2, unit->pos) > 500) { StopMove(); FinishCommand(); return; } } if (c.params.size() == 5) { const float3 pos(c.params[1], c.params[2], c.params[3]); const float radius = c.params[4] + 100.0f; // do not walk too far outside repair area if ((pos - unit->pos).SqLength2D() > radius * radius || (unit->isMoving && (((c.options & INTERNAL_ORDER) && !TargetInterceptable(unit, unit->speed.Length2D())) || builder->curBuild == unit) && !IsInBuildRange(unit))) { StopMove(); FinishCommand(); return; } } // do not consider units under construction irreparable // even if they can be repaired bool canRepairUnit = true; canRepairUnit &= ((unit->beingBuilt) || (unit->unitDef->repairable && (unit->health < unit->maxHealth))); canRepairUnit &= ((unit != owner) || owner->unitDef->canSelfRepair); canRepairUnit &= (!unit->soloBuilder || (unit->soloBuilder == owner)); canRepairUnit &= (!(c.options & INTERNAL_ORDER) || (c.options & CONTROL_KEY) || !IsUnitBeingReclaimed(unit, owner)); canRepairUnit &= (UpdateTargetLostTimer(unit->id) != 0); if (canRepairUnit) { if (MoveInBuildRange(unit)) { builder->SetRepairTarget(unit); } } else { StopMove(); FinishCommand(); } } else if (c.params.size() == 4) { // area repair const float3 pos = c.GetPos(0); const float radius = c.params[3]; builder->StopBuild(); if (FindRepairTargetAndRepair(pos, radius, c.options, false, (c.options & META_KEY))) { inCommand = false; SlowUpdate(); return; } if (!(c.options & ALT_KEY)) { FinishCommand(); } } else { FinishCommand(); } return; }
void CBuilderCAI::ExecuteReclaim(Command& c) { CBuilder* builder = (CBuilder*) owner; // not all builders are reclaim-capable by default if (!owner->unitDef->canReclaim) return; if (c.params.size() == 1 || c.params.size() == 5) { const int signedId = (int) c.params[0]; if (signedId < 0) { LOG_L(L_WARNING, "Trying to reclaim unit or feature with id < 0 (%i), aborting.", signedId); return; } const unsigned int uid = signedId; //FIXME add a per-unit solution to better balance the load? const bool checkForBetterTarget = (gs->frameNum % (5 * UNIT_SLOWUPDATE_RATE)) < UNIT_SLOWUPDATE_RATE; if (checkForBetterTarget && (c.options & INTERNAL_ORDER) && (c.params.size() >= 5)) { // regular check if there is a closer reclaim target CSolidObject* obj; if (uid >= uh->MaxUnits()) { obj = featureHandler->GetFeature(uid - uh->MaxUnits()); } else { obj = uh->GetUnit(uid); } if (obj) { const float3 pos(c.params[1], c.params[2], c.params[3]); const float radius = c.params[4]; const float curdist = pos.SqDistance2D(obj->pos); const bool recUnits = !!(c.options & META_KEY); const bool recEnemyOnly = (c.options & META_KEY) && (c.options & CONTROL_KEY); const bool recSpecial = !!(c.options & CONTROL_KEY); ReclaimOption recopt = REC_NORESCHECK; if (recUnits) recopt |= REC_UNITS; if (recEnemyOnly) recopt |= REC_ENEMYONLY; if (recSpecial) recopt |= REC_SPECIAL; const int rid = FindReclaimTarget(pos, radius, c.options, recopt, curdist); if ((rid > 0) && (rid != uid)) { FinishCommand(); RemoveUnitFromReclaimers(owner); RemoveUnitFromFeatureReclaimers(owner); return; } } } if (uid >= uh->MaxUnits()) { // reclaim feature CFeature* feature = featureHandler->GetFeature(uid - uh->MaxUnits()); if (feature != NULL) { bool featureBeingResurrected = IsFeatureBeingResurrected(feature->id, owner); featureBeingResurrected &= (c.options & INTERNAL_ORDER) && !(c.options & CONTROL_KEY); if (featureBeingResurrected || !ReclaimObject(feature)) { StopMove(); FinishCommand(); RemoveUnitFromFeatureReclaimers(owner); } else { AddUnitToFeatureReclaimers(owner); } } else { StopMove(); FinishCommand(); RemoveUnitFromFeatureReclaimers(owner); } RemoveUnitFromReclaimers(owner); } else { // reclaim unit CUnit* unit = uh->GetUnit(uid); if (unit != NULL && c.params.size() == 5) { const float3 pos(c.params[1], c.params[2], c.params[3]); const float radius = c.params[4] + 100.0f; // do not walk too far outside reclaim area const bool outOfReclaimRange = (pos.SqDistance2D(unit->pos) > radius * radius) || (builder->curReclaim == unit && unit->isMoving && !IsInBuildRange(unit)); const bool busyAlliedBuilder = unit->unitDef->builder && !unit->commandAI->commandQue.empty() && teamHandler->Ally(owner->allyteam, unit->allyteam); if (outOfReclaimRange || busyAlliedBuilder) { StopMove(); RemoveUnitFromReclaimers(owner); FinishCommand(); RemoveUnitFromFeatureReclaimers(owner); return; } } if (unit != NULL && unit != owner && unit->unitDef->reclaimable && UpdateTargetLostTimer(unit->id) && unit->AllowedReclaim(owner)) { if (!ReclaimObject(unit)) { StopMove(); FinishCommand(); } else { AddUnitToReclaimers(owner); } } else { RemoveUnitFromReclaimers(owner); FinishCommand(); } RemoveUnitFromFeatureReclaimers(owner); } } else if (c.params.size() == 4) { // area reclaim const float3 pos = c.GetPos(0); const float radius = c.params[3]; const bool recUnits = !!(c.options & META_KEY); const bool recEnemyOnly = (c.options & META_KEY) && (c.options & CONTROL_KEY); const bool recSpecial = !!(c.options & CONTROL_KEY); RemoveUnitFromReclaimers(owner); RemoveUnitFromFeatureReclaimers(owner); builder->StopBuild(); ReclaimOption recopt = REC_NORESCHECK; if (recUnits) recopt |= REC_UNITS; if (recEnemyOnly) recopt |= REC_ENEMYONLY; if (recSpecial) recopt |= REC_SPECIAL; if (FindReclaimTargetAndReclaim(pos, radius, c.options, recopt)) { inCommand = false; SlowUpdate(); return; } if(!(c.options & ALT_KEY)){ FinishCommand(); } } else { // wrong number of parameters RemoveUnitFromReclaimers(owner); RemoveUnitFromFeatureReclaimers(owner); FinishCommand(); } }
void CBuilderCAI::ExecuteFight(Command& c) { assert((c.options & INTERNAL_ORDER) || owner->unitDef->canFight); CBuilder* builder = (CBuilder*) owner; if (tempOrder) { tempOrder = false; inCommand = true; } if (c.params.size() < 3) { // this shouldnt happen but anyway ... LOG_L(L_ERROR, "Received a Fight command with less than 3 params on %s in BuilderCAI", owner->unitDef->humanName.c_str()); return; } if (c.params.size() >= 6) { if (!inCommand) { commandPos1 = float3(c.params[3], c.params[4], c.params[5]); } } else { // Some hackery to make sure the line (commandPos1,commandPos2) is NOT // rotated (only shortened) if we reach this because the previous return // fight command finished by the 'if((curPos-pos).SqLength2D()<(64*64)){' // condition, but is actually updated correctly if you click somewhere // outside the area close to the line (for a new command). commandPos1 = ClosestPointOnLine(commandPos1, commandPos2, owner->pos); if (f3SqDist(owner->pos, commandPos1) > (96 * 96)) { commandPos1 = owner->pos; } } float3 pos = c.GetPos(0); if (!inCommand) { inCommand = true; commandPos2 = pos; } float3 curPosOnLine = ClosestPointOnLine(commandPos1, commandPos2, owner->pos); if (c.params.size() >= 6) { pos = curPosOnLine; } if (pos != goalPos) { SetGoal(pos, owner->pos); } const bool resurrectMode = !!(c.options & ALT_KEY); const bool reclaimEnemyMode = !!(c.options & META_KEY); const bool reclaimEnemyOnlyMode = (c.options & CONTROL_KEY) && (c.options & META_KEY); ReclaimOption recopt; if (resurrectMode) recopt |= REC_NONREZ; if (reclaimEnemyMode) recopt |= REC_ENEMY; if (reclaimEnemyOnlyMode) recopt |= REC_ENEMYONLY; const float searchRadius = (owner->immobile ? 0 : (300 * owner->moveState)) + builder->buildDistance; if (!reclaimEnemyOnlyMode && (owner->unitDef->canRepair || owner->unitDef->canAssist) && // Priority 1: Repair FindRepairTargetAndRepair(curPosOnLine, searchRadius, c.options, true, resurrectMode)){ tempOrder = true; inCommand = false; if (lastPC1 != gs->frameNum) { //avoid infinite loops lastPC1 = gs->frameNum; SlowUpdate(); } return; } if (!reclaimEnemyOnlyMode && resurrectMode && owner->unitDef->canResurrect && // Priority 2: Resurrect (optional) FindResurrectableFeatureAndResurrect(curPosOnLine, searchRadius, c.options, false)) { tempOrder = true; inCommand = false; if (lastPC2 != gs->frameNum) { //avoid infinite loops lastPC2 = gs->frameNum; SlowUpdate(); } return; } if (owner->unitDef->canReclaim && // Priority 3: Reclaim / reclaim non resurrectable (optional) / reclaim enemy units (optional) FindReclaimTargetAndReclaim(curPosOnLine, searchRadius, c.options, recopt)) { tempOrder = true; inCommand = false; if (lastPC3 != gs->frameNum) { //avoid infinite loops lastPC3 = gs->frameNum; SlowUpdate(); } return; } if (f3SqDist(owner->pos, pos) < (64*64)) { FinishCommand(); return; } if(owner->haveTarget && owner->moveType->progressState!=AMoveType::Done){ StopMove(); } else if(owner->moveType->progressState!=AMoveType::Active){ owner->moveType->StartMoving(goalPos, 8); } return; }
void CBuilderCAI::GiveCommandReal(const Command& c, bool fromSynced) { if (!AllowedCommand(c, fromSynced)) return; // don't guard yourself if ((c.GetID() == CMD_GUARD) && (c.params.size() == 1) && ((int)c.params[0] == owner->id)) { return; } // stop current build if the new command is a not queued and replaces the current buidcmd //FIXME should happen just before CMobileCAI::GiveCommandReal? (the new cmd can still be skipped!) if (!(c.options & SHIFT_KEY) && nonQueingCommands.find(c.GetID()) == nonQueingCommands.end() && c.GetID() != CMD_WAIT) { building = false; ((CBuilder*) owner)->StopBuild(); } map<int,string>::iterator boi = buildOptions.find(c.GetID()); if (boi != buildOptions.end()) { if (c.params.size() < 3) { return; } BuildInfo bi; bi.pos = c.GetPos(0); if (c.params.size() == 4) bi.buildFacing = abs((int)c.params[3]) % NUM_FACINGS; bi.def = unitDefHandler->GetUnitDefByName(boi->second); bi.pos = helper->Pos2BuildPos(bi, true); // We are a static building, check if the buildcmd is in range if (!owner->unitDef->canmove) { const float radius = GetBuildOptionRadius(bi.def, c.GetID()); if (!IsInBuildRange(bi.pos, radius)) { return; } } // check if the buildpos is blocked if it is a nanoframe help to finish it //FIXME finish it just if it is of the same unitdef? CFeature* feature = NULL; if (!uh->TestUnitBuildSquare(bi, feature, owner->allyteam, true)) { if (!feature && owner->unitDef->canAssist) { const int yardxpos = int(bi.pos.x + 4) / SQUARE_SIZE; const int yardypos = int(bi.pos.z + 4) / SQUARE_SIZE; const CSolidObject* s = groundBlockingObjectMap->GroundBlocked(yardxpos, yardypos); const CUnit* u = dynamic_cast<const CUnit*>(s); if ( (u != NULL) && u->beingBuilt && (u->buildProgress == 0.0f) && (!u->soloBuilder || (u->soloBuilder == owner)) ) { Command c2(CMD_REPAIR, c.options | INTERNAL_ORDER); c2.params.push_back(u->id); CMobileCAI::GiveCommandReal(c2); CMobileCAI::GiveCommandReal(c); } } return; } } CMobileCAI::GiveCommandReal(c); }
void CSelectedUnitsHandlerAI::SelectAttack(const Command& cmd, int player) { std::vector<int> targets; if (cmd.params.size() == 4) { SelectCircleUnits(cmd.GetPos(0), cmd.params[3], player, targets); } else { SelectRectangleUnits(cmd.GetPos(0), cmd.GetPos(3), player, targets); } if (targets.empty()) return; const bool queueing = !!(cmd.options & SHIFT_KEY); const std::vector<int>& selected = selectedUnitsHandler.netSelected[player]; const unsigned int targetsCount = targets.size(); const unsigned int selectedCount = selected.size(); if (selectedCount == 0) return; Command attackCmd(CMD_ATTACK, cmd.options, 0.0f); // delete the attack commands and bail for CONTROL_KEY if (cmd.options & CONTROL_KEY) { attackCmd.options |= SHIFT_KEY; for (unsigned int s = 0; s < selectedCount; s++) { const CUnit* unit = unitHandler->GetUnit( selected[s] ); if (unit == nullptr) continue; CCommandAI* commandAI = unit->commandAI; for (unsigned int t = 0; t < targetsCount; t++) { attackCmd.params[0] = targets[t]; if (commandAI->WillCancelQueued(attackCmd)) { commandAI->GiveCommand(attackCmd, false); } } } return; } // get the group center float3 midPos; unsigned int realCount = 0; for (unsigned int s = 0; s < selectedCount; s++) { CUnit* unit = unitHandler->GetUnit(selected[s]); if (unit == nullptr) continue; if (queueing) { midPos += LastQueuePosition(unit); } else { midPos += unit->midPos; } realCount++; } if (realCount == 0) return; midPos /= realCount; // sort the targets std::vector<DistInfo> distVec; for (unsigned int t = 0; t < targetsCount; t++) { const CUnit* unit = unitHandler->GetUnit( targets[t] ); const float3 unitPos = float3(unit->midPos); DistInfo di; di.unitID = targets[t]; di.dist = (unitPos - midPos).SqLength2D(); distVec.push_back(di); } stable_sort(distVec.begin(), distVec.end()); // give the commands for (unsigned int s = 0; s < selectedCount; s++) { if (!queueing) { // clear it for the first command attackCmd.options &= ~SHIFT_KEY; } CUnit* unit = unitHandler->GetUnit(selected[s]); if (unit == nullptr) continue; CCommandAI* commandAI = unit->commandAI; for (unsigned t = 0; t < targetsCount; t++) { attackCmd.params[0] = distVec[t].unitID; if (!queueing || !commandAI->WillCancelQueued(attackCmd)) { commandAI->GiveCommand(attackCmd, false); AddUnitSetMaxSpeedCommand(unit, attackCmd.options); // following commands are always queued attackCmd.options |= SHIFT_KEY; } } } }
void CSelectedUnitsAI::SelectAttack(const Command& cmd, int player) { std::vector<int> targets; const float3& pos0 = cmd.GetPos(0); if (cmd.params.size() == 4) { SelectCircleUnits(pos0, cmd.params[3], targets, player); } else { const float3& pos1 = cmd.GetPos(3); SelectRectangleUnits(pos0, pos1, targets, player); } if (targets.empty()) { return; } const int targetsCount = (int)targets.size(); const std::vector<int>& selected = selectedUnits.netSelected[player]; const int selectedCount = (int)selected.size(); if (selectedCount <= 0) { return; } Command attackCmd(CMD_ATTACK, cmd.options, 0.0f); // delete the attack commands and bail for CONTROL_KEY if (cmd.options & CONTROL_KEY) { attackCmd.options |= SHIFT_KEY; for (int s = 0; s < selectedCount; s++) { const CUnit* unit = uh->units[selected[s]]; if (unit == NULL) { continue; } CCommandAI* commandAI = uh->units[selected[s]]->commandAI; for (int t = 0; t < targetsCount; t++) { attackCmd.params[0] = targets[t]; if (commandAI->WillCancelQueued(attackCmd)) { commandAI->GiveCommand(attackCmd, false); } } } return; } const bool queueing = !!(cmd.options & SHIFT_KEY); // get the group center float3 midPos(ZeroVector); int realCount = 0; for (int s = 0; s < selectedCount; s++) { CUnit* unit = uh->units[selected[s]]; if (unit == NULL) { continue; } if (queueing) { midPos += LastQueuePosition(unit); } else { midPos += unit->midPos; } realCount++; } if (realCount <= 0) { return; } midPos /= (float)realCount; // sort the targets std::vector<DistInfo> distVec; int t; for (t = 0; t < targetsCount; t++) { DistInfo di; di.unitID = targets[t]; const CUnit* unit = uh->units[di.unitID]; const float3 unitPos = queueing ? LastQueuePosition(unit) : float3(unit->midPos); di.dist = (unitPos - midPos).SqLength2D(); distVec.push_back(di); } sort(distVec.begin(), distVec.end()); // give the commands for (int s = 0; s < selectedCount; s++) { if (!queueing) { // clear it for the first command attackCmd.options &= ~SHIFT_KEY; } CUnit* unit = uh->units[selected[s]]; if (unit == NULL) { continue; } CCommandAI* commandAI = unit->commandAI; for (t = 0; t < targetsCount; t++) { attackCmd.params[0] = distVec[t].unitID; if (!queueing || !commandAI->WillCancelQueued(attackCmd)) { commandAI->GiveCommand(attackCmd, false); AddUnitSetMaxSpeedCommand(unit, attackCmd.options); // following commands are always queued attackCmd.options |= SHIFT_KEY; } } } }
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); } }
/** * @brief Executes the Fight command c */ void CMobileCAI::ExecuteFight(Command& c) { assert((c.options & INTERNAL_ORDER) || owner->unitDef->canFight); if (c.params.size() == 1 && !owner->weapons.empty()) { CWeapon* w = owner->weapons.front(); if ((orderTarget != NULL) && !w->AttackUnit(orderTarget, false)) { CUnit* newTarget = CGameHelper::GetClosestValidTarget(owner->pos, owner->maxRange, owner->allyteam, this); if ((newTarget != NULL) && w->AttackUnit(newTarget, false)) { c.params[0] = newTarget->id; inCommand = false; } else { w->AttackUnit(orderTarget, false); } } ExecuteAttack(c); return; } if (tempOrder) { inCommand = true; tempOrder = false; } if (c.params.size() < 3) { LOG_L(L_ERROR, "Received a Fight command with less than 3 params on %s in MobileCAI", owner->unitDef->humanName.c_str()); return; } if (c.params.size() >= 6) { if (!inCommand) { commandPos1 = c.GetPos(3); } } else { // Some hackery to make sure the line (commandPos1,commandPos2) is NOT // rotated (only shortened) if we reach this because the previous return // fight command finished by the 'if((curPos-pos).SqLength2D()<(64*64)){' // condition, but is actually updated correctly if you click somewhere // outside the area close to the line (for a new command). commandPos1 = ClosestPointOnLine(commandPos1, commandPos2, owner->pos); if ((owner->pos - commandPos1).SqLength2D() > (96 * 96)) { commandPos1 = owner->pos; } } float3 pos = c.GetPos(0); if (!inCommand) { inCommand = true; commandPos2 = pos; lastUserGoal = commandPos2; } if (c.params.size() >= 6) { pos = ClosestPointOnLine(commandPos1, commandPos2, owner->pos); } if (pos != goalPos) { SetGoal(pos, owner->pos); } if (owner->unitDef->canAttack && owner->fireState >= FIRESTATE_FIREATWILL && !owner->weapons.empty()) { const float3 curPosOnLine = ClosestPointOnLine(commandPos1, commandPos2, owner->pos); const float searchRadius = owner->maxRange + 100 * owner->moveState * owner->moveState; CUnit* enemy = CGameHelper::GetClosestValidTarget(curPosOnLine, searchRadius, owner->allyteam, this); if (enemy != NULL) { PushOrUpdateReturnFight(); // make the attack-command inherit <c>'s options // NOTE: see AirCAI::ExecuteFight why we do not set INTERNAL_ORDER Command c2(CMD_ATTACK, c.options, enemy->id); commandQue.push_front(c2); inCommand = false; tempOrder = true; // avoid infinite loops (?) if (lastPC != gs->frameNum) { lastPC = gs->frameNum; SlowUpdate(); } return; } } if ((owner->pos - goalPos).SqLength2D() < (64 * 64) || (owner->moveType->progressState == AMoveType::Failed)){ FinishCommand(); } }
void CTransportCAI::UnloadLandFlood(Command& c) { //land, then release all units at once CTransportUnit* transport = static_cast<CTransportUnit*>(owner); CUnit* transportee = NULL; if (inCommand) { if (!owner->script->IsBusy()) { FinishCommand(); } } else { const std::list<CTransportUnit::TransportedUnit>& transportees = transport->GetTransportedUnits(); if (transportees.empty()) { FinishCommand(); return; } if (c.params.size() < 4) { transportee = (transportees.front()).unit; } else { const int unitID = c.params[3]; for (auto it = transportees.begin(); it != transportees.end(); ++it) { CUnit* carried = it->unit; if (unitID == carried->id) { transportee = carried; break; } } if (transportee == NULL) { FinishCommand(); return; } } // move to position float3 pos = c.GetPos(0); if (isFirstIteration) { if (goalPos.SqDistance2D(pos) > 400) { SetGoal(startingDropPos, owner->pos); } } if (startingDropPos.SqDistance2D(owner->pos) < Square(owner->unitDef->loadingRadius * 0.9f)) { CHoverAirMoveType* am = dynamic_cast<CHoverAirMoveType*>(owner->moveType); if (am != NULL) { // lower to ground startingDropPos.y = CGround::GetHeightAboveWater(startingDropPos.x, startingDropPos.z); const float3 wantedPos = startingDropPos + UpVector * transportee->model->height; SetGoal(wantedPos, owner->pos); am->SetWantedAltitude(1.0f); am->SetAllowLanding(true); am->maxDrift = 1.0f; // when on our way down start animations for unloading gear if (isFirstIteration) { owner->script->StartUnload(); } isFirstIteration = false; // once at ground if (owner->pos.y - CGround::GetHeightAboveWater(wantedPos.x, wantedPos.z) < SQUARE_SIZE) { // nail it to the ground before it tries jumping up, only to land again... am->SetState(am->AIRCRAFT_LANDED); // call this so that other animations such as opening doors may be started owner->script->TransportDrop((transportees.front()).unit, pos); transport->DetachUnitFromAir(transportee, pos); FinishCommand(); if (transportees.empty()) { am->SetAllowLanding(true); owner->script->EndTransport(); am->UpdateLanded(); } } } else { // land transports inCommand = true; isFirstIteration = false; StopMove(); owner->script->TransportDrop((transportees.front()).unit, pos); transport->DetachUnitFromAir(transportee, pos); FinishCommand(); if (transportees.empty()) { owner->script->EndTransport(); } } } } }