void CTeam::SelfDestruct() { for (CUnitSet::iterator ui = units.begin(); ui != units.end(); ++ui) { CUnit* unit = (*ui); if (unit != NULL && unit->unitDef->canSelfD) { if (unit->beingBuilt) { unit->KillUnit(false, true, NULL); // kill units under construction without explosion } else { unit->KillUnit(true, false, NULL); } } } Died(); }
void CTransportUnit::KillUnit(bool selfDestruct, bool reclaimed, CUnit* attacker, bool) { std::list<TransportedUnit>::iterator ti; for (ti = transported.begin(); ti != transported.end(); ++ti) { CUnit* u = ti->unit; u->transporter = 0; u->DeleteDeathDependence(this); // prevent a position teleport on the next movetype update if // the transport died in a place that the unit being carried // could not get to on its own if (!u->pos.IsInBounds()) { u->KillUnit(false, false, 0x0, false); continue; } else { const float gh = ground->GetHeight2(u->pos.x, u->pos.z); if (gh < -u->unitDef->maxWaterDepth || gh > -u->unitDef->minWaterDepth) { // note: should also check movedef restraints? u->KillUnit(false, false, 0x0, false); continue; } } if (!unitDef->releaseHeld) { if (!selfDestruct) { // we don't want it to leave a corpse u->DoDamage(DamageArray() * 1000000, 0, ZeroVector); } u->KillUnit(selfDestruct, reclaimed, attacker); } else { u->stunned = (u->paralyzeDamage > u->health); if (CGroundMoveType* mt = dynamic_cast<CGroundMoveType*>(u->moveType)) { mt->StartFlying(); } u->speed = speed; eventHandler.UnitUnloaded(u, this); } } CUnit::KillUnit(selfDestruct, reclaimed, attacker); }
int CUnitScript::GetUnitVal(int val, int p1, int p2, int p3, int p4) { // may happen in case one uses Spring.GetUnitCOBValue (Lua) on a unit with CNullUnitScript if (!unit) { ShowScriptError("Error: no unit (in GetUnitVal)"); return 0; } #ifndef _CONSOLE switch (val) { case ACTIVATION: if (unit->activated) return 1; else return 0; break; case STANDINGMOVEORDERS: return unit->moveState; break; case STANDINGFIREORDERS: return unit->fireState; break; case HEALTH: { if (p1 <= 0) return int((unit->health / unit->maxHealth) * 100.0f); const CUnit* u = uh->GetUnit(p1); if (u == NULL) return 0; else return int((u->health / u->maxHealth) * 100.0f); } case INBUILDSTANCE: if (unit->inBuildStance) return 1; else return 0; case BUSY: if (busy) return 1; else return 0; break; case PIECE_XZ: { if (!PieceExists(p1)) { ShowScriptError("Invalid piecenumber for get piece_xz"); break; } const float3 relPos = GetPiecePos(p1); const float3 absPos = unit->pos + unit->frontdir * relPos.z + unit->updir * relPos.y + unit->rightdir * relPos.x; return PACKXZ(absPos.x, absPos.z); } case PIECE_Y: { if (!PieceExists(p1)) { ShowScriptError("Invalid piecenumber for get piece_y"); break; } const float3 relPos = GetPiecePos(p1); const float3 absPos = unit->pos + unit->frontdir * relPos.z + unit->updir * relPos.y + unit->rightdir * relPos.x; return int(absPos.y * COBSCALE); } case UNIT_XZ: { if (p1 <= 0) return PACKXZ(unit->pos.x, unit->pos.z); const CUnit* u = uh->GetUnit(p1); if (u == NULL) return PACKXZ(0, 0); else return PACKXZ(u->pos.x, u->pos.z); } case UNIT_Y: { if (p1 <= 0) return int(unit->pos.y * COBSCALE); const CUnit* u = uh->GetUnit(p1); if (u == NULL) return 0; else return int(u->pos.y * COBSCALE); } case UNIT_HEIGHT: { if (p1 <= 0) return int(unit->radius * COBSCALE); const CUnit* u = uh->GetUnit(p1); if (u == NULL) return 0; else return int(u->radius * COBSCALE); } case XZ_ATAN: return int(RAD2TAANG*math::atan2((float)UNPACKX(p1), (float)UNPACKZ(p1)) + 32768 - unit->heading); case XZ_HYPOT: return int(math::hypot((float)UNPACKX(p1), (float)UNPACKZ(p1)) * COBSCALE); case ATAN: return int(RAD2TAANG*math::atan2((float)p1, (float)p2)); case HYPOT: return int(math::hypot((float)p1, (float)p2)); case GROUND_HEIGHT: return int(ground->GetHeightAboveWater(UNPACKX(p1), UNPACKZ(p1)) * COBSCALE); case GROUND_WATER_HEIGHT: return int(ground->GetHeightReal(UNPACKX(p1), UNPACKZ(p1)) * COBSCALE); case BUILD_PERCENT_LEFT: return int((1.0f - unit->buildProgress) * 100); case YARD_OPEN: if (yardOpen) return 1; else return 0; case BUGGER_OFF: break; case ARMORED: if (unit->armoredState) return 1; else return 0; case VETERAN_LEVEL: return int(100 * unit->experience); case CURRENT_SPEED: return int(unit->speed.Length() * COBSCALE); case ON_ROAD: return 0; case IN_WATER: return (unit->pos.y < 0.0f) ? 1 : 0; case MAX_ID: return uh->MaxUnits()-1; case MY_ID: return unit->id; case UNIT_TEAM: { const CUnit* u = uh->GetUnit(p1); return (u != NULL)? unit->team : 0; } case UNIT_ALLIED: { const CUnit* u = uh->GetUnit(p1); if (u != NULL) { return teamHandler->Ally(unit->allyteam, u->allyteam) ? 1 : 0; } return 0; } case UNIT_BUILD_PERCENT_LEFT: { const CUnit* u = uh->GetUnit(p1); if (u != NULL) { return int((1.0f - u->buildProgress) * 100); } return 0; } case MAX_SPEED: { return int(unit->moveType->GetMaxSpeed() * COBSCALE); } break; case REVERSING: { CGroundMoveType* gmt = dynamic_cast<CGroundMoveType*>(unit->moveType); return ((gmt != NULL)? int(gmt->IsReversing()): 0); } break; case CLOAKED: return !!unit->isCloaked; case WANT_CLOAK: return !!unit->wantCloak; case UPRIGHT: return !!unit->upright; case POW: return int(math::pow(((float)p1)/COBSCALE,((float)p2)/COBSCALE)*COBSCALE); case PRINT: LOG("Value 1: %d, 2: %d, 3: %d, 4: %d", p1, p2, p3, p4); break; case HEADING: { if (p1 <= 0) { return unit->heading; } const CUnit* u = uh->GetUnit(p1); if (u != NULL) { return u->heading; } return -1; } case TARGET_ID: { if (unit->weapons[p1 - 1]) { const CWeapon* weapon = unit->weapons[p1 - 1]; const TargetType tType = weapon->targetType; if (tType == Target_Unit) return unit->weapons[p1 - 1]->targetUnit->id; else if (tType == Target_None) return -1; else if (tType == Target_Pos) return -2; else // Target_Intercept return -3; } return -4; // weapon does not exist } case LAST_ATTACKER_ID: return unit->lastAttacker? unit->lastAttacker->id: -1; case LOS_RADIUS: return unit->realLosRadius; case AIR_LOS_RADIUS: return unit->realAirLosRadius; case RADAR_RADIUS: return unit->radarRadius; case JAMMER_RADIUS: return unit->jammerRadius; case SONAR_RADIUS: return unit->sonarRadius; case SONAR_JAM_RADIUS: return unit->sonarJamRadius; case SEISMIC_RADIUS: return unit->seismicRadius; case DO_SEISMIC_PING: float pingSize; if (p1 == 0) { pingSize = unit->seismicSignature; } else { pingSize = p1; } unit->DoSeismicPing(pingSize); break; case CURRENT_FUEL: return int(unit->currentFuel * float(COBSCALE)); case TRANSPORT_ID: return unit->transporter?unit->transporter->id:-1; case SHIELD_POWER: { if (unit->shieldWeapon == NULL) { return -1; } const CPlasmaRepulser* shield = (CPlasmaRepulser*) unit->shieldWeapon; return int(shield->curPower * float(COBSCALE)); } case STEALTH: { return unit->stealth ? 1 : 0; } case SONAR_STEALTH: { return unit->sonarStealth ? 1 : 0; } case CRASHING: return !!unit->crashing; case ALPHA_THRESHOLD: { return int(unit->alphaThreshold * 255); } case COB_ID: { if (p1 <= 0) { return unit->unitDef->cobID; } else { const CUnit* u = uh->GetUnit(p1); return ((u == NULL)? -1 : u->unitDef->cobID); } } case PLAY_SOUND: { // FIXME: this can currently only work for CCobInstance, because Lua can not get sound IDs // (however, for Lua scripts there is already LuaUnsyncedCtrl::PlaySoundFile) CCobInstance* cob = dynamic_cast<CCobInstance*>(this); if (cob == NULL) { return 1; } const CCobFile* script = cob->GetScriptAddr(); if (script == NULL) { return 1; } if ((p1 < 0) || (static_cast<size_t>(p1) >= script->sounds.size())) { return 1; } switch (p3) { //who hears the sound case 0: //ALOS if (!loshandler->InAirLos(unit->pos,gu->myAllyTeam)) { return 0; } break; case 1: //LOS if (!(unit->losStatus[gu->myAllyTeam] & LOS_INLOS)) { return 0; } break; case 2: //ALOS or radar if (!(loshandler->InAirLos(unit->pos,gu->myAllyTeam) || unit->losStatus[gu->myAllyTeam] & (LOS_INRADAR))) { return 0; } break; case 3: //LOS or radar if (!(unit->losStatus[gu->myAllyTeam] & (LOS_INLOS | LOS_INRADAR))) { return 0; } break; case 4: //everyone break; case 5: //allies if (unit->allyteam != gu->myAllyTeam) { return 0; } break; case 6: //team if (unit->team != gu->myTeam) { return 0; } break; case 7: //enemies if (unit->allyteam == gu->myAllyTeam) { return 0; } break; } if (p4 == 0) { Channels::General.PlaySample(script->sounds[p1], unit->pos, unit->speed, float(p2) / COBSCALE); } else { Channels::General.PlaySample(script->sounds[p1], float(p2) / COBSCALE); } return 0; } case SET_WEAPON_UNIT_TARGET: { const unsigned int weaponID = p1 - 1; const unsigned int targetID = p2; const bool userTarget = !!p3; if (weaponID >= unit->weapons.size()) { return 0; } CWeapon* weapon = unit->weapons[weaponID]; if (weapon == NULL) { return 0; } //! if targetID is 0, just sets weapon->haveUserTarget //! to false (and targetType to None) without attacking CUnit* target = (targetID > 0)? uh->GetUnit(targetID): NULL; return (weapon->AttackUnit(target, userTarget) ? 1 : 0); } case SET_WEAPON_GROUND_TARGET: { const int weaponID = p1 - 1; const float3 pos = float3(float(UNPACKX(p2)), float(p3) / float(COBSCALE), float(UNPACKZ(p2))); const bool userTarget = !!p4; if ((weaponID < 0) || (static_cast<size_t>(weaponID) >= unit->weapons.size())) { return 0; } CWeapon* weapon = unit->weapons[weaponID]; if (weapon == NULL) { return 0; } return weapon->AttackGround(pos, userTarget) ? 1 : 0; } case MIN: return std::min(p1, p2); case MAX: return std::max(p1, p2); case ABS: return abs(p1); case KSIN: return int(1024*math::sinf(TAANG2RAD*(float)p1)); case KCOS: return int(1024*math::cosf(TAANG2RAD*(float)p1)); case KTAN: return int(1024*math::tanf(TAANG2RAD*(float)p1)); case SQRT: return int(math::sqrt((float)p1)); case FLANK_B_MODE: return unit->flankingBonusMode; case FLANK_B_DIR: switch (p1) { case 1: return int(unit->flankingBonusDir.x * COBSCALE); case 2: return int(unit->flankingBonusDir.y * COBSCALE); case 3: return int(unit->flankingBonusDir.z * COBSCALE); case 4: unit->flankingBonusDir.x = (p2/(float)COBSCALE); return 0; case 5: unit->flankingBonusDir.y = (p2/(float)COBSCALE); return 0; case 6: unit->flankingBonusDir.z = (p2/(float)COBSCALE); return 0; case 7: unit->flankingBonusDir = float3(p2/(float)COBSCALE, p3/(float)COBSCALE, p4/(float)COBSCALE).Normalize(); return 0; default: return(-1); } case FLANK_B_MOBILITY_ADD: return int(unit->flankingBonusMobilityAdd * COBSCALE); case FLANK_B_MAX_DAMAGE: return int((unit->flankingBonusAvgDamage + unit->flankingBonusDifDamage) * COBSCALE); case FLANK_B_MIN_DAMAGE: return int((unit->flankingBonusAvgDamage - unit->flankingBonusDifDamage) * COBSCALE); case KILL_UNIT: { //! ID 0 is reserved for the script's owner CUnit* u = (p1 > 0)? uh->GetUnit(p1): this->unit; if (u == NULL) { return 0; } if (u->beingBuilt) { // no explosions and no corpse for units under construction u->KillUnit(false, true, NULL); } else { u->KillUnit(p2 != 0, p3 != 0, NULL); } return 1; } case WEAPON_RELOADSTATE: { const int np1 = -p1; if (p1 > 0 && static_cast<size_t>(p1) <= unit->weapons.size()) { return unit->weapons[p1-1]->reloadStatus; } else if (np1 > 0 && static_cast<size_t>(np1) <= unit->weapons.size()) { const int old = unit->weapons[np1 - 1]->reloadStatus; unit->weapons[np1 - 1]->reloadStatus = p2; return old; } else { return -1; } } case WEAPON_RELOADTIME: { const int np1 = -p1; if (p1 > 0 && static_cast<size_t>(p1) <= unit->weapons.size()) { return unit->weapons[p1-1]->reloadTime; } else if (np1 > 0 && static_cast<size_t>(np1) <= unit->weapons.size()) { const int old = unit->weapons[np1 - 1]->reloadTime; unit->weapons[np1 - 1]->reloadTime = p2; return old; } else { return -1; } } case WEAPON_ACCURACY: { const int np1 = -p1; if (p1 > 0 && static_cast<size_t>(p1) <= unit->weapons.size()) { return int(unit->weapons[p1-1]->accuracy * COBSCALE); } else if (np1 > 0 && static_cast<size_t>(np1) <= unit->weapons.size()) { const int old = unit->weapons[np1 - 1]->accuracy * COBSCALE; unit->weapons[np1 - 1]->accuracy = float(p2) / COBSCALE; return old; } else { return -1; } } case WEAPON_SPRAY: { const int np1 = -p1; if (p1 > 0 && static_cast<size_t>(p1) <= unit->weapons.size()) { return int(unit->weapons[p1-1]->sprayAngle * COBSCALE); } else if (np1 > 0 && static_cast<size_t>(np1) <= unit->weapons.size()) { const int old = unit->weapons[np1 - 1]->sprayAngle * COBSCALE; unit->weapons[np1 - 1]->sprayAngle = float(p2) / COBSCALE; return old; } else { return -1; } } case WEAPON_RANGE: { const int np1 = -p1; if (p1 > 0 && static_cast<size_t>(p1) <= unit->weapons.size()) { return int(unit->weapons[p1 - 1]->range * COBSCALE); } else if (np1 > 0 && static_cast<size_t>(np1) <= unit->weapons.size()) { const int old = unit->weapons[np1 - 1]->range * COBSCALE; unit->weapons[np1 - 1]->range = float(p2) / COBSCALE; return old; } else { return -1; } } case WEAPON_PROJECTILE_SPEED: { const int np1 = -p1; if (p1 > 0 && static_cast<size_t>(p1) <= unit->weapons.size()) { return int(unit->weapons[p1-1]->projectileSpeed * COBSCALE); } else if (np1 > 0 && static_cast<size_t>(np1) <= unit->weapons.size()) { const int old = unit->weapons[np1 - 1]->projectileSpeed * COBSCALE; unit->weapons[np1 - 1]->projectileSpeed = float(p2) / COBSCALE; return old; } else { return -1; } } case GAME_FRAME: { return gs->frameNum; } default: if ((val >= GLOBAL_VAR_START) && (val <= GLOBAL_VAR_END)) { return globalVars[val - GLOBAL_VAR_START]; } else if ((val >= TEAM_VAR_START) && (val <= TEAM_VAR_END)) { return teamVars[unit->team][val - TEAM_VAR_START]; } else if ((val >= ALLY_VAR_START) && (val <= ALLY_VAR_END)) { return allyVars[unit->allyteam][val - ALLY_VAR_START]; } else if ((val >= UNIT_VAR_START) && (val <= UNIT_VAR_END)) { const int varID = val - UNIT_VAR_START; if (p1 == 0) { return unitVars[varID]; } else if (p1 > 0) { // get the unit var for another unit const CUnit* u = uh->GetUnit(p1); if (u != NULL && u->script != NULL) { return u->script->unitVars[varID]; } } else { // set the unit var for another unit p1 = -p1; CUnit* u = uh->GetUnit(p1); if (u != NULL && u->script != NULL) { u->script->unitVars[varID] = p2; return 1; } } return 0; } else { LOG_L(L_ERROR, "CobError: Unknown get constant %d (params = %d %d %d %d)", val, p1, p2, p3, p4); } } #endif return 0; }
void CUnitHandler::Update() { auto UNIT_SANITY_CHECK = [](const CUnit* unit) { unit->pos.AssertNaNs(); unit->midPos.AssertNaNs(); unit->relMidPos.AssertNaNs(); unit->speed.AssertNaNs(); unit->deathSpeed.AssertNaNs(); unit->rightdir.AssertNaNs(); unit->updir.AssertNaNs(); unit->frontdir.AssertNaNs(); if (unit->unitDef->IsGroundUnit()) { assert(unit->pos.x >= -(float3::maxxpos * 16.0f)); assert(unit->pos.x <= (float3::maxxpos * 16.0f)); assert(unit->pos.z >= -(float3::maxzpos * 16.0f)); assert(unit->pos.z <= (float3::maxzpos * 16.0f)); } }; DeleteUnitsNow(); { SCOPED_TIMER("Unit::MoveType::Update"); for (activeUpdateUnit = 0; activeUpdateUnit < activeUnits.size();++activeUpdateUnit) { CUnit* unit = activeUnits[activeUpdateUnit]; AMoveType* moveType = unit->moveType; UNIT_SANITY_CHECK(unit); if (moveType->Update()) { eventHandler.UnitMoved(unit); } if (!unit->pos.IsInBounds() && (unit->speed.w > MAX_UNIT_SPEED)) { // this unit is not coming back, kill it now without any death // sequence (so deathScriptFinished becomes true immediately) unit->KillUnit(nullptr, false, true, false); } UNIT_SANITY_CHECK(unit); assert(activeUnits[activeUpdateUnit] == unit); } } { // Delete dead units for (activeUpdateUnit = 0; activeUpdateUnit < activeUnits.size();++activeUpdateUnit) { CUnit* unit = activeUnits[activeUpdateUnit]; if (!unit->deathScriptFinished) continue; // there are many ways to fiddle with "deathScriptFinished", so a unit // may arrive here not having been properly killed (with isDead still // false) // make sure we always call Killed; no-op if isDead is already true unit->KillUnit(nullptr, false, true, true); DeleteUnit(unit); assert(activeUnits[activeUpdateUnit] == unit); } } { SCOPED_TIMER("Unit::UpdateLocalModel"); for (CUnit* unit: activeUnits) { // UnitScript only applies piece-space transforms so // we apply the forward kinematics update separately // (only if we have any dirty pieces) // add ID as offset so the bounding-box update does // not run at the same time for every model unit->localModel.Update(gs->frameNum + unit->id); } } { SCOPED_TIMER("Unit::SlowUpdate"); assert(activeSlowUpdateUnit >= 0); // reset the iterator every <UNIT_SLOWUPDATE_RATE> frames if ((gs->frameNum % UNIT_SLOWUPDATE_RATE) == 0) { activeSlowUpdateUnit = 0; } // stagger the SlowUpdate's unsigned int n = (activeUnits.size() / UNIT_SLOWUPDATE_RATE) + 1; for (; activeSlowUpdateUnit < activeUnits.size() && n != 0; ++activeSlowUpdateUnit) { CUnit* unit = activeUnits[activeSlowUpdateUnit]; UNIT_SANITY_CHECK(unit); unit->SlowUpdate(); unit->SlowUpdateWeapons(); UNIT_SANITY_CHECK(unit); n--; } } { SCOPED_TIMER("Unit::Update"); for (activeUpdateUnit = 0; activeUpdateUnit < activeUnits.size();++activeUpdateUnit) { CUnit* unit = activeUnits[activeUpdateUnit]; UNIT_SANITY_CHECK(unit); unit->Update(); UNIT_SANITY_CHECK(unit); assert(activeUnits[activeUpdateUnit] == unit); } } { SCOPED_TIMER("Unit::Weapon::Update"); for (activeUpdateUnit = 0; activeUpdateUnit < activeUnits.size();++activeUpdateUnit) { CUnit* unit = activeUnits[activeUpdateUnit]; if (unit->CanUpdateWeapons()) { for (CWeapon* w: unit->weapons) { w->Update(); } } assert(activeUnits[activeUpdateUnit] == unit); } } }
void CUnitHandler::Update() { { GML_STDMUTEX_LOCK(runit); // Update if (!unitsToBeRemoved.empty()) { GML_RECMUTEX_LOCK(obj); // Update while (!unitsToBeRemoved.empty()) { eventHandler.DeleteSyncedObjects(); // the unit destructor may invoke eventHandler, so we need to call these for every unit to clear invaild references from the batching systems GML_RECMUTEX_LOCK(unit); // Update eventHandler.DeleteSyncedUnits(); GML_RECMUTEX_LOCK(proj); // Update - projectile drawing may access owner() and lead to crash GML_RECMUTEX_LOCK(sel); // Update - unit is removed from selectedUnits in ~CObject, which is too late. GML_RECMUTEX_LOCK(quad); // Update - make sure unit does not get partially deleted before before being removed from the quadfield CUnit* delUnit = unitsToBeRemoved.back(); unitsToBeRemoved.pop_back(); DeleteUnitNow(delUnit); } } eventHandler.UpdateUnits(); } GML::UpdateTicks(); #define VECTOR_SANITY_CHECK(v) \ assert(!math::isnan(v.x) && !math::isinf(v.x)); \ assert(!math::isnan(v.y) && !math::isinf(v.y)); \ assert(!math::isnan(v.z) && !math::isinf(v.z)); #define MAPPOS_SANITY_CHECK(unit) \ if (unit->unitDef->IsGroundUnit()) { \ assert(unit->pos.x >= -(float3::maxxpos * 16.0f)); \ assert(unit->pos.x <= (float3::maxxpos * 16.0f)); \ assert(unit->pos.z >= -(float3::maxzpos * 16.0f)); \ assert(unit->pos.z <= (float3::maxzpos * 16.0f)); \ } #define UNIT_SANITY_CHECK(unit) \ VECTOR_SANITY_CHECK(unit->pos); \ VECTOR_SANITY_CHECK(unit->midPos); \ VECTOR_SANITY_CHECK(unit->relMidPos); \ VECTOR_SANITY_CHECK(unit->speed); \ VECTOR_SANITY_CHECK(unit->deathSpeed); \ VECTOR_SANITY_CHECK(unit->residualImpulse); \ VECTOR_SANITY_CHECK(unit->rightdir); \ VECTOR_SANITY_CHECK(unit->updir); \ VECTOR_SANITY_CHECK(unit->frontdir); \ MAPPOS_SANITY_CHECK(unit); { SCOPED_TIMER("Unit::MoveType::Update"); std::list<CUnit*>::iterator usi; for (usi = activeUnits.begin(); usi != activeUnits.end(); ++usi) { CUnit* unit = *usi; AMoveType* moveType = unit->moveType; UNIT_SANITY_CHECK(unit); if (moveType->Update()) { eventHandler.UnitMoved(unit); } if (!unit->pos.IsInBounds() && (unit->speed.SqLength() > (MAX_UNIT_SPEED * MAX_UNIT_SPEED))) { // this unit is not coming back, kill it now without any death // sequence (so deathScriptFinished becomes true immediately) unit->KillUnit(NULL, false, true, false); } UNIT_SANITY_CHECK(unit); GML::GetTicks(unit->lastUnitUpdate); } } { SCOPED_TIMER("Unit::Update"); std::list<CUnit*>::iterator usi; for (usi = activeUnits.begin(); usi != activeUnits.end(); ++usi) { CUnit* unit = *usi; UNIT_SANITY_CHECK(unit); if (unit->deathScriptFinished) { // there are many ways to fiddle with "deathScriptFinished", so a unit may // arrive here without having been properly killed (and isDead still false), // which can result in MT deadlocking -- FIXME verify this // (KU returns early if isDead) unit->KillUnit(NULL, false, true); DeleteUnit(unit); } else { unit->Update(); } UNIT_SANITY_CHECK(unit); } } { SCOPED_TIMER("Unit::SlowUpdate"); // reset the iterator every <UNIT_SLOWUPDATE_RATE> frames if ((gs->frameNum & (UNIT_SLOWUPDATE_RATE - 1)) == 0) { activeSlowUpdateUnit = activeUnits.begin(); } // stagger the SlowUpdate's int n = (activeUnits.size() / UNIT_SLOWUPDATE_RATE) + 1; for (; activeSlowUpdateUnit != activeUnits.end() && n != 0; ++activeSlowUpdateUnit) { CUnit* unit = *activeSlowUpdateUnit; UNIT_SANITY_CHECK(unit); unit->SlowUpdate(); UNIT_SANITY_CHECK(unit); n--; } } }
void CUnitHandler::Update() { { if (!unitsToBeRemoved.empty()) { while (!unitsToBeRemoved.empty()) { eventHandler.DeleteSyncedObjects(); // the unit destructor may invoke eventHandler, so we need to call these for every unit to clear invaild references from the batching systems eventHandler.DeleteSyncedUnits(); CUnit* delUnit = unitsToBeRemoved.back(); unitsToBeRemoved.pop_back(); DeleteUnitNow(delUnit); } } eventHandler.UpdateUnits(); } #define MAPPOS_SANITY_CHECK(unit) \ if (unit->unitDef->IsGroundUnit()) { \ assert(unit->pos.x >= -(float3::maxxpos * 16.0f)); \ assert(unit->pos.x <= (float3::maxxpos * 16.0f)); \ assert(unit->pos.z >= -(float3::maxzpos * 16.0f)); \ assert(unit->pos.z <= (float3::maxzpos * 16.0f)); \ } #define UNIT_SANITY_CHECK(unit) \ unit->pos.AssertNaNs(); \ unit->midPos.AssertNaNs(); \ unit->relMidPos.AssertNaNs(); \ unit->speed.AssertNaNs(); \ unit->deathSpeed.AssertNaNs(); \ unit->rightdir.AssertNaNs(); \ unit->updir.AssertNaNs(); \ unit->frontdir.AssertNaNs(); \ MAPPOS_SANITY_CHECK(unit); { SCOPED_TIMER("Unit::MoveType::Update"); for (auto usi = activeUnits.begin(); usi != activeUnits.end(); ++usi) { CUnit* unit = *usi; AMoveType* moveType = unit->moveType; UNIT_SANITY_CHECK(unit); if (moveType->Update()) { eventHandler.UnitMoved(unit); } if (!unit->pos.IsInBounds() && (Square(unit->speed.w) > (MAX_UNIT_SPEED * MAX_UNIT_SPEED))) { // this unit is not coming back, kill it now without any death // sequence (so deathScriptFinished becomes true immediately) unit->KillUnit(NULL, false, true, false); } UNIT_SANITY_CHECK(unit); } } { // Delete dead units for (auto usi = activeUnits.begin(); usi != activeUnits.end(); ++usi) { CUnit* unit = *usi; if (unit->deathScriptFinished) { // there are many ways to fiddle with "deathScriptFinished", so a unit may // arrive here without having been properly killed (and isDead still false), // which can result in MT deadlocking -- FIXME verify this // (KU returns early if isDead) unit->KillUnit(NULL, false, true); DeleteUnit(unit); } } } { SCOPED_TIMER("Unit::UpdatePieceMatrices"); for (auto usi = activeUnits.begin(); usi != activeUnits.end(); ++usi) { // UnitScript only applies piece-space transforms so // we apply the forward kinematics update separately // (only if we have any dirty pieces) CUnit* unit = *usi; unit->localModel->UpdatePieceMatrices(); } } { SCOPED_TIMER("Unit::Update"); for (auto usi = activeUnits.begin(); usi != activeUnits.end(); ++usi) { CUnit* unit = *usi; UNIT_SANITY_CHECK(unit); unit->Update(); UNIT_SANITY_CHECK(unit); } } { SCOPED_TIMER("Unit::SlowUpdate"); // reset the iterator every <UNIT_SLOWUPDATE_RATE> frames if ((gs->frameNum & (UNIT_SLOWUPDATE_RATE - 1)) == 0) { activeSlowUpdateUnit = activeUnits.begin(); } // stagger the SlowUpdate's unsigned int n = (activeUnits.size() / UNIT_SLOWUPDATE_RATE) + 1; for (; activeSlowUpdateUnit != activeUnits.end() && n != 0; ++activeSlowUpdateUnit) { CUnit* unit = *activeSlowUpdateUnit; UNIT_SANITY_CHECK(unit); unit->SlowUpdate(); UNIT_SANITY_CHECK(unit); n--; } } }
void CTransportUnit::KillUnit(bool selfDestruct, bool reclaimed, CUnit* attacker, bool) { if (!isDead) { // guard against recursive invocation via // transportee->KillUnit // helper->Explosion // helper->DoExplosionDamage // unit->DoDamage // unit->KillUnit // in the case that unit == this isDead = true; // ::KillUnit might be called multiple times while !deathScriptFinished, // but it makes no sense to kill/detach our transportees more than once std::list<TransportedUnit>::iterator ti; for (ti = transportedUnits.begin(); ti != transportedUnits.end(); ++ti) { CUnit* transportee = ti->unit; assert(transportee != this); if (transportee->isDead) continue; const float gh = ground->GetHeightReal(transportee->pos.x, transportee->pos.z); transportee->transporter = NULL; transportee->DeleteDeathDependence(this, DEPENDENCE_TRANSPORTER); // prevent a position teleport on the next movetype update if // the transport died in a place that the unit being carried // could not get to on its own if (!transportee->pos.IsInBounds()) { transportee->KillUnit(false, false, NULL, false); continue; } else { // immobile units can still be transported // via script trickery, guard against this if (!transportee->unitDef->IsAllowedTerrainHeight(gh)) { transportee->KillUnit(false, false, NULL, false); continue; } } if (!unitDef->releaseHeld) { if (!selfDestruct) { // we don't want it to leave a corpse transportee->DoDamage(DamageArray(1e6f), ZeroVector, NULL, -DAMAGE_EXTSOURCE_KILLED); } transportee->KillUnit(selfDestruct, reclaimed, attacker); } else { // place unit near the place of death of the transport // if it's a ground transport and uses a piece-in-ground method // to hide units if (transportee->pos.y < gh) { const float k = (transportee->radius + radius) * std::max(unitDef->unloadSpread, 1.0f); // try to unload in a presently unoccupied spot // unload on a wreck if suitable position not found for (int i = 0; i < 10; ++i) { float3 pos = transportee->pos; pos.x += (gs->randFloat() * 2 * k - k); pos.z += (gs->randFloat() * 2 * k - k); pos.y = ground->GetHeightReal(transportee->pos.x, transportee->pos.z); if (qf->GetUnitsExact(pos, transportee->radius + 2).empty()) { transportee->Move3D(pos, false); break; } } } else if (CGroundMoveType* mt = dynamic_cast<CGroundMoveType*>(transportee->moveType)) { mt->StartFlying(); } transportee->moveType->SlowUpdate(); transportee->moveType->LeaveTransport(); // issue a move order so that unit won't try to return to pick-up pos in IdleCheck() if (unitDef->canfly && transportee->unitDef->canmove) { Command c(CMD_MOVE); c.params.push_back(transportee->pos.x); c.params.push_back(ground->GetHeightAboveWater(transportee->pos.x, transportee->pos.z)); c.params.push_back(transportee->pos.z); transportee->commandAI->GiveCommand(c); } transportee->stunned = (transportee->paralyzeDamage > (modInfo.paralyzeOnMaxHealth? transportee->maxHealth: transportee->health)); transportee->speed = speed * (0.5f + 0.5f * gs->randFloat()); if (CBuilding* building = dynamic_cast<CBuilding*>(transportee)) { // this building may end up in a strange position, so kill it building->KillUnit(selfDestruct, reclaimed, attacker); } eventHandler.UnitUnloaded(transportee, this); } } transportedUnits.clear(); // make sure CUnit::KillUnit does not return early isDead = false; } CUnit::KillUnit(selfDestruct, reclaimed, attacker); }
void CTransportUnit::KillUnit(bool selfDestruct, bool reclaimed, CUnit* attacker, bool) { std::list<TransportedUnit>::iterator ti; for (ti = transported.begin(); ti != transported.end(); ++ti) { CUnit* u = ti->unit; const float gh = ground->GetHeight2(u->pos.x, u->pos.z); u->transporter = 0; u->DeleteDeathDependence(this); // prevent a position teleport on the next movetype update if // the transport died in a place that the unit being carried // could not get to on its own if (!u->pos.IsInBounds()) { u->KillUnit(false, false, NULL, false); continue; } else { // immobile units can still be transported // via script trickery, guard against this if (u->unitDef->movedata != NULL && gh < -u->unitDef->movedata->depth) { // always treat depth as maxWaterDepth (fails if // the transportee is a ship, but so does using // UnitDef::{min, max}WaterDepth) u->KillUnit(false, false, NULL, false); continue; } } if (!unitDef->releaseHeld) { if (!selfDestruct) { // we don't want it to leave a corpse u->DoDamage(DamageArray() * 1000000, 0, ZeroVector); } u->KillUnit(selfDestruct, reclaimed, attacker); } else { // place unit near the place of death of the transport // if it's a ground transport and uses a piece-in-ground method // to hide units if (u->pos.y < gh) { const float k = (u->radius + radius)*std::max(unitDef->unloadSpread, 1.f); // try to unload in a presently unoccupied spot // unload on a wreck if suitable position not found for (int i = 0; i<10; ++i) { float3 pos = u->pos; pos.x += gs->randFloat()*2*k - k; pos.z += gs->randFloat()*2*k - k; pos.y = ground->GetHeight2(u->pos.x, u->pos.z); if (qf->GetUnitsExact(pos, u->radius + 2).empty()) { u->pos = pos; break; } } u->UpdateMidPos(); } else if (CGroundMoveType* mt = dynamic_cast<CGroundMoveType*>(u->moveType)) { mt->StartFlying(); } u->stunned = (u->paralyzeDamage > u->health); u->moveType->LeaveTransport(); u->speed = speed*(0.5f + 0.5f*gs->randFloat()); eventHandler.UnitUnloaded(u, this); } } CUnit::KillUnit(selfDestruct, reclaimed, attacker); }
void CUnitHandler::Update() { { GML_STDMUTEX_LOCK(runit); // Update if (!toBeRemoved.empty()) { GML_RECMUTEX_LOCK(unit); // Update - for anti-deadlock purposes. GML_RECMUTEX_LOCK(sel); // Update - unit is removed from selectedUnits in ~CObject, which is too late. GML_RECMUTEX_LOCK(quad); // Update - make sure unit does not get partially deleted before before being removed from the quadfield GML_STDMUTEX_LOCK(proj); // Update - projectile drawing may access owner() and lead to crash eventHandler.DeleteSyncedUnits(); while (!toBeRemoved.empty()) { CUnit* delUnit = toBeRemoved.back(); toBeRemoved.pop_back(); DeleteUnitNow(delUnit); } } eventHandler.UpdateUnits(); } GML_UPDATE_TICKS(); { SCOPED_TIMER("Unit Movetype update"); std::list<CUnit*>::iterator usi; for (usi = activeUnits.begin(); usi != activeUnits.end(); ++usi) { CUnit* unit = *usi; AMoveType* moveType = unit->moveType; if (moveType->Update()) { eventHandler.UnitMoved(unit); } GML_GET_TICKS(unit->lastUnitUpdate); } } { SCOPED_TIMER("Unit update"); std::list<CUnit*>::iterator usi; for (usi = activeUnits.begin(); usi != activeUnits.end(); ++usi) { CUnit* unit = *usi; if (unit->deathScriptFinished) { // there are many ways to fiddle with "deathScriptFinished", so a unit may // arrive here without having been properly killed (and isDead still false), // which can result in MT deadlocking -- FIXME verify this // (KU returns early if isDead) unit->KillUnit(false, true, NULL); DeleteUnit(unit); } else { unit->Update(); } } } { SCOPED_TIMER("Unit slow update"); if (!(gs->frameNum & (UNIT_SLOWUPDATE_RATE - 1))) { slowUpdateIterator = activeUnits.begin(); } int n = (activeUnits.size() / UNIT_SLOWUPDATE_RATE) + 1; for (; slowUpdateIterator != activeUnits.end() && n != 0; ++ slowUpdateIterator) { (*slowUpdateIterator)->SlowUpdate(); n--; } } // for timer destruction }