/* See if there is a target in range */ BOOL aiChooseTarget(BASE_OBJECT *psObj, BASE_OBJECT **ppsTarget, int weapon_slot, BOOL bUpdateTarget, UWORD *targetOrigin) { BASE_OBJECT *psTarget = NULL; DROID *psCommander; SDWORD curTargetWeight=-1; UWORD tmpOrigin = ORIGIN_UNKNOWN; if (targetOrigin) { *targetOrigin = ORIGIN_UNKNOWN; } /* Get the sensor range */ switch (psObj->type) { case OBJ_DROID: if (((DROID *)psObj)->asWeaps[weapon_slot].nStat == 0) { return false; } if (((DROID *)psObj)->asWeaps[0].nStat == 0 && ((DROID *)psObj)->droidType != DROID_SENSOR) { // Can't attack without a weapon return false; } break; case OBJ_STRUCTURE: if (((STRUCTURE *)psObj)->numWeaps == 0 || ((STRUCTURE *)psObj)->asWeaps[0].nStat == 0) { // Can't attack without a weapon return false; } break; default: break; } /* See if there is a something in range */ if (psObj->type == OBJ_DROID) { BASE_OBJECT *psCurrTarget = ((DROID *)psObj)->psActionTarget[0]; /* find a new target */ int newTargetWeight = aiBestNearestTarget((DROID *)psObj, &psTarget, weapon_slot, NULL); /* Calculate weight of the current target if updating; but take care not to target * ourselves... */ if (bUpdateTarget && psCurrTarget != psObj) { curTargetWeight = targetAttackWeight(psCurrTarget, psObj, weapon_slot); } if (newTargetWeight >= 0 // found a new target && (!bUpdateTarget // choosing a new target, don't care if current one is better || curTargetWeight <= 0 // attacker had no valid target, use new one || newTargetWeight > curTargetWeight + OLD_TARGET_THRESHOLD) // updating and new target is better && validTarget(psObj, psTarget, weapon_slot) && (aiDroidHasRange((DROID *)psObj, psTarget, weapon_slot) || (secondaryGetState((DROID *)psObj, DSO_HALTTYPE) != DSS_HALT_HOLD))) { ASSERT(!isDead(psTarget), "aiChooseTarget: Droid found a dead target!"); *ppsTarget = psTarget; return true; } } else if (psObj->type == OBJ_STRUCTURE) { WEAPON_STATS *psWStats = NULL; int tarDist, longRange = 0; BOOL bCommanderBlock = false; ASSERT(((STRUCTURE *)psObj)->asWeaps[weapon_slot].nStat > 0, "no weapons on structure"); psWStats = ((STRUCTURE *)psObj)->asWeaps[weapon_slot].nStat + asWeaponStats; longRange = proj_GetLongRange(psWStats); // see if there is a target from the command droids psTarget = NULL; psCommander = cmdDroidGetDesignator(psObj->player); if (!proj_Direct(psWStats) && (psCommander != NULL) && aiStructHasRange((STRUCTURE *)psObj, (BASE_OBJECT *)psCommander, weapon_slot)) { // there is a commander that can fire designate for this structure // set bCommanderBlock so that the structure does not fire until the commander // has a target - (slow firing weapons will not be ready to fire otherwise). bCommanderBlock = true; // I do believe this will never happen, check for yourself :-) debug(LOG_NEVER,"Commander %d is good enough for fire designation", psCommander->id); if (psCommander->action == DACTION_ATTACK && psCommander->psActionTarget[0] != NULL && !aiObjectIsProbablyDoomed(psCommander->psActionTarget[0])) { // the commander has a target to fire on if (aiStructHasRange((STRUCTURE *)psObj, psCommander->psActionTarget[0], weapon_slot)) { // target in range - fire on it tmpOrigin = ORIGIN_COMMANDER; psTarget = psCommander->psActionTarget[0]; } else { // target out of range - release the commander block bCommanderBlock = false; } } } // indirect fire structures use sensor towers first tarDist = longRange * longRange; if (psTarget == NULL && !bCommanderBlock && !proj_Direct(psWStats)) { psTarget = aiSearchSensorTargets(psObj, weapon_slot, psWStats, &tmpOrigin); } if (psTarget == NULL && !bCommanderBlock) { BASE_OBJECT *psCurr; gridStartIterate(psObj->pos.x, psObj->pos.y, PREVIOUS_DEFAULT_GRID_SEARCH_RADIUS); psCurr = gridIterate(); while (psCurr != NULL) { /* Check that it is a valid target */ if (psCurr->type != OBJ_FEATURE && !aiObjectIsProbablyDoomed(psCurr) && aiStructHasRange((STRUCTURE *)psObj, psCurr, weapon_slot) && !aiCheckAlliances(psCurr->player, psObj->player) && validTarget(psObj, psCurr, weapon_slot) && psCurr->visible[psObj->player]) { // See if in sensor range and visible int distSq = objPosDiffSq(psCurr->pos, psObj->pos); if (distSq < tarDist || (psTarget && psTarget->type == OBJ_STRUCTURE && ((STRUCTURE *)psTarget)->status != SS_BUILT) || (psTarget && aiObjIsWall(psTarget) && !aiObjIsWall(psCurr))) { tmpOrigin = ORIGIN_VISUAL; psTarget = psCurr; tarDist = distSq; } } psCurr = gridIterate(); } } if (psTarget) { ASSERT(!psTarget->died, "aiChooseTarget: Structure found a dead target!"); if (targetOrigin) { *targetOrigin = tmpOrigin; } *ppsTarget = psTarget; return true; } } return false; }
/** print out weapon information * \param psStats the weapon to print the info for */ static void printWeaponInfo(const WEAPON_STATS* psStats) { const char *pWC, *pWSC, *pMM; switch (psStats->weaponClass) { case WC_KINETIC: //bullets etc pWC = "WC_KINETIC"; break; //case WC_EXPLOSIVE: //rockets etc // pWC = "WC_EXPLOSIVE"; // break; case WC_HEAT: //laser etc pWC = "WC_HEAT"; break; //case WC_MISC: //others we haven't thought of! // pWC = "WC_MISC"; // break; default: pWC = "UNKNOWN CLASS"; break; } switch (psStats->weaponSubClass) { case WSC_MGUN: pWSC = "WSC_MGUN"; break; case WSC_CANNON: pWSC = "WSC_CANNON"; break; /*case WSC_ARTILLARY: pWSC = "WSC_ARTILLARY"; break;*/ case WSC_MORTARS: pWSC = "WSC_MORTARS"; break; case WSC_MISSILE: pWSC = "WSC_MISSILE"; break; case WSC_ROCKET: pWSC = "WSC_ROCKET"; break; case WSC_ENERGY: pWSC = "WSC_ENERGY"; break; case WSC_GAUSS: pWSC = "WSC_GAUSS"; break; case WSC_FLAME: pWSC = "WSC_FLAME"; break; /*case WSC_CLOSECOMBAT: pWSC = "WSC_CLOSECOMBAT"; break;*/ case WSC_HOWITZERS: pWSC = "WSC_HOWITZERS"; break; case WSC_ELECTRONIC: pWSC = "WSC_ELECTRONIC"; break; case WSC_AAGUN: pWSC = "WSC_AAGUN"; break; case WSC_SLOWMISSILE: pWSC = "WSC_SLOWMISSILE"; break; case WSC_SLOWROCKET: pWSC = "WSC_SLOWROCKET"; break; case WSC_LAS_SAT: pWSC = "WSC_LAS_SAT"; break; case WSC_BOMB: pWSC = "WSC_BOMB"; break; case WSC_COMMAND: pWSC = "WSC_COMMAND"; break; case WSC_EMP: pWSC = "WSC_EMP"; break; default: pWSC = "UNKNOWN SUB CLASS"; break; } switch (psStats->movementModel) { case MM_DIRECT: pMM = "MM_DIRECT"; break; case MM_INDIRECT: pMM = "MM_INDIRECT"; break; case MM_HOMINGDIRECT: pMM = "MM_HOMINGDIRECT"; break; case MM_HOMINGINDIRECT: pMM = "MM_HOMINGINDIRECT"; break; case MM_ERRATICDIRECT: pMM = "MM_ERRATICDIRECT"; break; case MM_SWEEP: pMM = "MM_SWEEP"; break; default: pMM = "UNKNOWN MOVE MODEL"; break; } CONPRINTF(ConsoleString,(ConsoleString,"Weapon: ")); printComponentInfo((COMPONENT_STATS *)psStats); CONPRINTF(ConsoleString,(ConsoleString," sRng %d lRng %d mRng %d %s\n" " sHt %d lHt %d pause %d dam %d\n", psStats->shortRange, proj_GetLongRange(psStats), psStats->minRange, proj_Direct(psStats) ? "direct" : "indirect", //psStats->shortHit, psStats->longHit, psStats->firePause, psStats->damage)); weaponShortHit(psStats,(UBYTE)selectedPlayer), weaponLongHit(psStats, (UBYTE)selectedPlayer), weaponFirePause(psStats,(UBYTE)selectedPlayer), weaponDamage(psStats, (UBYTE)selectedPlayer))); CONPRINTF(ConsoleString,(ConsoleString," rad %d radHt %d radDam %d\n" " inTime %d inDam %d inRad %d\n", psStats->radius, psStats->radiusHit, psStats->radiusDamage, psStats->incenTime, psStats->incenDamage, psStats->incenRadius)); CONPRINTF(ConsoleString,(ConsoleString," flSpd %d %s\n", psStats->flightSpeed, psStats->fireOnMove ? "fireOnMove" : "not fireOnMove")); CONPRINTF(ConsoleString,(ConsoleString," %s %s %s\n", pWC, pWSC, pMM)); CONPRINTF(ConsoleString,(ConsoleString," %srotate recoil %d\n" " dLife %d radLife %d\n", psStats->rotate ? "" : "not ", psStats->recoilValue, psStats->directLife, psStats->radiusLife)); }
/* See if there is a target in range */ bool aiChooseTarget(BASE_OBJECT *psObj, BASE_OBJECT **ppsTarget, int weapon_slot, bool bUpdateTarget, TARGET_ORIGIN *targetOrigin) { BASE_OBJECT *psTarget = NULL; DROID *psCommander; SDWORD curTargetWeight = -1; TARGET_ORIGIN tmpOrigin = ORIGIN_UNKNOWN; if (targetOrigin) { *targetOrigin = ORIGIN_UNKNOWN; } switch (psObj->type) { case OBJ_DROID: if (((DROID *)psObj)->asWeaps[weapon_slot].nStat == 0) { return false; } if (((DROID *)psObj)->asWeaps[0].nStat == 0 && ((DROID *)psObj)->droidType != DROID_SENSOR) { return false; // Can't target without a weapon or sensor } break; case OBJ_STRUCTURE: if (((STRUCTURE *)psObj)->numWeaps == 0 || ((STRUCTURE *)psObj)->asWeaps[0].nStat == 0) { // Can't attack without a weapon return false; } break; default: break; } /* See if there is a something in range */ if (psObj->type == OBJ_DROID) { BASE_OBJECT *psCurrTarget = ((DROID *)psObj)->psActionTarget[0]; /* find a new target */ int newTargetWeight = aiBestNearestTarget((DROID *)psObj, &psTarget, weapon_slot); /* Calculate weight of the current target if updating; but take care not to target * ourselves... */ if (bUpdateTarget && psCurrTarget != psObj) { curTargetWeight = targetAttackWeight(psCurrTarget, psObj, weapon_slot); } if (newTargetWeight >= 0 // found a new target && (!bUpdateTarget // choosing a new target, don't care if current one is better || curTargetWeight <= 0 // attacker had no valid target, use new one || newTargetWeight > curTargetWeight + OLD_TARGET_THRESHOLD) // updating and new target is better && validTarget(psObj, psTarget, weapon_slot) && aiDroidHasRange((DROID *)psObj, psTarget, weapon_slot)) { ASSERT(!isDead(psTarget), "Droid found a dead target!"); *ppsTarget = psTarget; return true; } } else if (psObj->type == OBJ_STRUCTURE) { WEAPON_STATS *psWStats = NULL; bool bCommanderBlock = false; ASSERT(((STRUCTURE *)psObj)->asWeaps[weapon_slot].nStat > 0, "no weapons on structure"); psWStats = ((STRUCTURE *)psObj)->asWeaps[weapon_slot].nStat + asWeaponStats; int longRange = proj_GetLongRange(psWStats, psObj->player); // see if there is a target from the command droids psTarget = NULL; psCommander = cmdDroidGetDesignator(psObj->player); if (!proj_Direct(psWStats) && (psCommander != NULL) && aiStructHasRange((STRUCTURE *)psObj, (BASE_OBJECT *)psCommander, weapon_slot)) { // there is a commander that can fire designate for this structure // set bCommanderBlock so that the structure does not fire until the commander // has a target - (slow firing weapons will not be ready to fire otherwise). bCommanderBlock = true; // I do believe this will never happen, check for yourself :-) debug(LOG_NEVER, "Commander %d is good enough for fire designation", psCommander->id); if (psCommander->action == DACTION_ATTACK && psCommander->psActionTarget[0] != NULL && !psCommander->psActionTarget[0]->died) { // the commander has a target to fire on if (aiStructHasRange((STRUCTURE *)psObj, psCommander->psActionTarget[0], weapon_slot)) { // target in range - fire on it tmpOrigin = ORIGIN_COMMANDER; psTarget = psCommander->psActionTarget[0]; } else { // target out of range - release the commander block bCommanderBlock = false; } } } // indirect fire structures use sensor towers first if (psTarget == NULL && !bCommanderBlock && !proj_Direct(psWStats)) { psTarget = aiSearchSensorTargets(psObj, weapon_slot, psWStats, &tmpOrigin); } if (psTarget == NULL && !bCommanderBlock) { int targetValue = -1; int tarDist = INT32_MAX; int srange = longRange; if (!proj_Direct(psWStats) && srange > objSensorRange(psObj)) { // search radius of indirect weapons limited by their sight, unless they use // external sensors to provide fire designation srange = objSensorRange(psObj); } static GridList gridList; // static to avoid allocations. gridList = gridStartIterate(psObj->pos.x, psObj->pos.y, srange); for (GridIterator gi = gridList.begin(); gi != gridList.end(); ++gi) { BASE_OBJECT *psCurr = *gi; /* Check that it is a valid target */ if (psCurr->type != OBJ_FEATURE && !psCurr->died && !aiCheckAlliances(psCurr->player, psObj->player) && validTarget(psObj, psCurr, weapon_slot) && psCurr->visible[psObj->player] == UBYTE_MAX && aiStructHasRange((STRUCTURE *)psObj, psCurr, weapon_slot)) { int newTargetValue = targetAttackWeight(psCurr, psObj, weapon_slot); // See if in sensor range and visible int distSq = objPosDiffSq(psCurr->pos, psObj->pos); if (newTargetValue < targetValue || (newTargetValue == targetValue && distSq >= tarDist)) { continue; } tmpOrigin = ORIGIN_VISUAL; psTarget = psCurr; tarDist = distSq; targetValue = newTargetValue; } } } if (psTarget) { ASSERT(!psTarget->died, "Structure found a dead target!"); if (targetOrigin) { *targetOrigin = tmpOrigin; } *ppsTarget = psTarget; return true; } } return false; }
// Find the best nearest target for a droid // Returns integer representing target priority, -1 if failed SDWORD aiBestNearestTarget(DROID *psDroid, BASE_OBJECT **ppsObj, int weapon_slot, UWORD *targetOrigin) { SDWORD bestMod = 0,newMod, failure = -1; BASE_OBJECT *psTarget = NULL, *friendlyObj, *bestTarget = NULL, *iter, *targetInQuestion, *tempTarget; BOOL electronic = false; STRUCTURE *targetStructure; WEAPON_EFFECT weaponEffect; UWORD tmpOrigin = ORIGIN_UNKNOWN; // reset origin if (targetOrigin) { *targetOrigin = ORIGIN_UNKNOWN; } //don't bother looking if empty vtol droid if (vtolEmpty(psDroid)) { return failure; } /* Return if have no weapons */ // The ai orders a non-combat droid to patrol = crash without it... if ((psDroid->asWeaps[0].nStat == 0 || psDroid->numWeaps == 0) && psDroid->droidType != DROID_SENSOR) { return failure; } // Check if we have a CB target to begin with if (!proj_Direct(asWeaponStats + psDroid->asWeaps[weapon_slot].nStat)) { WEAPON_STATS *psWStats = psWStats = psDroid->asWeaps[weapon_slot].nStat + asWeaponStats; bestTarget = aiSearchSensorTargets((BASE_OBJECT *)psDroid, weapon_slot, psWStats, &tmpOrigin); bestMod = targetAttackWeight(bestTarget, (BASE_OBJECT *)psDroid, weapon_slot); } weaponEffect = ((WEAPON_STATS *)(asWeaponStats + psDroid->asWeaps[weapon_slot].nStat))->weaponEffect; electronic = electronicDroid(psDroid); // Range was previously 9*TILE_UNITS. Increasing this doesn't seem to help much, though. Not sure why. gridStartIterate(psDroid->pos.x, psDroid->pos.y, psDroid->sensorRange + 6*TILE_UNITS); for (iter = gridIterate(); iter != NULL; iter = gridIterate()) { friendlyObj = NULL; targetInQuestion = iter; /* This is a friendly unit, check if we can reuse its target */ if(aiCheckAlliances(targetInQuestion->player,psDroid->player)) { friendlyObj = targetInQuestion; targetInQuestion = NULL; /* Can we see what it is doing? */ if(friendlyObj->visible[psDroid->player]) { if(friendlyObj->type == OBJ_DROID) { DROID *friendlyDroid = (DROID *)friendlyObj; /* See if friendly droid has a target */ tempTarget = friendlyDroid->psActionTarget[0]; if(tempTarget && !aiObjectIsProbablyDoomed(tempTarget)) { //make sure a weapon droid is targeting it if(friendlyDroid->numWeaps > 0) { // make sure this target wasn't assigned explicitly to this droid if(friendlyDroid->order != DORDER_ATTACK) { // make sure target is near enough if (aiDroidHasRange(psDroid, tempTarget, weapon_slot)) { targetInQuestion = tempTarget; //consider this target } } } } } else if(friendlyObj->type == OBJ_STRUCTURE) { tempTarget = ((STRUCTURE*)friendlyObj)->psTarget[0]; if (tempTarget && !aiObjectIsProbablyDoomed(tempTarget) && aiDroidHasRange(psDroid, tempTarget, weapon_slot)) { targetInQuestion = tempTarget; } } } } if (targetInQuestion != NULL && targetInQuestion != (BASE_OBJECT *)psDroid // in case friendly unit had me as target && (targetInQuestion->type == OBJ_DROID || targetInQuestion->type == OBJ_STRUCTURE || targetInQuestion->type == OBJ_FEATURE) && targetInQuestion->visible[psDroid->player] && !aiCheckAlliances(targetInQuestion->player,psDroid->player) && validTarget((BASE_OBJECT *)psDroid, targetInQuestion, weapon_slot) && aiDroidHasRange(psDroid, targetInQuestion, weapon_slot)) { if (targetInQuestion->type == OBJ_DROID) { // in multiPlayer - don't attack Transporters with EW if (bMultiPlayer) { // if not electronic then valid target if (!electronic || (electronic && ((DROID *)targetInQuestion)->droidType != DROID_TRANSPORTER)) { //only a valid target if NOT a transporter psTarget = targetInQuestion; } } else { psTarget = targetInQuestion; } } else if (targetInQuestion->type == OBJ_STRUCTURE) { STRUCTURE *psStruct = (STRUCTURE *)targetInQuestion; if (electronic) { /* don't want to target structures with resistance of zero if using electronic warfare */ if (validStructResistance((STRUCTURE *)targetInQuestion)) { psTarget = targetInQuestion; } } else if (psStruct->asWeaps[weapon_slot].nStat > 0) { // structure with weapons - go for this psTarget = targetInQuestion; } else if ((psStruct->pStructureType->type != REF_WALL && psStruct->pStructureType->type != REF_WALLCORNER) || driveModeActive() || (bMultiPlayer && !isHumanPlayer(psDroid->player))) { psTarget = targetInQuestion; } } else if (targetInQuestion->type == OBJ_FEATURE && gameTime - psDroid->lastFrustratedTime < FRUSTRATED_TIME && ((FEATURE *)targetInQuestion)->psStats->damageable && !(game.scavengers && psDroid->player == 7)) // hack to avoid scavs blowing up their nice feature walls { psTarget = targetInQuestion; } /* Check if our weapon is most effective against this object */ if(psTarget != NULL && psTarget == targetInQuestion) //was assigned? { newMod = targetAttackWeight(psTarget, (BASE_OBJECT *)psDroid, weapon_slot); /* Remember this one if it's our best target so far */ if( newMod >= 0 && (newMod > bestMod || bestTarget == NULL)) { bestMod = newMod; tmpOrigin = ORIGIN_ALLY; bestTarget = psTarget; } } } } if (bestTarget) { ASSERT(!bestTarget->died, "aiBestNearestTarget: AI gave us a target that is already dead."); targetStructure = visGetBlockingWall((BASE_OBJECT *)psDroid, bestTarget); /* See if target is blocked by a wall; only affects direct weapons */ if (proj_Direct(asWeaponStats + psDroid->asWeaps[weapon_slot].nStat) && targetStructure) { //are we any good against walls? if(asStructStrengthModifier[weaponEffect][targetStructure->pStructureType->strength] >= 100) //can attack atleast with default strength { bestTarget = (BASE_OBJECT *)targetStructure; //attack wall } } if (targetOrigin) { *targetOrigin = tmpOrigin; } *ppsObj = bestTarget; return bestMod; } return failure; }
/* Calculates attack priority for a certain target */ static SDWORD targetAttackWeight(BASE_OBJECT *psTarget, BASE_OBJECT *psAttacker, SDWORD weapon_slot) { SDWORD targetTypeBonus = 0, damageRatio = 0, attackWeight = 0, noTarget = -1; UDWORD weaponSlot; DROID *targetDroid = NULL, *psAttackerDroid = NULL, *psGroupDroid, *psDroid; STRUCTURE *targetStructure = NULL; WEAPON_EFFECT weaponEffect; WEAPON_STATS *attackerWeapon; bool bEmpWeap = false, bCmdAttached = false, bTargetingCmd = false, bDirect = false; if (psTarget == NULL || psAttacker == NULL || psTarget->died) { return noTarget; } ASSERT(psTarget != psAttacker, "targetAttackWeight: Wanted to evaluate the worth of attacking ourselves..."); targetTypeBonus = 0; //Sensors/ecm droids, non-military structures get lower priority /* Get attacker weapon effect */ if (psAttacker->type == OBJ_DROID) { psAttackerDroid = (DROID *)psAttacker; attackerWeapon = (WEAPON_STATS *)(asWeaponStats + psAttackerDroid->asWeaps[weapon_slot].nStat); //check if this droid is assigned to a commander bCmdAttached = hasCommander(psAttackerDroid); //find out if current target is targeting our commander if (bCmdAttached) { if (psTarget->type == OBJ_DROID) { psDroid = (DROID *)psTarget; //go through all enemy weapon slots for (weaponSlot = 0; !bTargetingCmd && weaponSlot < ((DROID *)psTarget)->numWeaps; weaponSlot++) { //see if this weapon is targeting our commander if (psDroid->psActionTarget[weaponSlot] == (BASE_OBJECT *)psAttackerDroid->psGroup->psCommander) { bTargetingCmd = true; } } } else { if (psTarget->type == OBJ_STRUCTURE) { //go through all enemy weapons for (weaponSlot = 0; !bTargetingCmd && weaponSlot < ((STRUCTURE *)psTarget)->numWeaps; weaponSlot++) { if (((STRUCTURE *)psTarget)->psTarget[weaponSlot] == (BASE_OBJECT *)psAttackerDroid->psGroup->psCommander) { bTargetingCmd = true; } } } } } } else if (psAttacker->type == OBJ_STRUCTURE) { attackerWeapon = ((WEAPON_STATS *)(asWeaponStats + ((STRUCTURE *)psAttacker)->asWeaps[weapon_slot].nStat)); } else /* feature */ { ASSERT(!"invalid attacker object type", "targetAttackWeight: Invalid attacker object type"); return noTarget; } bDirect = proj_Direct(attackerWeapon); if (psAttacker->type == OBJ_DROID && psAttackerDroid->droidType == DROID_SENSOR) { // Sensors are considered a direct weapon, // but for computing expected damage it makes more sense to use indirect damage bDirect = false; } //Get weapon effect weaponEffect = attackerWeapon->weaponEffect; //See if attacker is using an EMP weapon bEmpWeap = (attackerWeapon->weaponSubClass == WSC_EMP); int dist = iHypot(removeZ(psAttacker->pos - psTarget->pos)); bool tooClose = dist <= attackerWeapon->upgrade[psAttacker->player].minRange; if (tooClose) { dist = objSensorRange(psAttacker); // If object is too close to fire at, consider it to be at maximum range. } /* Calculate attack weight */ if (psTarget->type == OBJ_DROID) { targetDroid = (DROID *)psTarget; if (targetDroid->died) { debug(LOG_NEVER, "Target droid is dead, skipping invalid droid.\n"); return noTarget; } /* Calculate damage this target suffered */ if (targetDroid->originalBody == 0) // FIXME Somewhere we get 0HP droids from { damageRatio = 0; debug(LOG_ERROR, "targetAttackWeight: 0HP droid detected!"); debug(LOG_ERROR, " Type: %i Name: \"%s\" Owner: %i \"%s\")", targetDroid->droidType, targetDroid->aName, targetDroid->player, getPlayerName(targetDroid->player)); } else { damageRatio = 100 - 100 * targetDroid->body / targetDroid->originalBody; } assert(targetDroid->originalBody != 0); // Assert later so we get the info from above /* See if this type of a droid should be prioritized */ switch (targetDroid->droidType) { case DROID_SENSOR: case DROID_ECM: case DROID_PERSON: case DROID_TRANSPORTER: case DROID_SUPERTRANSPORTER: case DROID_DEFAULT: case DROID_ANY: break; case DROID_CYBORG: case DROID_WEAPON: case DROID_CYBORG_SUPER: targetTypeBonus = WEIGHT_WEAPON_DROIDS; break; case DROID_COMMAND: targetTypeBonus = WEIGHT_COMMAND_DROIDS; break; case DROID_CONSTRUCT: case DROID_REPAIR: case DROID_CYBORG_CONSTRUCT: case DROID_CYBORG_REPAIR: targetTypeBonus = WEIGHT_SERVICE_DROIDS; break; } /* Now calculate the overall weight */ attackWeight = asWeaponModifier[weaponEffect][(asPropulsionStats + targetDroid->asBits[COMP_PROPULSION])->propulsionType] // Our weapon's effect against target + asWeaponModifierBody[weaponEffect][(asBodyStats + targetDroid->asBits[COMP_BODY])->size] + WEIGHT_DIST_TILE_DROID * objSensorRange(psAttacker) / TILE_UNITS - WEIGHT_DIST_TILE_DROID * dist / TILE_UNITS // farther droids are less attractive + WEIGHT_HEALTH_DROID * damageRatio / 100 // we prefer damaged droids + targetTypeBonus; // some droid types have higher priority /* If attacking with EMP try to avoid targets that were already "EMPed" */ if (bEmpWeap && (targetDroid->lastHitWeapon == WSC_EMP) && ((gameTime - targetDroid->timeLastHit) < EMP_DISABLE_TIME)) //target still disabled { attackWeight /= EMP_DISABLED_PENALTY_F; } } else if (psTarget->type == OBJ_STRUCTURE) { targetStructure = (STRUCTURE *)psTarget; /* Calculate damage this target suffered */ damageRatio = 100 - 100 * targetStructure->body / structureBody(targetStructure); /* See if this type of a structure should be prioritized */ switch (targetStructure->pStructureType->type) { case REF_DEFENSE: targetTypeBonus = WEIGHT_WEAPON_STRUCT; break; case REF_RESOURCE_EXTRACTOR: targetTypeBonus = WEIGHT_DERRICK_STRUCT; break; case REF_FACTORY: case REF_CYBORG_FACTORY: case REF_REPAIR_FACILITY: targetTypeBonus = WEIGHT_MILITARY_STRUCT; break; default: break; } /* Now calculate the overall weight */ attackWeight = asStructStrengthModifier[weaponEffect][targetStructure->pStructureType->strength] // Our weapon's effect against target + WEIGHT_DIST_TILE_STRUCT * objSensorRange(psAttacker) / TILE_UNITS - WEIGHT_DIST_TILE_STRUCT * dist / TILE_UNITS // farther structs are less attractive + WEIGHT_HEALTH_STRUCT * damageRatio / 100 // we prefer damaged structures + targetTypeBonus; // some structure types have higher priority /* Go for unfinished structures only if nothing else found (same for non-visible structures) */ if (targetStructure->status != SS_BUILT) //a decoy? { attackWeight /= WEIGHT_STRUCT_NOTBUILT_F; } /* EMP should only attack structures if no enemy droids are around */ if (bEmpWeap) { attackWeight /= EMP_STRUCT_PENALTY_F; } } else //a feature { return 1; } /* We prefer objects we can see and can attack immediately */ if (!visibleObject((BASE_OBJECT *)psAttacker, psTarget, true)) { attackWeight /= WEIGHT_NOT_VISIBLE_F; } if (tooClose) { attackWeight /= TOO_CLOSE_PENALTY_F; } /* Penalty for units that are already considered doomed (but the missile might miss!) */ if (aiObjectIsProbablyDoomed(psTarget, bDirect)) { /* indirect firing units have slow reload times, so give the target a chance to die, * and give a different unit a chance to get in range, too. */ if (weaponROF(attackerWeapon, psAttacker->player) < TARGET_DOOMED_SLOW_RELOAD_T) { debug(LOG_NEVER, "Not killing unit - doomed. My ROF: %i (%s)", weaponROF(attackerWeapon, psAttacker->player), getName(attackerWeapon)); return noTarget; } attackWeight /= TARGET_DOOMED_PENALTY_F; } /* Commander-related criterias */ if (bCmdAttached) //attached to a commander and don't have a target assigned by some order { ASSERT(psAttackerDroid->psGroup->psCommander != NULL, "Commander is NULL"); //if commander is being targeted by our target, try to defend the commander if (bTargetingCmd) { attackWeight += WEIGHT_CMD_RANK * (1 + getDroidLevel(psAttackerDroid->psGroup->psCommander)); } //fire support - go through all droids assigned to the commander for (psGroupDroid = psAttackerDroid->psGroup->psList; psGroupDroid; psGroupDroid = psGroupDroid->psGrpNext) { for (weaponSlot = 0; weaponSlot < psGroupDroid->numWeaps; weaponSlot++) { //see if this droid is currently targeting current target if (psGroupDroid->order.psObj == psTarget || psGroupDroid->psActionTarget[weaponSlot] == psTarget) { //we prefer targets that are already targeted and hence will be destroyed faster attackWeight += WEIGHT_CMD_SAME_TARGET; } } } } return std::max<int>(1, attackWeight); }
// get the correct type mask for a structure target static UDWORD scrStructTargetMask(STRUCTURE *psStruct) { UDWORD mask = 0; STRUCTURE_STATS *psStats; WEAPON_STATS *psWStats; psStats = psStruct->pStructureType; switch (psStats->type) { case REF_HQ: mask = SCR_ST_HQ; break; case REF_FACTORY: case REF_FACTORY_MODULE: mask = SCR_ST_FACTORY; break; case REF_POWER_GEN: case REF_POWER_MODULE: mask = SCR_ST_POWER_GEN; break; case REF_RESOURCE_EXTRACTOR: mask = SCR_ST_RESOURCE_EXTRACTOR; break; case REF_DEFENSE: //if (psStats->numWeaps == 0 && psStats->pSensor != NULL) if (psStats->psWeapStat[0] == NULL && psStats->pSensor != NULL) { mask = SCR_ST_SENSOR; } //else if (psStats->numWeaps > 0) else if (psStats->psWeapStat[0] != NULL) { psWStats = psStats->psWeapStat[0]; if (!proj_Direct(psWStats)) { mask = SCR_ST_DEF_IDF; } else if (psWStats->surfaceToAir & SHOOT_IN_AIR) { mask = SCR_ST_DEF_AIR; } else { mask = SCR_ST_DEF_GROUND; } } break; case REF_WALL: case REF_WALLCORNER: mask = 0; // ignore walls for now break; case REF_RESEARCH: case REF_RESEARCH_MODULE: mask = SCR_ST_RESEARCH; break; case REF_REPAIR_FACILITY: mask = SCR_ST_REPAIR_FACILITY; break; case REF_COMMAND_CONTROL: mask = SCR_ST_COMMAND_CONTROL; break; case REF_CYBORG_FACTORY: mask = SCR_ST_CYBORG_FACTORY; break; case REF_VTOL_FACTORY: mask = SCR_ST_VTOL_FACTORY; break; case REF_REARM_PAD: mask = SCR_ST_REARM_PAD; break; case REF_MISSILE_SILO: //don't want the assert! mask = 0; break; case REF_LAB: case REF_BRIDGE: case REF_DEMOLISH: case REF_BLASTDOOR: case REF_GATE: default: ASSERT( false, "scrStructTargetMask: unknown or invalid target structure type" ); mask = 0; break; } return mask; }
static UDWORD scrDroidTargetMask(DROID *psDroid) { UDWORD mask = 0; WEAPON_STATS *psWStats; BODY_STATS *psBStats; PROPULSION_STATS *psPStats; // get the turret type switch (psDroid->droidType) { case DROID_PERSON: case DROID_CYBORG: case DROID_CYBORG_SUPER: case DROID_WEAPON: //if (psDroid->numWeaps > 0) if (psDroid->asWeaps[0].nStat > 0) { psWStats = asWeaponStats + psDroid->asWeaps[0].nStat; if (!proj_Direct(psWStats)) { mask |= SCR_DT_WEAP_IDF; } else if (psWStats->surfaceToAir & SHOOT_IN_AIR) { mask |= SCR_DT_WEAP_AIR; } else { mask |= SCR_DT_WEAP_GROUND; } } else { mask |= SCR_DT_SENSOR; } break; case DROID_SENSOR: case DROID_ECM: mask |= SCR_DT_SENSOR; break; case DROID_CONSTRUCT: case DROID_CYBORG_CONSTRUCT: mask |= SCR_DT_CONSTRUCT; break; case DROID_COMMAND: mask |= SCR_DT_COMMAND; break; case DROID_REPAIR: case DROID_CYBORG_REPAIR: mask |= SCR_DT_REPAIR; break; case DROID_TRANSPORTER: case DROID_SUPERTRANSPORTER: break; case DROID_DEFAULT: case DROID_ANY: default: ASSERT( false, "scrUnitTargetMask: unknown or invalid target unit type" ); break; } // get the body type psBStats = asBodyStats + psDroid->asBits[COMP_BODY].nStat; switch (psBStats->size) { case SIZE_LIGHT: mask |= SCR_DT_LIGHT; break; case SIZE_MEDIUM: mask |= SCR_DT_MEDIUM; break; case SIZE_HEAVY: mask |= SCR_DT_HEAVY; break; case SIZE_SUPER_HEAVY: mask |= SCR_DT_SUPER_HEAVY; break; default: ASSERT( false, "scrUnitTargetMask: unknown or invalid target body size" ); break; } // get the propulsion type psPStats = asPropulsionStats + psDroid->asBits[COMP_PROPULSION].nStat; switch(psPStats->propulsionType) { case PROPULSION_TYPE_WHEELED: mask |= SCR_DT_WHEEL; break; case PROPULSION_TYPE_TRACKED: mask |= SCR_DT_TRACK; break; case PROPULSION_TYPE_LEGGED: case PROPULSION_TYPE_JUMP: mask |= SCR_DT_LEGS; break; case PROPULSION_TYPE_HOVER: mask |= SCR_DT_HOVER; break; case PROPULSION_TYPE_LIFT: mask |= SCR_DT_VTOL; break; case PROPULSION_TYPE_HALF_TRACKED: mask |= SCR_DT_HTRACK; break; case PROPULSION_TYPE_PROPELLOR: mask |= SCR_DT_PROPELLOR; break; case PROPULSION_TYPE_SKI: default: ASSERT( false, "scrUnitTargetMask: unknown or invalid target unit propulsion type" ); break; } return mask; }
/* Fire a weapon at something */ bool combFire(WEAPON *psWeap, BASE_OBJECT *psAttacker, BASE_OBJECT *psTarget, int weapon_slot) { WEAPON_STATS *psStats; UDWORD firePause; SDWORD longRange; int compIndex; CHECK_OBJECT(psAttacker); CHECK_OBJECT(psTarget); ASSERT(psWeap != NULL, "Invalid weapon pointer"); /* Watermelon:dont shoot if the weapon_slot of a vtol is empty */ if (psAttacker->type == OBJ_DROID && isVtolDroid(((DROID *)psAttacker))) { if (((DROID *)psAttacker)->sMove.iAttackRuns[weapon_slot] >= getNumAttackRuns(((DROID *)psAttacker), weapon_slot)) { objTrace(psAttacker->id, "VTOL slot %d is empty", weapon_slot); return false; } } /* Get the stats for the weapon */ compIndex = psWeap->nStat; ASSERT_OR_RETURN( false , compIndex < numWeaponStats, "Invalid range referenced for numWeaponStats, %d > %d", compIndex, numWeaponStats); psStats = asWeaponStats + compIndex; // check valid weapon/prop combination if (!validTarget(psAttacker, psTarget, weapon_slot)) { return false; } unsigned fireTime = gameTime - deltaGameTime; // Can fire earliest at the start of the tick. /*see if reload-able weapon and out of ammo*/ if (psStats->reloadTime && !psWeap->ammo) { fireTime = std::max(fireTime, psWeap->lastFired + weaponReloadTime(psStats, psAttacker->player)); if (gameTime <= fireTime) { return false; } //reset the ammo level psWeap->ammo = psStats->numRounds; } /* See when the weapon last fired to control it's rate of fire */ firePause = weaponFirePause(psStats, psAttacker->player); firePause = std::max(firePause, 1u); // Don't shoot infinitely many shots at once. fireTime = std::max(fireTime, psWeap->lastFired + firePause); if (gameTime <= fireTime) { /* Too soon to fire again */ return false; } if (psTarget->visible[psAttacker->player] != UBYTE_MAX) { // Can't see it - can't hit it objTrace(psAttacker->id, "combFire(%u[%s]->%u): Object has no indirect sight of target", psAttacker->id, psStats->pName, psTarget->id); return false; } /* Check we can see the target */ if (psAttacker->type == OBJ_DROID && !isVtolDroid((DROID *)psAttacker) && (proj_Direct(psStats) || actionInsideMinRange((DROID *)psAttacker, psTarget, psStats))) { if(!lineOfFire(psAttacker, psTarget, weapon_slot, true)) { // Can't see the target - can't hit it with direct fire objTrace(psAttacker->id, "combFire(%u[%s]->%u): Droid has no direct line of sight to target", psAttacker->id, ((DROID *)psAttacker)->aName, psTarget->id); return false; } } else if ((psAttacker->type == OBJ_STRUCTURE) && (((STRUCTURE *)psAttacker)->pStructureType->height == 1) && proj_Direct(psStats)) { // a bunker can't shoot through walls if (!lineOfFire(psAttacker, psTarget, weapon_slot, true)) { // Can't see the target - can't hit it with direct fire objTrace(psAttacker->id, "combFire(%u[%s]->%u): Structure has no direct line of sight to target", psAttacker->id, ((STRUCTURE *)psAttacker)->pStructureType->pName, psTarget->id); return false; } } else if ( proj_Direct(psStats) ) { // VTOL or tall building if (!lineOfFire(psAttacker, psTarget, weapon_slot, false)) { // Can't see the target - can't hit it with direct fire objTrace(psAttacker->id, "combFire(%u[%s]->%u): Tall object has no direct line of sight to target", psAttacker->id, psStats->pName, psTarget->id); return false; } } Vector3i deltaPos = psTarget->pos - psAttacker->pos; // if the turret doesn't turn, check if the attacker is in alignment with the target if (psAttacker->type == OBJ_DROID && !psStats->rotate) { uint16_t targetDir = iAtan2(removeZ(deltaPos)); int dirDiff = abs(angleDelta(targetDir - psAttacker->rot.direction)); if (dirDiff > FIXED_TURRET_DIR) { return false; } } /* Now see if the target is in range - also check not too near */ int dist = iHypot(removeZ(deltaPos)); longRange = proj_GetLongRange(psStats); /* modification by CorvusCorax - calculate shooting angle */ int min_angle = 0; // only calculate for indirect shots if (!proj_Direct(psStats) && dist > 0) { min_angle = arcOfFire(psAttacker,psTarget,weapon_slot,true); // prevent extremely steep shots min_angle = std::min(min_angle, DEG(PROJ_ULTIMATE_PITCH)); // adjust maximum range of unit if forced to shoot very steep if (min_angle > DEG(PROJ_MAX_PITCH)) { //do not allow increase of max range though if (iSin(2*min_angle) < iSin(2*DEG(PROJ_MAX_PITCH))) // If PROJ_MAX_PITCH == 45, then always iSin(2*min_angle) <= iSin(2*DEG(PROJ_MAX_PITCH)), and the test is redundant. { longRange = longRange * iSin(2*min_angle) / iSin(2*DEG(PROJ_MAX_PITCH)); } } } int baseHitChance = 0; if ((dist <= psStats->shortRange) && (dist >= psStats->minRange)) { // get weapon chance to hit in the short range baseHitChance = weaponShortHit(psStats,psAttacker->player); } else if ((dist <= longRange && dist >= psStats->minRange) || (psAttacker->type == OBJ_DROID && !proj_Direct(psStats) && actionInsideMinRange((DROID *)psAttacker, psTarget, psStats))) { // get weapon chance to hit in the long range baseHitChance = weaponLongHit(psStats,psAttacker->player); // adapt for height adjusted artillery shots if (min_angle > DEG(PROJ_MAX_PITCH)) { baseHitChance = baseHitChance * iCos(min_angle) / iCos(DEG(PROJ_MAX_PITCH)); } } else { /* Out of range */ objTrace(psAttacker->id, "combFire(%u[%s]->%u): Out of range", psAttacker->id, psStats->pName, psTarget->id); return false; } // apply experience accuracy modifiers to the base //hit chance, not to the final hit chance int resultHitChance = baseHitChance; // add the attacker's experience if (psAttacker->type == OBJ_DROID) { SDWORD level = getDroidEffectiveLevel((DROID *) psAttacker); // increase total accuracy by EXP_ACCURACY_BONUS % for each experience level resultHitChance += EXP_ACCURACY_BONUS * level * baseHitChance / 100; } // subtract the defender's experience if (psTarget->type == OBJ_DROID) { SDWORD level = getDroidEffectiveLevel((DROID *) psTarget); // decrease weapon accuracy by EXP_ACCURACY_BONUS % for each experience level resultHitChance -= EXP_ACCURACY_BONUS * level * baseHitChance / 100; } // fire while moving modifiers if (psAttacker->type == OBJ_DROID && ((DROID *)psAttacker)->sMove.Status != MOVEINACTIVE) { switch (psStats->fireOnMove) { case FOM_NO: // Can't fire while moving return false; break; case FOM_PARTIAL: resultHitChance = FOM_PARTIAL_ACCURACY_PENALTY * resultHitChance / 100; break; case FOM_YES: // can fire while moving break; } } /* -------!!! From that point we are sure that we are firing !!!------- */ // Add a random delay to the next shot. // TODO Add deltaFireTime to the time it takes to fire next. If just adding to psWeap->lastFired, it might put it in the future, causing assertions. And if not sometimes putting it in the future, the fire rate would be lower than advertised. //int fireJitter = firePause/100; // ±1% variation in fire rate. //int deltaFireTime = gameRand(fireJitter*2 + 1) - fireJitter; /* note when the weapon fired */ psWeap->lastFired = fireTime; /* reduce ammo if salvo */ if (psStats->reloadTime) { psWeap->ammo--; } // increment the shots counter psWeap->shotsFired++; // predicted X,Y offset per sec Vector3i predict = psTarget->pos; //Watermelon:Target prediction if (isDroid(psTarget)) { DROID *psDroid = castDroid(psTarget); int32_t flightTime; if (proj_Direct(psStats) || dist <= psStats->minRange) { flightTime = dist * GAME_TICKS_PER_SEC / psStats->flightSpeed; } else { int32_t vXY, vZ; // Unused, we just want the flight time. flightTime = projCalcIndirectVelocities(dist, deltaPos.z, psStats->flightSpeed, &vXY, &vZ, min_angle); } if (psTarget->lastHitWeapon == WSC_EMP) { int empTime = EMP_DISABLE_TIME - (gameTime - psTarget->timeLastHit); CLIP(empTime, 0, EMP_DISABLE_TIME); if (empTime >= EMP_DISABLE_TIME * 9/10) { flightTime = 0; /* Just hit. Assume they'll get hit again */ } else { flightTime = MAX(0, flightTime - empTime); } } predict += Vector3i(iSinCosR(psDroid->sMove.moveDir, psDroid->sMove.speed*flightTime / GAME_TICKS_PER_SEC), 0); if (!isFlying(psDroid)) { predict.z = map_Height(removeZ(predict)); // Predict that the object will be on the ground. } } /* Fire off the bullet to the miss location. The miss is only visible if the player owns the target. (Why? - Per) */ // What bVisible really does is to make the projectile audible even if it misses you. Since the target is NULL, proj_SendProjectile can't check if it was fired at you. bool bVisibleAnyway = psTarget->player == selectedPlayer; // see if we were lucky to hit the target bool isHit = gameRand(100) <= resultHitChance; if (isHit) { /* Kerrrbaaang !!!!! a hit */ objTrace(psAttacker->id, "combFire: [%s]->%u: resultHitChance=%d, visibility=%d", psStats->pName, psTarget->id, resultHitChance, (int)psTarget->visible[psAttacker->player]); syncDebug("hit=(%d,%d,%d)", predict.x, predict.y, predict.z); } else /* Deal with a missed shot */ { const int minOffset = 5; int missDist = 2 * (100 - resultHitChance) + minOffset; Vector3i miss = Vector3i(iSinCosR(gameRand(DEG(360)), missDist), 0); predict += miss; psTarget = NULL; // Missed the target, so don't expect to hit it. objTrace(psAttacker->id, "combFire: Missed shot by (%4d,%4d)", miss.x, miss.y); syncDebug("miss=(%d,%d,%d)", predict.x, predict.y, predict.z); } // Make sure we don't pass any negative or out of bounds numbers to proj_SendProjectile CLIP(predict.x, 0, world_coord(mapWidth - 1)); CLIP(predict.y, 0, world_coord(mapHeight - 1)); proj_SendProjectileAngled(psWeap, psAttacker, psAttacker->player, predict, psTarget, bVisibleAnyway, weapon_slot, min_angle, fireTime); return true; }
/** print out weapon information * \param psStats the weapon to print the info for */ static void printWeaponInfo(const WEAPON_STATS *psStats) { const char *pWC, *pWSC, *pMM; switch (psStats->weaponClass) { case WC_KINETIC: //bullets etc pWC = "WC_KINETIC"; break; case WC_HEAT: //laser etc pWC = "WC_HEAT"; break; default: pWC = "UNKNOWN CLASS"; break; } switch (psStats->weaponSubClass) { case WSC_MGUN: pWSC = "WSC_MGUN"; break; case WSC_CANNON: pWSC = "WSC_CANNON"; break; case WSC_MORTARS: pWSC = "WSC_MORTARS"; break; case WSC_MISSILE: pWSC = "WSC_MISSILE"; break; case WSC_ROCKET: pWSC = "WSC_ROCKET"; break; case WSC_ENERGY: pWSC = "WSC_ENERGY"; break; case WSC_GAUSS: pWSC = "WSC_GAUSS"; break; case WSC_FLAME: pWSC = "WSC_FLAME"; break; case WSC_HOWITZERS: pWSC = "WSC_HOWITZERS"; break; case WSC_ELECTRONIC: pWSC = "WSC_ELECTRONIC"; break; case WSC_AAGUN: pWSC = "WSC_AAGUN"; break; case WSC_SLOWMISSILE: pWSC = "WSC_SLOWMISSILE"; break; case WSC_SLOWROCKET: pWSC = "WSC_SLOWROCKET"; break; case WSC_LAS_SAT: pWSC = "WSC_LAS_SAT"; break; case WSC_BOMB: pWSC = "WSC_BOMB"; break; case WSC_COMMAND: pWSC = "WSC_COMMAND"; break; case WSC_EMP: pWSC = "WSC_EMP"; break; default: pWSC = "UNKNOWN SUB CLASS"; break; } switch (psStats->movementModel) { case MM_DIRECT: pMM = "MM_DIRECT"; break; case MM_INDIRECT: pMM = "MM_INDIRECT"; break; case MM_HOMINGDIRECT: pMM = "MM_HOMINGDIRECT"; break; case MM_HOMINGINDIRECT: pMM = "MM_HOMINGINDIRECT"; break; default: pMM = "UNKNOWN MOVE MODEL"; break; } CONPRINTF(ConsoleString, (ConsoleString, "Weapon: ")); printComponentInfo((COMPONENT_STATS *)psStats); CONPRINTF(ConsoleString, (ConsoleString, " lRng %d mRng %d %s\n" " lHt %d pause %d dam %d\n", proj_GetLongRange(psStats, selectedPlayer), psStats->upgrade[selectedPlayer].minRange, proj_Direct(psStats) ? "direct" : "indirect", weaponLongHit(psStats, selectedPlayer), weaponFirePause(psStats, selectedPlayer), weaponDamage(psStats, selectedPlayer))); CONPRINTF(ConsoleString, (ConsoleString, " rad %d radDam %d\n" " inTime %d inDam %d inRad %d\n", psStats->upgrade[selectedPlayer].radius, psStats->upgrade[selectedPlayer].radiusDamage, psStats->upgrade[selectedPlayer].periodicalDamageTime, psStats->upgrade[selectedPlayer].periodicalDamage, psStats->upgrade[selectedPlayer].periodicalDamageRadius)); CONPRINTF(ConsoleString, (ConsoleString, " flSpd %d %s\n", psStats->flightSpeed, psStats->fireOnMove ? "fireOnMove" : "not fireOnMove")); CONPRINTF(ConsoleString, (ConsoleString, " %s %s %s\n", pWC, pWSC, pMM)); CONPRINTF(ConsoleString, (ConsoleString, " %srotate recoil %d\n" " radLife %d\n", psStats->rotate ? "" : "not ", psStats->recoilValue, psStats->radiusLife)); }