void CPlasmaRepulser::NewProjectile(CWeaponProjectile* p) { if (weaponDef->smartShield && teamHandler->AlliedTeams(p->owner()->team, owner->team)) { return; } float3 dir = p->speed; if (p->targetPos != ZeroVector) { dir = p->targetPos - p->pos; // assume that it will travel roughly in the direction of the targetpos if it have one } dir.y = 0.0f; dir.SafeNormalize(); const float3 dif = owner->pos - p->pos; if (weaponDef->exteriorShield && (dif.SqLength() < sqRadius)) { return; } const float closeLength = std::max(0.0f, dif.dot(dir)); const float3 closeVect = dif - dir * closeLength; const float closeDist = closeVect.SqLength2D(); // TODO: this isn't good enough in case shield is mounted on a mobile unit, // and this unit moves relatively fast compared to the projectile. // it should probably be: radius + closeLength / |projectile->speed| * |owner->speed|, // but this still doesn't solve anything for e.g. teleporting shields. if (closeDist < Square(radius * 1.5f)) { incomingProjectiles[p->id] = p; AddDeathDependence(p, DEPENDENCE_REPULSED); } }
void CFeature::DoDamage(const DamageArray& damages, const float3& impulse, CUnit*, int) { if (damages.paralyzeDamageTime) { return; // paralyzers do not damage features } residualImpulse = impulse; health -= damages[0]; if (health <= 0 && def->destructable) { CFeature* deathFeature = featureHandler->CreateWreckage( pos, def->deathFeature, heading, buildFacing, 1, team, -1, false, NULL ); if (deathFeature) { // if a partially reclaimed corpse got blasted, // ensure its wreck is not worth the full amount // (which might be more than the amount remaining) deathFeature->reclaimLeft = reclaimLeft; } featureHandler->DeleteFeature(this); blockHeightChanges = false; if (def->drawType >= DRAWTYPE_TREE) { if (impulse.SqLength2D() > 0.25f) { treeDrawer->AddFallingTree(pos, impulse, def->drawType - 1); } } } }
float3 CCannon::GetWantedDir(const float3& diff) { // try to cache results, sacrifice some (not much too much even for a pewee) accuracy // it saves a dozen or two expensive calculations per second when 5 guardians // are shooting at several slow- and fast-moving targets if (fabs(diff.x - lastDiff.x) < (SQUARE_SIZE / 4.0f) && fabs(diff.y - lastDiff.y) < (SQUARE_SIZE / 4.0f) && fabs(diff.z - lastDiff.z) < (SQUARE_SIZE / 4.0f)) { return lastDir; } const float Dsq = diff.SqLength(); const float DFsq = diff.SqLength2D(); const float& g = gravity; const float& v = projectileSpeed; const float dy = diff.y; const float dxz = math::sqrt(DFsq); float Vxz = 0.0f; float Vy = 0.0f; if (Dsq == 0.0f) { Vy = highTrajectory ? v : -v; } else { // FIXME: temporary safeguards against FP overflow // (introduced by extreme off-map unit positions; the term // DFsq * Dsq * ... * dy should never even approach 1e38) if (Dsq < 1e12f && fabs(dy) < 1e6f) { const float root1 = v*v*v*v + 2.0f*v*v*g*dy - g*g*DFsq; if (root1 >= 0.0f) { const float root2 = 2.0f * DFsq * Dsq * (v * v + g * dy + (highTrajectory ? -1.0f : 1.0f) * math::sqrt(root1)); if (root2 >= 0.0f) { Vxz = math::sqrt(root2) / (2.0f * Dsq); Vy = (dxz == 0.0f || Vxz == 0.0f) ? v : (Vxz * dy / dxz - dxz * g / (2.0f * Vxz)); } } } } float3 dir = ZeroVector; if (Vxz != 0.0f || Vy != 0.0f) { dir.x = diff.x; dir.z = diff.z; dir.SafeNormalize(); dir *= Vxz; dir.y = Vy; dir.SafeNormalize(); lastDiff = diff; lastDir = dir; } return dir; }
float3 CCannon::GetWantedDir(const float3& diff) { // try to cache results, sacrifice some (not much too much even for a pewee) accuracy // it saves a dozen or two expensive calculations per second when 5 guardians // are shooting at several slow- and fast-moving targets if (fabs(diff.x-lastDiff.x) < SQUARE_SIZE/4.f && fabs(diff.y-lastDiff.y) < SQUARE_SIZE/4.f && fabs(diff.z-lastDiff.z) < SQUARE_SIZE/4.f) { return lastDir; } float Dsq = diff.SqLength(); float DFsq = diff.SqLength2D(); float g = gravity; float v = projectileSpeed; float dy = diff.y; float dxz = sqrt(DFsq); float Vxz; float Vy; if(Dsq == 0) { Vxz = 0; Vy = highTrajectory ? v : -v; } else { float root1 = v*v*v*v + 2*v*v*g*dy-g*g*DFsq; if(root1 >= 0) { float root2 = 2*DFsq*Dsq*(v*v + g*dy + (highTrajectory ? -1 : 1) * sqrt(root1)); if(root2 >= 0) { Vxz = sqrt(root2)/(2*Dsq); Vy = (dxz == 0 || Vxz == 0) ? v : (Vxz*dy/dxz - dxz*g/(2*Vxz)); } else { Vxz = 0; Vy = 0; } } else { Vxz = 0; Vy = 0; } } float3 dir(diff.x, 0, diff.z); dir.SafeNormalize(); dir *= Vxz; dir.y = Vy; dir.SafeNormalize(); lastDiff = diff; lastDir = dir; return dir; }
float3 CCannon::GetWantedDir2(const float3& diff) const { const float Dsq = diff.SqLength(); const float DFsq = diff.SqLength2D(); const float g = gravity; const float v = projectileSpeed; const float dy = diff.y; const float dxz = math::sqrt(DFsq); float Vxz = 0.0f; float Vy = 0.0f; if (Dsq == 0.0f) { Vy = highTrajectory ? v : -v; } else { // FIXME: temporary safeguards against FP overflow // (introduced by extreme off-map unit positions; the term // DFsq * Dsq * ... * dy should never even approach 1e38) if (Dsq < 1e12f && math::fabs(dy) < 1e6f) { const float root1 = v*v*v*v + 2.0f*v*v*g*dy - g*g*DFsq; if (root1 >= 0.0f) { const float root2 = 2.0f * DFsq * Dsq * (v * v + g * dy + (highTrajectory ? -1.0f : 1.0f) * math::sqrt(root1)); if (root2 >= 0.0f) { Vxz = math::sqrt(root2) / (2.0f * Dsq); Vy = (dxz == 0.0f || Vxz == 0.0f) ? v : (Vxz * dy / dxz - dxz * g / (2.0f * Vxz)); } } } } float3 dir = ZeroVector; if (Vxz != 0.0f || Vy != 0.0f) { dir.x = diff.x; dir.z = diff.z; dir.SafeNormalize(); dir *= Vxz; dir.y = Vy; dir.SafeNormalize(); } return dir; }
void CPlasmaRepulser::NewProjectile(CWeaponProjectile* p) { // PlasmaRepulser instances are created if type == "Shield", // but projectiles are removed from incomingProjectiles only // if def->interceptor || def->isShield --> dangling pointers // due to isShield being a separate tag (in 91.0) assert(weaponDef->isShield); if (weaponDef->smartShield && p->owner() && teamHandler->AlliedTeams(p->owner()->team, owner->team)) { return; } float3 dir = p->speed; if (p->GetTargetPos() != ZeroVector) { // assume projectile will travel roughly in the direction of its targetpos dir = p->GetTargetPos() - p->pos; } dir.y = 0.0f; dir.SafeNormalize(); const float3 dif = owner->pos - p->pos; if (weaponDef->exteriorShield && (dif.SqLength() < sqRadius)) { return; } const float closeLength = std::max(0.0f, dif.dot(dir)); const float3 closeVect = dif - dir * closeLength; const float closeDist = closeVect.SqLength2D(); // TODO: this isn't good enough in case shield is mounted on a mobile unit, // and this unit moves relatively fast compared to the projectile. // it should probably be: radius + closeLength / |projectile->speed| * |owner->speed|, // but this still doesn't solve anything for e.g. teleporting shields. if (closeDist < Square(radius * 1.5f)) { incomingProjectiles[p->id] = p; AddDeathDependence(p, DEPENDENCE_REPULSED); } }
void CInterceptHandler::Update(bool forced) { if (((gs->frameNum % UNIT_SLOWUPDATE_RATE) != 0) && !forced) return; for (CWeapon* w: interceptors) { const WeaponDef* wDef = w->weaponDef; const CUnit* wOwner = w->owner; assert(wDef->interceptor || wDef->isShield); for (CWeaponProjectile* p: interceptables) { if (!p->CanBeInterceptedBy(wDef)) continue; if (w->incomingProjectiles.find(p->id) != w->incomingProjectiles.end()) continue; const int pAllyTeam = p->GetAllyteamID(); if (teamHandler->IsValidAllyTeam(pAllyTeam) && teamHandler->Ally(wOwner->allyteam, pAllyTeam)) continue; // note: will be called every Update so long as gadget does not return true if (!eventHandler.AllowWeaponInterceptTarget(wOwner, w, p)) continue; // there are four cases when an interceptor <w> should fire at a projectile <p>: // 1. p's target position inside w's interception circle (w's owner can move!) // 2. p's current position inside w's interception circle // 3. p's projected impact position inside w's interception circle // 4. p's trajectory intersects w's interception circle // // these checks all need to be evaluated periodically, not just // when a projectile is created and handed to AddInterceptTarget const float weaponDist = w->aimFromPos.distance(p->pos); const float impactDist = CGround::LineGroundCol(p->pos, p->pos + p->dir * weaponDist); const float3& pImpactPos = p->pos + p->dir * impactDist; const float3& pTargetPos = p->GetTargetPos(); const float3 pWeaponVec = p->pos - w->aimFromPos; if (w->aimFromPos.SqDistance2D(pTargetPos) < Square(wDef->coverageRange)) { w->AddDeathDependence(p, DEPENDENCE_INTERCEPT); w->incomingProjectiles[p->id] = p; continue; // 1 } if (false /*wDef->noFlyThroughIntercept*/) { // <w> is just a static interceptor and fires only at projectiles // TARGETED within its current interception area; any projectiles // CROSSING its interception area aren't targeted //XXX implement in lua? continue; } if (pWeaponVec.SqLength2D() < Square(wDef->coverageRange)) { w->AddDeathDependence(p, DEPENDENCE_INTERCEPT); w->incomingProjectiles[p->id] = p; continue; // 2 } if (w->aimFromPos.SqDistance2D(pImpactPos) < Square(wDef->coverageRange)) { const float3 pTargetDir = (pTargetPos - p->pos).SafeNormalize(); const float3 pImpactDir = (pImpactPos - p->pos).SafeNormalize(); // the projected impact position can briefly shift into the covered // area during transition from vertical to horizontal flight, so we // perform an extra test (NOTE: assumes non-parabolic trajectory) if (pTargetDir.dot(pImpactDir) >= 0.999f) { w->AddDeathDependence(p, DEPENDENCE_INTERCEPT); w->incomingProjectiles[p->id] = p; continue; // 3 } } const float3 pMinSepPos = p->pos + p->dir * Clamp(-(pWeaponVec.dot(p->dir)), 0.0f, impactDist); const float3 pMinSepVec = w->aimFromPos - pMinSepPos; if (pMinSepVec.SqLength() < Square(wDef->coverageRange)) { w->AddDeathDependence(p, DEPENDENCE_INTERCEPT); w->incomingProjectiles[p->id] = p; continue; // 4 } } } }
/** * @brief Causes this CMobileCAI to execute the attack order c */ void CMobileCAI::ExecuteAttack(Command &c) { assert(owner->unitDef->canAttack); // limit how far away we fly if (tempOrder && (owner->moveState < 2) && orderTarget && LinePointDist(ClosestPointOnLine(commandPos1, commandPos2, owner->pos), commandPos2, orderTarget->pos) > (500 * owner->moveState + owner->maxRange)) { StopMove(); FinishCommand(); return; } // check if we are in direct command of attacker if (!inCommand) { // don't start counting until the owner->AttackGround() order is given owner->commandShotCount = -1; if (c.params.size() == 1) { CUnit* targetUnit = uh->GetUnit(c.params[0]); // check if we have valid target parameter and that we aren't attacking ourselves if (targetUnit != NULL && targetUnit != owner) { float3 fix = targetUnit->pos + owner->posErrorVector * 128; float3 diff = float3(fix - owner->pos).Normalize(); SetGoal(fix - diff * targetUnit->radius, owner->pos); orderTarget = targetUnit; AddDeathDependence(orderTarget); inCommand = true; } else { // unit may not fire on itself, cancel order StopMove(); FinishCommand(); return; } } else if (c.params.size() >= 3) { // user gave force-fire attack command float3 pos(c.params[0], c.params[1], c.params[2]); SetGoal(pos, owner->pos); inCommand = true; } } else if ((c.params.size() == 3) && (owner->commandShotCount > 0) && (commandQue.size() > 1)) { // the trailing CMD_SET_WANTED_MAX_SPEED in a command pair does not count if ((commandQue.size() > 2) || (commandQue.back().id != CMD_SET_WANTED_MAX_SPEED)) { StopMove(); FinishCommand(); return; } } // 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) { //bool b1 = owner->AttackUnit(orderTarget, c.id == CMD_DGUN); bool b2 = false; bool b3 = false; bool b4 = false; float edgeFactor = 0.f; // percent offset to target center float3 diff = owner->pos - orderTarget->midPos; if (owner->weapons.size() > 0) { if (!(c.options & ALT_KEY) && SkipParalyzeTarget(orderTarget)) { StopMove(); FinishCommand(); return; } CWeapon* w = owner->weapons.front(); // if we have at least one weapon then check if we // can hit target with our first (meanest) one b2 = w->TryTargetRotate(orderTarget, c.id == CMD_DGUN); b3 = Square(w->range - (w->relWeaponPos).Length()) > (orderTarget->pos.SqDistance(owner->pos)); b4 = w->TryTargetHeading(GetHeadingFromVector(-diff.x, -diff.z), orderTarget->pos, orderTarget != NULL, orderTarget); edgeFactor = fabs(w->targetBorder); } float diffLength2d = diff.Length2D(); // 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 (b2) { if (!(tempOrder && owner->moveState == 0) && (diffLength2d * 1.4f > owner->maxRange - orderTarget->speed.SqLength() / owner->unitDef->maxAcc) && b4 && diff.dot(orderTarget->speed) < 0) { SetGoal(owner->pos + (orderTarget->speed * 80), owner->pos, SQUARE_SIZE, orderTarget->speed.Length() * 1.1f); } else { StopMove(); // FIXME kill magic frame number if (gs->frameNum > lastCloseInTry + MAX_CLOSE_IN_RETRY_TICKS) { owner->moveType->KeepPointingTo(orderTarget->midPos, std::min((float) owner->losRadius * loshandler->losDiv, owner->maxRange * 0.9f), true); } } owner->AttackUnit(orderTarget, c.id == CMD_DGUN); } // 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 == 0) { StopMove(); FinishCommand(); return; } // if ((our movetype has type TAAirMoveType 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(diffLength2d < (owner->maxRange * 0.9f)){ if (dynamic_cast<CTAAirMoveType*>(owner->moveType) || (diff.SqLength2D() < 1024)) { StopMove(); owner->moveType->KeepPointingTo(orderTarget->midPos, std::min((float) owner->losRadius * loshandler->losDiv, owner->maxRange * 0.9f), 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 && b3 && diffLength2d < (owner->maxRange * 0.9f)) { moveDir ^= (owner->moveType->progressState == AMoveType::Failed); float sin = moveDir ? 3.0/5 : -3.0/5; float cos = 4.0/5; float3 goalDiff(0, 0, 0); goalDiff.x = diff.dot(float3(cos, 0, -sin)); goalDiff.z = diff.dot(float3(sin, 0, cos)); goalDiff *= (diffLength2d < (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 ((orderTarget->pos + owner->posErrorVector * 128).SqDistance2D(goalPos) > Square(10 + orderTarget->pos.distance2D(owner->pos) * 0.2f)) { // 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) float3 fix = orderTarget->pos + (orderTarget->losStatus[owner->allyteam] & LOS_INLOS ? float3(0.f,0.f,0.f) : owner->posErrorVector * 128); float3 norm = float3(fix - owner->pos).Normalize(); float3 goal = fix - norm*(orderTarget->radius*edgeFactor*0.8f); SetGoal(goal, owner->pos); if (lastCloseInTry < gs->frameNum + MAX_CLOSE_IN_RETRY_TICKS) lastCloseInTry = gs->frameNum; } } // user is attacking ground else if (c.params.size() >= 3) { const float3 pos(c.params[0], c.params[1], c.params[2]); const float3 diff = owner->pos - pos; if (owner->weapons.size() > 0) { // if we have at least one weapon then check if // we can hit position with our first (assumed // to be meanest) one CWeapon* w = owner->weapons.front(); // XXX hack - dgun overrides any checks if (c.id == CMD_DGUN) { float rr = owner->maxRange * owner->maxRange; for (vector<CWeapon*>::iterator it = owner->weapons.begin(); it != owner->weapons.end(); ++it) { if (dynamic_cast<CDGunWeapon*>(*it)) rr = (*it)->range * (*it)->range; } if (diff.SqLength() < rr) { StopMove(); owner->AttackGround(pos, c.id == CMD_DGUN); owner->moveType->KeepPointingTo(pos, owner->maxRange * 0.9f, true); } } else { const bool inAngle = w->TryTargetRotate(pos, c.id == CMD_DGUN); const bool inRange = diff.SqLength2D() < Square(w->range - (w->relWeaponPos).Length2D()); if (inAngle || inRange) { StopMove(); owner->AttackGround(pos, c.id == CMD_DGUN); owner->moveType->KeepPointingTo(pos, owner->maxRange * 0.9f, true); } } } else if (diff.SqLength2D() < 1024) { StopMove(); owner->moveType->KeepPointingTo(pos, owner->maxRange * 0.9f, true); } // if we are more than 10 units distant from target position then keeping moving closer else if (pos.SqDistance2D(goalPos) > 100) { SetGoal(pos, owner->pos); } } }
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); } }
void CGrassBlockDrawer::DrawQuad(int x, int y) { const float maxDetailedDist = gd->maxDetailedDist; CGrassDrawer::NearGrassStruct* nearGrass = gd->nearGrass; if (abs(x - cx) <= gd->detailedBlocks && abs(y - cy) <= gd->detailedBlocks) { //! blocks close to the camera for (int y2 = y * grassBlockSize; y2 < (y + 1) * grassBlockSize; ++y2) { for (int x2 = x * grassBlockSize; x2 < (x + 1) * grassBlockSize; ++x2) { if (gd->grassMap[y2 * gs->mapx / grassSquareSize + x2]) { float3 squarePos((x2 + 0.5f) * gSSsq, 0.0f, (y2 + 0.5f) * gSSsq); squarePos.y = CGround::GetHeightReal(squarePos.x, squarePos.z, false); const float sqdist = (camera->GetPos() - squarePos).SqLength(); CGrassDrawer::NearGrassStruct* ng = &nearGrass[(y2 & 31) * 32 + (x2 & 31)]; if (sqdist < (maxDetailedDist * maxDetailedDist)) { //! close grass, draw directly rng.Seed(y2 * 1025 + x2); for (int a = 0; a < gd->numTurfs; a++) { const float dx = (x2 + rng.RandFloat()) * gSSsq; const float dy = (y2 + rng.RandFloat()) * gSSsq; float3 pos(dx, CGround::GetHeightReal(dx, dy, false), dy); pos.y -= CGround::GetSlope(dx, dy, false) * 10.0f + 0.03f; if (ng->square != y2 * 2048 + x2) { const float3 v = squarePos - camera->GetPos(); ng->rotation = GetHeadingFromVector(v.x, v.z) * 180.0f / 32768 + 180; //FIXME make more random ng->square = y2 * 2048 + x2; } glPushMatrix(); glTranslatef3(pos); glRotatef(ng->rotation, 0.0f, 1.0f, 0.0f); glCallList(gd->grassDL); glPopMatrix(); } } else { //! near but not close, save for later drawing CGrassDrawer::InviewNearGrass iv; iv.dist = sqdist; iv.x = x2; iv.y = y2; inviewNearGrass.push_back(iv); ng->square = -1; } } } } return; } const float3 dif(camera->GetPos().x - ((x + 0.5f) * bMSsq), 0.0f, camera->GetPos().z - ((y + 0.5f) * bMSsq)); const float dist = dif.SqLength2D(); if (dist < Square(gd->maxGrassDist)) { const int curSquare = y * gd->blocksX + x; const int curModSquare = (y & 31) * 32 + (x & 31); CGrassDrawer::GrassStruct* grass = gd->grass + curModSquare; grass->lastSeen = globalRendering->drawFrame; if (grass->square != curSquare) { grass->square = curSquare; delete grass->va; grass->va = NULL; } if (!grass->va) { grass->va = new CVertexArray; grass->pos = float3((x + 0.5f) * bMSsq, CGround::GetHeightReal((x + 0.5f) * bMSsq, (y + 0.5f) * bMSsq, false), (y + 0.5f) * bMSsq); CVertexArray* va = grass->va; va->Initialize(); for (int y2 = y * grassBlockSize; y2 < (y + 1) * grassBlockSize; ++y2) { for (int x2 = x * grassBlockSize; x2 < (x + 1) * grassBlockSize; ++x2) { if (gd->grassMap[y2 * gs->mapx / grassSquareSize + x2]) { rng.Seed(y2 * 1025 + x2); for (int a = 0; a < gd->numTurfs; a++) { const float dx = (x2 + rng.RandFloat()) * gSSsq; const float dy = (y2 + rng.RandFloat()) * gSSsq; const float col = 1.0f; float3 pos(dx, CGround::GetHeightReal(dx, dy, false) + 0.5f, dy); pos.y -= (CGround::GetSlope(dx, dy, false) * 10.0f + 0.03f); va->AddVertexTN(pos, 0.0f, 0.0f, float3(-partTurfSize, -partTurfSize, col)); va->AddVertexTN(pos, 1.0f / 16.0f, 0.0f, float3( partTurfSize, -partTurfSize, col)); va->AddVertexTN(pos, 1.0f / 16.0f, 1.0f, float3( partTurfSize, partTurfSize, col)); va->AddVertexTN(pos, 0.0f, 1.0f, float3(-partTurfSize, partTurfSize, col)); } } } } } CGrassDrawer::InviewGrass ig; ig.num = curModSquare; ig.dist = dif.Length2D(); inviewGrass.push_back(ig); } }