void structureBodyUpgrade(FUNCTION *pFunction, STRUCTURE *psBuilding) { UWORD increase, prevBaseBody, newBaseBody; switch (psBuilding->pStructureType->type) { case REF_WALL: case REF_WALLCORNER: case REF_DEFENSE: case REF_BLASTDOOR: case REF_GATE: increase = ((WALLDEFENCE_UPGRADE_FUNCTION *)pFunction)->body; break; default: increase = ((STRUCTURE_UPGRADE_FUNCTION *)pFunction)->body; break; } prevBaseBody = (UWORD)structureBody(psBuilding); newBaseBody = (UWORD)(structureBaseBody(psBuilding) + (structureBaseBody(psBuilding) * increase) / 100); if (newBaseBody > prevBaseBody) { psBuilding->body = (UWORD)((psBuilding->body * newBaseBody) / prevBaseBody); //psBuilding->baseBodyPoints = newBaseBody; } }
// recv lassat info on the receiving end. bool recvLasSat(NETQUEUE queue) { BASE_OBJECT *psObj; UBYTE player, targetplayer; STRUCTURE *psStruct; uint32_t id, targetid; NETbeginDecode(queue, GAME_LASSAT); NETuint8_t(&player); NETuint32_t(&id); NETuint32_t(&targetid); NETuint8_t(&targetplayer); NETend(); psStruct = IdToStruct(id, player); psObj = IdToPointer(targetid, targetplayer); if (psStruct && !canGiveOrdersFor(queue.index, psStruct->player)) { syncDebug("Wrong player."); return false; } if (psStruct && psObj && psStruct->pStructureType->psWeapStat[0]->weaponSubClass == WSC_LAS_SAT) { // Lassats have just one weapon unsigned firePause = weaponFirePause(&asWeaponStats[psStruct->asWeaps[0].nStat], player); unsigned damLevel = PERCENT(psStruct->body, structureBody(psStruct)); if (damLevel < HEAVY_DAMAGE_LEVEL) { firePause += firePause; } if (isHumanPlayer(player) && gameTime - psStruct->asWeaps[0].lastFired <= firePause) { /* Too soon to fire again */ return true ^ false; // Return value meaningless and ignored. } // Give enemy no quarter, unleash the lasat proj_SendProjectile(&psStruct->asWeaps[0], NULL, player, psObj->pos, psObj, true, 0); psStruct->asWeaps[0].lastFired = gameTime; psStruct->asWeaps[0].ammo = 1; // abducting this field for keeping track of triggers // Play 5 second countdown message audio_QueueTrackPos(ID_SOUND_LAS_SAT_COUNTDOWN, psObj->pos.x, psObj->pos.y, psObj->pos.z); } return true; }
/* Fire a weapon at something */ bool combFire(WEAPON *psWeap, BASE_OBJECT *psAttacker, BASE_OBJECT *psTarget, int weapon_slot) { WEAPON_STATS *psStats; UDWORD damLevel; UDWORD firePause; SDWORD longRange; DROID *psDroid = NULL; 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; } /*see if reload-able weapon and out of ammo*/ if (psStats->reloadTime && !psWeap->ammo) { if (gameTime - psWeap->lastFired < weaponReloadTime(psStats, psAttacker->player)) { 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); // increase the pause if heavily damaged switch (psAttacker->type) { case OBJ_DROID: psDroid = (DROID *)psAttacker; damLevel = PERCENT(psDroid->body, psDroid->originalBody); break; case OBJ_STRUCTURE: damLevel = PERCENT(((STRUCTURE *)psAttacker)->body, structureBody((STRUCTURE *)psAttacker)); break; default: damLevel = 100; break; } if (damLevel < HEAVY_DAMAGE_LEVEL) { firePause += firePause; } if (gameTime - psWeap->lastFired <= firePause) { /* Too soon to fire again */ return false; } // add a random delay to the fire // With logical updates, a good graphics gard no longer gives a better ROF. // TODO Should still replace this with something saner, such as a ±1% random deviation in reload time. int fireChance = gameTime - (psWeap->lastFired + firePause); if (gameRand(RANDOM_PAUSE) > fireChance) { 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(psDroid, 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(psDroid, 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 !!!------- */ /* note when the weapon fired */ psWeap->lastFired = gameTime; /* 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 / 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); } /* 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=%hhu : ", psStats->pName, psTarget->id, resultHitChance, 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); return true; }
// Get values from a base object bool scrBaseObjGet(UDWORD index) { INTERP_TYPE type = VAL_VOID; BASE_OBJECT *psObj; DROID *psDroid; STRUCTURE *psStruct; FEATURE *psFeature; if (!stackPopParams(1, ST_BASEOBJECT, &psObj)) { debug(LOG_ERROR, "scrBaseObjGet: stackPopParams failed"); return false; } // Check this is a valid pointer ASSERT_OR_RETURN(false, psObj, "Passed a NULL pointer to a base object"); ASSERT_OR_RETURN(false, psObj->type == OBJ_DROID || psObj->type == OBJ_STRUCTURE || psObj->type == OBJ_FEATURE, "Invalid object %p of type %d", psObj, psObj->type); // set the type and return value switch (index) { case OBJID_POSX: type = VAL_INT; scrFunctionResult.v.ival = (SDWORD)psObj->pos.x; break; case OBJID_POSY: type = VAL_INT; scrFunctionResult.v.ival = (SDWORD)psObj->pos.y; break; case OBJID_POSZ: type = VAL_INT; scrFunctionResult.v.ival = (SDWORD)psObj->pos.z; break; case OBJID_ID: type = VAL_INT; scrFunctionResult.v.ival = (SDWORD)psObj->id; break; case OBJID_PLAYER: type = VAL_INT; scrFunctionResult.v.ival = (SDWORD)psObj->player; break; case OBJID_TYPE: type = VAL_INT; scrFunctionResult.v.ival = (SDWORD)psObj->type; break; case OBJID_ORDER: if (psObj->type != OBJ_DROID) { debug(LOG_ERROR, "scrBaseObjGet: order only valid for a droid"); return false; } type = VAL_INT; scrFunctionResult.v.ival = ((DROID *)psObj)->order.type; if (scrFunctionResult.v.ival == DORDER_GUARD && ((DROID *)psObj)->order.psObj == NULL) { scrFunctionResult.v.ival = DORDER_NONE; } break; //new member variable case OBJID_ACTION: if (psObj->type != OBJ_DROID) { debug(LOG_ERROR, "scrBaseObjGet: action only valid for a droid"); return false; } type = VAL_INT; scrFunctionResult.v.ival = (SDWORD)((DROID *)psObj)->action; break; //new member variable - if droid is selected (humans only) case OBJID_SELECTED: if (psObj->type != OBJ_DROID) { debug(LOG_ERROR, "scrBaseObjGet: selected only valid for a droid"); return false; } type = VAL_BOOL; scrFunctionResult.v.bval = (SDWORD)((DROID *)psObj)->selected; break; case OBJID_STRUCTSTATTYPE: if (psObj->type == OBJ_STRUCTURE) { type = VAL_INT; scrFunctionResult.v.ival = ((STRUCTURE *)psObj)->pStructureType->type; } else { debug(LOG_ERROR, ".stattype is only supported by Structures"); return false; } break; case OBJID_ORDERX: if (psObj->type != OBJ_DROID) { debug(LOG_ERROR, "scrBaseObjGet: order only valid for a droid"); return false; } type = VAL_INT; scrFunctionResult.v.ival = ((DROID *)psObj)->order.pos.x; break; case OBJID_ORDERY: if (psObj->type != OBJ_DROID) { debug(LOG_ERROR, "scrBaseObjGet: order only valid for a droid"); return false; } type = VAL_INT; scrFunctionResult.v.ival = ((DROID *)psObj)->order.pos.y; break; case OBJID_DROIDTYPE: if (psObj->type != OBJ_DROID) { debug(LOG_ERROR, "scrBaseObjGet: droidType only valid for a droid"); return false; } type = VAL_INT; scrFunctionResult.v.ival = (SDWORD)((DROID *)psObj)->droidType; break; case OBJID_CLUSTERID: if (psObj->type == OBJ_FEATURE) { debug(LOG_ERROR, "scrBaseObjGet: clusterID not valid for features"); return false; } type = VAL_INT; scrFunctionResult.v.ival = clustGetClusterID(psObj); break; case OBJID_HEALTH: switch (psObj->type) { case OBJ_DROID: psDroid = (DROID *)psObj; type = VAL_INT; scrFunctionResult.v.ival = psDroid->body * 100 / psDroid->originalBody; break; case OBJ_FEATURE: psFeature = (FEATURE *)psObj; type = VAL_INT; if (psFeature->psStats->damageable) { scrFunctionResult.v.ival = psFeature->body * 100 / psFeature->psStats->body; } else { scrFunctionResult.v.ival = 100; } break; case OBJ_STRUCTURE: psStruct = (STRUCTURE *)psObj; type = VAL_INT; //val = psStruct->body * 100 / psStruct->baseBodyPoints; scrFunctionResult.v.ival = psStruct->body * 100 / structureBody(psStruct); break; default: break; } break; case OBJID_BODY: if (psObj->type != OBJ_DROID) { debug(LOG_ERROR, "scrBaseObjGet: body only valid for a droid"); return false; } type = (INTERP_TYPE)ST_BODY; scrFunctionResult.v.ival = (SDWORD)((DROID *)psObj)->asBits[COMP_BODY].nStat; break; case OBJID_PROPULSION: if (psObj->type != OBJ_DROID) { debug(LOG_ERROR, "scrBaseObjGet: propulsion only valid for a droid"); return false; } type = (INTERP_TYPE)ST_PROPULSION; scrFunctionResult.v.ival = (SDWORD)((DROID *)psObj)->asBits[COMP_PROPULSION].nStat; break; case OBJID_WEAPON: //TODO: only returns first weapon now type = (INTERP_TYPE)ST_WEAPON; switch (psObj->type) { case OBJ_DROID: if (((DROID *)psObj)->asWeaps[0].nStat == 0) { scrFunctionResult.v.ival = 0; }else{ scrFunctionResult.v.ival = (SDWORD)((DROID *)psObj)->asWeaps[0].nStat; } break; case OBJ_STRUCTURE: if (((STRUCTURE *)psObj)->numWeaps == 0 || ((STRUCTURE *)psObj)->asWeaps[0].nStat == 0) { scrFunctionResult.v.ival = 0; }else{ scrFunctionResult.v.ival = (SDWORD)((STRUCTURE *)psObj)->asWeaps[0].nStat; } break; default: //only droids and structures can have a weapon debug(LOG_ERROR, "scrBaseObjGet: weapon only valid for droids and structures" ); return false; break; } break; case OBJID_STRUCTSTAT: //droid.stat - now returns the type of structure a truck is building for droids if (psObj->type == OBJ_STRUCTURE) { type = (INTERP_TYPE)ST_STRUCTURESTAT; scrFunctionResult.v.ival = ((STRUCTURE *)psObj)->pStructureType - asStructureStats; } else if (psObj->type == OBJ_DROID) { type = (INTERP_TYPE)ST_STRUCTURESTAT; scrFunctionResult.v.ival = ((DROID *)psObj)->order.psStats - asStructureStats; } else //Nothing else supported { debug(LOG_ERROR, "scrBaseObjGet(): .stat only valid for structures and droids"); return false; } break; case OBJID_TARGET: //added object->psTarget if (psObj->type == OBJ_STRUCTURE) { type = (INTERP_TYPE)ST_BASEOBJECT; scrFunctionResult.v.oval = ((STRUCTURE *)psObj)->psTarget[0]; } else if (psObj->type == OBJ_DROID) { type = (INTERP_TYPE)ST_BASEOBJECT; scrFunctionResult.v.oval = ((DROID *)psObj)->order.psObj; } else //Nothing else supported { debug(LOG_ERROR, "scrBaseObjGet(): .target only valid for structures and droids"); return false; } break; case OBJID_GROUP: if (psObj->type != OBJ_DROID) { debug(LOG_ERROR, "scrBaseObjGet: group only valid for a droid"); return false; } type = (INTERP_TYPE)ST_GROUP; scrFunctionResult.v.oval = ((DROID *)psObj)->psGroup; break; case OBJID_HITPOINTS: type = VAL_INT; switch (psObj->type) { case OBJ_DROID: scrFunctionResult.v.ival = (SDWORD)((DROID *)psObj)->body; break; case OBJ_STRUCTURE: scrFunctionResult.v.ival = (SDWORD)((STRUCTURE *)psObj)->body; break; case OBJ_FEATURE: scrFunctionResult.v.ival = (SDWORD)((FEATURE *)psObj)->body; break; default: debug(LOG_ERROR, "scrBaseObjGet: unknown object type"); return false; break; } break; case OBJID_ORIG_HITPOINTS: type = VAL_INT; switch (psObj->type) { case OBJ_DROID: scrFunctionResult.v.ival = (SDWORD)((DROID *)psObj)->originalBody; break; case OBJ_STRUCTURE: scrFunctionResult.v.ival = (SDWORD)structureBody((STRUCTURE *)psObj); break; case OBJ_FEATURE: scrFunctionResult.v.ival = ((FEATURE *)psObj)->psStats->body; break; default: debug(LOG_ERROR, "scrBaseObjGet: unknown object type"); return false; break; } break; default: debug(LOG_ERROR, "scrBaseObjGet: unknown variable index"); return false; break; } // Return the value if (!stackPushResult(type, &scrFunctionResult)) { debug(LOG_ERROR, "scrBaseObjGet: stackPushResult() failed"); return false; } return true; }
/* 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; if (psTarget == NULL || psAttacker == NULL || aiObjectIsProbablyDoomed(psTarget)) { 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; } //Get weapon effect weaponEffect = attackerWeapon->weaponEffect; //See if attacker is using an EMP weapon bEmpWeap = (attackerWeapon->weaponSubClass == WSC_EMP); /* 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 = 1 - 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_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].nStat)->propulsionType] // Our weapon's effect against target + WEIGHT_DIST_TILE_DROID * psAttacker->sensorRange/TILE_UNITS - WEIGHT_DIST_TILE_DROID * map_coord(iHypot(psAttacker->pos.x - targetDroid->pos.x, psAttacker->pos.y - targetDroid->pos.y)) // farer droids are less attractive + WEIGHT_HEALTH_DROID * damageRatio // 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 = 1 - 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 * psAttacker->sensorRange/TILE_UNITS - WEIGHT_DIST_TILE_STRUCT * map_coord(iHypot(psAttacker->pos.x - targetStructure->pos.x, psAttacker->pos.y - targetStructure->pos.y)) // farer structs are less attractive + WEIGHT_HEALTH_STRUCT * damageRatio // 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; } /* 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->psTarget == psTarget || psGroupDroid->psActionTarget[weaponSlot] == psTarget) { //we prefer targets that are already targeted and hence will be destroyed faster attackWeight += WEIGHT_CMD_SAME_TARGET; } } } } return attackWeight; }
// //////////////////////////////////////////////////////////////////////////// // remove structures from map before campaign play. static BOOL cleanMap(UDWORD player) { DROID *psD,*psD2; STRUCTURE *psStruct; BOOL firstFact,firstRes; bMultiPlayer = false; bMultiMessages = false; firstFact = true; firstRes = true; switch(game.base) { case CAMP_CLEAN: //clean map while(apsStructLists[player]) //strip away structures. { removeStruct(apsStructLists[player], true); } psD = apsDroidLists[player]; // remove all but construction droids. while(psD) { psD2=psD->psNext; if (psD->droidType != DROID_CONSTRUCT && psD->droidType != DROID_CYBORG_CONSTRUCT) { killDroid(psD); } psD = psD2; } break; case CAMP_BASE: //just structs, no walls psStruct = apsStructLists[player]; while(psStruct) { if ( (psStruct->pStructureType->type == REF_WALL) ||(psStruct->pStructureType->type == REF_WALLCORNER) ||(psStruct->pStructureType->type == REF_DEFENSE) ||(psStruct->pStructureType->type == REF_BLASTDOOR) ||(psStruct->pStructureType->type == REF_GATE) ||(psStruct->pStructureType->type == REF_CYBORG_FACTORY) ||(psStruct->pStructureType->type == REF_COMMAND_CONTROL)) { removeStruct(psStruct, true); psStruct= apsStructLists[player]; //restart,(list may have changed). } else if( (psStruct->pStructureType->type == REF_FACTORY) ||(psStruct->pStructureType->type == REF_RESEARCH) ||(psStruct->pStructureType->type == REF_POWER_GEN)) { if(psStruct->pStructureType->type == REF_FACTORY ) { if(firstFact == true) { firstFact = false; removeStruct(psStruct, true); psStruct= apsStructLists[player]; } else // don't delete, just rejig! { if(((FACTORY*)psStruct->pFunctionality)->capacity != 0) { ((FACTORY*)psStruct->pFunctionality)->capacity = 0; ((FACTORY*)psStruct->pFunctionality)->productionOutput = (UBYTE)((PRODUCTION_FUNCTION*)psStruct->pStructureType->asFuncList[0])->productionOutput; psStruct->sDisplay.imd = psStruct->pStructureType->pIMD; psStruct->body = (UWORD)(structureBody(psStruct)); } psStruct = psStruct->psNext; } } else if(psStruct->pStructureType->type == REF_RESEARCH) { if(firstRes == true) { firstRes = false; removeStruct(psStruct, true); psStruct= apsStructLists[player]; } else { if(((RESEARCH_FACILITY*)psStruct->pFunctionality)->capacity != 0) { // downgrade research ((RESEARCH_FACILITY*)psStruct->pFunctionality)->capacity = 0; ((RESEARCH_FACILITY*)psStruct->pFunctionality)->researchPoints = ((RESEARCH_FUNCTION*)psStruct->pStructureType->asFuncList[0])->researchPoints; psStruct->sDisplay.imd = psStruct->pStructureType->pIMD; psStruct->body = (UWORD)(structureBody(psStruct)); } psStruct=psStruct->psNext; } } else if(psStruct->pStructureType->type == REF_POWER_GEN) { if(((POWER_GEN*)psStruct->pFunctionality)->capacity != 0) { // downgrade powergen. ((POWER_GEN*)psStruct->pFunctionality)->capacity = 0; psStruct->sDisplay.imd = psStruct->pStructureType->pIMD; psStruct->body = (UWORD)(structureBody(psStruct)); } structurePowerUpgrade(psStruct); psStruct=psStruct->psNext; } } else { psStruct=psStruct->psNext; } } break; case CAMP_WALLS: //everything. break; default: debug( LOG_FATAL, "Unknown Campaign Style" ); abort(); break; } bMultiPlayer = true; bMultiMessages = true; return true; }