/** * helper for TestTrajectoryCone * @return true if object <o> is in the firing trajectory, false otherwise */ inline bool TestTrajectoryConeHelper( const float3& from, const float3& flatdir, float length, float linear, float quadratic, float spread, float baseSize, const CSolidObject* o) { const CollisionVolume* cv = o->collisionVolume; float3 dif = (o->midPos + cv->GetOffsets()) - from; const float3 flatdif(dif.x, 0, dif.z); float closeFlatLength = flatdif.dot(flatdir); if (closeFlatLength <= 0) return false; if (closeFlatLength > length) closeFlatLength = length; if (fabs(linear - quadratic * closeFlatLength) < 0.15f) { // relatively flat region -> use approximation dif.y -= (linear + quadratic * closeFlatLength) * closeFlatLength; // NOTE: overly conservative for non-spherical volumes const float3 closeVect = dif - flatdir * closeFlatLength; const float r = cv->GetBoundingRadius() + spread * closeFlatLength + baseSize; if (closeVect.SqLength() < r * r) { return true; } } else { float3 newfrom = from + flatdir * closeFlatLength; newfrom.y += (linear + quadratic * closeFlatLength) * closeFlatLength; float3 dir = flatdir; dir.y = linear + quadratic * closeFlatLength; dir.Normalize(); dif = (o->midPos + cv->GetOffsets()) - newfrom; const float closeLength = dif.dot(dir); // NOTE: overly conservative for non-spherical volumes const float3 closeVect = dif - dir * closeLength; const float r = cv->GetBoundingRadius() + spread * closeFlatLength + baseSize; if (closeVect.SqLength() < r * r) { return true; } } return false; }
static float GetCamDistOfGrassBlock(const int x, const int y, const bool square = false) { float3 quadCenter = float3(x, 0.f, y) * gSSsq; quadCenter.y = CGround::GetHeightReal(quadCenter.x, quadCenter.z, false); const float3 dif = camera->GetPos() - quadCenter; return (square) ? dif.SqLength() : dif.Length(); }
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); } }
static float GetCamDistOfGrassBlock(const int x, const int y, const bool square = false) { const float qx = x * gSSsq; const float qz = y * gSSsq; const float3 mid = float3(qx, CGround::GetHeightReal(qx, qz, false), qz); const float3 dif = camera->GetPos() - mid; return (square) ? dif.SqLength() : dif.Length(); }
float CPlasmaRepulser::NewBeam(CWeapon* emitter, float3 start, float3 dir, float length, float3& newDir) { if (!isEnabled) { return -1.0f; } // ::Update handles projectile <-> shield interactions for normal weapons, but // does not get called when the owner is stunned (CUnit::Update returns early) // BeamLasers and LightningCannons are hitscan (their projectiles do not move), // they call InterceptHandler to figure out if a shield is in the way so check // the stunned-state here keep things consistent if (owner->stunned) { return -1.0f; } if (emitter->weaponDef->damages[0] > curPower) { return -1.0f; } if (weaponDef->smartShield && teamHandler->AlliedTeams(emitter->owner->team, owner->team)) { return -1.0f; } const float3 dif = weaponPos - start; if (weaponDef->exteriorShield && dif.SqLength() < sqRadius) { return -1.0f; } const float closeLength = dif.dot(dir); if (closeLength < 0.0f) { return -1.0f; } const float3 closeVect = dif - dir * closeLength; const float tmp = sqRadius - closeVect.SqLength(); if ((tmp > 0.0f) && (length > (closeLength - math::sqrt(tmp)))) { const float colLength = closeLength - math::sqrt(tmp); const float3 colPoint = start + dir * colLength; const float3 normal = (colPoint - weaponPos).Normalize(); newDir = dir - normal * normal.dot(dir) * 2; return colLength; } return -1.0f; }
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; }
bool C3DOParser::IsBasePlate(S3DOPiece* obj, S3DOPrimitive* face) { if (!(face->primNormal.dot(-UpVector) > 0.99f)) return false; if (face->indices.size() != 4) return false; const float3 s1 = obj->vertexPos[face->indices[0]] - obj->vertexPos[face->indices[1]]; const float3 s2 = obj->vertexPos[face->indices[1]] - obj->vertexPos[face->indices[2]]; if (s1.SqLength()<900 || s2.SqLength()<900) return false; for(int vi: face->indices) { if (obj->vertexPos[vi].y > 0) { return false; } } return true; }
float CPlasmaRepulser::NewBeam(CWeapon* emitter, float3 start, float3 dir, float length, float3& newDir) { if (!isEnabled) { return -1; } if (emitter->weaponDef->damages[0] > curPower) { return -1; } if (weaponDef->smartShield && teamHandler->AlliedTeams(emitter->owner->team,owner->team)) { return -1; } const float3 dif = weaponPos - start; if (weaponDef->exteriorShield && dif.SqLength() < sqRadius) { return -1; } const float closeLength = dif.dot(dir); if (closeLength < 0) { return -1; } const float3 closeVect = dif-dir*closeLength; const float tmp = sqRadius - closeVect.SqLength(); if ((tmp > 0) && (length > (closeLength - sqrt(tmp)))) { float colLength = closeLength - sqrt(tmp); float3 colPoint = start + dir * colLength; float3 normal = colPoint - weaponPos; normal.Normalize(); newDir = dir-normal*normal.dot(dir) * 2; return colLength; } return -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.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; }
bool CCamera::InView(const float3& p, float radius) const { const float3 t(p - pos); if ((t.dot(rgtFrustumSideDir) > radius) || (t.dot(lftFrustumSideDir) > radius) || (t.dot(botFrustumSideDir) > radius) || (t.dot(topFrustumSideDir) > radius)) { return false; } const float lsq = t.SqLength(); if (lsq > Square(globalRendering->viewRange)) { return false; } return true; }
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; }
bool CCamera::InView(const float3& p, float radius) { const float3 t = (p - pos); const float lsq = t.SqLength(); if (lsq < 2500.0f) { return true; } else if (lsq > Square(globalRendering->viewRange)) { return false; } if ((t.dot(rightside) > radius) || (t.dot(leftside) > radius) || (t.dot(bottom) > radius) || (t.dot(top) > radius)) { return false; } return true; }
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); } }
/** * helper for TestCone * @return true if object <o> is in the firing cone, false otherwise */ inline bool TestConeHelper(const float3& from, const float3& weaponDir, float length, float spread, const CSolidObject* obj) { // account for any offset, since we want to know if our shots might hit const float3 objDir = (obj->midPos + obj->collisionVolume->GetOffsets()) - from; // weaponDir defines the center of the cone float closeLength = objDir.dot(weaponDir); if (closeLength <= 0) return false; if (closeLength > length) closeLength = length; const float3 closeVect = objDir - weaponDir * closeLength; // NOTE: same caveat wrt. use of volumeBoundingRadius // as for ::Explosion(), this will result in somewhat // over-conservative tests for non-spherical volumes const float r = obj->collisionVolume->GetBoundingRadius() + spread * closeLength + 1; return (closeVect.SqLength() < r * r); }
void CHoverAirMoveType::UpdateAirPhysics() { const float3& pos = owner->pos; float3& speed = owner->speed; if (!((gs->frameNum + owner->id) & 3)) { CheckForCollision(); } const float yspeed = speed.y; speed.y = 0.0f; const float3 deltaSpeed = wantedSpeed - speed; const float deltaDotSpeed = (speed != ZeroVector)? deltaSpeed.dot(speed): 1.0f; if (deltaDotSpeed == 0.0f) { // we have the wanted speed } else if (deltaDotSpeed > 0.0f) { // accelerate const float sqdl = deltaSpeed.SqLength(); if (sqdl < Square(accRate)) { speed = wantedSpeed; } else { speed += (deltaSpeed / math::sqrt(sqdl) * accRate); } } else { // break const float sqdl = deltaSpeed.SqLength(); if (sqdl < Square(decRate)) { speed = wantedSpeed; } else { speed += (deltaSpeed / math::sqrt(sqdl) * decRate); } } float minHeight = 0.0f; // absolute ground height at (pos.x, pos.z) float curHeight = 0.0f; // relative ground height at (pos.x, pos.z) == pos.y - minHeight (altitude) float wh = wantedHeight; // wanted RELATIVE height (altitude) float ws = 0.0f; // wanted vertical speed if (UseSmoothMesh()) { minHeight = owner->unitDef->canSubmerge? smoothGround->GetHeight(pos.x, pos.z): smoothGround->GetHeightAboveWater(pos.x, pos.z); } else { minHeight = owner->unitDef->canSubmerge? ground->GetHeightReal(pos.x, pos.z): ground->GetHeightAboveWater(pos.x, pos.z); } // [?] aircraft should never be able to end up below terrain // if (pos.y < minHeight) // owner->Move1D(std::min(minHeight - pos.y, altitudeRate), 1, true); speed.y = yspeed; curHeight = pos.y - minHeight; if (curHeight < 4.0f) { speed.x *= 0.95f; speed.z *= 0.95f; } if (lastColWarningType == 2) { const float3 dir = lastColWarning->midPos - owner->midPos; const float3 sdir = lastColWarning->speed - speed; if (speed.dot(dir + sdir * 20.0f) < 0.0f) { if (lastColWarning->midPos.y > owner->pos.y) { wh -= 30.0f; } else { wh += 50.0f; } } } if (curHeight < wh) { ws = altitudeRate; if ((speed.y > 0.0001f) && (((wh - curHeight) / speed.y) * accRate * 1.5f) < speed.y) { ws = 0.0f; } } else { ws = -altitudeRate; if ((speed.y < -0.0001f) && (((wh - curHeight) / speed.y) * accRate * 0.7f) < -speed.y) { ws = 0.0f; } } if (!owner->beingBuilt) { if (math::fabs(wh - curHeight) > 2.0f) { if (speed.y > ws) { speed.y = std::max(ws, speed.y - accRate * 1.5f); } else { // accelerate upward faster if close to ground speed.y = std::min(ws, speed.y + accRate * ((curHeight < 20.0f)? 2.0f: 0.7f)); } } else { speed.y *= 0.95; } } if (modInfo.allowAircraftToLeaveMap || (pos + speed).IsInBounds()) { owner->Move3D(speed, true); } }
void CHoverAirMoveType::UpdateAirPhysics() { const float3& pos = owner->pos; const float4& spd = owner->speed; // copy vertical speed const float yspeed = spd.y; if (((gs->frameNum + owner->id) & 3) == 0) { CheckForCollision(); } // cancel out vertical speed, acc and dec are applied in xz-plane owner->SetVelocity(spd * XZVector); const float3 deltaSpeed = wantedSpeed - spd; const float deltaDotSpeed = deltaSpeed.dot(spd); const float deltaSpeedSq = deltaSpeed.SqLength(); if (deltaDotSpeed >= 0.0f) { // accelerate if (deltaSpeedSq < Square(accRate)) { owner->SetVelocity(wantedSpeed); } else { if (deltaSpeedSq > 0.0f) { owner->SetVelocity(spd + (deltaSpeed / math::sqrt(deltaSpeedSq) * accRate)); } } } else { // deccelerate if (deltaSpeedSq < Square(decRate)) { owner->SetVelocity(wantedSpeed); } else { if (deltaSpeedSq > 0.0f) { owner->SetVelocity(spd + (deltaSpeed / math::sqrt(deltaSpeedSq) * decRate)); } } } // absolute and relative ground height at (pos.x, pos.z) // if this aircraft uses the smoothmesh, these values are // calculated with respect to that (for changing vertical // speed, but not for ground collision) float curAbsHeight = owner->unitDef->canSubmerge? CGround::GetHeightReal(pos.x, pos.z): CGround::GetHeightAboveWater(pos.x, pos.z); // always stay above the actual terrain (therefore either the value of // <midPos.y - radius> or pos.y must never become smaller than the real // ground height) // note: unlike StrafeAirMoveType, UpdateTakeoff and UpdateLanding call // UpdateAirPhysics() so we ignore terrain while we are in those states if (modInfo.allowAircraftToHitGround) { const bool groundContact = (curAbsHeight > (owner->midPos.y - owner->radius)); const bool handleContact = (aircraftState != AIRCRAFT_LANDED && aircraftState != AIRCRAFT_TAKEOFF && padStatus == PAD_STATUS_FLYING); if (groundContact && handleContact) { owner->Move(UpVector * (curAbsHeight - (owner->midPos.y - owner->radius) + 0.01f), true); } } if (UseSmoothMesh()) { curAbsHeight = owner->unitDef->canSubmerge? smoothGround->GetHeight(pos.x, pos.z): smoothGround->GetHeightAboveWater(pos.x, pos.z); } // restore original vertical speed, then compute new UpdateVerticalSpeed(spd, pos.y - curAbsHeight, yspeed); if (modInfo.allowAircraftToLeaveMap || (pos + spd).IsInBounds()) { owner->Move(spd, true); } }
void CAssParser::LoadPieceTransformations( SAssPiece* piece, const S3DModel* model, const aiNode* pieceNode, const LuaTable& pieceTable ) { aiVector3D aiScaleVec, aiTransVec; aiQuaternion aiRotateQuat; // process transforms pieceNode->mTransformation.Decompose(aiScaleVec, aiRotateQuat, aiTransVec); // metadata-scaling piece->scales = pieceTable.GetFloat3("scale", aiVectorToFloat3(aiScaleVec)); piece->scales.x = pieceTable.GetFloat("scalex", piece->scales.x); piece->scales.y = pieceTable.GetFloat("scaley", piece->scales.y); piece->scales.z = pieceTable.GetFloat("scalez", piece->scales.z); if (piece->scales.x != piece->scales.y || piece->scales.y != piece->scales.z) { // LOG_SL(LOG_SECTION_MODEL, L_WARNING, "Spring doesn't support non-uniform scaling"); piece->scales.y = piece->scales.x; piece->scales.z = piece->scales.x; } // metadata-translation piece->offset = pieceTable.GetFloat3("offset", aiVectorToFloat3(aiTransVec)); piece->offset.x = pieceTable.GetFloat("offsetx", piece->offset.x); piece->offset.y = pieceTable.GetFloat("offsety", piece->offset.y); piece->offset.z = pieceTable.GetFloat("offsetz", piece->offset.z); // metadata-rotation // NOTE: // these rotations are "pre-scripting" but "post-modelling" // together with the (baked) aiRotateQuad they determine the // model's pose *before* any animations execute // // float3 rotAngles = pieceTable.GetFloat3("rotate", aiQuaternionToRadianAngles(aiRotateQuat) * RADTODEG); float3 pieceRotAngles = pieceTable.GetFloat3("rotate", ZeroVector); pieceRotAngles.x = pieceTable.GetFloat("rotatex", pieceRotAngles.x); pieceRotAngles.y = pieceTable.GetFloat("rotatey", pieceRotAngles.y); pieceRotAngles.z = pieceTable.GetFloat("rotatez", pieceRotAngles.z); pieceRotAngles *= DEGTORAD; LOG_SL(LOG_SECTION_PIECE, L_INFO, "(%d:%s) Assimp offset (%f,%f,%f), rotate (%f,%f,%f,%f), scale (%f,%f,%f)", model->numPieces, piece->name.c_str(), aiTransVec.x, aiTransVec.y, aiTransVec.z, aiRotateQuat.w, aiRotateQuat.x, aiRotateQuat.y, aiRotateQuat.z, aiScaleVec.x, aiScaleVec.y, aiScaleVec.z ); LOG_SL(LOG_SECTION_PIECE, L_INFO, "(%d:%s) Relative offset (%f,%f,%f), rotate (%f,%f,%f), scale (%f,%f,%f)", model->numPieces, piece->name.c_str(), piece->offset.x, piece->offset.y, piece->offset.z, pieceRotAngles.x, pieceRotAngles.y, pieceRotAngles.z, piece->scales.x, piece->scales.y, piece->scales.z ); // NOTE: // at least collada (.dae) files generated by Blender represent // a coordinate-system that differs from the "standard" formats // (3DO, S3O, ...) for which existing tools at least have prior // knowledge of Spring's expectations --> let the user override // the ROOT rotational transform and the rotation-axis mapping // used by animation scripts (but re-modelling/re-exporting is // always preferred!) even though AssImp should convert models // to its own system which matches that of Spring // // .dae : x=Rgt, y=-Fwd, z= Up, as=(-1, -1, 1), am=AXIS_XZY (if Z_UP) // .dae : x=Rgt, y=-Fwd, z= Up, as=(-1, -1, 1), am=AXIS_XZY (if Y_UP) [!?] // .blend: ???? piece->bakedRotMatrix = aiMatrixToMatrix(aiMatrix4x4t<float>(aiRotateQuat.GetMatrix())); if (piece == model->GetRootPiece()) { const float3 xaxis = pieceTable.GetFloat3("xaxis", piece->bakedRotMatrix.GetX()); const float3 yaxis = pieceTable.GetFloat3("yaxis", piece->bakedRotMatrix.GetY()); const float3 zaxis = pieceTable.GetFloat3("zaxis", piece->bakedRotMatrix.GetZ()); if (math::fabs(xaxis.SqLength() - yaxis.SqLength()) < 0.01f && math::fabs(yaxis.SqLength() - zaxis.SqLength()) < 0.01f) { piece->bakedRotMatrix = CMatrix44f(ZeroVector, xaxis, yaxis, zaxis); } } piece->rotAxisSigns = pieceTable.GetFloat3("rotAxisSigns", float3(-OnesVector)); piece->axisMapType = AxisMappingType(pieceTable.GetInt("rotAxisMap", AXIS_MAPPING_XYZ)); // construct 'baked' part of the piece-space matrix // AssImp order is translate * rotate * scale * v; // we leave the translation and scale parts out and // put those in <offset> and <scales> --> transform // is just R instead of T * R * S // // note: for all non-AssImp models this is identity! // piece->ComposeRotation(piece->bakedRotMatrix, pieceRotAngles); piece->SetHasIdentityRotation(piece->bakedRotMatrix.IsIdentity() == 0); assert(piece->bakedRotMatrix.IsOrthoNormal() == 0); }
void CInterceptHandler::Update(bool forced) { if (((gs->frameNum % UNIT_SLOWUPDATE_RATE) != 0) && !forced) return; std::list<CWeapon*>::iterator wit; std::map<int, CWeaponProjectile*>::const_iterator pit; for (wit = interceptors.begin(); wit != interceptors.end(); ++wit) { CWeapon* w = *wit; const WeaponDef* wDef = w->weaponDef; const CUnit* wOwner = w->owner; // const float3& wOwnerPos = wOwner->pos; const float3& wPos = w->weaponPos; assert(wDef->interceptor || wDef->isShield); for (pit = interceptables.begin(); pit != interceptables.end(); ++pit) { CWeaponProjectile* p = pit->second; const WeaponDef* pDef = p->weaponDef; if ((pDef->targetable & wDef->interceptor) == 0) continue; if (w->incomingProjectiles.find(p->id) != w->incomingProjectiles.end()) continue; const CUnit* pOwner = p->owner(); const int pAllyTeam = (pOwner != NULL)? pOwner->allyteam: -1; if (pAllyTeam != -1 && teamHandler->Ally(wOwner->allyteam, pAllyTeam)) 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 interceptDist = w->weaponPos.distance(p->pos); const float impactDist = ground->LineGroundCol(p->pos, p->pos + p->dir * interceptDist); const float3& pFlightPos = p->pos; const float3& pImpactPos = p->pos + p->dir * impactDist; const float3& pTargetPos = p->targetPos; if ((pTargetPos - wPos).SqLength2D() < Square(wDef->coverageRange)) { w->AddDeathDependence(p, CObject::DEPENDENCE_INTERCEPT); w->incomingProjectiles[p->id] = p; continue; // 1 } if (wDef->interceptor == 1) { // <w> is just a static interceptor and fires only at projectiles // TARGETED within its current interception area; any projectiles // CROSSING its interception area are fired at only if interceptor // is >= 2 continue; } if ((pFlightPos - wPos).SqLength2D() < Square(wDef->coverageRange)) { w->AddDeathDependence(p, CObject::DEPENDENCE_INTERCEPT); w->incomingProjectiles[p->id] = p; continue; // 2 } if ((pImpactPos - wPos).SqLength2D() < Square(wDef->coverageRange)) { const float3 pTargetDir = (pTargetPos - pFlightPos).SafeNormalize(); const float3 pImpactDir = (pImpactPos - pFlightPos).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, CObject::DEPENDENCE_INTERCEPT); w->incomingProjectiles[p->id] = p; continue; // 3 } } const float3 pCurSeparationVec = wPos - pFlightPos; const float pMinSeparationDist = std::max(pCurSeparationVec.dot(p->dir), 0.0f); const float3 pMinSeparationPos = pFlightPos + (p->dir * pMinSeparationDist); const float3 pMinSeparationVec = wPos - pMinSeparationPos; if (pMinSeparationVec.SqLength() < Square(wDef->coverageRange)) { w->AddDeathDependence(p, CObject::DEPENDENCE_INTERCEPT); w->incomingProjectiles[p->id] = p; continue; // 4 } } } }
void C3DOParser::GetPrimitives(S3DOPiece* obj, int pos, int num, int excludePrim) { map<int,int> prevHashes; for(int a=0;a<num;a++){ if(excludePrim==a){ continue; } curOffset=pos+a*sizeof(_Primitive); _Primitive p; READ_PRIMITIVE(p); S3DOPrimitive sp; sp.numVertex=p.NumberOfVertexIndexes; if(sp.numVertex<3) continue; sp.vertices.reserve(sp.numVertex); sp.vnormals.reserve(sp.numVertex); curOffset=p.OffsetToVertexIndexArray; boost::uint16_t w; list<int> orderVert; for(int b=0;b<sp.numVertex;b++){ SimStreamRead(&w,2); swabWordInPlace(w); sp.vertices.push_back(w); orderVert.push_back(w); } orderVert.sort(); int vertHash=0; for(list<int>::iterator vi=orderVert.begin();vi!=orderVert.end();++vi) vertHash=(vertHash+(*vi))*(*vi); std::string texName; if (p.OffsetToTextureName != 0) { texName = StringToLower(GetText(p.OffsetToTextureName)); if (teamtex.find(texName) == teamtex.end()) { texName += "00"; } } else { texName = "ta_color" + IntToString(p.PaletteEntry, "%i"); } if ((sp.texture = texturehandler3DO->Get3DOTexture(texName)) == NULL) { LOG_L(L_WARNING, "[%s] unknown 3DO texture \"%s\" for piece \"%s\"", __FUNCTION__, texName.c_str(), obj->name.c_str()); // assign a dummy texture (the entire atlas) sp.texture = texturehandler3DO->Get3DOTexture("___dummy___"); } const float3 v0v1 = (obj->vertices[sp.vertices[1]].pos - obj->vertices[sp.vertices[0]].pos); const float3 v0v2 = (obj->vertices[sp.vertices[2]].pos - obj->vertices[sp.vertices[0]].pos); float3 n = (-v0v1.cross(v0v2)).SafeNormalize(); // set the primitive-normal and copy it <numVertex> // times (vnormals get overwritten by CalcNormals()) sp.primNormal = n; sp.vnormals.insert(sp.vnormals.begin(), sp.numVertex, n); // sometimes there are more than one selection primitive (??) if (n.dot(DownVector) > 0.99f) { bool ignore=true; if(sp.numVertex!=4) { ignore=false; } else { const float3 s1 = obj->vertices[sp.vertices[0]].pos - obj->vertices[sp.vertices[1]].pos; const float3 s2 = obj->vertices[sp.vertices[1]].pos - obj->vertices[sp.vertices[2]].pos; if(s1.SqLength()<900 || s2.SqLength()<900) ignore=false; if (ignore) { for(int a=0;a<sp.numVertex;++a) { if(obj->vertices[sp.vertices[a]].pos.y>0) { ignore=false; break; } } } } if(ignore) continue; } map<int,int>::iterator phi; if((phi=prevHashes.find(vertHash))!=prevHashes.end()){ if(n.y>0) obj->prims[phi->second]=sp; continue; } else { prevHashes[vertHash]=obj->prims.size(); obj->prims.push_back(sp); obj->isEmpty = false; } curOffset = p.OffsetToVertexIndexArray; for (int b = 0; b < sp.numVertex; b++) { SimStreamRead(&w, 2); swabWordInPlace(w); obj->vertices[w].prims.push_back(obj->prims.size() - 1); } } }
void CHoverAirMoveType::UpdateAirPhysics() { float3& pos = owner->pos; float3& speed = owner->speed; if (!((gs->frameNum + owner->id) & 3)) { CheckForCollision(); } const float yspeed = speed.y; speed.y = 0.0f; const float3 delta = wantedSpeed - speed; const float deltaDotSpeed = (speed != ZeroVector)? delta.dot(speed): 1.0f; if (deltaDotSpeed == 0.0f) { // we have the wanted speed } else if (deltaDotSpeed > 0.0f) { // accelerate const float sqdl = delta.SqLength(); if (sqdl < Square(accRate)) { speed = wantedSpeed; } else { speed += delta / math::sqrt(sqdl) * accRate; } } else { // break const float sqdl = delta.SqLength(); if (sqdl < Square(decRate)) { speed = wantedSpeed; } else { speed += delta / math::sqrt(sqdl) * decRate; } } float minH = 0.0f; // minimum altitude at (pos.x, pos.z) float curH = 0.0f; // current altitude at (pos.x, pos.z) if (UseSmoothMesh()) { minH = owner->unitDef->canSubmerge? smoothGround->GetHeight(pos.x, pos.z): smoothGround->GetHeightAboveWater(pos.x, pos.z); } else { minH = owner->unitDef->canSubmerge? ground->GetHeightReal(pos.x, pos.z): ground->GetHeightAboveWater(pos.x, pos.z); } speed.y = yspeed; pos.y = std::max(pos.y, minH); curH = pos.y - minH; if (curH < 4.0f) { speed.x *= 0.95f; speed.z *= 0.95f; } float wh = wantedHeight; if (lastColWarningType == 2) { const float3 dir = lastColWarning->midPos - owner->midPos; const float3 sdir = lastColWarning->speed - speed; if (speed.dot(dir + sdir * 20.0f) < 0.0f) { if (lastColWarning->midPos.y > owner->pos.y) { wh -= 30.0f; } else { wh += 50.0f; } } } float ws = 0.0f; if (curH < wh) { ws = altitudeRate; if (speed.y > 0.0001f && (wh - curH) / speed.y * accRate * 1.5f < speed.y) { ws = 0.0f; } } else { ws = -altitudeRate; if (speed.y < -0.0001f && (wh - curH) / speed.y * accRate * 0.7f < -speed.y) { ws = 0.0f; } } if (fabs(wh - curH) > 2.0f) { if (speed.y > ws) { speed.y = std::max(ws, speed.y - accRate * 1.5f); } else if (!owner->beingBuilt) { // let them accelerate upward faster if close to ground speed.y = std::min(ws, speed.y + accRate * (curH < 20.0f? 2.0f: 0.7f)); } } else { speed.y = speed.y * 0.95; } if (modInfo.allowAirPlanesToLeaveMap || (pos + speed).CheckInBounds()) { pos += speed; } }
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 } } } }
void CHoverAirMoveType::UpdateAirPhysics() { const float3& pos = owner->pos; const float4& spd = owner->speed; // copy vertical speed const float yspeed = spd.y; if (((gs->frameNum + owner->id) & 3) == 0) { CheckForCollision(); } owner->SetVelocity(spd * XZVector); const float3 deltaSpeed = wantedSpeed - spd; const float deltaDotSpeed = deltaSpeed.dot(spd); const float deltaSpeedSq = deltaSpeed.SqLength(); if (deltaDotSpeed >= 0.0f) { // accelerate if (deltaSpeedSq < Square(accRate)) { owner->SetVelocity(wantedSpeed); } else { if (deltaSpeedSq > 0.0f) { owner->SetVelocity(spd + (deltaSpeed / math::sqrt(deltaSpeedSq) * accRate)); } } } else { // deccelerate if (deltaSpeedSq < Square(decRate)) { owner->SetVelocity(wantedSpeed); } else { if (deltaSpeedSq > 0.0f) { owner->SetVelocity(spd + (deltaSpeed / math::sqrt(deltaSpeedSq) * decRate)); } } } // absolute and relative ground height at (pos.x, pos.z) // if this aircraft uses the smoothmesh, these values are // calculated with respect to that (for changing vertical // speed, but not for ground collision) float curAbsHeight = owner->unitDef->canSubmerge? ground->GetHeightReal(pos.x, pos.z): ground->GetHeightAboveWater(pos.x, pos.z); float curRelHeight = 0.0f; float wh = wantedHeight; // wanted RELATIVE height (altitude) float ws = 0.0f; // wanted vertical speed // always stay above the actual terrain (therefore either the value of // <midPos.y - radius> or pos.y must never become smaller than the real // ground height) // note: unlike StrafeAirMoveType, UpdateTakeoff and UpdateLanding call // UpdateAirPhysics() so we ignore terrain while we are in those states if (modInfo.allowAircraftToHitGround) { const bool groundContact = (curAbsHeight > (owner->midPos.y - owner->radius)); const bool handleContact = (aircraftState != AIRCRAFT_LANDED && aircraftState != AIRCRAFT_TAKEOFF && padStatus == PAD_STATUS_FLYING); if (groundContact && handleContact) { owner->Move(UpVector * (curAbsHeight - (owner->midPos.y - owner->radius) + 0.01f), true); } } if (UseSmoothMesh()) { curAbsHeight = owner->unitDef->canSubmerge? smoothGround->GetHeight(pos.x, pos.z): smoothGround->GetHeightAboveWater(pos.x, pos.z); } // restore original vertical speed owner->SetVelocity((spd * XZVector) + (UpVector * yspeed)); if (lastColWarningType == 2) { const float3 dir = lastColWarning->midPos - owner->midPos; const float3 sdir = lastColWarning->speed - spd; if (spd.dot(dir + sdir * 20.0f) < 0.0f) { if (lastColWarning->midPos.y > owner->pos.y) { wh -= 30.0f; } else { wh += 50.0f; } } } curRelHeight = pos.y - curAbsHeight; if (curRelHeight < wh) { ws = altitudeRate; if ((spd.y > 0.0001f) && (((wh - curRelHeight) / spd.y) * accRate * 1.5f) < spd.y) { ws = 0.0f; } } else { ws = -altitudeRate; if ((spd.y < -0.0001f) && (((wh - curRelHeight) / spd.y) * accRate * 0.7f) < -spd.y) { ws = 0.0f; } } if (!owner->beingBuilt) { if (math::fabs(wh - curRelHeight) > 2.0f) { if (spd.y > ws) { owner->SetVelocity((spd * XZVector) + (UpVector * std::max(ws, spd.y - accRate * 1.5f))); } else { // accelerate upward faster if close to ground owner->SetVelocity((spd * XZVector) + (UpVector * std::min(ws, spd.y + accRate * ((curRelHeight < 20.0f)? 2.0f: 0.7f)))); } } else { owner->SetVelocity((spd * XZVector) + (UpVector * spd.y * 0.95f)); } } owner->SetSpeed(spd); if (modInfo.allowAircraftToLeaveMap || (pos + spd).IsInBounds()) { owner->Move(spd, true); } }
void CHoverAirMoveType::UpdateAirPhysics() { const float3& pos = owner->pos; float3& speed = owner->speed; if (!((gs->frameNum + owner->id) & 3)) { CheckForCollision(); } const float yspeed = speed.y; speed.y = 0.0f; const float3 deltaSpeed = wantedSpeed - speed; const float deltaDotSpeed = (speed != ZeroVector)? deltaSpeed.dot(speed): 1.0f; if (deltaDotSpeed == 0.0f) { // we have the wanted speed } else if (deltaDotSpeed > 0.0f) { // accelerate const float sqdl = deltaSpeed.SqLength(); if (sqdl < Square(accRate)) { speed = wantedSpeed; } else { speed += (deltaSpeed / math::sqrt(sqdl) * accRate); } } else { // break const float sqdl = deltaSpeed.SqLength(); if (sqdl < Square(decRate)) { speed = wantedSpeed; } else { speed += (deltaSpeed / math::sqrt(sqdl) * decRate); } } // absolute and relative ground height at (pos.x, pos.z) // if this aircraft uses the smoothmes, these values are // calculated with respect to that for changing vertical // speed, but not for ground collision float curAbsHeight = owner->unitDef->canSubmerge? ground->GetHeightReal(pos.x, pos.z): ground->GetHeightAboveWater(pos.x, pos.z); float curRelHeight = 0.0f; float wh = wantedHeight; // wanted RELATIVE height (altitude) float ws = 0.0f; // wanted vertical speed // always stay above the actual terrain (therefore either the value of // <midPos.y - radius> or pos.y must never become smaller than the real // ground height) // note: unlike StrafeAirMoveType, UpdateTakeoff calls UpdateAirPhysics // so we ignore terrain while we are in the takeoff state to avoid jumps if (modInfo.allowAircraftToHitGround) { const bool groundContact = (curAbsHeight > (owner->midPos.y - owner->radius)); const bool handleContact = (aircraftState != AIRCRAFT_LANDED && aircraftState != AIRCRAFT_TAKEOFF); if (groundContact && handleContact) { owner->Move1D(curAbsHeight - (owner->midPos.y - owner->radius) + 0.01f, 1, true); } } if (UseSmoothMesh()) { curAbsHeight = owner->unitDef->canSubmerge? smoothGround->GetHeight(pos.x, pos.z): smoothGround->GetHeightAboveWater(pos.x, pos.z); } speed.y = yspeed; curRelHeight = pos.y - curAbsHeight; if (lastColWarningType == 2) { const float3 dir = lastColWarning->midPos - owner->midPos; const float3 sdir = lastColWarning->speed - speed; if (speed.dot(dir + sdir * 20.0f) < 0.0f) { if (lastColWarning->midPos.y > owner->pos.y) { wh -= 30.0f; } else { wh += 50.0f; } } } if (curRelHeight < wh) { ws = altitudeRate; if ((speed.y > 0.0001f) && (((wh - curRelHeight) / speed.y) * accRate * 1.5f) < speed.y) { ws = 0.0f; } } else { ws = -altitudeRate; if ((speed.y < -0.0001f) && (((wh - curRelHeight) / speed.y) * accRate * 0.7f) < -speed.y) { ws = 0.0f; } } if (!owner->beingBuilt) { if (math::fabs(wh - curRelHeight) > 2.0f) { if (speed.y > ws) { speed.y = std::max(ws, speed.y - accRate * 1.5f); } else { // accelerate upward faster if close to ground speed.y = std::min(ws, speed.y + accRate * ((curRelHeight < 20.0f)? 2.0f: 0.7f)); } } else { speed.y *= 0.95; } } if (modInfo.allowAircraftToLeaveMap || (pos + speed).IsInBounds()) { owner->Move3D(speed, true); } }
void AAirMoveType::CheckForCollision() { if (!collide) return; const SyncedFloat3& pos = owner->midPos; const SyncedFloat3& forward = owner->frontdir; const float3 midTestPos = pos + forward * 121.0f; const std::vector<CUnit*>& others = quadField->GetUnitsExact(midTestPos, 115.0f); float dist = 200.0f; if (lastColWarning) { DeleteDeathDependence(lastColWarning, DEPENDENCE_LASTCOLWARN); lastColWarning = NULL; lastColWarningType = 0; } for (CUnit* unit: others) { if (unit == owner || !unit->unitDef->canfly) { continue; } const SyncedFloat3& op = unit->midPos; const float3 dif = op - pos; const float3 forwardDif = forward * (forward.dot(dif)); if (forwardDif.SqLength() >= (dist * dist)) { continue; } const float3 ortoDif = dif - forwardDif; const float frontLength = forwardDif.Length(); // note: radii are multiplied by two const float minOrtoDif = (unit->radius + owner->radius) * 2.0f + frontLength * 0.1f + 10; if (ortoDif.SqLength() < (minOrtoDif * minOrtoDif)) { dist = frontLength; lastColWarning = const_cast<CUnit*>(unit); } } if (lastColWarning != NULL) { lastColWarningType = 2; AddDeathDependence(lastColWarning, DEPENDENCE_LASTCOLWARN); return; } for (CUnit* u: others) { if (u == owner) continue; if ((u->midPos - pos).SqLength() < (dist * dist)) { lastColWarning = u; } } if (lastColWarning != NULL) { lastColWarningType = 1; AddDeathDependence(lastColWarning, DEPENDENCE_LASTCOLWARN); } }
/** * @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 CPlasmaRepulser::Update(void) { const int defHitFrames = weaponDef->visibleShieldHitFrames; const bool couldBeVisible = (weaponDef->visibleShield || (defHitFrames > 0)); const int defRechargeDelay = weaponDef->shieldRechargeDelay; rechargeDelay -= (rechargeDelay > 0) ? 1 : 0; if (startShowingShield) { // one-time iteration when shield first goes online // (adds the projectile parts, this assumes owner is // not mobile) startShowingShield = false; if (couldBeVisible) { // 32 parts for (int y = 0; y < 16; y += 4) { for (int x = 0; x < 32; x += 4) { visibleShieldParts.push_back( new CShieldPartProjectile(owner->pos, x, y, radius, weaponDef->shieldBadColor, weaponDef->shieldAlpha, weaponDef->visuals.texture1, owner) ); } } } } if (isEnabled && (curPower < weaponDef->shieldPower) && rechargeDelay <= 0) { if (owner->UseEnergy(weaponDef->shieldPowerRegenEnergy * (1.0f / 30.0f))) { curPower += weaponDef->shieldPowerRegen * (1.0f / 30.0f); } } weaponPos = owner->pos + (owner->frontdir * relWeaponPos.z) + (owner->updir * relWeaponPos.y) + (owner->rightdir * relWeaponPos.x); if (couldBeVisible) { float drawAlpha = 0.0f; if (hitFrames > 0) { drawAlpha += float(hitFrames) / float(defHitFrames); hitFrames--; } if (weaponDef->visibleShield) { drawAlpha += 1.0f; } drawAlpha = std::min(1.0f, drawAlpha * weaponDef->shieldAlpha); const bool drawMe = (drawAlpha > 0.0f); if (drawMe || wasDrawn) { const float colorMix = std::min(1.0f, curPower / std::max(1.0f, weaponDef->shieldPower)); const float3 color = (weaponDef->shieldGoodColor * colorMix) + (weaponDef->shieldBadColor * (1.0f - colorMix)); std::list<CShieldPartProjectile*>::iterator si; for (si = visibleShieldParts.begin(); si != visibleShieldParts.end(); ++si) { CShieldPartProjectile* part = *si; part->centerPos = weaponPos; part->color = color; if (isEnabled) { part->baseAlpha = drawAlpha; } else { part->baseAlpha = 0.0f; } } } wasDrawn = drawMe; } if (isEnabled) { for (std::list<CWeaponProjectile*>::iterator pi=incoming.begin();pi!=incoming.end();++pi) { const float3 dif = (*pi)->pos-owner->pos; if ((*pi)->checkCol && dif.SqLength()<sqRadius && curPower > (*pi)->weaponDef->damages[0]) { if (teamHandler->Team(owner->team)->energy > weaponDef->shieldEnergyUse) { rechargeDelay = defRechargeDelay; if (weaponDef->shieldRepulser) { // bounce the projectile const int type = (*pi)->ShieldRepulse(this, weaponPos, weaponDef->shieldForce, weaponDef->shieldMaxSpeed); if (type == 0) { continue; } else if (type == 1) { owner->UseEnergy(weaponDef->shieldEnergyUse); if (weaponDef->shieldPower != 0) { curPower -= (*pi)->weaponDef->damages[0]; } } else { owner->UseEnergy(weaponDef->shieldEnergyUse / 30.0f); if (weaponDef->shieldPower != 0) { curPower -= (*pi)->weaponDef->damages[0] / 30.0f; } } if (weaponDef->visibleShieldRepulse) { std::list<CWeaponProjectile*>::iterator i; for (i=hasGfx.begin();i!=hasGfx.end();i++) if (*i==*pi) { break; } if (i == hasGfx.end()) { hasGfx.insert(hasGfx.end(),*pi); const float colorMix = std::min(1.0f, curPower / std::max(1.0f, weaponDef->shieldPower)); const float3 color = (weaponDef->shieldGoodColor * colorMix) + (weaponDef->shieldBadColor * (1.0f - colorMix)); new CRepulseGfx(owner, *pi, radius, color); } } if (defHitFrames > 0) { hitFrames = defHitFrames; } } else { // kill the projectile if (owner->UseEnergy(weaponDef->shieldEnergyUse)) { if (weaponDef->shieldPower != 0) { curPower -= (*pi)->weaponDef->damages[0]; } (*pi)->Collision(owner); if (defHitFrames > 0) { hitFrames = defHitFrames; } } } } else { // Calculate the amount of energy we wanted to pull /* Domipheus: TODO Commented out for now, ShieldRepulse has side effects, design needs altering. if(weaponDef->shieldRepulser) { //bounce the projectile int type=(*pi)->ShieldRepulse(this,weaponPos,weaponDef->shieldForce,weaponDef->shieldMaxSpeed); if (type==1){ teamHandler->Team(owner->team)->energyPullAmount += weaponDef->shieldEnergyUse; } else { teamHandler->Team(owner->team)->energyPullAmount += weaponDef->shieldEnergyUse/30.0f; } } else { //kill the projectile teamHandler->Team(owner->team)->energyPullAmount += weaponDef->shieldEnergyUse; }*/ } } } } }
void CPlasmaRepulser::Update(void) { const int defHitFrames = weaponDef->visibleShieldHitFrames; const bool couldBeVisible = (weaponDef->visibleShield || (defHitFrames > 0)); if (startShowingShield) { startShowingShield = false; if (couldBeVisible) { // 32 parts for (int y = 0; y < 16; y += 4) { for (int x = 0; x < 32; x += 4) { visibleShieldParts.push_back( SAFE_NEW CShieldPartProjectile(owner->pos, x, y, radius, weaponDef->shieldBadColor, weaponDef->shieldAlpha, weaponDef->visuals.texture1, owner) ); } } } } if (isEnabled && (curPower < weaponDef->shieldPower)) { if (owner->UseEnergy(weaponDef->shieldPowerRegenEnergy * (1.0f / 30.0f))) { curPower += weaponDef->shieldPowerRegen * (1.0f / 30.0f); } } weaponPos = owner->pos + (owner->frontdir * relWeaponPos.z) + (owner->updir * relWeaponPos.y) + (owner->rightdir * relWeaponPos.x); if (couldBeVisible) { float drawAlpha = 0.0f; const int oldFrames = hitFrames; if (hitFrames > 0) { drawAlpha += float(hitFrames) / float(defHitFrames); hitFrames--; } if ((isEnabled != wasEnabled) || (hitFrames != oldFrames) || (curPower != lastPower)) { if (weaponDef->visibleShield) { drawAlpha += 1.0f; } drawAlpha = min(1.0f, drawAlpha * weaponDef->shieldAlpha); const float colorMix = min(1.0f, curPower / max(1.0f, weaponDef->shieldPower)); const float3 color = (weaponDef->shieldGoodColor * colorMix) + (weaponDef->shieldBadColor * (1.0f - colorMix)); std::list<CShieldPartProjectile*>::iterator si; for (si = visibleShieldParts.begin(); si != visibleShieldParts.end(); ++si) { (*si)->centerPos = weaponPos; (*si)->color = color; if (isEnabled) { (*si)->baseAlpha = drawAlpha; } else { (*si)->baseAlpha = 0.0f; } } } } if (isEnabled) { for (std::list<CWeaponProjectile*>::iterator pi=incoming.begin();pi!=incoming.end();++pi) { const float3 dif = (*pi)->pos-owner->pos; if ((*pi)->checkCol && dif.SqLength()<sqRadius && curPower > (*pi)->weaponDef->damages[0]) { if (gs->Team(owner->team)->energy > weaponDef->shieldEnergyUse) { if (weaponDef->shieldRepulser) { // bounce the projectile const int type = (*pi)->ShieldRepulse(this, weaponPos, weaponDef->shieldForce, weaponDef->shieldMaxSpeed); if (type == 0) { continue; } else if (type == 1) { owner->UseEnergy(weaponDef->shieldEnergyUse); if (weaponDef->shieldPower != 0) { curPower -= (*pi)->weaponDef->damages[0]; } } else { owner->UseEnergy(weaponDef->shieldEnergyUse / 30.0f); if (weaponDef->shieldPower != 0) { curPower -= (*pi)->weaponDef->damages[0] / 30.0f; } } if (weaponDef->visibleShieldRepulse) { if (hasGfx.find(*pi) == hasGfx.end()) { hasGfx.insert(*pi); const float colorMix = min(1.0f, curPower / max(1.0f, weaponDef->shieldPower)); const float3 color = (weaponDef->shieldGoodColor * colorMix) + (weaponDef->shieldBadColor * (1.0f - colorMix)); SAFE_NEW CRepulseGfx(owner, *pi, radius, color); } } if (defHitFrames > 0) { hitFrames = defHitFrames; } } else { // kill the projectile if (owner->UseEnergy(weaponDef->shieldEnergyUse)) { if (weaponDef->shieldPower != 0) { curPower -= (*pi)->weaponDef->damages[0]; } (*pi)->Collision(owner); if (defHitFrames > 0) { hitFrames = defHitFrames; } } } } else { // Calculate the amount of energy we wanted to pull /* Domipheus: TODO Commented out for now, ShieldRepulse has side effects, design needs altering. if(weaponDef->shieldRepulser) { //bounce the projectile int type=(*pi)->ShieldRepulse(this,weaponPos,weaponDef->shieldForce,weaponDef->shieldMaxSpeed); if (type==1){ gs->Team(owner->team)->energyPullAmount += weaponDef->shieldEnergyUse; } else { gs->Team(owner->team)->energyPullAmount += weaponDef->shieldEnergyUse/30.0f; } } else { //kill the projectile gs->Team(owner->team)->energyPullAmount += weaponDef->shieldEnergyUse; }*/ } } } } lastPower = curPower; wasEnabled = isEnabled; }
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); } }