// //////////////////////////////////////////////////////////////////////////// // pick a droid to send, NULL otherwise. static DROID* pickADroid(void) { DROID *pD, *ret = NULL; // ret: dummy initialisation. unsigned player = MAX_PLAYERS; unsigned i; // Pick a random player who has at least one droid. for (i = 0; i < 200; ++i) { unsigned p = gameRand(MAX_PLAYERS); if (apsDroidLists[p] != NULL) { player = p; break; } } if (player == MAX_PLAYERS) { return NULL; // No players have any droids, with high probability... } // O(n) where n is number of droids. Slow, but hard to beat on a linked list. (One call of a pick n droids function would be just as fast.) i = 0; for (pD = apsDroidLists[player]; pD != NULL; pD = pD->psNext) { if (gameRand(++i) == 0) { ret = pD; } } return ret; }
bool addOilDrum(uint8_t count) { syncDebug("Adding %d oil drums.", count); int featureIndex; for (featureIndex = 0; featureIndex < numFeatureStats && asFeatureStats[featureIndex].subType != FEAT_OIL_DRUM; ++featureIndex) {} if (featureIndex >= numFeatureStats) { debug(LOG_WARNING, "No oil drum feature!"); return false; // Return value ignored. } for (unsigned n = 0; n < count; ++n) { uint32_t x, y; for (int i = 0; i < 3; ++i) // try three times { // Between 10 and mapwidth - 10 x = gameRand(mapWidth - 20) + 10; y = gameRand(mapHeight - 20) + 10; if (pickATileGen(&x, &y, LOOK_FOR_EMPTY_TILE, zonedPAT)) { break; } x = INVALID_XY; } if (x == INVALID_XY) { syncDebug("Did not find location for oil drum."); debug(LOG_FEATURE, "Unable to find a free location."); continue; } FEATURE *pF = buildFeature(&asFeatureStats[featureIndex], world_coord(x), world_coord(y), false); if (pF) { pF->player = ANYPLAYER; syncDebugFeature(pF, '+'); } else { debug(LOG_ERROR, "Couldn't build oil drum?"); } } return true; }
// //////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////// // Structure Checking, to ensure smoke and stuff is consistent across machines. // this func is recursive! static STRUCTURE *pickAStructure(unsigned player) { STRUCTURE *pS, *ret = NULL; unsigned i; // O(n) where n is number of structures. Slow, but hard to beat on a linked list. i = 0; for (pS = apsStructLists[player]; pS != NULL; pS = pS->psNext) { if (gameRand(++i) == 0) { ret = pS; } } return ret; }
/* 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; }
/* Create a feature on the map */ FEATURE * buildFeature(FEATURE_STATS *psStats, UDWORD x, UDWORD y,BOOL FromSave) { UDWORD mapX, mapY; UDWORD width,breadth, foundationMin,foundationMax, height; UDWORD startX,startY,max,min; SDWORD i; UBYTE vis; //try and create the Feature FEATURE* psFeature = createFeature(); if (psFeature == NULL) { debug(LOG_WARNING, "Feature couldn't be built."); return NULL; } psFeature->psStats = psStats; // features are not in the cluster system // this will cause an assert when they still end up there psFeature->cluster = ~0; //add the feature to the list - this enables it to be drawn whilst being built addFeature(psFeature); // get the terrain average height startX = map_coord(x); startY = map_coord(y); foundationMin = TILE_MAX_HEIGHT; foundationMax = TILE_MIN_HEIGHT; for (breadth = 0; breadth < psStats->baseBreadth; breadth++) { for (width = 0; width < psStats->baseWidth; width++) { getTileMaxMin(startX + width, startY + breadth, &max, &min); if (foundationMin > min) { foundationMin = min; } if (foundationMax < max) { foundationMax = max; } } } //return the average of max/min height height = (foundationMin + foundationMax) / 2; // snap the coords to a tile if (!FromSave) { x = (x & ~TILE_MASK) + psStats->baseWidth %2 * TILE_UNITS/2; y = (y & ~TILE_MASK) + psStats->baseBreadth%2 * TILE_UNITS/2; } else { if ((x & TILE_MASK) != psStats->baseWidth %2 * TILE_UNITS/2 || (y & TILE_MASK) != psStats->baseBreadth%2 * TILE_UNITS/2) { debug(LOG_WARNING, "Feature not aligned. position (%d,%d), size (%d,%d)", x, y, psStats->baseWidth, psStats->baseBreadth); } } psFeature->pos.x = x; psFeature->pos.y = y; /* Dump down the building wrecks at random angles - still looks shit though */ if(psStats->subType == FEAT_BUILD_WRECK || psStats->subType == FEAT_TREE) { psFeature->rot.direction = gameRand(DEG_360); } else { psFeature->rot.direction = 0; } psFeature->selected = false; psFeature->body = psStats->body; psFeature->player = MAX_PLAYERS+1; //set the player out of range to avoid targeting confusions objSensorCache((BASE_OBJECT *)psFeature, NULL); objEcmCache((BASE_OBJECT *)psFeature, NULL); psFeature->bTargetted = false; psFeature->timeLastHit = 0; psFeature->lastHitWeapon = WSC_NUM_WEAPON_SUBCLASSES; // no such weapon // it has never been drawn psFeature->sDisplay.frameNumber = 0; if(getRevealStatus()) { vis = 0; } else { if(psStats->visibleAtStart) { vis = UBYTE_MAX; } else { vis = 0; } } // note that the advanced armour system current unused for features for (i = 0; i < NUM_HIT_SIDES; i++) { int j; for (j = 0; j < WC_NUM_WEAPON_CLASSES; j++) { psFeature->armour[i][j] = psFeature->psStats->armourValue; } } memset(psFeature->seenThisTick, 0, sizeof(psFeature->seenThisTick)); memset(psFeature->visible, 0, sizeof(psFeature->visible)); //load into the map data mapX = map_coord(x) - psStats->baseWidth/2; mapY = map_coord(y) - psStats->baseBreadth/2; // set up the imd for the feature if(psFeature->psStats->subType==FEAT_BUILD_WRECK) { psFeature->sDisplay.imd = getRandomWreckageImd(); } else { psFeature->sDisplay.imd = psStats->psImd; } ASSERT_OR_RETURN(NULL, psFeature->sDisplay.imd, "No IMD for feature"); // make sure we have an imd. for (width = 0; width < psStats->baseWidth; width++) { for (breadth = 0; breadth < psStats->baseBreadth; breadth++) { MAPTILE *psTile = mapTile(mapX + width, mapY + breadth); //check not outside of map - for load save game ASSERT_OR_RETURN(NULL, mapX + width < mapWidth, "x coord bigger than map width - %s, id = %d", getName(psFeature->psStats->pName), psFeature->id); ASSERT_OR_RETURN(NULL, mapY + breadth < mapHeight, "y coord bigger than map height - %s, id = %d", getName(psFeature->psStats->pName), psFeature->id); if (width != psStats->baseWidth && breadth != psStats->baseBreadth) { if (TileHasFeature(psTile)) { FEATURE *psBlock = (FEATURE *)psTile->psObject; debug(LOG_ERROR, "%s(%d) already placed at (%d+%d, %d+%d) when trying to place %s(%d) at (%d+%d, %d+%d) - removing it", getName(psBlock->psStats->pName), psBlock->id, map_coord(psBlock->pos.x), psBlock->psStats->baseWidth, map_coord(psBlock->pos.y), psBlock->psStats->baseBreadth, getName(psFeature->psStats->pName), psFeature->id, mapX, psStats->baseWidth, mapY, psStats->baseBreadth); removeFeature(psBlock); } psTile->psObject = (BASE_OBJECT*)psFeature; // if it's a tall feature then flag it in the map. if (psFeature->sDisplay.imd->max.y > TALLOBJECT_YMAX) { auxSetBlocking(mapX + width, mapY + breadth, AIR_BLOCKED); } if (psStats->subType != FEAT_GEN_ARTE && psStats->subType != FEAT_OIL_DRUM && psStats->subType != FEAT_BUILD_WRECK) { auxSetBlocking(mapX + width, mapY + breadth, FEATURE_BLOCKED); } } if( (!psStats->tileDraw) && (FromSave == false) ) { psTile->height = height; } } } psFeature->pos.z = map_TileHeight(mapX,mapY);//jps 18july97 // // set up the imd for the feature // if(psFeature->psStats->subType==FEAT_BUILD_WRECK) // { // psFeature->sDisplay.imd = wreckageImds[rand()%MAX_WRECKAGE]; // } // else // { // psFeature->sDisplay.imd = psStats->psImd; // } return psFeature; }
/* Create a feature on the map */ FEATURE * buildFeature(FEATURE_STATS *psStats, UDWORD x, UDWORD y,bool FromSave) { UDWORD mapX, mapY; UDWORD width,breadth, foundationMin,foundationMax, height; UDWORD startX,startY,max,min; //try and create the Feature FEATURE *psFeature = new FEATURE(generateSynchronisedObjectId(), psStats); if (psFeature == NULL) { debug(LOG_WARNING, "Feature couldn't be built."); return NULL; } // features are not in the cluster system // this will cause an assert when they still end up there psFeature->cluster = ~0; //add the feature to the list - this enables it to be drawn whilst being built addFeature(psFeature); // get the terrain average height startX = map_coord(x); startY = map_coord(y); foundationMin = TILE_MAX_HEIGHT; foundationMax = TILE_MIN_HEIGHT; for (breadth = 0; breadth < psStats->baseBreadth; breadth++) { for (width = 0; width < psStats->baseWidth; width++) { getTileMaxMin(startX + width, startY + breadth, &max, &min); if (foundationMin > min) { foundationMin = min; } if (foundationMax < max) { foundationMax = max; } } } //return the average of max/min height height = (foundationMin + foundationMax) / 2; // snap the coords to a tile if (!FromSave) { x = (x & ~TILE_MASK) + psStats->baseWidth %2 * TILE_UNITS/2; y = (y & ~TILE_MASK) + psStats->baseBreadth%2 * TILE_UNITS/2; } else { if ((x & TILE_MASK) != psStats->baseWidth %2 * TILE_UNITS/2 || (y & TILE_MASK) != psStats->baseBreadth%2 * TILE_UNITS/2) { debug(LOG_WARNING, "Feature not aligned. position (%d,%d), size (%d,%d)", x, y, psStats->baseWidth, psStats->baseBreadth); } } psFeature->pos.x = x; psFeature->pos.y = y; if (psStats->subType == FEAT_TREE) { psFeature->rot.direction = gameRand(DEG_360); } else { psFeature->rot.direction = 0; } psFeature->body = psStats->body; psFeature->inFire = false; objSensorCache((BASE_OBJECT *)psFeature, NULL); objEcmCache((BASE_OBJECT *)psFeature, NULL); // it has never been drawn psFeature->sDisplay.frameNumber = 0; for (int j = 0; j < WC_NUM_WEAPON_CLASSES; j++) { psFeature->armour[j] = psFeature->psStats->armourValue; } memset(psFeature->seenThisTick, 0, sizeof(psFeature->seenThisTick)); memset(psFeature->visible, 0, sizeof(psFeature->visible)); //load into the map data mapX = map_coord(x) - psStats->baseWidth/2; mapY = map_coord(y) - psStats->baseBreadth/2; // set up the imd for the feature psFeature->sDisplay.imd = psStats->psImd; ASSERT_OR_RETURN(NULL, psFeature->sDisplay.imd, "No IMD for feature"); // make sure we have an imd. for (width = 0; width < psStats->baseWidth; width++) { for (breadth = 0; breadth < psStats->baseBreadth; breadth++) { MAPTILE *psTile = mapTile(mapX + width, mapY + breadth); //check not outside of map - for load save game ASSERT_OR_RETURN(NULL, mapX + width < mapWidth, "x coord bigger than map width - %s, id = %d", getName(psFeature->psStats->pName), psFeature->id); ASSERT_OR_RETURN(NULL, mapY + breadth < mapHeight, "y coord bigger than map height - %s, id = %d", getName(psFeature->psStats->pName), psFeature->id); if (width != psStats->baseWidth && breadth != psStats->baseBreadth) { if (TileHasFeature(psTile)) { FEATURE *psBlock = (FEATURE *)psTile->psObject; debug(LOG_ERROR, "%s(%d) already placed at (%d+%d, %d+%d) when trying to place %s(%d) at (%d+%d, %d+%d) - removing it", getName(psBlock->psStats->pName), psBlock->id, map_coord(psBlock->pos.x), psBlock->psStats->baseWidth, map_coord(psBlock->pos.y), psBlock->psStats->baseBreadth, getName(psFeature->psStats->pName), psFeature->id, mapX, psStats->baseWidth, mapY, psStats->baseBreadth); removeFeature(psBlock); } psTile->psObject = (BASE_OBJECT*)psFeature; // if it's a tall feature then flag it in the map. if (psFeature->sDisplay.imd->max.y > TALLOBJECT_YMAX) { auxSetBlocking(mapX + width, mapY + breadth, AIR_BLOCKED); } if (psStats->subType != FEAT_GEN_ARTE && psStats->subType != FEAT_OIL_DRUM) { auxSetBlocking(mapX + width, mapY + breadth, FEATURE_BLOCKED); } } if( (!psStats->tileDraw) && (FromSave == false) ) { psTile->height = height; } } } psFeature->pos.z = map_TileHeight(mapX,mapY);//jps 18july97 return psFeature; }
/* 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; }
/* Create a feature on the map */ FEATURE * buildFeature(FEATURE_STATS *psStats, UDWORD x, UDWORD y,bool FromSave) { //try and create the Feature FEATURE *psFeature = new FEATURE(generateSynchronisedObjectId(), psStats); if (psFeature == NULL) { debug(LOG_WARNING, "Feature couldn't be built."); return NULL; } // features are not in the cluster system // this will cause an assert when they still end up there psFeature->cluster = ~0; //add the feature to the list - this enables it to be drawn whilst being built addFeature(psFeature); // snap the coords to a tile if (!FromSave) { x = (x & ~TILE_MASK) + psStats->baseWidth %2 * TILE_UNITS/2; y = (y & ~TILE_MASK) + psStats->baseBreadth%2 * TILE_UNITS/2; } else { if ((x & TILE_MASK) != psStats->baseWidth %2 * TILE_UNITS/2 || (y & TILE_MASK) != psStats->baseBreadth%2 * TILE_UNITS/2) { debug(LOG_WARNING, "Feature not aligned. position (%d,%d), size (%d,%d)", x, y, psStats->baseWidth, psStats->baseBreadth); } } psFeature->pos.x = x; psFeature->pos.y = y; StructureBounds b = getStructureBounds(psFeature); // get the terrain average height int foundationMin = INT32_MAX; int foundationMax = INT32_MIN; for (int breadth = 0; breadth <= b.size.y; ++breadth) { for (int width = 0; width <= b.size.x; ++width) { int h = map_TileHeight(b.map.x + width, b.map.y + breadth); foundationMin = std::min(foundationMin, h); foundationMax = std::max(foundationMax, h); } } //return the average of max/min height int height = (foundationMin + foundationMax) / 2; if (psStats->subType == FEAT_TREE) { psFeature->rot.direction = gameRand(DEG_360); } else { psFeature->rot.direction = 0; } psFeature->body = psStats->body; psFeature->periodicalDamageStart = 0; psFeature->periodicalDamage = 0; // it has never been drawn psFeature->sDisplay.frameNumber = 0; memset(psFeature->seenThisTick, 0, sizeof(psFeature->seenThisTick)); memset(psFeature->visible, 0, sizeof(psFeature->visible)); // set up the imd for the feature psFeature->sDisplay.imd = psStats->psImd; ASSERT_OR_RETURN(NULL, psFeature->sDisplay.imd, "No IMD for feature"); // make sure we have an imd. for (int breadth = 0; breadth < b.size.y; ++breadth) { for (int width = 0; width < b.size.x; ++width) { MAPTILE *psTile = mapTile(b.map.x + width, b.map.y + breadth); //check not outside of map - for load save game ASSERT_OR_RETURN(NULL, b.map.x + width < mapWidth, "x coord bigger than map width - %s, id = %d", getName(psFeature->psStats), psFeature->id); ASSERT_OR_RETURN(NULL, b.map.y + breadth < mapHeight, "y coord bigger than map height - %s, id = %d", getName(psFeature->psStats), psFeature->id); if (width != psStats->baseWidth && breadth != psStats->baseBreadth) { if (TileHasFeature(psTile)) { FEATURE *psBlock = (FEATURE *)psTile->psObject; debug(LOG_ERROR, "%s(%d) already placed at (%d+%d, %d+%d) when trying to place %s(%d) at (%d+%d, %d+%d) - removing it", getName(psBlock->psStats), psBlock->id, map_coord(psBlock->pos.x), psBlock->psStats->baseWidth, map_coord(psBlock->pos.y), psBlock->psStats->baseBreadth, getName(psFeature->psStats), psFeature->id, b.map.x, b.size.x, b.map.y, b.size.y); removeFeature(psBlock); } psTile->psObject = (BASE_OBJECT*)psFeature; // if it's a tall feature then flag it in the map. if (psFeature->sDisplay.imd->max.y > TALLOBJECT_YMAX) { auxSetBlocking(b.map.x + width, b.map.y + breadth, AIR_BLOCKED); } if (psStats->subType != FEAT_GEN_ARTE && psStats->subType != FEAT_OIL_DRUM) { auxSetBlocking(b.map.x + width, b.map.y + breadth, FEATURE_BLOCKED); } } if( (!psStats->tileDraw) && (FromSave == false) ) { psTile->height = height; } } } psFeature->pos.z = map_TileHeight(b.map.x, b.map.y);//jps 18july97 return psFeature; }
/////////////////////////////////////////////////////////////////////////////// // splatter artifact gifts randomly about. void addMultiPlayerRandomArtifacts(uint8_t quantity, FEATURE_TYPE type) { FEATURE *pF = NULL; int i, featureStat, count; uint32_t x, y; uint8_t player = ANYPLAYER; debug(LOG_FEATURE, "Sending %u artifact(s) type: (%s)", quantity, feature_names[type]); NETbeginEncode(NET_ARTIFACTS, NET_ALL_PLAYERS); NETuint8_t(&quantity); NETenum(&type); for(featureStat = 0; featureStat < numFeatureStats && asFeatureStats[featureStat].subType != type; featureStat++); ASSERT(mapWidth > 20, "map not big enough"); ASSERT(mapHeight > 20, "map not big enough"); for (count = 0; count < quantity; count++) { for (i = 0; i < 3; i++) // try three times { // Between 10 and mapwidth - 10 x = (gameRand(mapWidth - 20)) + 10; y = (gameRand(mapHeight - 20)) + 10; if (pickATileGen(&x, &y, LOOK_FOR_EMPTY_TILE, zonedPAT)) { break; } else if (i == 2) { debug(LOG_FEATURE, "Unable to find a free location after 3 tries; giving up."); x = INVALID_XY; } } if (x != INVALID_XY) // at least one of the tries succeeded { pF = buildFeature(asFeatureStats + featureStat, world_coord(x), world_coord(y), false); if (pF) { pF->player = player; } else { x = INVALID_XY; } } NETuint32_t(&x); NETuint32_t(&y); if (pF) { NETuint32_t(&pF->id); } else { NETuint32_t(&x); // just give them a dummy value; it'll never be used } NETuint8_t(&player); } NETend(); }