/** * @brief Check if some projectiles on geoscape reached their destination. * @note Destination is not necessarily an aircraft, in case the projectile missed its initial target. * @param[in] projectile Pointer to the projectile * @param[in] movement distance that the projectile will do up to next draw of geoscape * @sa AIRFIGHT_CampaignRunProjectiles */ static qboolean AIRFIGHT_ProjectileReachedTarget (const aircraftProjectile_t *projectile, float movement) { float distance; if (!projectile->aimedAircraft) /* the target is idle, its position is in idleTarget*/ distance = GetDistanceOnGlobe(projectile->idleTarget, projectile->pos[0]); else { /* the target is moving, pointer to the other aircraft is aimedAircraft */ distance = GetDistanceOnGlobe(projectile->aimedAircraft->pos, projectile->pos[0]); } /* projectile reaches its target */ if (distance < movement) return qtrue; assert(projectile->aircraftItem); /* check if the projectile went farther than it's range */ distance = (float) projectile->time * projectile->aircraftItem->craftitem.weaponSpeed / (float)SECONDS_PER_HOUR; if (distance > projectile->aircraftItem->craftitem.stats[AIR_STATS_WRANGE]) return qtrue; return qfalse; }
/** * @brief Calculates the total frame count (minutes) needed for producing an item * @param[in] base Pointer to the base the production happen * @param[in] prodData Pointer to the productionData structure */ static int PR_CalculateTotalFrames (const base_t* base, const productionData_t* prodData) { /* Check how many workers hired in this base. */ const signed int allWorkers = E_CountHired(base, EMPL_WORKER); /* We will not use more workers than workspace capacity in this base. */ const signed int maxWorkers = std::min(allWorkers, CAP_GetMax(base, CAP_WORKSPACE)); double timeDefault; if (PR_IsProductionData(prodData)) { const technology_t* tech = PR_GetTech(prodData); /* This is the default production time for PRODUCE_WORKERS workers. */ timeDefault = tech->produceTime; } else { const storedUFO_t* storedUFO = prodData->data.ufo; /* This is the default disassemble time for PRODUCE_WORKERS workers. */ timeDefault = storedUFO->comp->time; /* Production is 4 times longer when installation is on Antipodes * Penalty starts when distance is greater than 45 degrees */ timeDefault *= std::max(1.0, GetDistanceOnGlobe(storedUFO->installation->pos, base->pos) / 45.0); } /* Calculate the time needed for production of the item for our amount of workers. */ const float rate = PRODUCE_WORKERS / ccs.curCampaign->produceRate; double const timeScaled = timeDefault * (MINUTES_PER_HOUR * rate) / std::max(1, maxWorkers); /* Don't allow to return a time of less than 1 (you still need at least 1 minute to produce an item). */ return std::max(1.0, timeScaled) + 1; }
/** * @brief Choose the weapon an attacking aircraft will use to fire on a target. * @param[in] slot Pointer to the first weapon slot of attacking base or aircraft. * @param[in] maxSlot maximum number of weapon slots in attacking base or aircraft. * @param[in] pos position of attacking base or aircraft. * @param[in] targetPos Pointer to the aimed aircraft. * @return indice of the slot to use (in array weapons[]), * -1 AIRFIGHT_WEAPON_CAN_NOT_SHOOT_AT_THE_MOMENT no weapon to use atm, * -2 AIRFIGHT_WEAPON_CAN_NEVER_SHOOT if no weapon to use at all. * @sa AIRFIGHT_CheckWeapon */ int AIRFIGHT_ChooseWeapon (const aircraftSlot_t const *slot, int maxSlot, const vec2_t pos, const vec2_t targetPos) { int slotIdx = AIRFIGHT_WEAPON_CAN_NEVER_SHOOT; int i; float distance0 = 99999.9f; const float distance = GetDistanceOnGlobe(pos, targetPos); /* We choose the usable weapon with the smallest range */ for (i = 0; i < maxSlot; i++) { const int weaponStatus = AIRFIGHT_CheckWeapon(slot + i, distance); /* set slotIdx to AIRFIGHT_WEAPON_CAN_NOT_SHOOT_AT_THE_MOMENT if needed */ /* this will only happen if weapon_state is AIRFIGHT_WEAPON_CAN_NOT_SHOOT_AT_THE_MOMENT * and no weapon has been found that can shoot. */ if (weaponStatus > slotIdx) slotIdx = AIRFIGHT_WEAPON_CAN_NOT_SHOOT_AT_THE_MOMENT; /* select this weapon if this is the one with the shortest range */ if (weaponStatus >= AIRFIGHT_WEAPON_CAN_SHOOT && distance < distance0) { slotIdx = i; distance0 = distance; } } return slotIdx; }
/** * @brief Update alien interest for one PHALANX installation (radar tower, SAM, ...) * @param[in] ufo Pointer to the aircraft_t * @param[in] installation Pointer to the installation * @sa UFO_UpdateAlienInterestForOneBase */ static void UFO_UpdateAlienInterestForOneInstallation (const aircraft_t const *ufo, installation_t *installation) { float probability; float distance; const float decreasingDistance = 10.0f; /**< above this distance, probability to detect base will decrease by @c decreasingFactor */ const float decreasingFactor = 5.0f; /* ufo can't find base if it's too far */ distance = GetDistanceOnGlobe(ufo->pos, installation->pos); if (distance > MAX_DETECTING_RANGE) return; /* UFO has an increased probability to find a base if it is firing at it */ switch (UFO_IsTargetOfInstallation(ufo, installation)) { case UFO_IS_TARGET_OF_MISSILE: probability = 0.01f; break; case UFO_IS_TARGET_OF_LASER: probability = 0.001f; break; default: probability = 0.0001f; break; } /* decrease probability if the ufo is far from base */ if (distance > decreasingDistance) probability /= decreasingFactor; /* probability must depend on DETECTION_INTERVAL (in case we change the value) */ probability *= DETECTION_INTERVAL; installation->alienInterest += probability; }
/** * @brief Change destination of projectile to an idle point of the map, close to its former target. * @param[in] projectile The projectile to update */ static void AIRFIGHT_MissTarget (aircraftProjectile_t* projectile) { vec3_t newTarget; float offset; assert(projectile); if (projectile->aimedAircraft) { VectorCopy(projectile->aimedAircraft->pos, newTarget); projectile->aimedAircraft = nullptr; } else { VectorCopy(projectile->idleTarget, newTarget); } /* get the distance between the projectile and target */ const float distance = GetDistanceOnGlobe(projectile->pos[0], newTarget); /* Work out how much the projectile should miss the target by. We dont want it too close * or too far from the original target. * * 1/3 distance between target and projectile * random (range -0.5 to 0.5) * * Then make sure the value is at least greater than 0.1 or less than -0.1 so that * the projectile doesn't land too close to the target. */ offset = (distance / 3) * (frand() - 0.5f); if (abs(offset) < 0.1f) offset = 0.1f; newTarget[0] = newTarget[0] + offset; newTarget[1] = newTarget[1] + offset; VectorCopy(newTarget, projectile->idleTarget); }
/** * @brief Check if one type of battery (missile or laser) can shoot now. * @param[in] installation Pointer to the firing intallation. * @param[in] weapons The installation weapon to check and fire. */ static void AIRFIGHT_InstallationShoot (const installation_t *installation, baseWeapon_t *weapons, int maxWeapons) { int i, test; float distance; for (i = 0; i < maxWeapons; i++) { aircraft_t *target = weapons[i].target; aircraftSlot_t *slot = &(weapons[i].slot); /* if no target, can't shoot */ if (!target) continue; /* If the weapon is not ready in base, can't shoot. */ if (slot->installationTime > 0) continue; /* if weapon is reloading, can't shoot */ if (slot->delayNextShot > 0) continue; /* check that the ufo is still visible */ if (!UFO_IsUFOSeenOnGeoscape(target)) { weapons[i].target = NULL; continue; } /* Check if we can still fire on this target. */ distance = GetDistanceOnGlobe(installation->pos, target->pos); test = AIRFIGHT_CheckWeapon(slot, distance); /* weapon unable to shoot, reset target */ if (test == AIRFIGHT_WEAPON_CAN_NEVER_SHOOT) { weapons[i].target = NULL; continue; } /* we can't shoot with this weapon atm, wait to see if UFO comes closer */ else if (test == AIRFIGHT_WEAPON_CAN_NOT_SHOOT_AT_THE_MOMENT) continue; /* target is too far, wait to see if UFO comes closer */ else if (distance > slot->ammo->craftitem.stats[AIR_STATS_WRANGE]) continue; /* shoot */ if (AIRFIGHT_AddProjectile(NULL, installation, NULL, target, slot)) { slot->delayNextShot = slot->ammo->craftitem.weaponDelay; /* will we miss the target ? */ if (frand() > AIRFIGHT_ProbabilityToHit(NULL, target, slot)) AIRFIGHT_MissTarget(&ccs.projectiles[ccs.numProjectiles - 1], qfalse); } } }
/** * @brief Check if the ufo can shoot at a PHALANX aircraft */ static void UFO_SearchAircraftTarget (const campaign_t* campaign, aircraft_t *ufo) { float distance = 999999.; /* UFO never try to attack a PHALANX aircraft except if they came on earth in that aim */ if (ufo->mission->stage != STAGE_INTERCEPT) { /* Check if UFO is defending itself */ if (ufo->aircraftTarget) UFO_CheckShootBack(campaign, ufo, ufo->aircraftTarget); return; } /* check if the ufo is already attacking an aircraft */ if (ufo->aircraftTarget) { /* check if the target disappeared from geoscape (fled in a base) */ if (AIR_IsAircraftOnGeoscape(ufo->aircraftTarget)) AIRFIGHT_ExecuteActions(campaign, ufo, ufo->aircraftTarget); else ufo->aircraftTarget = NULL; return; } ufo->status = AIR_TRANSIT; AIR_Foreach(phalanxAircraft) { /* check that aircraft is flying */ if (AIR_IsAircraftOnGeoscape(phalanxAircraft)) { /* get the distance from ufo to aircraft */ const float dist = GetDistanceOnGlobe(ufo->pos, phalanxAircraft->pos); /* check out of reach */ if (dist > MAX_DETECTING_RANGE) continue; /* choose the nearest target */ if (dist < distance) { distance = dist; if (UFO_SendPursuingAircraft(ufo, phalanxAircraft) && UFO_IsUFOSeenOnGeoscape(ufo)) { /* stop time and notify */ MSO_CheckAddNewMessage(NT_UFO_ATTACKING, _("Notice"), va(_("A UFO is flying toward %s"), phalanxAircraft->name), qfalse, MSG_STANDARD, NULL); /** @todo present a popup with possible orders like: return to base, attack the ufo, try to flee the rockets */ } } } } }
/** * @brief Calculates the total frame count (minutes) needed for producing an item for a single worker * @param[in] base Pointer to the base the production happen * @param[in] prodData Pointer to the productionData structure */ static int PR_CalculateTotalFrames (const base_t* base, const productionData_t* prodData) { double time; if (PR_IsProductionData(prodData)) { const technology_t* tech = PR_GetTech(prodData); time = tech->produceTime; } else { const storedUFO_t* storedUFO = prodData->data.ufo; time = storedUFO->comp->time; /* Production is 4 times longer when installation is on Antipodes * Penalty starts when distance is greater than 45 degrees */ time *= std::max(1.0, GetDistanceOnGlobe(storedUFO->installation->pos, base->pos) / 45.0); } /* Calculate the time needed for production of the item for our amount of workers. */ time *= MINUTES_PER_HOUR * ccs.curCampaign->produceRate; /* Don't allow to return a time of less than 1 (you still need at least 1 minute to produce an item). */ return std::max(1.0, time) + 1; }
/** * @brief Change the value of 1 pixel in XVI overlay, the new value is higher than old one. * @param[in] xMin Minimum column (this volumn will be reached). * @param[in] xMax Maximum column (this volumn won't be reached). * @param[in] centerPos Position of the center of the circle. * @param[in] y current row in XVI overlay. * @param[in] yLat Latitude (in degree) of the current Row. * @param[in] xviLevel Level of XVI at the center of the circle (ie at @c center ). * @param[in] radius Radius of the circle. */ static void CP_DrawXVIOverlayPixel (int xMin, int xMax, const vec2_t centerPos, int y, const float yLat, int xviLevel, float radius) { int x; vec2_t currentPos; currentPos[1] = yLat; for (x = xMin; x < xMax; x++) { const int previousLevel = CP_GetXVILevel(x, y); float distance; int newLevel; currentPos[0] = 180.0f - 360.0f * x / ((float) XVI_WIDTH); distance = GetDistanceOnGlobe(centerPos, currentPos); newLevel = ceil((xviLevel * (radius - distance)) / radius); if (newLevel > previousLevel) CP_SetXVILevel(x, y, xviLevel); } }
/** * @brief Check events for UFOs: Appears or disappears on radars * @return qtrue if any new ufo was detected during this iteration, qfalse otherwise */ qboolean UFO_CampaignCheckEvents (void) { qboolean newDetection; aircraft_t *ufo; newDetection = qfalse; /* For each ufo in geoscape */ ufo = NULL; while ((ufo = UFO_GetNext(ufo)) != NULL) { char detectedBy[MAX_VAR] = ""; float minDistance = -1; /* detected tells us whether or not a UFO is detected NOW, whereas ufo->detected tells * us whether or not the UFO was detected PREVIOUSLY. */ qboolean detected = qfalse; base_t *base; /* don't update UFO status id UFO is landed or crashed */ if (ufo->landed) continue; /* note: We can't exit these loops as soon as we found the UFO detected * RADAR_CheckUFOSensored registers the UFO in every radars' detection list * which detect it */ /* Check if UFO is detected by an aircraft */ AIR_Foreach(aircraft) { if (!AIR_IsAircraftOnGeoscape(aircraft)) continue; /* maybe the ufo is already detected, don't reset it */ if (RADAR_CheckUFOSensored(&aircraft->radar, aircraft->pos, ufo, detected | ufo->detected)) { const int distance = GetDistanceOnGlobe(aircraft->pos, ufo->pos); detected = qtrue; if (minDistance < 0 || minDistance > distance) { minDistance = distance; Q_strncpyz(detectedBy, aircraft->name, sizeof(detectedBy)); } } } /* Check if UFO is detected by a base */ base = NULL; while ((base = B_GetNext(base)) != NULL) { if (!B_GetBuildingStatus(base, B_POWER)) continue; /* maybe the ufo is already detected, don't reset it */ if (RADAR_CheckUFOSensored(&base->radar, base->pos, ufo, detected | ufo->detected)) { const int distance = GetDistanceOnGlobe(base->pos, ufo->pos); detected = qtrue; if (minDistance < 0 || minDistance > distance) { minDistance = distance; Q_strncpyz(detectedBy, base->name, sizeof(detectedBy)); } } } /* Check if UFO is detected by a radartower */ INS_Foreach(installation) { /* maybe the ufo is already detected, don't reset it */ if (RADAR_CheckUFOSensored(&installation->radar, installation->pos, ufo, detected | ufo->detected)) { const int distance = GetDistanceOnGlobe(installation->pos, ufo->pos); detected = qtrue; if (minDistance < 0 || minDistance > distance) { minDistance = distance; Q_strncpyz(detectedBy, installation->name, sizeof(detectedBy)); } } } /* Check if ufo appears or disappears on radar */ if (detected != ufo->detected) { if (detected) { /* if UFO is aiming a PHALANX aircraft, warn player */ if (ufo->aircraftTarget) { /* stop time and notify */ MSO_CheckAddNewMessage(NT_UFO_ATTACKING, _("Notice"), va(_("A UFO is flying toward %s"), ufo->aircraftTarget->name), qfalse, MSG_STANDARD, NULL); /** @todo present a popup with possible orders like: return to base, attack the ufo, try to flee the rockets * @sa UFO_SearchAircraftTarget */ } else { MSO_CheckAddNewMessage(NT_UFO_SPOTTED, _("Notice"), va(_("Our radar detected a new UFO near %s"), detectedBy), qfalse, MSG_UFOSPOTTED, NULL); } newDetection = qtrue; UFO_DetectNewUFO(ufo); } else if (!detected) { MSO_CheckAddNewMessage(NT_UFO_SIGNAL_LOST, _("Notice"), _("Our radar has lost the tracking on a UFO"), qfalse, MSG_UFOLOST, NULL); /* Make this UFO undetected */ ufo->detected = qfalse; /* Notify that ufo disappeared */ AIR_AircraftsUFODisappear(ufo); MAP_NotifyUFODisappear(ufo); /* Deactivate Radar overlay */ RADAR_DeactivateRadarOverlay(); } } } return newDetection; }
/** * @brief Check if the ufo can shoot at a PHALANX aircraft and whether it should follow another ufo */ static void UFO_SearchAircraftTarget (const campaign_t* campaign, aircraft_t* ufo, float maxDetectionRange = MAX_DETECTING_RANGE) { float distance = 999999.; /* UFO never try to attack a PHALANX aircraft except if they came on earth in that aim */ if (ufo->mission->stage != STAGE_INTERCEPT) { /* Check if UFO is defending itself */ if (ufo->aircraftTarget) UFO_CheckShootBack(campaign, ufo, ufo->aircraftTarget); return; } /* check if the ufo is already attacking an aircraft */ if (ufo->aircraftTarget) { /* check if the target disappeared from geoscape (fled in a base) */ if (AIR_IsAircraftOnGeoscape(ufo->aircraftTarget)) AIRFIGHT_ExecuteActions(campaign, ufo, ufo->aircraftTarget); else ufo->aircraftTarget = nullptr; return; } ufo->status = AIR_TRANSIT; AIR_Foreach(phalanxAircraft) { /* check that aircraft is flying */ if (AIR_IsAircraftOnGeoscape(phalanxAircraft)) { /* get the distance from ufo to aircraft */ const float dist = GetDistanceOnGlobe(ufo->pos, phalanxAircraft->pos); /* check out of reach */ if (dist > maxDetectionRange) continue; /* choose the nearest target */ if (dist < distance) { distance = dist; if (UFO_SendPursuingAircraft(ufo, phalanxAircraft) && UFO_IsUFOSeenOnGeoscape(ufo)) { /* stop time and notify */ MSO_CheckAddNewMessage(NT_UFO_ATTACKING, _("Notice"), va(_("A UFO is flying toward %s"), phalanxAircraft->name)); /** @todo present a popup with possible orders like: return to base, attack the ufo, try to flee the rockets */ return; } } } } /* if this ufo is a leader, it does not try to search another one */ if (ufo->leader) return; aircraft_t* otherUFO = nullptr; const float polarCoordinatesOffset = 1.0f; while ((otherUFO = UFO_GetNextOnGeoscape(otherUFO)) != nullptr) { if (otherUFO == ufo) continue; if (otherUFO->leader) { vec2_t dest; AIR_GetDestinationWhilePursuing(ufo, otherUFO, dest); dest[0] += polarCoordinatesOffset; dest[1] += polarCoordinatesOffset; GEO_CalcLine(ufo->pos, dest, &ufo->route); ufo->time = 0; ufo->point = 0; break; } } }