static void Finish(COrder_Built &order, CUnit &unit) { const CUnitType &type = *unit.Type; CPlayer &player = *unit.Player; DebugPrint("%d: Building %s(%s) ready.\n" _C_ player.Index _C_ type.Ident.c_str() _C_ type.Name.c_str()); // HACK: the building is ready now player.UnitTypesCount[type.Slot]++; if (unit.Active) { player.UnitTypesAiActiveCount[type.Slot]++; } unit.Constructed = 0; if (unit.Frame < 0) { unit.Frame = -1; } else { unit.Frame = 0; } CUnit *worker = order.GetWorkerPtr(); if (worker != NULL) { if (type.BoolFlag[BUILDERLOST_INDEX].value) { // Bye bye worker. LetUnitDie(*worker); worker = NULL; } else { // Drop out the worker. worker->ClearAction(); DropOutOnSide(*worker, LookingW, &unit); // If we can harvest from the new building, do it. if (worker->Type->ResInfo[type.GivesResource]) { CommandResource(*worker, unit, 0); } // If we can reurn goods to a new depot, do it. if (worker->CurrentResource && worker->ResourcesHeld > 0 && type.CanStore[worker->CurrentResource]) { CommandReturnGoods(*worker, &unit, 0); } } } if (type.GivesResource && type.StartingResources != 0) { // Has StartingResources, Use those unit.ResourcesHeld = type.StartingResources; } player.Notify(NotifyGreen, unit.tilePos, _("New %s done"), type.Name.c_str()); if (&player == ThisPlayer) { if (type.MapSound.Ready.Sound) { PlayUnitSound(unit, VoiceReady); } else if (worker) { PlayUnitSound(*worker, VoiceWorkCompleted); } else { PlayUnitSound(unit, VoiceBuilding); } } if (player.AiEnabled) { /* Worker can be NULL */ AiWorkComplete(worker, unit); } // FIXME: Vladi: this is just a hack to test wall fixing, // FIXME: also not sure if the right place... // FIXME: Johns: hardcoded unit-type wall / more races! if (&type == UnitTypeOrcWall || &type == UnitTypeHumanWall) { Map.SetWall(unit.tilePos, &type == UnitTypeHumanWall); unit.Remove(NULL); UnitLost(unit); UnitClearOrders(unit); unit.Release(); return ; } UpdateForNewUnit(unit, 0); // Set the direction of the building if it supports them if (type.NumDirections > 1 && type.BoolFlag[NORANDOMPLACING_INDEX].value == false) { if (type.BoolFlag[WALL_INDEX].value) { // Special logic for walls CorrectWallDirections(unit); CorrectWallNeighBours(unit); } else { unit.Direction = (MyRand() >> 8) & 0xFF; // random heading } UnitUpdateHeading(unit); } if (IsOnlySelected(unit) || &player == ThisPlayer) { SelectedUnitChanged(); } MapUnmarkUnitSight(unit); unit.CurrentSightRange = unit.Stats->Variables[SIGHTRANGE_INDEX].Max; MapMarkUnitSight(unit); order.Finished = true; }
CUnit* CUnitLoader::LoadUnit(const UnitLoadParams& cparams) { CUnit* unit = NULL; UnitLoadParams& params = const_cast<UnitLoadParams&>(cparams); SCOPED_TIMER("UnitLoader::LoadUnit"); { GML_RECMUTEX_LOCK(sel); // LoadUnit - for anti deadlock purposes. GML_RECMUTEX_LOCK(quad); // LoadUnit - make sure other threads cannot access an incomplete unit const UnitDef* ud = params.unitDef; if (ud == NULL) return unit; // need to check this BEFORE creating the instance if (!unitHandler->CanAddUnit(cparams.unitID)) return unit; if (params.teamID < 0) { // FIXME use gs->gaiaTeamID ? (once it is always enabled) if ((params.teamID = teamHandler->GaiaTeamID()) < 0) throw content_error("Invalid team and no gaia team to put unit in"); } if (ud->IsTransportUnit()) { unit = new CTransportUnit(); } else if (ud->IsFactoryUnit()) { // special static builder structures that can always be given // move orders (which are passed on to all mobile buildees) unit = new CFactory(); } else if (ud->IsMobileBuilderUnit() || ud->IsStaticBuilderUnit()) { // all other types of non-structure "builders", including hubs and // nano-towers (the latter should not have any build-options at all, // whereas the former should be unable to build any mobile units) unit = new CBuilder(); } else if (ud->IsBuildingUnit()) { // static non-builder structures if (ud->IsExtractorUnit()) { unit = new CExtractorBuilding(); } else { unit = new CBuilding(); } } else { // regular mobile unit unit = new CUnit(); } unit->PreInit(params); if (ud->IsTransportUnit()) { new CTransportCAI(unit); } else if (ud->IsFactoryUnit()) { new CFactoryCAI(unit); } else if (ud->IsMobileBuilderUnit() || ud->IsStaticBuilderUnit()) { new CBuilderCAI(unit); } else if (ud->IsNonHoveringAirUnit()) { // non-hovering fighter or bomber aircraft; coupled to StrafeAirMoveType new CAirCAI(unit); } else if (ud->IsAirUnit()) { // all other aircraft; coupled to HoverAirMoveType new CMobileCAI(unit); } else if (ud->IsGroundUnit()) { new CMobileCAI(unit); } else { new CCommandAI(unit); } } unit->PostInit(params.builder); (eventBatchHandler->GetUnitCreatedDestroyedBatch()).enqueue(EventBatchHandler::UD(unit, unit->isCloaked)); if (params.flattenGround) { FlattenGround(unit); } return unit; }
void CBeamLaser::FireInternal(float3 curDir) { float actualRange = range; float rangeMod = 1.0f; if (!owner->unitDef->IsImmobileUnit()) { // help units fire while chasing rangeMod = 1.3f; } if (owner->UnderFirstPersonControl()) { rangeMod = 0.95f; } bool tryAgain = true; bool doDamage = true; float maxLength = range * rangeMod; float curLength = 0.0f; float3 curPos = weaponMuzzlePos; float3 hitPos; float3 newDir; // objects at the end of the beam CUnit* hitUnit = NULL; CFeature* hitFeature = NULL; CPlasmaRepulser* hitShield = NULL; CollisionQuery hitColQuery; if (!sweepFireState.IsSweepFiring()) { curDir += (gs->randVector() * SprayAngleExperience()); curDir.SafeNormalize(); // increase range if targets are searched for in a cylinder if (cylinderTargeting > 0.01f) { const float verticalDist = owner->radius * cylinderTargeting * curDir.y; const float maxLengthModSq = maxLength * maxLength + verticalDist * verticalDist; maxLength = math::sqrt(maxLengthModSq); } // adjust range if targetting edge of hitsphere if (targetType == Target_Unit && targetUnit != NULL && targetBorder != 0.0f) { maxLength += (targetUnit->radius * targetBorder); } } else { // restrict the range when sweeping maxLength = std::min(maxLength, sweepFireState.GetTargetDist3D() * 1.125f); } for (int tries = 0; tries < 5 && tryAgain; ++tries) { float beamLength = TraceRay::TraceRay(curPos, curDir, maxLength - curLength, collisionFlags, owner, hitUnit, hitFeature, &hitColQuery); if (hitUnit != NULL && teamHandler->AlliedTeams(hitUnit->team, owner->team)) { if (sweepFireState.IsSweepFiring() && !sweepFireState.DamageAllies()) { doDamage = false; break; } } if (!weaponDef->waterweapon) { // terminate beam at water surface if necessary if ((curDir.y < 0.0f) && ((curPos.y + curDir.y * beamLength) <= 0.0f)) { beamLength = curPos.y / -curDir.y; } } // if the beam gets intercepted, this modifies newDir // // we do more than one trace-iteration and set dir to // newDir only in the case there is a shield in our way const float shieldLength = interceptHandler.AddShieldInterceptableBeam(this, curPos, curDir, beamLength, newDir, hitShield); if (shieldLength < beamLength) { beamLength = shieldLength; tryAgain = hitShield->BeamIntercepted(this, salvoDamageMult); } else { tryAgain = false; } // same as hitColQuery.GetHitPos() if no water or shield in way hitPos = curPos + curDir * beamLength; { const float baseAlpha = weaponDef->intensity * 255.0f; const float startAlpha = (1.0f - (curLength ) / maxLength); const float endAlpha = (1.0f - (curLength + beamLength) / maxLength); ProjectileParams pparams = GetProjectileParams(); pparams.pos = curPos; pparams.end = hitPos; pparams.ttl = weaponDef->beamLaserTTL; pparams.startAlpha = Clamp(startAlpha * baseAlpha, 0.0f, 255.0f); pparams.endAlpha = Clamp(endAlpha * baseAlpha, 0.0f, 255.0f); WeaponProjectileFactory::LoadProjectile(pparams); } curPos = hitPos; curDir = newDir; curLength += beamLength; } if (!doDamage) return; if (hitUnit != NULL) { hitUnit->SetLastAttackedPiece(hitColQuery.GetHitPiece(), gs->frameNum); if (targetBorder > 0.0f) { actualRange += (hitUnit->radius * targetBorder); } } if (curLength < maxLength) { const DamageArray& baseDamages = (weaponDef->dynDamageExp <= 0.0f)? weaponDef->damages: weaponDefHandler->DynamicDamages( weaponDef->damages, weaponMuzzlePos, curPos, (weaponDef->dynDamageRange > 0.0f)? weaponDef->dynDamageRange: weaponDef->range, weaponDef->dynDamageExp, weaponDef->dynDamageMin, weaponDef->dynDamageInverted ); // make it possible to always hit with some minimal intensity (melee weapons have use for that) const float hitIntensity = std::max(minIntensity, 1.0f - curLength / (actualRange * 2.0f)); const DamageArray damages = baseDamages * (hitIntensity * salvoDamageMult); const CGameHelper::ExplosionParams params = { hitPos, curDir, damages, weaponDef, owner, hitUnit, hitFeature, craterAreaOfEffect, damageAreaOfEffect, weaponDef->edgeEffectiveness, weaponDef->explosionSpeed, 1.0f, // gfxMod weaponDef->impactOnly, weaponDef->noExplode || weaponDef->noSelfDamage, // ignoreOwner true, // damageGround -1u // projectileID }; helper->Explosion(params); } }
// called by {CRifle, CBeamLaser, CLightningCannon}::Fire(), CWeapon::HaveFreeLineOfFire(), and Skirmish AIs float TraceRay( const float3& start, const float3& dir, float length, int collisionFlags, const CUnit* owner, CUnit*& hitUnit, CFeature*& hitFeature ) { const bool ignoreEnemies = ((collisionFlags & Collision::NOENEMIES ) != 0); const bool ignoreAllies = ((collisionFlags & Collision::NOFRIENDLIES) != 0); const bool ignoreFeatures = ((collisionFlags & Collision::NOFEATURES ) != 0); const bool ignoreNeutrals = ((collisionFlags & Collision::NONEUTRALS ) != 0); const bool ignoreGround = ((collisionFlags & Collision::NOGROUND ) != 0); const bool ignoreUnits = ignoreEnemies && ignoreAllies && ignoreNeutrals; hitFeature = NULL; hitUnit = NULL; if (dir == ZeroVector) { return -1.0f; } if (!ignoreFeatures || !ignoreUnits) { GML_RECMUTEX_LOCK(quad); // TraceRay CollisionQuery cq; int* begQuad = NULL; int* endQuad = NULL; qf->GetQuadsOnRay(start, dir, length, begQuad, endQuad); // feature intersection if (!ignoreFeatures) { for (int* quadPtr = begQuad; quadPtr != endQuad; ++quadPtr) { const CQuadField::Quad& quad = qf->GetQuad(*quadPtr); for (std::list<CFeature*>::const_iterator ui = quad.features.begin(); ui != quad.features.end(); ++ui) { CFeature* f = *ui; // NOTE: // if f is non-blocking, ProjectileHandler will not test // for collisions with projectiles so we can skip it here if (!f->blocking) continue; if (CCollisionHandler::DetectHit(f, start, start + dir * length, &cq, true)) { const float3& intPos = (cq.b0)? cq.p0: cq.p1; const float len = (intPos - start).dot(dir); // same as (intPos - start).Length() // we want the closest feature (intersection point) on the ray if (len < length) { length = len; hitFeature = f; } } } } } // unit intersection if (!ignoreUnits) { for (int* quadPtr = begQuad; quadPtr != endQuad; ++quadPtr) { const CQuadField::Quad& quad = qf->GetQuad(*quadPtr); for (std::list<CUnit*>::const_iterator ui = quad.units.begin(); ui != quad.units.end(); ++ui) { CUnit* u = *ui; if (u == owner) continue; if (ignoreAllies && u->allyteam == owner->allyteam) continue; if (ignoreNeutrals && u->IsNeutral()) continue; if (ignoreEnemies && u->allyteam != owner->allyteam) continue; if (CCollisionHandler::DetectHit(u, start, start + dir * length, &cq, true)) { const float3& intPos = (cq.b0)? cq.p0: cq.p1; const float len = (intPos - start).dot(dir); // same as (intPos - start).Length() // we want the closest unit (intersection point) on the ray if (len < length) { length = len; hitUnit = u; } } } } if (hitUnit) hitFeature = NULL; } } if (!ignoreGround) { // ground intersection const float groundLength = ground->LineGroundCol(start, start + dir * length); if (length > groundLength && groundLength > 0) { length = groundLength; hitUnit = NULL; hitFeature = NULL; } } return length; }
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; } float3 relPos = GetPiecePos(p1); float3 pos = unit->pos + unit->frontdir * relPos.z + unit->updir * relPos.y + unit->rightdir * relPos.x; return PACKXZ(pos.x, pos.z); } case PIECE_Y: { if (!PieceExists(p1)) { ShowScriptError("Invalid piecenumber for get piece_y"); break; } float3 relPos = GetPiecePos(p1); float3 pos = unit->pos + unit->frontdir * relPos.z + unit->updir * relPos.y + unit->rightdir * relPos.x; return int(pos.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*atan2((float)UNPACKX(p1), (float)UNPACKZ(p1)) + 32768 - unit->heading); case XZ_HYPOT: return int(hypot((float)UNPACKX(p1), (float)UNPACKZ(p1)) * COBSCALE); case ATAN: return int(RAD2TAANG*atan2((float)p1, (float)p2)); case HYPOT: return int(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: if (unit->moveType) return int(unit->speed.Length() * COBSCALE); return 0; 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: if (unit->moveType) { return int(unit->moveType->maxSpeed * COBSCALE); } break; case REVERSING: if (unit->moveType) { 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(pow(((float)p1)/COBSCALE,((float)p2)/COBSCALE)*COBSCALE); case PRINT: logOutput.Print("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::UnitReply.PlaySample(script->sounds[p1], unit->pos, unit->speed, float(p2) / COBSCALE); } else { Channels::UnitReply.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*streflop::sinf(TAANG2RAD*(float)p1)); case KCOS: return int(1024*streflop::cosf(TAANG2RAD*(float)p1)); case KTAN: return int(1024*streflop::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: { if (p1 > 0 && static_cast<size_t>(p1) <= unit->weapons.size()) { return unit->weapons[p1-1]->reloadStatus; } else if (p1 < 0 && p1 >= (0 - unit->weapons.size())) { int old = unit->weapons[-p1-1]->reloadStatus; unit->weapons[-p1-1]->reloadStatus = p2; return old; } else { return -1; } } case WEAPON_RELOADTIME: { if (p1 > 0 && static_cast<size_t>(p1) <= unit->weapons.size()) { return unit->weapons[p1-1]->reloadTime; } else if (p1 < 0 && p1 >= 0 - unit->weapons.size()) { int old = unit->weapons[-p1-1]->reloadTime; unit->weapons[-p1-1]->reloadTime = p2; return old; } else { return -1; } } case WEAPON_ACCURACY: { if (p1 > 0 && static_cast<size_t>(p1) <= unit->weapons.size()) { return int(unit->weapons[p1-1]->accuracy * COBSCALE); } else if (p1 < 0 && p1 >= (0 - unit->weapons.size())) { int old = int(unit->weapons[-p1-1]->accuracy * COBSCALE); unit->weapons[-p1-1]->accuracy = float(p2) / COBSCALE; return old; } else { return -1; } } case WEAPON_SPRAY: { if (p1 > 0 && static_cast<size_t>(p1) <= unit->weapons.size()) { return int(unit->weapons[p1-1]->sprayAngle * COBSCALE); } else if (p1 < 0 && p1 >= (0 - unit->weapons.size())) { int old = int(unit->weapons[-p1-1]->sprayAngle * COBSCALE); unit->weapons[-p1-1]->sprayAngle = float(p2) / COBSCALE; return old; } else { return -1; } } case WEAPON_RANGE: { if (p1 > 0 && static_cast<size_t>(p1) <= unit->weapons.size()) { return int(unit->weapons[p1-1]->range * COBSCALE); } else if (p1 < 0 && p1 >= (0 - unit->weapons.size())) { int old = int(unit->weapons[-p1-1]->range * COBSCALE); unit->weapons[-p1-1]->range = float(p2) / COBSCALE; return old; } else { return -1; } } case WEAPON_PROJECTILE_SPEED: { if (p1 > 0 && static_cast<size_t>(p1) <= unit->weapons.size()) { return int(unit->weapons[p1-1]->projectileSpeed * COBSCALE); } else if (p1 < 0 && p1 >= (0 - unit->weapons.size())) { int old = int(unit->weapons[-p1-1]->projectileSpeed * COBSCALE); unit->weapons[-p1-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 { logOutput.Print("CobError: Unknown get constant %d (params = %d %d %d %d)", val, p1, p2, p3, p4); } } #endif return 0; }
//Based on function @ 0x004E8320 CUnit* UnitFinder::getNearest(int x, int y, int left, int top, int right, int bottom, UnitFinderCallbackMatchInterface &callback) { // Obtain finder indexes for all bounds UnitFinderData* const p_xbegin = unitOrderingX; UnitFinderData* const p_ybegin = unitOrderingY; UnitFinderData* const p_xend = unitOrderingX + *unitOrderingCount; UnitFinderData* const p_yend = unitOrderingY + *unitOrderingCount; // Create UnitFinderData elements for compatibility with stl functions UnitFinderData finderVal; // Search for the values using built-in binary search algorithm and comparator finderVal.position = x; UnitFinderData *pLeft = std::lower_bound(p_xbegin, p_xend, finderVal); UnitFinderData *pRight = pLeft + 1; finderVal.position = y; UnitFinderData *pTop = std::lower_bound(p_ybegin, p_yend, finderVal); UnitFinderData *pBottom = pTop + 1; CUnit *bestUnit = NULL; int bestDistance = 999999; bool canContinue, canNarrowSearchBounds; bool isUnitVisited[UNIT_ARRAY_LENGTH + 1] = {false}; do { canContinue = false; canNarrowSearchBounds = false; if (pLeft >= p_xbegin && pLeft->position >= left) { if (!isUnitVisited[pLeft->unitIndex]) { isUnitVisited[pLeft->unitIndex] = true; CUnit *unit = CUnit::getFromIndex(pLeft->unitIndex); if (left <= unit->getX() && unit->getX() < right && top <= unit->getY() && unit->getY() < bottom && callback.match(unit)) { int distance = scbw::getDistanceFast(x, y, unit->getX(), unit->getY()); if (distance < bestDistance) { bestUnit = unit; bestDistance = distance; canNarrowSearchBounds = true; } } } --pLeft; canContinue = true; } if (pRight < p_xend && pRight->position <= right) { if (!isUnitVisited[pRight->unitIndex]) { isUnitVisited[pRight->unitIndex] = true; CUnit *unit = CUnit::getFromIndex(pRight->unitIndex); if (left <= unit->getX() && unit->getX() < right && top <= unit->getY() && unit->getY() < bottom && callback.match(unit)) { int distance = scbw::getDistanceFast(x, y, unit->getX(), unit->getY()); if (distance < bestDistance) { bestUnit = unit; bestDistance = distance; canNarrowSearchBounds = true; } } } ++pRight; canContinue = true; } if (pTop >= p_ybegin && pTop->position >= top) { if (!isUnitVisited[pTop->unitIndex]) { isUnitVisited[pTop->unitIndex] = true; CUnit *unit = CUnit::getFromIndex(pTop->unitIndex); if (left <= unit->getX() && unit->getX() < right && top <= unit->getY() && unit->getY() < bottom && callback.match(unit)) { int distance = scbw::getDistanceFast(x, y, unit->getX(), unit->getY()); if (distance < bestDistance) { bestUnit = unit; bestDistance = distance; canNarrowSearchBounds = true; } } } --pTop; canContinue = true; } if (pBottom < p_yend && pBottom->position < bottom) { if (!isUnitVisited[pBottom->unitIndex]) { isUnitVisited[pBottom->unitIndex] = true; CUnit *unit = CUnit::getFromIndex(pBottom->unitIndex); if (left <= unit->getX() && unit->getX() < right && top <= unit->getY() && unit->getY() < bottom && callback.match(unit)) { int distance = scbw::getDistanceFast(x, y, unit->getX(), unit->getY()); if (distance < bestDistance) { bestUnit = unit; bestDistance = distance; canNarrowSearchBounds = true; } } } ++pBottom; canContinue = true; } //Narrow down search boundaries if (canNarrowSearchBounds) { left = std::max(left, x - bestDistance); top = std::max(top, y - bestDistance); right = std::min(right, x + bestDistance); bottom = std::min(bottom, y + bestDistance); } } while (canContinue); return bestUnit; }
void CBeamLaser::FireInternal(float3 dir, bool sweepFire) { float rangeMod = 1.0f; if (dynamic_cast<CBuilding*>(owner) == NULL) { // help units fire while chasing rangeMod = 1.3f; } #ifdef DIRECT_CONTROL_ALLOWED if (owner->directControl) { rangeMod = 0.95f; } #endif float maxLength = range * rangeMod; float curLength = 0.0f; float3 curPos = weaponMuzzlePos; float3 hitPos; dir += gs->randVector() * sprayAngle * (1 - owner->limExperience * 0.7f); dir.ANormalize(); bool tryAgain = true; // unit at the end of the beam CUnit* hit = 0; // increase range if targets are searched for in a cylinder if (cylinderTargetting > 0.01f) { // const float3 up(0, owner->radius*cylinderTargetting, 0); // const float uplen = up.dot(dir); const float uplen = owner->radius * cylinderTargetting * dir.y; maxLength = streflop::sqrtf(maxLength * maxLength + uplen * uplen); } // increase range if targetting edge of hitsphere if (targetType == Target_Unit && targetUnit && targetBorder != 0) { maxLength += targetUnit->radius * targetBorder; } for (int tries = 0; tries < 5 && tryAgain; ++tries) { tryAgain = false; hit = 0; float length = helper->TraceRay( curPos, dir, maxLength - curLength, weaponDef->damages[0], owner, hit, collisionFlags ); if (hit && hit->allyteam == owner->allyteam && sweepFire) { // never damage friendlies with sweepfire lastFireFrame = 0; return; } float3 newDir; CPlasmaRepulser* shieldHit = 0; const float shieldLength = interceptHandler.AddShieldInterceptableBeam(this, curPos, dir, length, newDir, shieldHit); if (shieldLength < length) { length = shieldLength; if (shieldHit->BeamIntercepted(this, damageMul)) { // repulsed tryAgain = true; } } hitPos = curPos + dir * length; const float baseAlpha = weaponDef->intensity * 255.0f; const float startAlpha = (1.0f - (curLength ) / (range * 1.3f)) * baseAlpha; const float endAlpha = (1.0f - (curLength + length) / (range * 1.3f)) * baseAlpha; if (weaponDef->largeBeamLaser) { new CLargeBeamLaserProjectile(curPos, hitPos, color, weaponDef->visuals.color2, owner, weaponDef); } else { new CBeamLaserProjectile( curPos, hitPos, startAlpha, endAlpha, color, weaponDef->visuals.color2, owner, weaponDef->thickness, weaponDef->corethickness, weaponDef->laserflaresize, weaponDef, weaponDef->visuals.beamttl, weaponDef->visuals.beamdecay ); } curPos = hitPos; curLength += length; dir = newDir; } // fix negative damage when hitting big spheres float actualRange = range; if (hit) { if (hit->unitDef->usePieceCollisionVolumes) { // getting the actual piece here is probably overdoing it hit->SetLastAttackedPiece(hit->localmodel->pieces[0], gs->frameNum); } if (targetBorder > 0) { actualRange += hit->radius * targetBorder; } } // make it possible to always hit with some minimal intensity (melee weapons have use for that) const float intensity = std::max(minIntensity, 1.0f - (curLength) / (actualRange * 2)); if (curLength < maxLength) { // Dynamic Damage DamageArray dynDamages; if (weaponDef->dynDamageExp > 0) { dynDamages = weaponDefHandler->DynamicDamages( weaponDef->damages, weaponMuzzlePos, curPos, weaponDef->dynDamageRange > 0? weaponDef->dynDamageRange: weaponDef->range, weaponDef->dynDamageExp, weaponDef->dynDamageMin, weaponDef->dynDamageInverted ); } helper->Explosion( hitPos, weaponDef->dynDamageExp > 0? dynDamages * (intensity * damageMul): weaponDef->damages * (intensity * damageMul), areaOfEffect, weaponDef->edgeEffectiveness, weaponDef->explosionSpeed, owner, true, 1.0f, false, false, weaponDef->explosionGenerator, hit, dir, weaponDef->id ); } if (targetUnit) { lastFireFrame = gs->frameNum; } }
/** ** Stop gathering from the resource, go home. ** ** @param unit Poiner to unit. ** ** @return TRUE if ready, otherwise FALSE. */ int COrder_Resource::StopGathering(CUnit &unit) { CUnit *source = 0; const ResourceInfo &resinfo = *unit.Type->ResInfo[this->CurrentResource]; //Wyrmgus start // if (!resinfo.TerrainHarvester) { if (!Map.Info.IsPointOnMap(this->goalPos)) { //Wyrmgus end //Wyrmgus start // if (resinfo.HarvestFromOutside) { if (this->GetGoal() && this->GetGoal()->Type->BoolFlag[HARVESTFROMOUTSIDE_INDEX].value) { //Wyrmgus end source = this->GetGoal(); this->ClearGoal(); } else { source = unit.Container; } source->Resource.Active--; Assert(source->Resource.Active >= 0); //Store resource position. this->Resource.Mine = source; if (Preference.MineNotifications && unit.Player->Index == ThisPlayer->Index && source->IsAlive() && !source->MineLow && source->ResourcesHeld * 100 / source->Variable[GIVERESOURCE_INDEX].Max <= 10 //Wyrmgus start // && source->Variable[GIVERESOURCE_INDEX].Max > DefaultIncomes[this->CurrentResource]) { && source->Variable[GIVERESOURCE_INDEX].Max > (DefaultIncomes[this->CurrentResource] * 10)) { //Wyrmgus end //Wyrmgus start // unit.Player->Notify(NotifyYellow, source->tilePos, _("%s is running low!"), source->Type->Name.c_str()); unit.Player->Notify(NotifyYellow, source->tilePos, _("Our %s is nearing depletion!"), source->Type->Name.c_str()); //Wyrmgus end source->MineLow = 1; } if (source->Type->MaxOnBoard) { int count = 0; CUnit *worker = source->Resource.Workers; CUnit *next = NULL; for (; NULL != worker; worker = worker->NextWorker) { Assert(worker->CurrentAction() == UnitActionResource); COrder_Resource &order = *static_cast<COrder_Resource *>(worker->CurrentOrder()); if (worker != &unit && order.IsGatheringWaiting()) { count++; if (next) { if (next->Wait > worker->Wait) { next = worker; } } else { next = worker; } } } if (next) { if (!unit.Player->AiEnabled) { DebugPrint("%d: Worker %d report: Unfreez resource gathering of %d <Wait %d> on %d [Assigned: %d Waiting %d].\n" _C_ unit.Player->Index _C_ UnitNumber(unit) _C_ UnitNumber(*next) _C_ next->Wait _C_ UnitNumber(*source) _C_ source->Resource.Assigned _C_ count); } next->Wait = 0; //source->Data.Resource.Waiting = count - 1; //Assert(source->Data.Resource.Assigned >= source->Data.Resource.Waiting); //StartGathering(next); } } } else { // Store resource position. this->Resource.Pos = unit.tilePos; Assert(this->Resource.Mine == NULL); } #ifdef DEBUG if (!unit.ResourcesHeld) { DebugPrint("Unit %d is empty???\n" _C_ UnitNumber(unit)); } #endif // Find and send to resource deposit. CUnit *depot = FindDeposit(unit, 1000, unit.CurrentResource); if (!depot || !unit.ResourcesHeld || this->Finished) { //Wyrmgus start // if (!(resinfo.HarvestFromOutside || resinfo.TerrainHarvester)) { if (!((source && source->Type->BoolFlag[HARVESTFROMOUTSIDE_INDEX].value) || Map.Info.IsPointOnMap(this->goalPos))) { //Wyrmgus end Assert(unit.Container); DropOutOnSide(unit, LookingW, source); } CUnit *mine = this->Resource.Mine; if (mine) { unit.DeAssignWorkerFromMine(*mine); this->Resource.Mine = NULL; } DebugPrint("%d: Worker %d report: Can't find a resource [%d] deposit.\n" _C_ unit.Player->Index _C_ UnitNumber(unit) _C_ unit.CurrentResource); this->Finished = true; return 0; } else { //Wyrmgus start // if (!(resinfo.HarvestFromOutside || resinfo.TerrainHarvester)) { if (!((source && source->Type->BoolFlag[HARVESTFROMOUTSIDE_INDEX].value) || Map.Info.IsPointOnMap(this->goalPos))) { //Wyrmgus end Assert(unit.Container); DropOutNearest(unit, depot->tilePos + depot->Type->GetHalfTileSize(), source); } UnitGotoGoal(unit, depot, SUB_MOVE_TO_DEPOT); } if (IsOnlySelected(unit)) { SelectedUnitChanged(); } #if 1 return 1; #endif }
void CBuilder::Update() { CBuilderCAI* cai = static_cast<CBuilderCAI*>(commandAI); const CCommandQueue& cQueue = cai->commandQue; const Command& fCommand = (!cQueue.empty())? cQueue.front(): Command(CMD_STOP); nanoPieceCache.Update(); if (!beingBuilt && !IsStunned()) { if (terraforming && inBuildStance) { assert(!mapDamage->disabled); // The map should not be deformed in the first place. const float* heightmap = readMap->GetCornerHeightMapSynced(); float terraformScale = 0.1; switch (terraformType) { case Terraform_Building: if (curBuild != NULL) { if (curBuild->terraformLeft <= 0) terraformScale = 0.0f; else terraformScale = (terraformSpeed + terraformHelp) / curBuild->terraformLeft; curBuild->terraformLeft -= (terraformSpeed + terraformHelp); terraformHelp = 0; terraformScale = std::min(terraformScale, 1.0f); // prevent building from timing out while terraforming for it curBuild->AddBuildPower(this, 0.0f); for (int z = tz1; z <= tz2; z++) { for (int x = tx1; x <= tx2; x++) { int idx = z * gs->mapxp1 + x; float ch = heightmap[idx]; readMap->AddHeight(idx, (curBuild->pos.y - ch) * terraformScale); } } if (curBuild->terraformLeft <= 0.0f) { terraforming = false; mapDamage->RecalcArea(tx1, tx2, tz1, tz2); curBuild->groundLevelled = true; if (eventHandler.TerraformComplete(this, curBuild)) { StopBuild(); } } } break; case Terraform_Restore: if (myTerraformLeft <= 0.0f) terraformScale = 0.0f; else terraformScale = (terraformSpeed + terraformHelp) / myTerraformLeft; myTerraformLeft -= (terraformSpeed + terraformHelp); terraformHelp = 0; terraformScale = std::min(terraformScale, 1.0f); for (int z = tz1; z <= tz2; z++) { for (int x = tx1; x <= tx2; x++) { int idx = z * gs->mapxp1 + x; float ch = heightmap[idx]; float oh = readMap->GetOriginalHeightMapSynced()[idx]; readMap->AddHeight(idx, (oh - ch) * terraformScale); } } if (myTerraformLeft <= 0.0f) { terraforming = false; mapDamage->RecalcArea(tx1, tx2, tz1, tz2); StopBuild(); } break; } ScriptDecloak(true); CreateNanoParticle(terraformCenter, terraformRadius * 0.5f, false); for (int z = tz1; z <= tz2; z++) { // smooth the borders x for (int x = 1; x <= 3; x++) { if (tx1 - 3 >= 0) { const float ch3 = heightmap[z * gs->mapxp1 + tx1 ]; const float ch = heightmap[z * gs->mapxp1 + tx1 - x]; const float ch2 = heightmap[z * gs->mapxp1 + tx1 - 3]; const float amount = ((ch3 * (3 - x) + ch2 * x) / 3 - ch) * terraformScale; readMap->AddHeight(z * gs->mapxp1 + tx1 - x, amount); } if (tx2 + 3 < gs->mapx) { const float ch3 = heightmap[z * gs->mapxp1 + tx2 ]; const float ch = heightmap[z * gs->mapxp1 + tx2 + x]; const float ch2 = heightmap[z * gs->mapxp1 + tx2 + 3]; const float amount = ((ch3 * (3 - x) + ch2 * x) / 3 - ch) * terraformScale; readMap->AddHeight(z * gs->mapxp1 + tx2 + x, amount); } } } for (int z = 1; z <= 3; z++) { // smooth the borders z for (int x = tx1; x <= tx2; x++) { if (tz1 - 3 >= 0) { const float ch3 = heightmap[(tz1 ) * gs->mapxp1 + x]; const float ch = heightmap[(tz1 - z) * gs->mapxp1 + x]; const float ch2 = heightmap[(tz1 - 3) * gs->mapxp1 + x]; const float adjust = ((ch3 * (3 - z) + ch2 * z) / 3 - ch) * terraformScale; readMap->AddHeight((tz1 - z) * gs->mapxp1 + x, adjust); } if (tz2 + 3 < gs->mapy) { const float ch3 = heightmap[(tz2 ) * gs->mapxp1 + x]; const float ch = heightmap[(tz2 + z) * gs->mapxp1 + x]; const float ch2 = heightmap[(tz2 + 3) * gs->mapxp1 + x]; const float adjust = ((ch3 * (3 - z) + ch2 * z) / 3 - ch) * terraformScale; readMap->AddHeight((tz2 + z) * gs->mapxp1 + x, adjust); } } } } else if (helpTerraform != NULL && inBuildStance) { if (helpTerraform->terraforming) { ScriptDecloak(true); helpTerraform->terraformHelp += terraformSpeed; CreateNanoParticle(helpTerraform->terraformCenter, helpTerraform->terraformRadius * 0.5f, false); } else { DeleteDeathDependence(helpTerraform, DEPENDENCE_TERRAFORM); helpTerraform = NULL; StopBuild(true); } } else if (curBuild != NULL && cai->IsInBuildRange(curBuild)) { if (fCommand.GetID() == CMD_WAIT) { if (curBuild->buildProgress < 1.0f) { // prevent buildee from decaying (we cannot call StopBuild here) curBuild->AddBuildPower(this, 0.0f); } else { // stop repairing (FIXME: should be much cleaner to let BuilderCAI // call this instead when a wait command is given?) StopBuild(); } } else { if (curBuild->soloBuilder != NULL && (curBuild->soloBuilder != this)) { StopBuild(); } else { if (inBuildStance || true) { // NOTE: // technically this block of code should be guarded by // "if (inBuildStance)", but doing so can create zombie // guarders because scripts might not set inBuildStance // to true when guard or repair orders are executed and // SetRepairTarget does not check for it // // StartBuild *does* ensure construction will not start // until inBuildStance is set to true by the builder's // script, and there are no cases during construction // when inBuildStance can become false yet the buildee // should be kept from decaying, so this is free from // serious side-effects (when repairing, a builder might // start adding build-power before having fully finished // its opening animation) // ScriptDecloak(true); // adjusted build-speed: use repair-speed on units with // progress >= 1 rather than raw build-speed on buildees // with progress < 1 float adjBuildSpeed = 0.0f; if (curBuild->buildProgress >= 1.0f) { adjBuildSpeed = std::min(repairSpeed, unitDef->maxRepairSpeed * 0.5f - curBuild->repairAmount); // repair } else { adjBuildSpeed = buildSpeed; // build } if (adjBuildSpeed > 0.0f && curBuild->AddBuildPower(this, adjBuildSpeed)) { CreateNanoParticle(curBuild->midPos, curBuild->radius * 0.5f, false); } else { // check if buildee finished construction if (!curBuild->beingBuilt && curBuild->health >= curBuild->maxHealth) { StopBuild(); } } } } } } else if (curReclaim != NULL && f3SqDist(curReclaim->pos, pos) < Square(buildDistance + curReclaim->radius) && inBuildStance) { if (fCommand.GetID() == CMD_WAIT) { StopBuild(); } else { ScriptDecloak(true); if (curReclaim->AddBuildPower(this, -reclaimSpeed)) { CreateNanoParticle(curReclaim->midPos, curReclaim->radius * 0.7f, true, (reclaimingUnit && curReclaim->team != team)); } } } else if (curResurrect != NULL && f3SqDist(curResurrect->pos, pos) < Square(buildDistance + curResurrect->radius) && inBuildStance) { const UnitDef* ud = curResurrect->udef; if (fCommand.GetID() == CMD_WAIT) { StopBuild(); } else { if (ud != NULL) { if ((modInfo.reclaimMethod != 1) && (curResurrect->reclaimLeft < 1)) { // This corpse has been reclaimed a little, need to restore // the resources before we can let the player resurrect it. curResurrect->AddBuildPower(this, repairSpeed); } else { // Corpse has been restored, begin resurrection const float step = resurrectSpeed / ud->buildTime; const bool resurrectAllowed = eventHandler.AllowFeatureBuildStep(this, curResurrect, step); const bool canExecResurrect = (resurrectAllowed && UseEnergy(ud->energy * step * modInfo.resurrectEnergyCostFactor)); if (canExecResurrect) { curResurrect->resurrectProgress += step; curResurrect->resurrectProgress = std::min(curResurrect->resurrectProgress, 1.0f); CreateNanoParticle(curResurrect->midPos, curResurrect->radius * 0.7f, (gs->randInt() & 1)); } if (curResurrect->resurrectProgress >= 1.0f) { if (curResurrect->tempNum != (gs->tempNum - 1)) { // resurrect finished and we are the first curResurrect->UnBlock(); UnitLoadParams resurrecteeParams = {ud, this, curResurrect->pos, ZeroVector, -1, team, curResurrect->buildFacing, false, false}; CUnit* resurrectee = unitLoader->LoadUnit(resurrecteeParams); assert(ud == resurrectee->unitDef); if (!ud->canBeAssisted) { resurrectee->soloBuilder = this; resurrectee->AddDeathDependence(this, DEPENDENCE_BUILDER); } // TODO: make configurable if this should happen resurrectee->health *= 0.05f; for (CUnitSet::iterator it = cai->resurrecters.begin(); it != cai->resurrecters.end(); ++it) { CBuilder* bld = static_cast<CBuilder*>(*it); CCommandAI* bldCAI = bld->commandAI; if (bldCAI->commandQue.empty()) continue; Command& c = bldCAI->commandQue.front(); if (c.GetID() != CMD_RESURRECT || c.params.size() != 1) continue; if ((c.params[0] - unitHandler->MaxUnits()) != curResurrect->id) continue; if (!teamHandler->Ally(allyteam, bld->allyteam)) continue; // all units that were rezzing shall assist the repair too bld->lastResurrected = resurrectee->id; // prevent FinishCommand from removing this command when the // feature is deleted, since it is needed to start the repair // (WTF!) c.params[0] = INT_MAX / 2; } // prevent double/triple/... resurrection if more than one // builder is resurrecting (such that resurrectProgress can // possibly become >= 1 again *this* simframe) curResurrect->resurrectProgress = 0.0f; curResurrect->tempNum = gs->tempNum++; // this takes one simframe to do the deletion featureHandler->DeleteFeature(curResurrect); } StopBuild(true); } } } else { StopBuild(true); } } } else if (curCapture != NULL && f3SqDist(curCapture->pos, pos) < Square(buildDistance + curCapture->radius) && inBuildStance) { if (fCommand.GetID() == CMD_WAIT) { StopBuild(); } else { if (curCapture->team != team) { const float captureMagicNumber = (150.0f + (curCapture->buildTime / captureSpeed) * (curCapture->health + curCapture->maxHealth) / curCapture->maxHealth * 0.4f); const float captureProgressStep = 1.0f / captureMagicNumber; const float captureProgressTemp = std::min(curCapture->captureProgress + captureProgressStep, 1.0f); const float captureFraction = captureProgressTemp - curCapture->captureProgress; const float energyUseScaled = curCapture->energyCost * captureFraction * modInfo.captureEnergyCostFactor; const bool captureAllowed = (eventHandler.AllowUnitBuildStep(this, curCapture, captureProgressStep)); const bool canExecCapture = (captureAllowed && UseEnergy(energyUseScaled)); if (canExecCapture) { curCapture->captureProgress += captureProgressStep; curCapture->captureProgress = std::min(curCapture->captureProgress, 1.0f); CreateNanoParticle(curCapture->midPos, curCapture->radius * 0.7f, false, true); if (curCapture->captureProgress >= 1.0f) { if (!curCapture->ChangeTeam(team, CUnit::ChangeCaptured)) { // capture failed if (team == gu->myTeam) { LOG_L(L_WARNING, "%s: Capture failed, unit type limit reached", unitDef->humanName.c_str()); eventHandler.LastMessagePosition(pos); } } curCapture->captureProgress = 0.0f; StopBuild(true); } } } else { StopBuild(true); } } } } CUnit::Update(); }
/** ** Start harvesting the resource. ** ** @param unit Pointer to unit. ** ** @return TRUE if ready, otherwise FALSE. */ int COrder_Resource::StartGathering(CUnit &unit) { CUnit *goal; const ResourceInfo &resinfo = *unit.Type->ResInfo[this->CurrentResource]; Assert(!unit.IX); Assert(!unit.IY); //Wyrmgus start // if (resinfo.TerrainHarvester) { if (Map.Info.IsPointOnMap(this->goalPos)) { //Wyrmgus end // This shouldn't happened? #if 0 if (!Map.IsTerrainResourceOnMap(unit.Orders->goalPos, this->CurrentResource)) { DebugPrint("Wood gone, just like that?\n"); return 0; } #endif UnitHeadingFromDeltaXY(unit, this->goalPos - unit.tilePos); if (resinfo.WaitAtResource) { this->TimeToHarvest = std::max<int>(1, resinfo.WaitAtResource * SPEEDUP_FACTOR / unit.Player->SpeedResourcesHarvest[resinfo.ResourceId]); } else { this->TimeToHarvest = 1; } this->DoneHarvesting = 0; if (this->CurrentResource != unit.CurrentResource) { DropResource(unit); unit.CurrentResource = this->CurrentResource; } return 1; } goal = this->GetGoal(); // Target is dead, stop getting resources. if (!goal || goal->IsVisibleAsGoal(*unit.Player) == false) { // Find an alternative, but don't look too far. this->goalPos.x = -1; this->goalPos.y = -1; if ((goal = UnitFindResource(unit, unit, 15, this->CurrentResource, unit.Player->AiEnabled))) { this->State = SUB_START_RESOURCE; this->SetGoal(goal); } else { this->ClearGoal(); this->Finished = true; } return 0; } // FIXME: 0 can happen, if to near placed by map designer. Assert(unit.MapDistanceTo(*goal) <= 1); // Update the heading of a harvesting unit to looks straight at the resource. //Wyrmgus start // UnitHeadingFromDeltaXY(unit, goal->tilePos - unit.tilePos + goal->Type->GetHalfTileSize()); UnitHeadingFromDeltaXY(unit, Vec2i(goal->tilePos.x * PixelTileSize.x, goal->tilePos.y * PixelTileSize.y) - Vec2i(unit.tilePos.x * PixelTileSize.x, unit.tilePos.y * PixelTileSize.y) + goal->Type->GetHalfTilePixelSize() - unit.Type->GetHalfTilePixelSize()); //Wyrmgus end // If resource is still under construction, wait! if ((goal->Type->MaxOnBoard && goal->Resource.Active >= goal->Type->MaxOnBoard) || goal->CurrentAction() == UnitActionBuilt) { // FIXME: Determine somehow when the resource will be free to use // FIXME: Could we somehow find another resource? Think minerals // FIXME: We should add a flag for that, and a limited range. // However the CPU usage is really low (no pathfinding stuff). unit.Wait = 10; return 0; } // Place unit inside the resource //Wyrmgus start // if (!resinfo.HarvestFromOutside) { if (!goal->Type->BoolFlag[HARVESTFROMOUTSIDE_INDEX].value) { //Wyrmgus end if (goal->Variable[MAXHARVESTERS_INDEX].Value == 0 || goal->Variable[MAXHARVESTERS_INDEX].Value > goal->InsideCount) { this->ClearGoal(); int selected = unit.Selected; unit.Remove(goal); if (selected && !Preference.DeselectInMine) { unit.Removed = 0; SelectUnit(unit); SelectionChanged(); unit.Removed = 1; } } else if (goal->Variable[MAXHARVESTERS_INDEX].Value <= goal->InsideCount) { //Resource is full, wait unit.Wait = 10; return 0; } } if (this->CurrentResource != unit.CurrentResource) { DropResource(unit); unit.CurrentResource = this->CurrentResource; } // Activate the resource goal->Resource.Active++; if (resinfo.WaitAtResource) { //Wyrmgus start // this->TimeToHarvest = std::max<int>(1, resinfo.WaitAtResource * SPEEDUP_FACTOR / unit.Player->SpeedResourcesHarvest[resinfo.ResourceId]); int wait_at_resource = resinfo.WaitAtResource; if (!goal->Type->BoolFlag[HARVESTFROMOUTSIDE_INDEX].value) { wait_at_resource = resinfo.WaitAtResource * 100 / resinfo.ResourceStep; } this->TimeToHarvest = std::max<int>(1, wait_at_resource * SPEEDUP_FACTOR / (unit.Player->SpeedResourcesHarvest[resinfo.ResourceId] + goal->Variable[TIMEEFFICIENCYBONUS_INDEX].Value)); //Wyrmgus end } else { this->TimeToHarvest = 1; } this->DoneHarvesting = 0; return 1; }
/** ** Gather the resource ** ** @param unit Pointer to unit. ** ** @return non-zero if ready, otherwise zero. */ int COrder_Resource::GatherResource(CUnit &unit) { CUnit *source = 0; const ResourceInfo &resinfo = *unit.Type->ResInfo[this->CurrentResource]; int addload; //Wyrmgus start bool harvest_from_outside = (this->GetGoal() && this->GetGoal()->Type->BoolFlag[HARVESTFROMOUTSIDE_INDEX].value); // if (resinfo.HarvestFromOutside || resinfo.TerrainHarvester) { if (harvest_from_outside || Map.Info.IsPointOnMap(this->goalPos)) { //Wyrmgus end AnimateActionHarvest(unit); } else { unit.Anim.CurrAnim = NULL; } this->TimeToHarvest--; if (this->DoneHarvesting) { //Wyrmgus start // Assert(resinfo.HarvestFromOutside || resinfo.TerrainHarvester); Assert(harvest_from_outside || Map.Info.IsPointOnMap(this->goalPos)); //Wyrmgus end return !unit.Anim.Unbreakable; } // Target gone? //Wyrmgus start // if (resinfo.TerrainHarvester && !Map.Field(this->goalPos)->IsTerrainResourceOnMap(this->CurrentResource)) { if (Map.Info.IsPointOnMap(this->goalPos) && !Map.Field(this->goalPos)->IsTerrainResourceOnMap(this->CurrentResource)) { //Wyrmgus end if (!unit.Anim.Unbreakable) { // Action now breakable, move to resource again. this->State = SUB_MOVE_TO_RESOURCE; // Give it some reasonable look while searching. // FIXME: which frame? unit.Frame = 0; } return 0; // No wood? Freeze!!! } while (!this->DoneHarvesting && this->TimeToHarvest < 0) { //FIXME: rb - how should it look for WaitAtResource == 0 if (resinfo.WaitAtResource) { // Wyrmgus start // this->TimeToHarvest += std::max<int>(1, resinfo.WaitAtResource * SPEEDUP_FACTOR / unit.Player->SpeedResourcesHarvest[resinfo.ResourceId]); int wait_at_resource = resinfo.WaitAtResource; int resource_harvest_speed = unit.Player->SpeedResourcesHarvest[resinfo.ResourceId]; if (!Map.Info.IsPointOnMap(this->goalPos) && !harvest_from_outside) { wait_at_resource = resinfo.WaitAtResource * 100 / resinfo.ResourceStep; } if (this->GetGoal()) { resource_harvest_speed += this->GetGoal()->Variable[TIMEEFFICIENCYBONUS_INDEX].Value; } this->TimeToHarvest += std::max<int>(1, wait_at_resource * SPEEDUP_FACTOR / resource_harvest_speed); //Wyrmgus end } else { this->TimeToHarvest += 1; } // Calculate how much we can load. //Wyrmgus start // if (resinfo.ResourceStep) { if (resinfo.ResourceStep && (harvest_from_outside || Map.Info.IsPointOnMap(this->goalPos))) { //Wyrmgus end addload = resinfo.ResourceStep; } else { addload = resinfo.ResourceCapacity; } // Make sure we don't bite more than we can chew. if (unit.ResourcesHeld + addload > resinfo.ResourceCapacity) { addload = resinfo.ResourceCapacity - unit.ResourcesHeld; } //Wyrmgus start // if (resinfo.TerrainHarvester) { if (Map.Info.IsPointOnMap(this->goalPos)) { //Wyrmgus end //Wyrmgus start CMapField &mf = *Map.Field(this->goalPos); if (addload > mf.Value) { addload = mf.Value; } mf.Value -= addload; //Wyrmgus end unit.ResourcesHeld += addload; //Wyrmgus start // if (addload && unit.ResourcesHeld == resinfo.ResourceCapacity) { if (mf.Value <= 0) { //Wyrmgus end //Wyrmgus start // Map.ClearWoodTile(this->goalPos); if (this->CurrentResource == WoodCost) { Map.ClearWoodTile(this->goalPos); } else if (this->CurrentResource == StoneCost) { Map.ClearRockTile(this->goalPos); } //Wyrmgus end } } else { //Wyrmgus start // if (resinfo.HarvestFromOutside) { if (harvest_from_outside) { //Wyrmgus end source = this->GetGoal(); } else { source = unit.Container; } Assert(source); Assert(source->ResourcesHeld <= 655350); //Wyrmgus start UpdateUnitVariables(*source); //update resource source's variables //Wyrmgus end bool is_visible = source->IsVisibleAsGoal(*unit.Player); // Target is not dead, getting resources. if (is_visible) { // Don't load more that there is. addload = std::min(source->ResourcesHeld, addload); unit.ResourcesHeld += addload; source->ResourcesHeld -= addload; } // End of resource: destroy the resource. // FIXME: implement depleted resources. if ((!is_visible) || (source->ResourcesHeld == 0)) { if (unit.Anim.Unbreakable) { return 0; } DebugPrint("%d: Worker %d report: Resource is destroyed\n" _C_ unit.Player->Index _C_ UnitNumber(unit)); bool dead = source->IsAlive() == false; // Improved version of DropOutAll that makes workers go to the depot. LoseResource(unit, *source); for (CUnit *uins = source->Resource.Workers; uins; uins = uins->NextWorker) { if (uins != &unit && uins->CurrentOrder()->Action == UnitActionResource) { COrder_Resource &order = *static_cast<COrder_Resource *>(uins->CurrentOrder()); if (!uins->Anim.Unbreakable && order.State == SUB_GATHER_RESOURCE) { order.LoseResource(*uins, *source); } } } // Don't destroy the resource twice. // This only happens when it's empty. if (!dead) { if (Preference.MineNotifications && unit.Player->Index == ThisPlayer->Index //Wyrmgus start // && source->Variable[GIVERESOURCE_INDEX].Max > DefaultIncomes[this->CurrentResource]) { && source->Variable[GIVERESOURCE_INDEX].Max > (DefaultIncomes[this->CurrentResource] * 10)) { //Wyrmgus end //Wyrmgus start // unit.Player->Notify(NotifyYellow, source->tilePos, _("%s has collapsed!"), source->Type->Name.c_str()); unit.Player->Notify(NotifyYellow, source->tilePos, _("Our %s has been depleted!"), source->Type->Name.c_str()); //Wyrmgus end } LetUnitDie(*source); // FIXME: make the workers inside look for a new resource. } source = NULL; return 0; } } //Wyrmgus start // if (resinfo.TerrainHarvester) { if (Map.Info.IsPointOnMap(this->goalPos)) { //Wyrmgus end if (unit.ResourcesHeld == resinfo.ResourceCapacity) { // Mark as complete. this->DoneHarvesting = true; } return 0; } else { //Wyrmgus start // if (resinfo.HarvestFromOutside) { if (harvest_from_outside) { //Wyrmgus end if ((unit.ResourcesHeld == resinfo.ResourceCapacity) || (source == NULL)) { // Mark as complete. this->DoneHarvesting = true; } return 0; } else { return unit.ResourcesHeld == resinfo.ResourceCapacity && source; } } } return 0; }
/** ** Control the unit action: getting a resource. ** ** This the generic function for oil, gold, ... ** ** @param unit Pointer to unit. */ void COrder_Resource::Execute(CUnit &unit) { // can be different by Cloning (trained unit)... this->worker = &unit; if (unit.Wait) { if (!unit.Waiting) { unit.Waiting = 1; unit.WaitBackup = unit.Anim; } //Wyrmgus start // UnitShowAnimation(unit, unit.Type->Animations->Still); UnitShowAnimation(unit, unit.GetAnimations()->Still); //Wyrmgus end unit.Wait--; return; } if (unit.Waiting) { unit.Anim = unit.WaitBackup; unit.Waiting = 0; } // Let's start mining. if (this->State == SUB_START_RESOURCE) { if (ActionResourceInit(unit) == false) { ResourceGiveUp(unit); return; } } // Move to the resource location. if (SUB_MOVE_TO_RESOURCE <= this->State && this->State < SUB_UNREACHABLE_RESOURCE) { const int ret = MoveToResource(unit); switch (ret) { case -1: { // Can't Reach this->State++; unit.Wait = 5; return; } case 1: { // Reached this->State = SUB_START_GATHERING; break; } case 0: // Move along. return; default: { Assert(0); break; } } } // Resource seems to be unreachable if (this->State == SUB_UNREACHABLE_RESOURCE) { if (this->FindAnotherResource(unit) == false) { ResourceGiveUp(unit); return; } } // Start gathering the resource if (this->State == SUB_START_GATHERING) { if (StartGathering(unit)) { this->State = SUB_GATHER_RESOURCE; } else { return; } } // Gather the resource. if (this->State == SUB_GATHER_RESOURCE) { if (GatherResource(unit)) { this->State = SUB_STOP_GATHERING; } else { return; } } // Stop gathering the resource. if (this->State == SUB_STOP_GATHERING) { if (StopGathering(unit)) { this->State = SUB_MOVE_TO_DEPOT; unit.pathFinderData->output.Cycles = 0; //moving counter } else { return; } } // Move back home. if (SUB_MOVE_TO_DEPOT <= this->State && this->State < SUB_UNREACHABLE_DEPOT) { const int ret = MoveToDepot(unit); switch (ret) { case -1: { // Can't Reach this->State++; unit.Wait = 5; return; } case 1: { // Reached this->State = SUB_RETURN_RESOURCE; return; } case 0: // Move along. return; default: { Assert(0); return; } } } // Depot seems to be unreachable if (this->State == SUB_UNREACHABLE_DEPOT) { ResourceGiveUp(unit); return; } // Unload resources at the depot. if (this->State == SUB_RETURN_RESOURCE) { if (WaitInDepot(unit)) { this->State = SUB_START_RESOURCE; // It's posible, though very rare that the unit's goal blows up // this cycle, but after this unit. Thus, next frame the unit // will start mining a destroyed site. If, on the otherhand we // are already in SUB_MOVE_TO_RESOURCE then we can handle it. // So, we pass through SUB_START_RESOURCE the very instant it // goes out of the depot. //HandleActionResource(order, unit); } } }
/** ** Wait in depot, for the resources stored. ** ** @param unit Pointer to unit. ** ** @return TRUE if ready, otherwise FALSE. */ bool COrder_Resource::WaitInDepot(CUnit &unit) { const ResourceInfo &resinfo = *unit.Type->ResInfo[this->CurrentResource]; const CUnit *depot = ResourceDepositOnMap(unit.tilePos, resinfo.ResourceId); //Assert(depot); // Range hardcoded. don't stray too far though //Wyrmgus start // if (resinfo.TerrainHarvester) { if (!this->Resource.Mine) { //Wyrmgus end Vec2i pos = this->Resource.Pos; //Wyrmgus start // if (FindTerrainType(unit.Type->MovementMask, MapFieldForest, 10, *unit.Player, pos, &pos)) { if ((this->CurrentResource == WoodCost && FindTerrainType(unit.Type->MovementMask, MapFieldForest, 10, *unit.Player, pos, &pos)) || (this->CurrentResource == StoneCost && FindTerrainType(unit.Type->MovementMask, MapFieldRocks, 10, *unit.Player, pos, &pos))) { //Wyrmgus end if (depot) { DropOutNearest(unit, pos, depot); } this->goalPos = pos; //Wyrmgus start if (this->CurrentResource == WoodCost) { //tree tiles can regrow, so we need to check if any have regrown closer to the worker Vec2i forestPos; int max_forest_range = std::max<int>(abs(unit.tilePos.x - this->goalPos.x), abs(unit.tilePos.y - this->goalPos.y)); if (FindTerrainType(unit.Type->MovementMask, MapFieldForest, max_forest_range, *unit.Player, unit.tilePos, &forestPos)) { if (PlaceReachable(unit, forestPos, 1, 1, 0, 1, max_forest_range * 4)) { this->goalPos = forestPos; } } } //Wyrmgus end } else { if (depot) { DropOutOnSide(unit, LookingW, depot); } this->Finished = true; return false; } } else { const unsigned int tooManyWorkers = 15; CUnit *mine = this->Resource.Mine; const int range = 15; CUnit *newdepot = NULL; CUnit *goal = NULL; const bool longWay = unit.pathFinderData->output.Cycles > 500; //Wyrmgus start // if (unit.Player->AiEnabled && AiPlayer && AiPlayer->BuildDepots) { if (depot && unit.Player->AiEnabled && AiPlayer && AiPlayer->BuildDepots) { //check if the depot is valid //Wyrmgus end // If the depot is overused, we need first to try to switch into another depot // Use depot's ref counter for that if (longWay || !mine || (depot->Refs > tooManyWorkers)) { newdepot = AiGetSuitableDepot(unit, *depot, &goal); if (newdepot == NULL && longWay) { // We need a new depot AiNewDepotRequest(unit); } } } // If goal is not NULL, then we got it in AiGetSuitableDepot if (!goal) { goal = UnitFindResource(unit, newdepot ? *newdepot : (mine ? *mine : unit), mine ? range : 1000, this->CurrentResource, unit.Player->AiEnabled, newdepot ? newdepot : depot); } if (goal) { if (depot) { DropOutNearest(unit, goal->tilePos + goal->Type->GetHalfTileSize(), depot); } if (goal != mine) { if (mine) { unit.DeAssignWorkerFromMine(*mine); } unit.AssignWorkerToMine(*goal); this->Resource.Mine = goal; } this->SetGoal(goal); this->goalPos.x = this->goalPos.y = -1; } else { #ifdef DEBUG const Vec2i &pos = mine ? mine->tilePos : unit.tilePos; DebugPrint("%d: Worker %d report: [%d,%d] Resource gone near [%d,%d] in range %d. Sit and play dumb.\n" _C_ unit.Player->Index _C_ UnitNumber(unit) _C_ unit.tilePos.x _C_ unit.tilePos.y _C_ pos.x _C_ pos.y _C_ range); #endif // DEBUG if (depot) { DropOutOnSide(unit, LookingW, depot); } if (mine) { unit.DeAssignWorkerFromMine(*mine); this->Resource.Mine = NULL; } this->Finished = true; return false; } } return true; }
/** ** Move to resource depot ** ** @param unit Pointer to unit. ** ** @return TRUE if reached, otherwise FALSE. */ int COrder_Resource::MoveToDepot(CUnit &unit) { const ResourceInfo &resinfo = *unit.Type->ResInfo[this->CurrentResource]; CUnit &goal = *this->GetGoal(); CPlayer &player = *unit.Player; Assert(&goal); switch (DoActionMove(unit)) { // reached end-point? case PF_UNREACHABLE: //Wyrmgus start //if is unreachable and is on a raft, see if the raft can move closer if ((Map.Field(unit.tilePos)->Flags & MapFieldBridge) && !unit.Type->BoolFlag[BRIDGE_INDEX].value && unit.Type->UnitType == UnitTypeLand) { std::vector<CUnit *> table; Select(unit.tilePos, unit.tilePos, table); for (size_t i = 0; i != table.size(); ++i) { if (!table[i]->Removed && table[i]->Type->BoolFlag[BRIDGE_INDEX].value && table[i]->CanMove()) { if (table[i]->CurrentAction() == UnitActionStill) { CommandStopUnit(*table[i]); CommandMove(*table[i], this->HasGoal() ? this->GetGoal()->tilePos : this->goalPos, FlushCommands); } return 0; } } } //Wyrmgus end return -1; case PF_REACHED: break; case PF_WAIT: if (unit.Player->AiEnabled) { this->Range++; if (this->Range >= 5) { this->Range = 0; AiCanNotMove(unit); } } default: if (unit.Anim.Unbreakable || goal.IsVisibleAsGoal(player)) { return 0; } break; } // // Target is dead, stop getting resources. // if (!goal.IsVisibleAsGoal(player)) { DebugPrint("%d: Worker %d report: Destroyed depot\n" _C_ player.Index _C_ UnitNumber(unit)); unit.CurrentOrder()->ClearGoal(); CUnit *depot = FindDeposit(unit, 1000, unit.CurrentResource); if (depot) { UnitGotoGoal(unit, depot, SUB_MOVE_TO_DEPOT); DebugPrint("%d: Worker %d report: Going to new deposit.\n" _C_ player.Index _C_ UnitNumber(unit)); } else { DebugPrint("%d: Worker %d report: Can't find a new resource deposit.\n" _C_ player.Index _C_ UnitNumber(unit)); // FIXME: perhaps we should choose an alternative this->Finished = true; } return 0; } // If resource depot is still under construction, wait! if (goal.CurrentAction() == UnitActionBuilt) { unit.Wait = 10; return 0; } this->ClearGoal(); unit.Wait = resinfo.WaitAtDepot; // Place unit inside the depot if (unit.Wait) { int selected = unit.Selected; unit.Remove(&goal); if (selected && !Preference.DeselectInMine) { unit.Removed = 0; SelectUnit(unit); SelectionChanged(); unit.Removed = 1; } unit.Anim.CurrAnim = NULL; } // Update resource. const int rindex = resinfo.FinalResource; //Wyrmgus start // player.ChangeResource(rindex, (unit.ResourcesHeld * player.Incomes[rindex]) / 100, true); // player.TotalResources[rindex] += (unit.ResourcesHeld * player.Incomes[rindex]) / 100; player.ChangeResource(rindex, (unit.ResourcesHeld * resinfo.FinalResourceConversionRate / 100 * player.Incomes[rindex]) / 100, true); player.TotalResources[rindex] += (unit.ResourcesHeld * resinfo.FinalResourceConversionRate / 100 * player.Incomes[rindex]) / 100; //Wyrmgus end unit.ResourcesHeld = 0; unit.CurrentResource = 0; if (unit.Wait) { //Wyrmgus start // unit.Wait /= std::max(1, unit.Player->SpeedResourcesReturn[resinfo.ResourceId] / SPEEDUP_FACTOR); unit.Wait /= std::max(1, (unit.Player->SpeedResourcesReturn[resinfo.ResourceId] + goal.Variable[TIMEEFFICIENCYBONUS_INDEX].Value) / SPEEDUP_FACTOR); //Wyrmgus end if (unit.Wait) { unit.Wait--; } } return 1; }
void CFactory::CreateComputerUnit(int nType) { PROFILE("CFactory::CreateComputerUnit(int)"); //static float xPos = 100; //static float yPos = 50; CUnit* unit = new CUnit(nType); // Use default shallow copy since no dynamic info in creation CUnit temp = CGame::GetInstance()->GetCPUUnitInfo(nType); unit->SetAttackPower(temp.GetAttackPower()); unit->SetAttackSpeed(temp.GetAttackSpeed()); unit->SetMaxHP(temp.GetMaxHP()); unit->SetCurrentHP(temp.GetMaxHP()); unit->SetRange(temp.GetRange()); unit->SetSpeed(temp.GetSpeed()); unit->SetState(IDLE); unit->SetDirection(NORTH_WEST); unit->SetIsPlayerUnit(false); // Register Events unit->SetAttackSoundID(CGame::GetInstance()->GetAttackSound(unit->GetType())); unit->SetDeathSoundID(CGame::GetInstance()->GetDeathSound(unit->GetType())); switch(CGame::GetInstance()->GetSelectedCity()->GetID()) { case KCITY1: break; case KCITY2: unit->SetAttackPower(unit->GetAttackPower()+2); break; case KCITY3: unit->SetAttackPower(unit->GetAttackPower()+2); unit->SetAttackSpeed(unit->GetAttackSpeed()-(unit->GetAttackSpeed()*.2f)); unit->SetSpeed(unit->GetSpeed()-(unit->GetSpeed()*.5f)); break; case XCITY1: break; case XCITY2: unit->SetAttackPower(unit->GetAttackPower()+2); break; case XCITY3: unit->SetAttackPower(unit->GetAttackPower()+2); unit->SetAttackSpeed(unit->GetAttackSpeed()-(unit->GetAttackSpeed()*.2f)); unit->SetSpeed(unit->GetSpeed()-(unit->GetSpeed()*.5f)); break; case JCITY1: break; case JCITY2: unit->SetAttackPower(unit->GetAttackPower()+2); break; case JCITY3: unit->SetAttackPower(unit->GetAttackPower()+2); unit->SetAttackSpeed(unit->GetAttackSpeed()-(unit->GetAttackSpeed()*.2f)); unit->SetSpeed(unit->GetSpeed()-(unit->GetSpeed()*.5f)); break; } // Add to manager ObjectManager::GetInstance()->AddObject(unit); // Let it know we aren't hanging on to it unit->Release(); STOP("CFactory::CreateComputerUnit(int)"); }
bool CBuilder::StartBuild(BuildInfo& buildInfo, CFeature*& feature, bool& waitStance) { StopBuild(false); TempHoldFire(-1); buildInfo.pos = CGameHelper::Pos2BuildPos(buildInfo, true); // Pass -1 as allyteam to behave like we have maphack. // This is needed to prevent building on top of cloaked stuff. const CGameHelper::BuildSquareStatus tbs = CGameHelper::TestUnitBuildSquare(buildInfo, feature, -1, true); switch (tbs) { case CGameHelper::BUILDSQUARE_OPEN: break; case CGameHelper::BUILDSQUARE_BLOCKED: case CGameHelper::BUILDSQUARE_OCCUPIED: { // the ground is blocked at the position we want // to build at; check if the blocking object is // of the same type as our buildee (which means // another builder has already started it) // note: even if construction has already started, // the buildee is *not* guaranteed to be the unit // closest to us CSolidObject* o = groundBlockingObjectMap->GroundBlocked(buildInfo.pos); CUnit* u = NULL; if (o != NULL) { u = dynamic_cast<CUnit*>(o); } else { // <pos> might map to a non-blocking portion // of the buildee's yardmap, fallback check u = CGameHelper::GetClosestFriendlyUnit(NULL, buildInfo.pos, buildDistance, allyteam); } if (u != NULL && CanAssistUnit(u, buildInfo.def)) { curBuild = u; AddDeathDependence(u, DEPENDENCE_BUILD); ScriptStartBuilding(u->pos, false); return true; } return false; } case CGameHelper::BUILDSQUARE_RECLAIMABLE: // caller should handle this return false; } if ((waitStance = !ScriptStartBuilding(buildInfo.pos, true))) { return false; } const UnitDef* buildeeDef = buildInfo.def; const UnitLoadParams buildeeParams = {buildeeDef, this, buildInfo.pos, ZeroVector, -1, team, buildInfo.buildFacing, true, false}; CUnit* buildee = unitLoader->LoadUnit(buildeeParams); // floating structures don't terraform the seabed const float groundheight = CGround::GetHeightReal(buildee->pos.x, buildee->pos.z); const bool onWater = (buildeeDef->floatOnWater && groundheight <= 0.0f); if (mapDamage->disabled || !buildeeDef->levelGround || onWater || buildeeDef->IsAirUnit() || !buildeeDef->IsImmobileUnit() ) { // skip the terraforming job buildee->terraformLeft = 0; buildee->groundLevelled = true; } else { tx1 = (int)max( 0.0f, (buildee->pos.x - (buildeeDef->xsize * 0.5f * SQUARE_SIZE)) / SQUARE_SIZE); tx2 = min(gs->mapx, tx1 + buildeeDef->xsize ); tz1 = (int)max( 0.0f, (buildee->pos.z - (buildeeDef->zsize * 0.5f * SQUARE_SIZE)) / SQUARE_SIZE); tz2 = min(gs->mapy, tz1 + buildeeDef->zsize ); buildee->terraformLeft = CalculateBuildTerraformCost(buildInfo); buildee->groundLevelled = false; terraforming = true; terraformType = Terraform_Building; terraformRadius = (tx2 - tx1) * SQUARE_SIZE; terraformCenter = buildee->pos; } if (!this->unitDef->canBeAssisted) { buildee->soloBuilder = this; buildee->AddDeathDependence(this, DEPENDENCE_BUILDER); } AddDeathDependence(buildee, DEPENDENCE_BUILD); curBuild = buildee; /* The ground isn't going to be terraformed. * When the building is completed, it'll 'pop' * into the correct height for the (un-flattened) * terrain it's on. * * To prevent this visual artifact, put the building * at the 'right' height to begin with. */ curBuild->moveType->SlowUpdate(); return true; }
bool CHoverAirMoveType::HandleCollisions() { float3& pos = owner->pos; if (pos != oldPos) { oldPos = pos; const bool checkCollisions = collide && (!loadingUnits) && (padStatus == 0) && (aircraftState != AIRCRAFT_TAKEOFF); if (checkCollisions) { const vector<CUnit*>& nearUnits = qf->GetUnitsExact(pos, owner->radius + 6); for (vector<CUnit*>::const_iterator ui = nearUnits.begin(); ui != nearUnits.end(); ++ui) { CUnit* unit = *ui; if (unit->transporter != NULL) continue; const float sqDist = (pos - unit->pos).SqLength(); const float totRad = owner->radius + unit->radius; if (sqDist <= 0.1f || sqDist >= (totRad * totRad)) continue; const float dist = math::sqrt(sqDist); const float3 dif = (pos - unit->pos).Normalize(); if (unit->mass >= CSolidObject::DEFAULT_MASS || unit->immobile) { pos -= dif * (dist - totRad); owner->UpdateMidPos(); owner->speed *= 0.99f; } else { const float part = owner->mass / (owner->mass + unit->mass); pos -= dif * (dist - totRad) * (1.0f - part); owner->UpdateMidPos(); unit->pos += dif * (dist - totRad) * (part); unit->UpdateMidPos(); const float colSpeed = -owner->speed.dot(dif) + unit->speed.dot(dif); owner->speed += (dif * colSpeed * (1.0f - part)); unit->speed -= (dif * colSpeed * (part)); } } } if (pos.x < 0.0f) { pos.x += 0.6f; owner->midPos.x += 0.6f; } else if (pos.x > float3::maxxpos) { pos.x -= 0.6f; owner->midPos.x -= 0.6f; } if (pos.z < 0.0f) { pos.z += 0.6f; owner->midPos.z += 0.6f; } else if (pos.z > float3::maxzpos) { pos.z -= 0.6f; owner->midPos.z -= 0.6f; } return true; } return false; }
// called by {CRifle, CBeamLaser, CLightningCannon}::Fire() and Skirmish AIs float TraceRay(const float3& start, const float3& dir, float length, int collisionFlags, const CUnit* owner, CUnit*& hitUnit, CFeature*& hitFeature) { const bool ignoreEnemies = !!(collisionFlags & Collision::NOENEMIES); const bool ignoreAllies = !!(collisionFlags & Collision::NOFRIENDLIES); const bool ignoreFeatures = !!(collisionFlags & Collision::NOFEATURES); const bool ignoreNeutrals = !!(collisionFlags & Collision::NONEUTRALS); const bool ignoreGround = !!(collisionFlags & Collision::NOGROUND); const bool ignoreUnits = ignoreEnemies && ignoreAllies && ignoreNeutrals; hitFeature = NULL; hitUnit = NULL; if (dir == ZeroVector) { return -1.0f; } CollisionQuery cq; { GML_RECMUTEX_LOCK(quad); // TraceRay const vector<int> &quads = qf->GetQuadsOnRay(start, dir, length); //! feature intersection if (!ignoreFeatures) { for (vector<int>::const_iterator qi = quads.begin(); qi != quads.end(); ++qi) { const CQuadField::Quad& quad = qf->GetQuad(*qi); for (std::list<CFeature*>::const_iterator ui = quad.features.begin(); ui != quad.features.end(); ++ui) { CFeature* f = *ui; if (!f->blocking || !f->collisionVolume) { // NOTE: why check the blocking property? continue; } if (CCollisionHandler::Intersect(f, start, start + dir * length, &cq)) { const float3& intPos = (cq.b0)? cq.p0: cq.p1; const float len = (intPos - start).dot(dir); //! same as (intPos - start).Length() //! we want the closest feature (intersection point) on the ray if (len < length) { length = len; hitFeature = f; } } } } } //! unit intersection if (!ignoreUnits) { for (vector<int>::const_iterator qi = quads.begin(); qi != quads.end(); ++qi) { const CQuadField::Quad& quad = qf->GetQuad(*qi); for (std::list<CUnit*>::const_iterator ui = quad.units.begin(); ui != quad.units.end(); ++ui) { CUnit* u = *ui; if (u == owner) continue; if (ignoreAllies && u->allyteam == owner->allyteam) continue; if (ignoreNeutrals && u->IsNeutral()) continue; if (ignoreEnemies && u->allyteam != owner->allyteam) continue; if (CCollisionHandler::Intersect(u, start, start + dir * length, &cq)) { const float3& intPos = (cq.b0)? cq.p0: cq.p1; const float len = (intPos - start).dot(dir); //! same as (intPos - start).Length() //! we want the closest unit (intersection point) on the ray if (len < length) { length = len; hitUnit = u; } } } } if (hitUnit) hitFeature = NULL; } } //GML_RECMUTEX_LOCK(quad); if (!ignoreGround) { //! ground intersection float groundLength = ground->LineGroundCol(start, start + dir * length); if (length > groundLength && groundLength > 0) { length = groundLength; hitUnit = NULL; hitFeature = NULL; } } return length; }
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 CLightningCannon::FireImpl() { float3 dir = targetPos - weaponMuzzlePos; dir.ANormalize(); dir += (gs->randVector() * sprayAngle + salvoError) * (1.0f - owner->limExperience * 0.5f); dir.ANormalize(); const CUnit* cu = NULL; float r = helper->TraceRay(weaponMuzzlePos, dir, range, 0, (const CUnit*)owner, cu, collisionFlags); CUnit* u = (cu == NULL) ? NULL : uh->units[cu->id]; float3 newDir; CPlasmaRepulser* shieldHit = NULL; const float shieldLength = interceptHandler.AddShieldInterceptableBeam(this, weaponMuzzlePos, dir, range, newDir, shieldHit); if (shieldLength < r) { r = shieldLength; if (shieldHit) { shieldHit->BeamIntercepted(this); } } if (u) { if (u->unitDef->usePieceCollisionVolumes) { u->SetLastAttackedPiece(u->localmodel->pieces[0], gs->frameNum); } } // Dynamic Damage DamageArray dynDamages; if (weaponDef->dynDamageExp > 0) { dynDamages = weaponDefHandler->DynamicDamages( weaponDef->damages, weaponMuzzlePos, targetPos, weaponDef->dynDamageRange > 0? weaponDef->dynDamageRange: weaponDef->range, weaponDef->dynDamageExp, weaponDef->dynDamageMin, weaponDef->dynDamageInverted ); } helper->Explosion( weaponMuzzlePos + dir * r, weaponDef->dynDamageExp > 0? dynDamages: weaponDef->damages, areaOfEffect, weaponDef->edgeEffectiveness, weaponDef->explosionSpeed, owner, false, 0.5f, weaponDef->noExplode || weaponDef->noSelfDamage, /*true*/ weaponDef->impactOnly, /*false*/ weaponDef->explosionGenerator, u, dir, weaponDef->id ); new CLightningProjectile( weaponMuzzlePos, weaponMuzzlePos + dir * (r + 10), owner, color, weaponDef, 10, this ); }
/** ** Toggle units from a particular type and belonging to the local player. ** ** The base is included in the selection and defines ** the type of the other units to be selected. ** ** @param base Toggle all units of same type. ** ** @return Number of units found, 0 means selection unchanged ** ** FIXME: toggle not written ** FIXME: should always select the nearest 9 units to the base! */ int ToggleUnitsByType(CUnit &base) { const CUnitType &type = *base.Type; // if unit is a cadaver or hidden (not on map) // no unit can be selected. if (base.Removed || base.IsAlive() == false) { return 0; } // if unit isn't belonging to the player, or is a static unit // (like a building), only 1 unit can be selected at the same time. if (!CanSelectMultipleUnits(*base.Player) || !type.BoolFlag[SELECTABLEBYRECTANGLE_INDEX].value) { return 0; } //Wyrmgus start if (Selected.size() && !UnitCanBeSelectedWith(*Selected[0], base)) { return 0; } //Wyrmgus end if (!SelectUnit(base)) { // Add base to selection return 0; } // // Search for other visible units of the same type // // select all visible units. // StephanR: should be (MapX,MapY,MapX+MapWidth-1,MapY+MapHeight-1) ??? // FIXME: this should probably be cleaner implemented if SelectUnitsByType() // took parameters of the selection rectangle as arguments */ const CViewport *vp = UI.MouseViewport; const Vec2i offset(1, 1); const Vec2i minPos = vp->MapPos - offset; const Vec2i vpSize(vp->MapWidth, vp->MapHeight); const Vec2i maxPos = vp->MapPos + vpSize + offset; std::vector<CUnit *> table; //Wyrmgus start // Select(minPos, maxPos, table, HasSameTypeAs(type)); Select(minPos, maxPos, table, UI.CurrentMapLayer->ID, HasSameTypeAs(type)); //Wyrmgus end // FIXME: peon/peasant with gold/wood & co are considered from // different type... idem for tankers for (size_t i = 0; i < table.size(); ++i) { CUnit &unit = *table[i]; if (!CanSelectMultipleUnits(*unit.Player)) { continue; } if (unit.IsUnusable()) { // guess SelectUnits doesn't check this continue; } if (&unit == &base) { // no need to have the same unit twice continue; } if (unit.TeamSelected) { // Somebody else onteam has this unit continue; } //Wyrmgus start if (Selected.size() && !UnitCanBeSelectedWith(*Selected[0], unit)) { continue; } //Wyrmgus end if (!SelectUnit(unit)) { // add unit to selection return Selected.size(); } } NetworkSendSelection(&Selected[0], Selected.size()); return Selected.size(); }
/** ** Assign workers to collect resources. ** ** If we have a shortage of a resource, let many workers collecting this. ** If no shortage, split workers to all resources. */ static void AiCollectResources() { std::vector<CUnit *> units_assigned[MaxCosts]; // Worker assigned to resource std::vector<CUnit *> units_unassigned[MaxCosts]; // Unassigned workers int num_units_with_resource[MaxCosts]; int num_units_assigned[MaxCosts]; int num_units_unassigned[MaxCosts]; int percent[MaxCosts]; int priority_resource[MaxCosts]; int priority_needed[MaxCosts]; int wanted[MaxCosts]; int total_harvester = 0; memset(num_units_with_resource, 0, sizeof(num_units_with_resource)); memset(num_units_unassigned, 0, sizeof(num_units_unassigned)); memset(num_units_assigned, 0, sizeof(num_units_assigned)); // Collect statistics about the current assignment const int n = AiPlayer->Player->GetUnitCount(); for (int i = 0; i < n; ++i) { CUnit &unit = AiPlayer->Player->GetUnit(i); if (!unit.Type->Harvester) { continue; } // See if it's assigned already if (unit.Orders.size() == 1 && unit.CurrentAction() == UnitActionResource) { const COrder_Resource &order = *static_cast<COrder_Resource *>(unit.CurrentOrder()); const int c = order.GetCurrentResource(); units_assigned[c].push_back(&unit); num_units_assigned[c]++; total_harvester++; continue; } // Ignore busy units. ( building, fighting, ... ) if (!unit.IsIdle()) { continue; } // Send workers with resources back home. if (unit.ResourcesHeld) { const int c = unit.CurrentResource; num_units_with_resource[c]++; CommandReturnGoods(unit, 0, FlushCommands); total_harvester++; continue; } // Look what the unit can do for (int c = 1; c < MaxCosts; ++c) { if (unit.Type->ResInfo[c]) { units_unassigned[c].push_back(&unit); num_units_unassigned[c]++; } } ++total_harvester; } if (!total_harvester) { return; } memset(wanted, 0, sizeof(wanted)); int percent_total = 100; for (int c = 1; c < MaxCosts; ++c) { percent[c] = AiPlayer->Collect[c]; if ((AiPlayer->NeededMask & (1 << c))) { // Double percent if needed percent_total += percent[c]; percent[c] <<= 1; } } // Turn percent values into harvester numbers. for (int c = 1; c < MaxCosts; ++c) { if (percent[c]) { // Wanted needs to be representative. if (total_harvester < 5) { wanted[c] = 1 + (percent[c] * 5) / percent_total; } else { wanted[c] = 1 + (percent[c] * total_harvester) / percent_total; } } } // Initialise priority & mapping for (int c = 0; c < MaxCosts; ++c) { priority_resource[c] = c; priority_needed[c] = wanted[c] - num_units_assigned[c] - num_units_with_resource[c]; if (c && num_units_assigned[c] > 1) { //first should go workers with lower ResourcesHeld value std::sort(units_assigned[c].begin(), units_assigned[c].end(), CmpWorkers); } } CUnit *unit; do { // sort resources by priority for (int i = 0; i < MaxCosts; ++i) { for (int j = i + 1; j < MaxCosts; ++j) { if (priority_needed[j] > priority_needed[i]) { std::swap(priority_needed[i], priority_needed[j]); std::swap(priority_resource[i], priority_resource[j]); } } } unit = NULL; // Try to complete each ressource in the priority order for (int i = 0; i < MaxCosts; ++i) { int c = priority_resource[i]; // If there is a free worker for c, take it. if (num_units_unassigned[c]) { // Take the unit. while (0 < num_units_unassigned[c] && !AiAssignHarvester(*units_unassigned[c][0], c)) { // can't assign to c => remove from units_unassigned ! units_unassigned[c][0] = units_unassigned[c][--num_units_unassigned[c]]; units_unassigned[c].pop_back(); } // unit is assigned if (0 < num_units_unassigned[c]) { unit = units_unassigned[c][0]; units_unassigned[c][0] = units_unassigned[c][--num_units_unassigned[c]]; units_unassigned[c].pop_back(); // remove it from other ressources for (int j = 0; j < MaxCosts; ++j) { if (j == c || !unit->Type->ResInfo[j]) { continue; } for (int k = 0; k < num_units_unassigned[j]; ++k) { if (units_unassigned[j][k] == unit) { units_unassigned[j][k] = units_unassigned[j][--num_units_unassigned[j]]; units_unassigned[j].pop_back(); break; } } } } } // Else : Take from already assigned worker with lower priority. if (!unit) { // Take from lower priority only (i+1). for (int j = i + 1; j < MaxCosts && !unit; ++j) { // Try to move worker from src_c to c const int src_c = priority_resource[j]; // Don't complete with lower priority ones... if (wanted[src_c] > wanted[c] || (wanted[src_c] == wanted[c] && num_units_assigned[src_c] <= num_units_assigned[c] + 1)) { continue; } for (int k = num_units_assigned[src_c] - 1; k >= 0 && !unit; --k) { unit = units_assigned[src_c][k]; Assert(unit->CurrentAction() == UnitActionResource); COrder_Resource &order = *static_cast<COrder_Resource *>(unit->CurrentOrder()); if (order.IsGatheringFinished()) { //worker returning with resource continue; } // unit can't harvest : next one if (!unit->Type->ResInfo[c] || !AiAssignHarvester(*unit, c)) { unit = NULL; continue; } // Remove from src_c units_assigned[src_c][k] = units_assigned[src_c][--num_units_assigned[src_c]]; units_assigned[src_c].pop_back(); // j need one more priority_needed[j]++; } } } // We just moved an unit. Adjust priority & retry if (unit) { // i got a new unit. priority_needed[i]--; // Recompute priority now break; } } } while (unit); // Unassigned units there can't be assigned ( ie : they can't move to ressource ) // IDEA : use transporter here. }
void CAirCAI::ExecuteAttack(Command& c) { assert(owner->unitDef->canAttack); targetAge++; if (tempOrder && owner->moveState == MOVESTATE_MANEUVER) { // limit how far away we fly if (orderTarget && LinePointDist(commandPos1, commandPos2, orderTarget->pos) > 1500) { owner->AttackUnit(NULL, false, false); FinishCommand(); return; } } if (inCommand) { if (targetDied || (c.params.size() == 1 && UpdateTargetLostTimer(int(c.params[0])) == 0)) { FinishCommand(); return; } if (orderTarget != NULL) { if (orderTarget->unitDef->canfly && orderTarget->IsCrashing()) { owner->AttackUnit(NULL, false, false); FinishCommand(); return; } if (!(c.options & ALT_KEY) && SkipParalyzeTarget(orderTarget)) { owner->AttackUnit(NULL, false, false); FinishCommand(); return; } } } else { targetAge = 0; if (c.params.size() == 1) { CUnit* targetUnit = unitHandler->GetUnit(c.params[0]); if (targetUnit == NULL) { FinishCommand(); return; } if (targetUnit == owner) { FinishCommand(); return; } if (targetUnit->GetTransporter() != NULL && !modInfo.targetableTransportedUnits) { FinishCommand(); return; } SetGoal(targetUnit->pos, owner->pos, cancelDistance); SetOrderTarget(targetUnit); owner->AttackUnit(targetUnit, (c.options & INTERNAL_ORDER) == 0, false); inCommand = true; } else { SetGoal(c.GetPos(0), owner->pos, cancelDistance); owner->AttackGround(c.GetPos(0), (c.options & INTERNAL_ORDER) == 0, false); inCommand = true; } } }
void AiNewDepotRequest(CUnit &worker) { #if 0 DebugPrint("%d: Worker %d report: Resource [%d] too far from depot, returning time [%d].\n" _C_ worker->Player->Index _C_ worker->Slot _C_ worker->CurrentResource _C_ worker->Data.Move.Cycles); #endif Assert(worker.CurrentAction() == UnitActionResource); COrder_Resource &order = *static_cast<COrder_Resource *>(worker.CurrentOrder()); const int resource = order.GetCurrentResource(); const Vec2i pos = order.GetHarvestLocation(); const int range = 15; if (pos.x != -1 && NULL != FindDepositNearLoc(*worker.Player, pos, range, resource)) { /* * New Depot has just be finished and worker just return to old depot * (far away) from new Deopt. */ return; } CUnitType *best_type = NULL; int best_cost = 0; //int best_mask = 0; // Count the already made build requests. int counter[UnitTypeMax]; AiGetBuildRequestsCount(*worker.Player->Ai, counter); const int n = AiHelpers.Depots[resource - 1].size(); for (int i = 0; i < n; ++i) { CUnitType &type = *AiHelpers.Depots[resource - 1][i]; if (counter[type.Slot]) { // Already ordered. return; } if (!AiRequestedTypeAllowed(*worker.Player, type)) { continue; } // Check if resources available. //int needmask = AiCheckUnitTypeCosts(type); int cost = 0; for (int c = 1; c < MaxCosts; ++c) { cost += type.Stats[worker.Player->Index].Costs[c]; } if (best_type == NULL || (cost < best_cost)) { best_type = &type; best_cost = cost; //best_mask = needmask; } } if (best_type) { //if(!best_mask) { AiBuildQueue queue; queue.Type = best_type; queue.Want = 1; queue.Made = 0; queue.Pos = pos; worker.Player->Ai->UnitTypeBuilt.push_back(queue); DebugPrint("%d: Worker %d report: Requesting new depot near [%d,%d].\n" _C_ worker.Player->Index _C_ UnitNumber(worker) _C_ queue.Pos.x _C_ queue.Pos.y); /* } else { AiPlayer->NeededMask |= best_mask; } */ } }
/** ** Save the state of a unit to file. ** ** @param unit Unit pointer to be saved. ** @param file Output file. */ void SaveUnit(const CUnit &unit, CFile *file) { CUnit *uins; int i; file->printf("\nUnit(%d, ", UnitNumber(unit)); // 'type and 'player must be first, needed to create the unit slot file->printf("\"type\", \"%s\", ", unit.Type->Ident.c_str()); if (unit.Seen.Type) { file->printf("\"seen-type\", \"%s\", ", unit.Seen.Type->Ident.c_str()); } file->printf("\"player\", %d,\n ", unit.Player->Index); if (unit.Next) { file->printf("\"next\", %d, ", UnitNumber(*unit.Next)); } file->printf("\"tile\", {%d, %d}, ", unit.tilePos.x, unit.tilePos.y); file->printf("\"refs\", %d, ", unit.Refs); #if 0 // latimerius: why is this so complex? // JOHNS: An unit can be owned by a new player and have still the old stats for (i = 0; i < PlayerMax; ++i) { if (&unit.Type->Stats[i] == unit.Stats) { file->printf("\"stats\", %d,\n ", i); break; } } // latimerius: what's the point of storing a pointer value anyway? if (i == PlayerMax) { file->printf("\"stats\", \"S%08X\",\n ", (int)unit.Stats); } #else file->printf("\"stats\", %d,\n ", unit.Player->Index); #endif file->printf("\"pixel\", {%d, %d}, ", unit.IX, unit.IY); file->printf("\"seen-pixel\", {%d, %d}, ", unit.Seen.IX, unit.Seen.IY); file->printf("\"frame\", %d, ", unit.Frame); if (unit.Seen.Frame != UnitNotSeen) { file->printf("\"seen\", %d, ", unit.Seen.Frame); } else { file->printf("\"not-seen\", "); } file->printf("\"direction\", %d,\n ", unit.Direction); file->printf("\"attacked\", %lu,\n ", unit.Attacked); file->printf(" \"current-sight-range\", %d,", unit.CurrentSightRange); if (unit.Burning) { file->printf(" \"burning\","); } if (unit.Destroyed) { file->printf(" \"destroyed\","); } if (unit.Removed) { file->printf(" \"removed\","); } if (unit.Selected) { file->printf(" \"selected\","); } if (unit.RescuedFrom) { file->printf(" \"rescued-from\", %d,", unit.RescuedFrom->Index); } // n0b0dy: How is this useful? // mr-russ: You can't always load units in order, it saved the information // so you can load a unit whose Container hasn't been loaded yet. // SEE unit loading code. if (unit.Container && unit.Removed) { file->printf(" \"host-info\", {%d, %d, %d, %d}, ", unit.Container->tilePos.x, unit.Container->tilePos.y, unit.Container->Type->TileWidth, unit.Container->Type->TileHeight); } file->printf(" \"seen-by-player\", \""); for (i = 0; i < PlayerMax; ++i) { file->printf("%c", (unit.Seen.ByPlayer & (1 << i)) ? 'X' : '_'); } file->printf("\",\n "); file->printf(" \"seen-destroyed\", \""); for (i = 0; i < PlayerMax; ++i) { file->printf("%c", (unit.Seen.Destroyed & (1 << i)) ? 'X' : '_'); } file->printf("\",\n "); if (unit.Constructed) { file->printf(" \"constructed\","); } if (unit.Seen.Constructed) { file->printf(" \"seen-constructed\","); } file->printf(" \"seen-state\", %d, ", unit.Seen.State); if (unit.Active) { file->printf(" \"active\","); } file->printf("\"ttl\", %lu, ", unit.TTL); for (i = 0; i < (int)UnitTypeVar.GetNumberVariable(); ++i) { file->printf("\"%s\", {Value = %d, Max = %d, Increase = %d, Enable = %s},\n ", UnitTypeVar.VariableNameLookup[i], unit.Variable[i].Value, unit.Variable[i].Max, unit.Variable[i].Increase, unit.Variable[i].Enable ? "true" : "false"); } file->printf("\"group-id\", %d,\n ", unit.GroupId); file->printf("\"last-group\", %d,\n ", unit.LastGroup); file->printf("\"resources-held\", %d,\n ", unit.ResourcesHeld); if (unit.CurrentResource) { file->printf("\"current-resource\", \"%s\",\n ", DefaultResourceNames[unit.CurrentResource].c_str()); } if (unit.SubAction && unit.IsAgressive() && (unit.CurrentAction() == UnitActionStill || unit.CurrentAction() == UnitActionStandGround)) { //Force recalculate Guard points //if unit atack from StandGround then attac target is recalculate //When unit first time handle action code. file->printf("\"sub-action\", 0, "); } else { file->printf("\"sub-action\", %d, ", unit.SubAction); } file->printf("\"wait\", %d, ", unit.Wait); file->printf("\"state\", %d,", unit.State); file->printf("\"anim-wait\", %d,", unit.Anim.Wait); for (i = 0; i < NumAnimations; ++i) { if (AnimationsArray[i] == unit.Anim.CurrAnim) { file->printf("\"curr-anim\", %d,", i); file->printf("\"anim\", %d,", unit.Anim.Anim - unit.Anim.CurrAnim); break; } } if (unit.Anim.Unbreakable) { file->printf(" \"unbreakable\","); } file->printf("\n \"blink\", %d,", unit.Blink); if (unit.Moving) { file->printf(" \"moving\","); } if (unit.ReCast) { file->printf(" \"re-cast\","); } if (unit.Boarded) { file->printf(" \"boarded\","); } if (unit.AutoRepair) { file->printf(" \"auto-repair\","); } if (unit.NextWorker) { if (unit.NextWorker->Destroyed) { /* this unit is destroyed so it's not in the global unit * array - this means it won't be saved!!! */ printf ("FIXME: storing destroyed Worker - loading will fail.\n"); } file->printf(" \"next-worker\", \"%s\",", UnitReference(*unit.NextWorker).c_str()); } file->printf(" \"units-boarded-count\", %d,", unit.BoardCount); if (unit.UnitInside) { file->printf("\n \"units-contained\", {"); uins = unit.UnitInside->PrevContained; for (i = unit.InsideCount; i; --i, uins = uins->PrevContained) { file->printf("\"%s\"", UnitReference(*uins).c_str()); if (i > 1) { file->printf(", "); } } file->printf("},\n "); } Assert((unsigned int)unit.OrderCount == unit.Orders.size()); file->printf("\"order-count\", %d,\n ", unit.OrderCount); file->printf("\"order-flush\", %d,\n ", unit.OrderFlush); file->printf("\"orders\", {"); for (i = 0; i < unit.OrderCount; ++i) { file->printf("\n "); SaveOrder(unit.Orders[i], file); if (i < unit.OrderCount - 1) { file->printf(","); } } file->printf("},\n \"saved-order\", "); SaveOrder((COrderPtr)(&unit.SavedOrder), file); file->printf(",\n \"critical-order\", "); SaveOrder((COrderPtr)(&unit.CriticalOrder), file); file->printf(",\n \"new-order\", "); SaveOrder((COrderPtr)(&unit.NewOrder), file); // // Order data part // switch (unit.CurrentAction()) { case UnitActionStill: // FIXME: support other resource types if (unit.Type->GivesResource) { file->printf(", \"resource-active\", %d", unit.Data.Resource.Active); if (unit.Type->CanHarvest) { file->printf(", \"data-resource\", {\"assigned\", %d", unit.Data.Resource.Assigned); if (unit.Data.Resource.Workers) { if (unit.Data.Resource.Workers->Destroyed) { /* this unit is destroyed so it's not in the global unit * array - this means it won't be saved!!! */ printf ("FIXME: storing destroyed Worker - loading will fail.\n"); } file->printf(", \"first-worker\", \"%s\"", UnitReference(*unit.Data.Resource.Workers).c_str()); } file->printf("}"); } } break; case UnitActionResource: file->printf(", \"data-res-worker\", {\"time-to-harvest\", %d", unit.Data.ResWorker.TimeToHarvest); if (unit.Data.ResWorker.DoneHarvesting) { file->printf(", \"done-harvesting\""); } file->printf("}"); break; case UnitActionBuilt: { CConstructionFrame *cframe; int frame; cframe = unit.Type->Construction->Frames; frame = 0; while (cframe != unit.Data.Built.Frame) { cframe = cframe->Next; ++frame; } file->printf(",\n \"data-built\", {"); if (unit.Data.Built.Worker) { file->printf("\"worker\", \"%s\", ", UnitReference(*unit.Data.Built.Worker).c_str()); } file->printf("\"progress\", %d, \"frame\", %d", unit.Data.Built.Progress, frame); if (unit.Data.Built.Cancel) { file->printf(", \"cancel\""); } file->printf("}"); break; } case UnitActionResearch: file->printf(",\n \"data-research\", {"); file->printf("\"ident\", \"%s\"", unit.Data.Research.Upgrade->Ident.c_str()); file->printf("}"); break; case UnitActionUpgradeTo: file->printf(",\n \"data-upgrade-to\", {"); file->printf("\"ticks\", %d", unit.Data.UpgradeTo.Ticks); file->printf("}"); break; case UnitActionTrain: file->printf(",\n \"data-train\", {"); file->printf("\"ticks\", %d ", unit.Data.Train.Ticks); file->printf("}"); break; default: file->printf(",\n \"data-move\", {"); if (unit.Data.Move.Cycles) { file->printf("\"cycles\", %d,", unit.Data.Move.Cycles); } if (unit.Data.Move.Fast) { file->printf("\"fast\", "); } if (unit.Data.Move.Length > 0) { file->printf("\"path\", {"); for (i = 0; i < unit.Data.Move.Length; ++i) { file->printf("%d, ", unit.Data.Move.Path[i]); } file->printf("}"); } file->printf("}"); break; } if (unit.Goal) { file->printf(",\n \"goal\", %d", UnitNumber(*unit.Goal)); } if (unit.AutoCastSpell) { for (i = 0; (unsigned int) i < SpellTypeTable.size(); ++i) { if (unit.AutoCastSpell[i]) { file->printf(",\n \"auto-cast\", \"%s\"", SpellTypeTable[i]->Ident.c_str()); } } } file->printf(")\n"); }
void CLightningCannon::FireImpl(bool scriptCall) { float3 curPos = weaponMuzzlePos; float3 curDir = (targetPos - curPos).Normalize(); float3 newDir = curDir; curDir += (gs->randVector() * SprayAngleExperience() + SalvoErrorExperience()); curDir.Normalize(); CUnit* hitUnit = NULL; CFeature* hitFeature = NULL; CPlasmaRepulser* hitShield = NULL; CollisionQuery hitColQuery; float boltLength = TraceRay::TraceRay(curPos, curDir, range, collisionFlags, owner, hitUnit, hitFeature, &hitColQuery); if (!weaponDef->waterweapon) { // terminate bolt at water surface if necessary if ((curDir.y < 0.0f) && ((curPos.y + curDir.y * boltLength) <= 0.0f)) { boltLength = curPos.y / -curDir.y; } } const float shieldLength = interceptHandler.AddShieldInterceptableBeam(this, curPos, curDir, range, newDir, hitShield); if (shieldLength < boltLength) { boltLength = shieldLength; hitShield->BeamIntercepted(this); } if (hitUnit != NULL) { hitUnit->SetLastAttackedPiece(hitColQuery.GetHitPiece(), gs->frameNum); } const DamageArray& damageArray = (weaponDef->dynDamageExp <= 0.0f)? weaponDef->damages: weaponDefHandler->DynamicDamages( weaponDef->damages, weaponMuzzlePos, targetPos, (weaponDef->dynDamageRange > 0.0f)? weaponDef->dynDamageRange: weaponDef->range, weaponDef->dynDamageExp, weaponDef->dynDamageMin, weaponDef->dynDamageInverted ); const CGameHelper::ExplosionParams params = { curPos + curDir * boltLength, // hitPos (same as hitColQuery.GetHitPos() if no water or shield in way) curDir, damageArray, weaponDef, owner, hitUnit, hitFeature, craterAreaOfEffect, damageAreaOfEffect, weaponDef->edgeEffectiveness, weaponDef->explosionSpeed, 0.5f, // gfxMod weaponDef->impactOnly, weaponDef->noExplode || weaponDef->noSelfDamage, // ignoreOwner false, // damageGround -1u // projectileID }; helper->Explosion(params); ProjectileParams pparams = GetProjectileParams(); pparams.pos = curPos; pparams.end = curPos + curDir * (boltLength + 10.0f); pparams.ttl = 10; WeaponProjectileFactory::LoadProjectile(pparams); }
bool CHoverAirMoveType::HandleCollisions(bool checkCollisions) { const float3& pos = owner->pos; if (pos != oldPos) { oldPos = pos; bool hitBuilding = false; // check for collisions if not on a pad, not being built, or not taking off // includes an extra condition for transports, which are exempt while loading if (!loadingUnits && checkCollisions) { const vector<CUnit*>& nearUnits = quadField->GetUnitsExact(pos, owner->radius + 6); for (vector<CUnit*>::const_iterator ui = nearUnits.begin(); ui != nearUnits.end(); ++ui) { CUnit* unit = *ui; if (unit->transporter != NULL) continue; const float sqDist = (pos - unit->pos).SqLength(); const float totRad = owner->radius + unit->radius; if (sqDist <= 0.1f || sqDist >= (totRad * totRad)) continue; const float dist = math::sqrt(sqDist); const float3 dif = (pos - unit->pos).Normalize(); if (unit->mass >= CSolidObject::DEFAULT_MASS || unit->immobile) { owner->Move(-dif * (dist - totRad), true); owner->SetVelocity(owner->speed * 0.99f); hitBuilding = true; } else { const float part = owner->mass / (owner->mass + unit->mass); owner->Move(-dif * (dist - totRad) * (1.0f - part), true); unit->Move(dif * (dist - totRad) * (part), true); const float colSpeed = -owner->speed.dot(dif) + unit->speed.dot(dif); owner->SetVelocity(owner->speed + (dif * colSpeed * (1.0f - part))); unit->SetVelocityAndSpeed(unit->speed - (dif * colSpeed * (part))); } } owner->SetSpeed(owner->speed); } if (hitBuilding && owner->IsCrashing()) { owner->KillUnit(NULL, true, false); return true; } if (pos.x < 0.0f) { owner->Move( RgtVector * 0.6f, true); } else if (pos.x > float3::maxxpos) { owner->Move(-RgtVector * 0.6f, true); } if (pos.z < 0.0f) { owner->Move( FwdVector * 0.6f, true); } else if (pos.z > float3::maxzpos) { owner->Move(-FwdVector * 0.6f, true); } return true; } return false; }
void CFactory::CreatePlayerUnit(int nType) { PROFILE("CFactory::CreatePlayerUnit(int)"); //static float xPos = 100; //static float yPos = 50; CUnit* unit = new CUnit(nType); // Use default shallow copy since no dynamic info in creation CUnit temp = CGame::GetInstance()->GetUnitInfo(nType); unit->SetAttackPower(temp.GetAttackPower()); unit->SetAttackSpeed(temp.GetAttackSpeed()); unit->SetMaxHP(temp.GetMaxHP()); unit->SetCurrentHP(temp.GetMaxHP()); unit->SetRange(temp.GetRange()); unit->SetSpeed(temp.GetSpeed()); unit->SetState(IDLE); unit->SetDirection(SOUTH_WEST); unit->SetIsPlayerUnit(true); unit->SetAttackSoundID(CGame::GetInstance()->GetAttackSound(unit->GetType())); // Register Events unit->SetDeathSoundID(CGame::GetInstance()->GetDeathSound(unit->GetType())); // Add to manager ObjectManager::GetInstance()->AddObject(unit); // Let it know we aren't hanging on to it unit->Release(); STOP("CFactory::CreatePlayerUnit(int)"); }
/** ** Select units from a particular type and belonging to the local player. ** ** The base is included in the selection and defines ** the type of the other units to be selected. ** ** @param base Select all units of same type. ** ** @return Number of units found, 0 means selection unchanged ** ** FIXME: 0 can't happen. Maybe when scripting will use it? ** ** FIXME: should always select the nearest 9 units to the base! */ int SelectUnitsByType(CUnit &base) { const CUnitType &type = *base.Type; const CViewport *vp = UI.MouseViewport; Assert(UI.MouseViewport); if (type.ClicksToExplode) { HandleSuicideClick(base); } // if unit is a cadaver or hidden (not on map) // no unit can be selected. if (base.Removed || base.IsAlive() == false) { return 0; } if (type.BoolFlag[ISNOTSELECTABLE_INDEX].value && GameRunning) { return 0; } if (base.TeamSelected) { // Somebody else onteam has this unit return 0; } UnSelectAll(); Selected.push_back(&base); base.Selected = 1; // if unit isn't belonging to the player or allied player, or is a static unit // (like a building), only 1 unit can be selected at the same time. if (!CanSelectMultipleUnits(*base.Player) || !type.BoolFlag[SELECTABLEBYRECTANGLE_INDEX].value) { return Selected.size(); } // // Search for other visible units of the same type // std::vector<CUnit *> table; // select all visible units. // StephanR: should be (MapX,MapY,MapX+MapWidth-1,MapY+MapHeight-1) ??? /* FIXME: this should probably be cleaner implemented if SelectUnitsByType() * took parameters of the selection rectangle as arguments */ const Vec2i offset(1, 1); const Vec2i minPos = vp->MapPos - offset; const Vec2i vpSize(vp->MapWidth, vp->MapHeight); const Vec2i maxPos = vp->MapPos + vpSize + offset; Select(minPos, maxPos, table, HasSameTypeAs(type)); // FIXME: peon/peasant with gold/wood & co are considered from // different type... idem for tankers for (size_t i = 0; i != table.size(); ++i) { CUnit &unit = *table[i]; if (!CanSelectMultipleUnits(*unit.Player)) { continue; } if (unit.IsUnusable()) { // guess SelectUnits doesn't check this continue; } if (&unit == &base) { // no need to have the same unit twice :) continue; } if (unit.TeamSelected) { // Somebody else onteam has this unit continue; } Selected.push_back(&unit); unit.Selected = 1; if (Selected.size() == MaxSelectable) { break; } } if (Selected.size() > 1) { for (size_t i = 0; i != Selected.size(); ++i) { Selected[i]->LastGroup = GroupId; } } NetworkSendSelection(&Selected[0], Selected.size()); return Selected.size(); }
/** ** Cast summon spell. ** ** @param caster Unit that casts the spell ** @param spell Spell-type pointer ** @param target Target unit that spell is addressed to ** @param goalPos coord of target spot when/if target does not exist ** ** @return =!0 if spell should be repeated, 0 if not */ /* virtual */ int Spell_Summon::Cast(CUnit &caster, const SpellType &spell, CUnit *target, const Vec2i &goalPos) { Vec2i pos = goalPos; bool cansummon; CUnitType &unittype = *this->UnitType; int ttl = this->TTL; if (this->RequireCorpse) { const Vec2i offset(1, 1); const Vec2i minPos = pos - offset; const Vec2i maxPos = pos + offset; CUnit *unit = FindUnit_If(minPos, maxPos, IsDyingAndNotABuilding()); cansummon = false; if (unit != NULL) { // Found a corpse. eliminate it and proceed to summoning. pos = unit->tilePos; unit->Remove(NULL); unit->Release(); cansummon = true; } } else { cansummon = true; } if (cansummon) { //Wyrmgus start // DebugPrint("Summoning a %s\n" _C_ unittype.Name.c_str()); DebugPrint("Summoning a %s\n" _C_ unittype.GetDefaultName(*caster.Player).c_str()); //Wyrmgus end // // Create units. // FIXME: do summoned units count on food? // target = MakeUnit(unittype, caster.Player); if (target != NULL) { target->tilePos = pos; DropOutOnSide(*target, LookingW, NULL); // To avoid defending summoned unit for AI target->Summoned = 1; // // set life span. ttl=0 results in a permanent unit. // if (ttl) { target->TTL = GameCycle + ttl; } // Insert summoned unit to AI force so it will help them in battle if (this->JoinToAiForce && caster.Player->AiEnabled) { int force = caster.Player->Ai->Force.GetForce(caster); if (force != -1) { caster.Player->Ai->Force[force].Insert(*target); target->GroupId = caster.GroupId; CommandDefend(*target, caster, FlushCommands); } } caster.Variable[MANA_INDEX].Value -= spell.ManaCost; } else { DebugPrint("Unable to allocate Unit"); } return 1; } return 0; }