void CWeaponProjectile::UpdateInterception() { if (target == NULL) return; CWeaponProjectile* po = dynamic_cast<CWeaponProjectile*>(target); if (po == NULL) return; // we are the interceptor, point us toward the interceptee pos each frame // (normally not needed, subclasses handle it directly in their Update()'s // *until* our owner dies) if (owner() == NULL) { targetPos = po->pos + po->speed; } if (hitscan) { if (ClosestPointOnLine(startPos, targetPos, po->pos).SqDistance(po->pos) < Square(weaponDef->collisionSize)) { po->Collision(); Collision(); } } else { // FIXME: if (pos.SqDistance(po->pos) < Square(weaponDef->collisionSize)) { if (pos.SqDistance(po->pos) < Square(damages->damageAreaOfEffect)) { po->Collision(); Collision(); } } }
void CProjectileHandler::CheckShieldCollisions( CProjectile* p, std::vector<CPlasmaRepulser*>& tempRepulsers, const float3 ppos0, const float3 ppos1) { if (!p->checkCol) return; if (!p->weapon) return; CWeaponProjectile* wpro = static_cast<CWeaponProjectile*>(p); const WeaponDef* wdef = wpro->GetWeaponDef(); //Bail early if (wdef->interceptedByShieldType == 0) return; CollisionQuery cq; for (CPlasmaRepulser* repulser: tempRepulsers) { assert(repulser != nullptr); if (!repulser->CanIntercept(wdef->interceptedByShieldType, p->GetAllyteamID())) continue; if (CCollisionHandler::DetectHit(repulser->owner, &repulser->collisionVolume, repulser->owner->GetTransformMatrix(true), ppos0, ppos1, &cq)) { if (!cq.InsideHit() || !repulser->weaponDef->exteriorShield || repulser->IsRepulsing(wpro)) { if (repulser->IncomingProjectile(wpro)) return; } } } }
void CProjectileHandler::CheckShieldCollisions( CProjectile* p, std::vector<CPlasmaRepulser*>& tempRepulsers, const float3 ppos0, const float3 ppos1) { if (!p->checkCol) return; if (!p->weapon) return; CWeaponProjectile* wpro = static_cast<CWeaponProjectile*>(p); const WeaponDef* wdef = wpro->GetWeaponDef(); //Bail early if (wdef->interceptedByShieldType == 0) return; CollisionQuery cq; for (CPlasmaRepulser* repulser: tempRepulsers) { assert(repulser != nullptr); if (!repulser->CanIntercept(wdef->interceptedByShieldType, p->GetAllyteamID())) continue; // we sometimes get false inside hits due to the movement of the shield // a very hacky solution is to increase the ray that's intersecting // by the last movement of the shield. // it's not 100% accurate so there's a bit of a FIXME here to do a real solution // (keep track in the projectile which shields it's in) const float3 effectivePPos0 = ppos0 + (ppos0 - ppos1) * repulser->deltaPos.Length(); if (CCollisionHandler::DetectHit(repulser->owner, &repulser->collisionVolume, repulser->owner->GetTransformMatrix(true), effectivePPos0, ppos1, &cq)) { if (!cq.InsideHit() || !repulser->weaponDef->exteriorShield || repulser->IsRepulsing(wpro)) { if (repulser->IncomingProjectile(wpro, cq.GetHitPos())) return; } } } }
CWeaponProjectile::CWeaponProjectile(const ProjectileParams& params) : CProjectile(params.pos, params.speed, params.owner, true, true, false, false) , damages(nullptr) , weaponDef(params.weaponDef) , target(params.target) , ttl(params.ttl) , bounces(0) , targeted(false) , startPos(params.pos) , targetPos(params.end) { projectileType = WEAPON_BASE_PROJECTILE; assert(weaponDef != nullptr); if (weaponDef->IsHitScanWeapon()) { hitscan = true; // the else-case (default) is handled in CProjectile::Init // // ray projectiles must all set this to false because their collision // detection is handled by the weapons firing them, ProjectileHandler // will skip any tests for these checkCol = false; // type has not yet been set by derived ctor's at this point // useAirLos = (projectileType != WEAPON_LIGHTNING_PROJECTILE); useAirLos = true; // NOTE: // {BeamLaser, Lightning}Projectile's do NOT actually move (their // speed is never added to pos) and never alter their speed either // they additionally override our ::Update (so CProjectile::Update // is also never called) which means assigning speed a non-zerovec // value should have no side-effects SetPosition(startPos); SetVelocityAndSpeed(targetPos - startPos); // ProjectileDrawer vis-culls by pos == startPos, but we // want to see the beam even if camera is near targetPos // --> use full distance for drawRadius SetRadiusAndHeight((targetPos - startPos).Length(), 0.0f); } collisionFlags = weaponDef->collisionFlags; weaponNum = params.weaponNum; alwaysVisible = weaponDef->visuals.alwaysVisible; ignoreWater = weaponDef->waterweapon; CSolidObject* so = NULL; CWeaponProjectile* po = NULL; if ((so = dynamic_cast<CSolidObject*>(target)) != NULL) { AddDeathDependence(so, DEPENDENCE_WEAPONTARGET); } if ((po = dynamic_cast<CWeaponProjectile*>(target)) != NULL) { po->SetBeingIntercepted(po->IsBeingIntercepted() || weaponDef->interceptSolo); AddDeathDependence(po, DEPENDENCE_INTERCEPTTARGET); } if (params.model != NULL) { model = params.model; } else { model = weaponDef->LoadModel(); } if (params.owner == NULL) { // the else-case (default) is handled in CProjectile::Init ownerID = params.ownerID; teamID = params.teamID; allyteamID = teamHandler->IsValidTeam(teamID)? teamHandler->AllyTeam(teamID): -1; } if (ownerID != -1u && weaponNum != -1u) { const CUnit* owner = unitHandler->GetUnit(ownerID); if (owner != nullptr && weaponNum < owner->weapons.size()) { damages = DynDamageArray::IncRef(owner->weapons[weaponNum]->damages); } } if (damages == nullptr) damages = DynDamageArray::IncRef(&weaponDef->damages); if (params.cegID != -1u) { cegID = params.cegID; } else { cegID = weaponDef->ptrailExplosionGeneratorID; } // must happen after setting position and velocity projectileHandler->AddProjectile(this); quadField->AddProjectile(this); ASSERT_SYNCED(id); if (weaponDef->targetable) { interceptHandler.AddInterceptTarget(this, targetPos); } }
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 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) { (*si)->Actualize(weaponPos, color, isEnabled ? drawAlpha : 0.0f); } } wasDrawn = drawMe; } if (!isEnabled) { return; } for (std::list<CWeaponProjectile*>::iterator pi = incoming.begin(); pi != incoming.end(); ++pi) { CWeaponProjectile* pro = *pi; if (!pro->checkCol) { continue; } if ((pro->pos - owner->pos).SqLength() > sqRadius) { // projectile does not hit the shield, don't touch it continue; } if (luaRules && luaRules->ShieldPreDamaged(pro, this, owner, weaponDef->shieldRepulser)) { // gadget handles the collision event, don't touch the projectile continue; } if (curPower < pro->weaponDef->damages[0]) { // shield does not have enough power, don't touch the projectile continue; } if (teamHandler->Team(owner->team)->energy < weaponDef->shieldEnergyUse) { // team does not have enough energy, don't touch the projectile continue; } rechargeDelay = defRechargeDelay; if (weaponDef->shieldRepulser) { // bounce the projectile const int type = pro->ShieldRepulse(this, weaponPos, weaponDef->shieldForce, weaponDef->shieldMaxSpeed); if (type == 0) { continue; } else if (type == 1) { owner->UseEnergy(weaponDef->shieldEnergyUse); if (weaponDef->shieldPower != 0) { curPower -= pro->weaponDef->damages[0]; } } else { owner->UseEnergy(weaponDef->shieldEnergyUse / 30.0f); if (weaponDef->shieldPower != 0) { curPower -= pro->weaponDef->damages[0] / 30.0f; } } if (weaponDef->visibleShieldRepulse) { std::list<CWeaponProjectile*>::iterator i; for (i = hasGfx.begin(); i != hasGfx.end(); ++i) { if (*i == pro) { break; } } if (i == hasGfx.end()) { hasGfx.insert(hasGfx.end(), pro); 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, pro, radius, color); } } if (defHitFrames > 0) { hitFrames = defHitFrames; } } else { // kill the projectile if (owner->UseEnergy(weaponDef->shieldEnergyUse)) { if (weaponDef->shieldPower != 0) { curPower -= pro->weaponDef->damages[0]; } pro->Collision(owner); if (defHitFrames > 0) { hitFrames = defHitFrames; } } } } }
void CPlasmaRepulser::Update() { const int defHitFrames = weaponDef->visibleShieldHitFrames; const int defRechargeDelay = weaponDef->shieldRechargeDelay; rechargeDelay -= (rechargeDelay > 0) ? 1 : 0; if (isEnabled && (curPower < weaponDef->shieldPower) && rechargeDelay <= 0) { if (owner->UseEnergy(weaponDef->shieldPowerRegenEnergy * (1.0f / GAME_SPEED))) { curPower += weaponDef->shieldPowerRegen * (1.0f / GAME_SPEED); } } if (hitFrames > 0) hitFrames--; weaponPos = owner->pos + (owner->frontdir * relWeaponPos.z) + (owner->updir * relWeaponPos.y) + (owner->rightdir * relWeaponPos.x); if (!isEnabled) { return; } for (std::map<int, CWeaponProjectile*>::iterator pi = incomingProjectiles.begin(); pi != incomingProjectiles.end(); ++pi) { assert(ph->GetMapPairBySyncedID(pi->first)); // valid projectile id? CWeaponProjectile* pro = pi->second; const WeaponDef* proWd = pro->weaponDef; if (!pro->checkCol) { continue; } if ((pro->pos - owner->pos).SqLength() > sqRadius) { // projectile does not hit the shield, don't touch it continue; } if (luaRules && luaRules->ShieldPreDamaged(pro, this, owner, weaponDef->shieldRepulser)) { // gadget handles the collision event, don't touch the projectile continue; } if (curPower < proWd->damages[0]) { // shield does not have enough power, don't touch the projectile continue; } if (teamHandler->Team(owner->team)->energy < weaponDef->shieldEnergyUse) { // team does not have enough energy, don't touch the projectile continue; } rechargeDelay = defRechargeDelay; if (weaponDef->shieldRepulser) { // bounce the projectile const int type = pro->ShieldRepulse(this, weaponPos, weaponDef->shieldForce, weaponDef->shieldMaxSpeed); if (type == 0) { continue; } else if (type == 1) { owner->UseEnergy(weaponDef->shieldEnergyUse); if (weaponDef->shieldPower != 0) { //FIXME some weapons do range dependent damage! (mantis #2345) curPower -= proWd->damages[0]; } } else { //FIXME why do all weapons except LASERs do only (1 / GAME_SPEED) damage??? owner->UseEnergy(weaponDef->shieldEnergyUse / GAME_SPEED); if (weaponDef->shieldPower != 0) { curPower -= proWd->damages[0] / GAME_SPEED; } } if (weaponDef->visibleShieldRepulse) { bool newlyAdded = hasGfx.insert(pro).second; if (newlyAdded) { 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, pro, radius, color); } } if (defHitFrames > 0) { hitFrames = defHitFrames; } } else { // kill the projectile if (owner->UseEnergy(weaponDef->shieldEnergyUse)) { if (weaponDef->shieldPower != 0) { //FIXME some weapons do range dependent damage! (mantis #2345) curPower -= proWd->damages[0]; } pro->Collision(owner); if (defHitFrames > 0) { hitFrames = defHitFrames; } } } } }