DROID *getNearestDroid(UDWORD x, UDWORD y, bool bSelected) { DROID *psDroid, *psBestUnit; UDWORD bestSoFar; /* Go thru' all the droids - how often have we seen this - a MACRO maybe? */ for (psDroid = apsDroidLists[selectedPlayer], psBestUnit = NULL, bestSoFar = UDWORD_MAX; psDroid; psDroid = psDroid->psNext) { if (!isVtolDroid(psDroid)) { /* Clever (?) bit that reads whether we're interested in droids being selected or not */ if (!bSelected || psDroid->selected) { uint32_t dist = iHypot(psDroid->pos.x - x, psDroid->pos.y - y); /* Is this the nearest one we got so far? */ if (dist < bestSoFar) { /* Yes, then keep a record of the distance for comparison... */ bestSoFar = dist; /* ..and store away the droid responsible */ psBestUnit = psDroid; } } } } return (psBestUnit); }
/** Calculate the distance to a tile from a point * * @ingroup pathfinding */ static inline int fpathDistToTile(int tileX, int tileY, int pointX, int pointY) { // get the difference in world coords int xdiff = world_coord(tileX) - pointX; int ydiff = world_coord(tileY) - pointY; return iHypot(xdiff, ydiff); }
static QScriptValue js_distBetweenTwoPoints(QScriptContext *context, QScriptEngine *engine) { int x1 = context->argument(0).toNumber(); int y1 = context->argument(1).toNumber(); int x2 = context->argument(2).toNumber(); int y2 = context->argument(3).toNumber(); return QScriptValue(iHypot(x1 - x2, y1 - y2)); }
BOOL fpathTileLOS(DROID *psDroid, Vector3i dest) { Vector2i dir = removeZ(dest - psDroid->pos); // Initialise the callback variables obstruction = false; rayCast(psDroid->pos, iAtan2(dir), iHypot(dir), fpathVisCallback, psDroid); return !obstruction; }
/** * Check what the videocard + drivers support and divide the loaded map into sectors that can be drawn. * It also determines the lightmap size. */ bool initTerrain(void) { int i, j, x, y, a, b, absX, absY; PIELIGHT colour[2][2], centerColour; int layer = 0; RenderVertex *geometry; RenderVertex *water; DecalVertex *decaldata; int geometrySize, geometryIndexSize; int waterSize, waterIndexSize; int textureSize, textureIndexSize; GLuint *geometryIndex; GLuint *waterIndex; GLuint *textureIndex; PIELIGHT *texture; int decalSize; int maxSectorSizeIndices, maxSectorSizeVertices; bool decreasedSize = false; // call VBO support hack before using it screen_EnableVBO(); // this information is useful to prevent crashes with buggy opengl implementations glGetIntegerv(GL_MAX_ELEMENTS_VERTICES, &GLmaxElementsVertices); glGetIntegerv(GL_MAX_ELEMENTS_INDICES, &GLmaxElementsIndices); // testing for crappy cards debug(LOG_TERRAIN, "GL_MAX_ELEMENTS_VERTICES: %i", (int)GLmaxElementsVertices); debug(LOG_TERRAIN, "GL_MAX_ELEMENTS_INDICES: %i", (int)GLmaxElementsIndices); // now we know these values, determine the maximum sector size achievable maxSectorSizeVertices = iSqrt(GLmaxElementsVertices/2)-1; maxSectorSizeIndices = iSqrt(GLmaxElementsIndices/12); debug(LOG_TERRAIN, "preferred sector size: %i", sectorSize); debug(LOG_TERRAIN, "maximum sector size due to vertices: %i", maxSectorSizeVertices); debug(LOG_TERRAIN, "maximum sector size due to indices: %i", maxSectorSizeIndices); if (sectorSize > maxSectorSizeVertices) { sectorSize = maxSectorSizeVertices; decreasedSize = true; } if (sectorSize > maxSectorSizeIndices) { sectorSize = maxSectorSizeIndices; decreasedSize = true; } if (decreasedSize) { if (sectorSize < 1) { debug(LOG_WARNING, "GL_MAX_ELEMENTS_VERTICES: %i", (int)GLmaxElementsVertices); debug(LOG_WARNING, "GL_MAX_ELEMENTS_INDICES: %i", (int)GLmaxElementsIndices); debug(LOG_WARNING, "maximum sector size due to vertices: %i", maxSectorSizeVertices); debug(LOG_WARNING, "maximum sector size due to indices: %i", maxSectorSizeIndices); debug(LOG_ERROR, "Your graphics card and/or drivers do not seem to support glDrawRangeElements, needed for the terrain renderer."); debug(LOG_ERROR, "- Do other 3D games work?"); debug(LOG_ERROR, "- Did you install the latest drivers correctly?"); debug(LOG_ERROR, "- Do you have a 3D window manager (Aero/Compiz) running?"); return false; } debug(LOG_WARNING, "decreasing sector size to %i to fit graphics card constraints", sectorSize); } // +4 = +1 for iHypot rounding, +1 for sector size rounding, +2 for edge of visibility terrainDistance = iHypot(visibleTiles.x/2, visibleTiles.y/2)+4+sectorSize/2; debug(LOG_TERRAIN, "visible tiles x:%i y: %i", visibleTiles.x, visibleTiles.y); debug(LOG_TERRAIN, "terrain view distance: %i", terrainDistance); ///////////////////// // Create the sectors xSectors = (mapWidth +sectorSize-1)/sectorSize; ySectors = (mapHeight+sectorSize-1)/sectorSize; sectors = (Sector *)malloc(sizeof(Sector)*xSectors*ySectors); //////////////////// // fill the geometry part of the sectors geometry = (RenderVertex *)malloc(sizeof(RenderVertex)*xSectors*ySectors*(sectorSize+1)*(sectorSize+1)*2); geometryIndex = (GLuint *)malloc(sizeof(GLuint)*xSectors*ySectors*sectorSize*sectorSize*12); geometrySize = 0; geometryIndexSize = 0; water = (RenderVertex *)malloc(sizeof(RenderVertex)*xSectors*ySectors*(sectorSize+1)*(sectorSize+1)*2); waterIndex = (GLuint *)malloc(sizeof(GLuint)*xSectors*ySectors*sectorSize*sectorSize*12); waterSize = 0; waterIndexSize = 0; for (x = 0; x < xSectors; x++) { for (y = 0; y < ySectors; y++) { sectors[x*ySectors + y].dirty = false; sectors[x*ySectors + y].geometryOffset = geometrySize; sectors[x*ySectors + y].geometrySize = 0; sectors[x*ySectors + y].waterOffset = waterSize; sectors[x*ySectors + y].waterSize = 0; setSectorGeometry(x, y, geometry, water, &geometrySize, &waterSize); sectors[x*ySectors + y].geometrySize = geometrySize - sectors[x*ySectors + y].geometryOffset; sectors[x*ySectors + y].waterSize = waterSize - sectors[x*ySectors + y].waterOffset; // and do the index buffers sectors[x*ySectors + y].geometryIndexOffset = geometryIndexSize; sectors[x*ySectors + y].geometryIndexSize = 0; sectors[x*ySectors + y].waterIndexOffset = waterIndexSize; sectors[x*ySectors + y].waterIndexSize = 0; for (i = 0; i < sectorSize; i++) { for (j = 0; j < sectorSize; j++) { if (x*sectorSize+i >= mapWidth || y*sectorSize+j >= mapHeight) { continue; // off map, so skip } /* One tile is composed of 4 triangles, * we need _2_ vertices per tile (1) * e.g. center and bottom left * the other 3 vertices are from the adjacent tiles * on their top and right. * (1) The top row and right column of tiles need 4 vertices per tile * because they do not have adjacent tiles on their top and right, * that is why we add _1_ row and _1_ column to provide the geometry * for these tiles. * This is the source of the '*2' and '+1' in the index math below. */ #define q(i,j,center) ((x*ySectors+y)*(sectorSize+1)*(sectorSize+1)*2 + ((i)*(sectorSize+1)+(j))*2+(center)) // First triangle geometryIndex[geometryIndexSize+0] = q(i ,j ,1); // Center vertex geometryIndex[geometryIndexSize+1] = q(i ,j ,0); // Bottom left geometryIndex[geometryIndexSize+2] = q(i+1,j ,0); // Bottom right // Second triangle geometryIndex[geometryIndexSize+3] = q(i ,j ,1); // Center vertex geometryIndex[geometryIndexSize+4] = q(i ,j+1,0); // Top left geometryIndex[geometryIndexSize+5] = q(i ,j ,0); // Bottom left // Third triangle geometryIndex[geometryIndexSize+6] = q(i ,j ,1); // Center vertex geometryIndex[geometryIndexSize+7] = q(i+1,j+1,0); // Top right geometryIndex[geometryIndexSize+8] = q(i ,j+1,0); // Top left // Fourth triangle geometryIndex[geometryIndexSize+9] = q(i ,j ,1); // Center vertex geometryIndex[geometryIndexSize+10] = q(i+1,j ,0); // Bottom right geometryIndex[geometryIndexSize+11] = q(i+1,j+1,0); // Top right geometryIndexSize += 12; if (isWater(i+x*sectorSize,j+y*sectorSize)) { waterIndex[waterIndexSize+0] = q(i ,j ,1); waterIndex[waterIndexSize+1] = q(i ,j ,0); waterIndex[waterIndexSize+2] = q(i+1,j ,0); waterIndex[waterIndexSize+3] = q(i ,j ,1); waterIndex[waterIndexSize+4] = q(i ,j+1,0); waterIndex[waterIndexSize+5] = q(i ,j ,0); waterIndex[waterIndexSize+6] = q(i ,j ,1); waterIndex[waterIndexSize+7] = q(i+1,j+1,0); waterIndex[waterIndexSize+8] = q(i ,j+1,0); waterIndex[waterIndexSize+9] = q(i ,j ,1); waterIndex[waterIndexSize+10] = q(i+1,j ,0); waterIndex[waterIndexSize+11] = q(i+1,j+1,0); waterIndexSize += 12; } } } sectors[x*ySectors + y].geometryIndexSize = geometryIndexSize - sectors[x*ySectors + y].geometryIndexOffset; sectors[x*ySectors + y].waterIndexSize = waterIndexSize - sectors[x*ySectors + y].waterIndexOffset; } } glGenBuffers(1, &geometryVBO); glError(); glBindBuffer(GL_ARRAY_BUFFER, geometryVBO); glError(); glBufferData(GL_ARRAY_BUFFER, sizeof(RenderVertex)*geometrySize, geometry, GL_DYNAMIC_DRAW); glError(); free(geometry); glGenBuffers(1, &geometryIndexVBO); glError(); glBindBuffer(GL_ARRAY_BUFFER, geometryIndexVBO); glError(); glBufferData(GL_ARRAY_BUFFER, sizeof(GLuint)*geometryIndexSize, geometryIndex, GL_STATIC_DRAW); glError(); free(geometryIndex); glGenBuffers(1, &waterVBO); glError(); glBindBuffer(GL_ARRAY_BUFFER, waterVBO); glError(); glBufferData(GL_ARRAY_BUFFER, sizeof(RenderVertex)*waterSize, water, GL_DYNAMIC_DRAW); glError(); free(water); glGenBuffers(1, &waterIndexVBO); glError(); glBindBuffer(GL_ARRAY_BUFFER, waterIndexVBO); glError(); glBufferData(GL_ARRAY_BUFFER, sizeof(GLuint)*waterIndexSize, waterIndex, GL_STATIC_DRAW); glError(); free(waterIndex); glBindBuffer(GL_ARRAY_BUFFER, 0); //////////////////// // fill the texture part of the sectors texture = (PIELIGHT *)malloc(sizeof(PIELIGHT)*xSectors*ySectors*(sectorSize+1)*(sectorSize+1)*2*numGroundTypes); textureIndex = (GLuint *)malloc(sizeof(GLuint)*xSectors*ySectors*sectorSize*sectorSize*12*numGroundTypes); textureSize = 0; textureIndexSize = 0; for (layer = 0; layer < numGroundTypes; layer++) { for (x = 0; x < xSectors; x++) { for (y = 0; y < ySectors; y++) { if (layer == 0) { sectors[x*ySectors + y].textureOffset = (int *)malloc(sizeof(int)*numGroundTypes); sectors[x*ySectors + y].textureSize = (int *)malloc(sizeof(int)*numGroundTypes); sectors[x*ySectors + y].textureIndexOffset = (int *)malloc(sizeof(int)*numGroundTypes); sectors[x*ySectors + y].textureIndexSize = (int *)malloc(sizeof(int)*numGroundTypes); } sectors[x*ySectors + y].textureOffset[layer] = textureSize; sectors[x*ySectors + y].textureSize[layer] = 0; sectors[x*ySectors + y].textureIndexOffset[layer] = textureIndexSize; sectors[x*ySectors + y].textureIndexSize[layer] = 0; //debug(LOG_WARNING, "offset when filling %i: %i", layer, xSectors*ySectors*(sectorSize+1)*(sectorSize+1)*2*layer); for (i = 0; i < sectorSize+1; i++) { for (j = 0; j < sectorSize+1; j++) { bool draw = false; bool off_map; // set transparency for (a=0;a<2;a++) { for(b=0;b<2;b++) { absX = x*sectorSize+i+a; absY = y*sectorSize+j+b; colour[a][b].rgba = 0x00FFFFFF; // transparent // extend the terrain type for the bottom and left edges of the map off_map = false; if (absX == mapWidth) { off_map = true; absX--; } if (absY == mapHeight) { off_map = true; absY--; } if (absX < 0 || absY < 0 || absX >= mapWidth || absY >= mapHeight) { // not on the map, so don't draw continue; } if (mapTile(absX,absY)->ground == layer) { colour[a][b].rgba = 0xFFFFFFFF; if (!off_map) { // if this point lies on the edge is may not force this tile to be drawn // otherwise this will give a bright line when fog is enabled draw = true; } } } } texture[xSectors*ySectors*(sectorSize+1)*(sectorSize+1)*2*layer+((x*ySectors+y)*(sectorSize+1)*(sectorSize+1)*2 + (i*(sectorSize+1)+j)*2)].rgba = colour[0][0].rgba; averageColour(¢erColour, colour[0][0], colour[0][1], colour[1][0], colour[1][1]); texture[xSectors*ySectors*(sectorSize+1)*(sectorSize+1)*2*layer+((x*ySectors+y)*(sectorSize+1)*(sectorSize+1)*2 + (i*(sectorSize+1)+j)*2+1)].rgba = centerColour.rgba; textureSize += 2; if ((draw) && i < sectorSize && j < sectorSize) { textureIndex[textureIndexSize+0] = q(i ,j ,1); textureIndex[textureIndexSize+1] = q(i ,j ,0); textureIndex[textureIndexSize+2] = q(i+1,j ,0); textureIndex[textureIndexSize+3] = q(i ,j ,1); textureIndex[textureIndexSize+4] = q(i ,j+1,0); textureIndex[textureIndexSize+5] = q(i ,j ,0); textureIndex[textureIndexSize+6] = q(i ,j ,1); textureIndex[textureIndexSize+7] = q(i+1,j+1,0); textureIndex[textureIndexSize+8] = q(i ,j+1,0); textureIndex[textureIndexSize+9] = q(i ,j ,1); textureIndex[textureIndexSize+10] = q(i+1,j ,0); textureIndex[textureIndexSize+11] = q(i+1,j+1,0); textureIndexSize += 12; } } } sectors[x*ySectors + y].textureSize[layer] = textureSize - sectors[x*ySectors + y].textureOffset[layer]; sectors[x*ySectors + y].textureIndexSize[layer] = textureIndexSize - sectors[x*ySectors + y].textureIndexOffset[layer]; } } } glGenBuffers(1, &textureVBO); glError(); glBindBuffer(GL_ARRAY_BUFFER, textureVBO); glError(); glBufferData(GL_ARRAY_BUFFER, sizeof(PIELIGHT)*xSectors*ySectors*(sectorSize+1)*(sectorSize+1)*2*numGroundTypes, texture, GL_STATIC_DRAW); glError(); free(texture); glGenBuffers(1, &textureIndexVBO); glError(); glBindBuffer(GL_ARRAY_BUFFER, textureIndexVBO); glError(); glBufferData(GL_ARRAY_BUFFER, sizeof(GLuint)*textureIndexSize, textureIndex, GL_STATIC_DRAW); glError(); free(textureIndex); glBindBuffer(GL_ARRAY_BUFFER, 0); // and finally the decals decaldata = (DecalVertex *)malloc(sizeof(DecalVertex)*mapWidth*mapHeight*12); decalSize = 0; for (x = 0; x < xSectors; x++) { for (y = 0; y < ySectors; y++) { sectors[x*ySectors + y].decalOffset = decalSize; sectors[x*ySectors + y].decalSize = 0; setSectorDecals(x, y, decaldata, &decalSize); sectors[x*ySectors + y].decalSize = decalSize - sectors[x*ySectors + y].decalOffset; } } debug(LOG_TERRAIN, "%i decals found", decalSize/12); glGenBuffers(1, &decalVBO); glError(); glBindBuffer(GL_ARRAY_BUFFER, decalVBO); glError(); glBufferData(GL_ARRAY_BUFFER, sizeof(DecalVertex)*decalSize, decaldata, GL_STATIC_DRAW); glError(); free(decaldata); glBindBuffer(GL_ARRAY_BUFFER, 0); lightmap_tex_num = 0; lightmapLastUpdate = 0; lightmapWidth = 1; lightmapHeight = 1; // determine the smallest power-of-two size we can use for the lightmap while (mapWidth > (lightmapWidth <<= 1)) {} while (mapHeight > (lightmapHeight <<= 1)) {} debug(LOG_TERRAIN, "the size of the map is %ix%i", mapWidth, mapHeight); debug(LOG_TERRAIN, "lightmap texture size is %ix%i", lightmapWidth, lightmapHeight); // Prepare the lightmap pixmap and texture lightmapPixmap = (GLubyte *)calloc(lightmapWidth * lightmapHeight, 3 * sizeof(GLubyte)); if (lightmapPixmap == NULL) { debug(LOG_FATAL, "Out of memory!"); abort(); return false; } glGenTextures(1, &lightmap_tex_num); glBindTexture(GL_TEXTURE_2D, lightmap_tex_num); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, lightmapWidth, lightmapHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, lightmapPixmap); terrainInitalised = true; glBindBuffer(GL_ARRAY_BUFFER, 0); // HACK Must unbind GL_ARRAY_BUFFER (in this function, at least), otherwise text rendering may mysteriously crash. 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; }
// not a direct script function but a helper for scrSkDefenseLocation and scrSkDefenseLocationB static bool defenseLocation(bool variantB) { SDWORD *pX, *pY, statIndex, statIndex2; UDWORD x, y, gX, gY, dist, player, nearestSoFar, count; GATEWAY *psGate, *psChosenGate; DROID *psDroid; UDWORD x1, x2, x3, x4, y1, y2, y3, y4; bool noWater; UDWORD minCount; UDWORD offset; if (!stackPopParams(6, VAL_REF | VAL_INT, &pX, VAL_REF | VAL_INT, &pY, ST_STRUCTURESTAT, &statIndex, ST_STRUCTURESTAT, &statIndex2, ST_DROID, &psDroid, VAL_INT, &player)) { debug(LOG_ERROR, "defenseLocation: failed to pop"); return false; } if (player >= MAX_PLAYERS) { ASSERT(false, "defenseLocation:player number is too high"); return false; } ASSERT_OR_RETURN(false, statIndex < numStructureStats, "Invalid range referenced for numStructureStats, %d > %d", statIndex, numStructureStats); ASSERT_OR_RETURN(false, statIndex2 < numStructureStats, "Invalid range referenced for numStructureStats, %d > %d", statIndex2, numStructureStats); STRUCTURE_STATS *psWStats = (asStructureStats + statIndex2); // check for wacky coords. if (*pX < 0 || *pX > world_coord(mapWidth) || *pY < 0 || *pY > world_coord(mapHeight) ) { goto failed; } x = map_coord(*pX); // change to tile coords. y = map_coord(*pY); // go down the gateways, find the nearest gateway with >1 empty tiles nearestSoFar = UDWORD_MAX; psChosenGate = NULL; for (psGate = gwGetGateways(); psGate; psGate = psGate->psNext) { if (auxTile(psGate->x1, psGate->y1, player) & AUXBITS_THREAT) { continue; // enemy can shoot there, not safe to build } count = 0; noWater = true; // does it have >1 tile unoccupied. if (psGate->x1 == psGate->x2) { // vert //skip gates that are too short if (variantB && (psGate->y2 - psGate->y1) <= 2) { continue; } gX = psGate->x1; for (gY = psGate->y1; gY <= psGate->y2; gY++) { if (! TileIsOccupied(mapTile(gX, gY))) { count++; } if (terrainType(mapTile(gX, gY)) == TER_WATER) { noWater = false; } } } else { // horiz //skip gates that are too short if (variantB && (psGate->x2 - psGate->x1) <= 2) { continue; } gY = psGate->y1; for (gX = psGate->x1; gX <= psGate->x2; gX++) { if (! TileIsOccupied(mapTile(gX, gY))) { count++; } if (terrainType(mapTile(gX, gY)) == TER_WATER) { noWater = false; } } } if (variantB) { minCount = 2; } else { minCount = 1; } if (count > minCount && noWater) //<NEW> min 2 tiles { // ok it's free. Is it the nearest one yet? /* Get gateway midpoint */ gX = (psGate->x1 + psGate->x2) / 2; gY = (psGate->y1 + psGate->y2) / 2; /* Estimate the distance to it */ dist = iHypot(x - gX, y - gY); /* Is it best we've found? */ if (dist < nearestSoFar && dist < 30) { /* Yes, then keep a record of it */ nearestSoFar = dist; psChosenGate = psGate; } } } if (!psChosenGate) // we have a gateway. { goto failed; } // find an unnocupied tile on that gateway. if (psChosenGate->x1 == psChosenGate->x2) { // vert gX = psChosenGate->x1; for (gY = psChosenGate->y1; gY <= psChosenGate->y2; gY++) { if (! TileIsOccupied(mapTile(gX, gY))) { y = gY; x = gX; break; } } } else { // horiz gY = psChosenGate->y1; for (gX = psChosenGate->x1; gX <= psChosenGate->x2; gX++) { if (! TileIsOccupied(mapTile(gX, gY))) { y = gY; x = gX; break; } } } // back to world coords and store result. *pX = world_coord(x) + (TILE_UNITS / 2); // return centre of tile. *pY = world_coord(y) + (TILE_UNITS / 2); scrFunctionResult.v.bval = true; if (!stackPushResult(VAL_BOOL, &scrFunctionResult)) // success { return false; } // order the droid to build two walls, one either side of the gateway. // or one in the case of a 2 size gateway. //find center of the gateway x = (psChosenGate->x1 + psChosenGate->x2) / 2; y = (psChosenGate->y1 + psChosenGate->y2) / 2; //find start pos of the gateway x1 = world_coord(psChosenGate->x1) + (TILE_UNITS / 2); y1 = world_coord(psChosenGate->y1) + (TILE_UNITS / 2); if (variantB) { offset = 2; } else { offset = 1; } if (psChosenGate->x1 == psChosenGate->x2) //vert { x2 = x1; //vert: end x pos of the first section = start x pos y2 = world_coord(y - 1) + TILE_UNITS / 2; //start y loc of the first sec x3 = x1; y3 = world_coord(y + offset) + TILE_UNITS / 2; } else //hor { x2 = world_coord(x - 1) + TILE_UNITS / 2; y2 = y1; x3 = world_coord(x + offset) + TILE_UNITS / 2; y3 = y1; } //end coords of the second section x4 = world_coord(psChosenGate->x2) + TILE_UNITS / 2; y4 = world_coord(psChosenGate->y2) + TILE_UNITS / 2; // first section. if (x1 == x2 && y1 == y2) //first sec is 1 tile only: ((2 tile gate) or (3 tile gate and first sec)) { orderDroidStatsLocDir(psDroid, DORDER_BUILD, psWStats, x1, y1, 0, ModeQueue); } else { orderDroidStatsTwoLocDir(psDroid, DORDER_LINEBUILD, psWStats, x1, y1, x2, y2, 0, ModeQueue); } // second section if (x3 == x4 && y3 == y4) { orderDroidStatsLocDirAdd(psDroid, DORDER_BUILD, psWStats, x3, y3, 0); } else { orderDroidStatsTwoLocDirAdd(psDroid, DORDER_LINEBUILD, psWStats, x3, y3, x4, y4, 0); } return true; failed: scrFunctionResult.v.bval = false; if (!stackPushResult(VAL_BOOL, &scrFunctionResult)) // failed! { return false; } return true; }
/* 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 (psWeap->usedAmmo >= 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 + 1; // Can fire earliest at the start of the tick. // See if reloadable weapon. if (psStats->reloadTime) { unsigned reloadTime = psWeap->lastFired + weaponReloadTime(psStats, psAttacker->player); if (psWeap->ammo == 0) // Out of ammo? { fireTime = std::max(fireTime, reloadTime); // Have to wait for weapon to reload before firing. if (gameTime < fireTime) { return false; } } if (reloadTime <= fireTime) { //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 hit the target */ bool tall = (psAttacker->type == OBJ_DROID && isVtolDroid((DROID *)psAttacker)) || (psAttacker->type == OBJ_STRUCTURE && ((STRUCTURE *)psAttacker)->pStructureType->height > 1); if (proj_Direct(psStats) && !lineOfFire(psAttacker, psTarget, weapon_slot, tall)) { // Can't see the target - can't hit it with direct fire objTrace(psAttacker->id, "combFire(%u[%s]->%u): No direct line of sight to target", psAttacker->id, objInfo(psAttacker), 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); int min_angle = 0; // Calculate angle 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 <= longRange && dist >= psStats->minRange) { // 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) && castDroid(psTarget)->sMove.bumpTime == 0) { 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 playerEmpTime = getEmpDisableTime(psTarget->player); int empTime = playerEmpTime - (gameTime - psTarget->timeLastHit); CLIP(empTime, 0, playerEmpTime); if (empTime >= playerEmpTime * 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; }
static inline unsigned WZ_DECL_CONST fpathGoodEstimate(PathCoord s, PathCoord f) { // Cost of moving horizontal/vertical = 70*2, cost of moving diagonal = 99*2, 99/70 = 1.41428571... ≈ √2 = 1.41421356... return iHypot((s.x - f.x)*140, (s.y - f.y)*140); }
void rayCast(Vector2i src, Vector2i dst, RAY_CALLBACK callback, void *data) { if (!callback(src, 0, data) || src == dst) // Start at src. { return; // Callback gave up after the first point, or there are no other points. } Vector2i srcM = map_coord(src); Vector2i dstM = map_coord(dst); Vector2i step, tile, cur, end; initSteps(srcM.x, dstM.x, tile.x, step.x, cur.x, end.x); initSteps(srcM.y, dstM.y, tile.y, step.y, cur.y, end.y); Vector2i prev(0, 0); // Dummy initialisation. bool first = true; Vector2i nextX(0, 0), nextY(0, 0); // Dummy initialisations. bool canX = tryStep(tile.x, step.x, cur.x, end.x, nextX.x, nextX.y, src.x, src.y, dst.x, dst.y); bool canY = tryStep(tile.y, step.y, cur.y, end.y, nextY.y, nextY.x, src.y, src.x, dst.y, dst.x); while (canX || canY) { int32_t xDist = abs(nextX.x - src.x) + abs(nextX.y - src.y); int32_t yDist = abs(nextY.x - src.x) + abs(nextY.y - src.y); Vector2i sel; Vector2i selTile; if (canX && (!canY || xDist < yDist)) // The line crosses a vertical grid line next. { sel = nextX; selTile = tile; canX = tryStep(tile.x, step.x, cur.x, end.x, nextX.x, nextX.y, src.x, src.y, dst.x, dst.y); } else // The line crosses a horizontal grid line next. { assert(canY); sel = nextY; selTile = tile; canY = tryStep(tile.y, step.y, cur.y, end.y, nextY.y, nextY.x, src.y, src.x, dst.y, dst.x); } if (!first) { // Find midpoint. Vector2i avg = (prev + sel) / 2; // But make sure it's on the right tile, since it could be off-by-one if the line passes exactly through a grid intersection. avg.x = std::min(std::max(avg.x, world_coord(selTile.x)), world_coord(selTile.x + 1) - 1); avg.y = std::min(std::max(avg.y, world_coord(selTile.y)), world_coord(selTile.y + 1) - 1); if (!worldOnMap(avg) || !callback(avg, iHypot(avg), data)) { return; // Callback doesn't want any more points, or we reached the edge of the map, so return. } } prev = sel; first = false; } // Include the endpoint. if (!worldOnMap(dst)) { return; // Stop, since reached the edge of the map. } callback(dst, iHypot(dst), data); }
/* 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; }