/* Will return false when we've hit the edge of the grid */ static bool getTileHeightCallback(Vector2i pos, int32_t dist, void *data) { HeightCallbackHelp_t *help = (HeightCallbackHelp_t *)data; #ifdef TEST_RAY Vector3i effect; #endif /* Are we still on the grid? */ if (clipXY(pos.x, pos.y)) { bool HasTallStructure = blockTile(map_coord(pos.x), map_coord(pos.y), AUX_MAP) & AIR_BLOCKED; if (dist > TILE_UNITS || HasTallStructure) { // Only do it the current tile is > TILE_UNITS away from the starting tile. Or.. // there is a tall structure on the current tile and the current tile is not the starting tile. /* Get height at this intersection point */ int height = map_Height(pos.x, pos.y), heightDiff; uint16_t newPitch; if (HasTallStructure) { height += TALLOBJECT_ADJUST; } if (height <= help->height) { heightDiff = 0; } else { heightDiff = height - help->height; } /* Work out the angle to this point from start point */ newPitch = iAtan2(heightDiff, dist); /* Is this the steepest we've found? */ if (angleDelta(newPitch - help->pitch) > 0) { /* Yes, then keep a record of it */ help->pitch = newPitch; } //--- #ifdef TEST_RAY effect.x = pos.x; effect.y = height; effect.z = pos.y; addEffect(&effect, EFFECT_EXPLOSION, EXPLOSION_TYPE_SMALL, false, NULL, 0); #endif } /* Not at edge yet - so exit */ return true; } /* We've hit edge of grid - so exit!! */ return false; }
void getBestPitchToEdgeOfGrid(UDWORD x, UDWORD y, uint16_t direction, uint16_t *pitch) { HeightCallbackHelp_t help = {map_Height(x,y), 0}; rayCast(Vector3i(x, y, 0), direction, 5430, getTileHeightCallback, &help); // FIXME Magic value *pitch = help.pitch; }
// free up a feature with no visual effects bool removeFeature(FEATURE *psDel) { MESSAGE *psMessage; Vector3i pos; ASSERT_OR_RETURN(false, psDel != NULL, "Invalid feature pointer"); ASSERT_OR_RETURN(false, !psDel->died, "Feature already dead"); //remove from the map data StructureBounds b = getStructureBounds(psDel); for (int breadth = 0; breadth < b.size.y; ++breadth) { for (int width = 0; width < b.size.x; ++width) { if (tileOnMap(b.map.x + width, b.map.y + breadth)) { MAPTILE *psTile = mapTile(b.map.x + width, b.map.y + breadth); if (psTile->psObject == psDel) { psTile->psObject = NULL; auxClearBlocking(b.map.x + width, b.map.y + breadth, FEATURE_BLOCKED | AIR_BLOCKED); } } } } if (psDel->psStats->subType == FEAT_GEN_ARTE || psDel->psStats->subType == FEAT_OIL_DRUM) { pos.x = psDel->pos.x; pos.z = psDel->pos.y; pos.y = map_Height(pos.x, pos.z) + 30; addEffect(&pos, EFFECT_EXPLOSION, EXPLOSION_TYPE_DISCOVERY, false, NULL, 0, gameTime - deltaGameTime + 1); if (psDel->psStats->subType == FEAT_GEN_ARTE) { scoreUpdateVar(WD_ARTEFACTS_FOUND); intRefreshScreen(); } } if (psDel->psStats->subType == FEAT_GEN_ARTE || psDel->psStats->subType == FEAT_OIL_RESOURCE) { for (unsigned player = 0; player < MAX_PLAYERS; ++player) { psMessage = findMessage((MSG_VIEWDATA *)psDel, MSG_PROXIMITY, player); while (psMessage) { removeMessage(psMessage, player); psMessage = findMessage((MSG_VIEWDATA *)psDel, MSG_PROXIMITY, player); } } } debug(LOG_DEATH, "Killing off feature %s id %d (%p)", objInfo(psDel), psDel->id, psDel); killFeature(psDel); return true; }
void getBestPitchToEdgeOfGrid(UDWORD x, UDWORD y, uint16_t direction, uint16_t *pitch) { HeightCallbackHelp_t help = {map_Height(x, y), 0}; Vector3i src(x, y, 0); Vector3i delta(iSinCosR(direction, 5430), 0); rayCast(src.xy, (src + delta).xy, getTileHeightCallback, &help); // FIXME Magic value *pitch = help.pitch; }
/* Sets up the dummy target for the camera */ static void setUpRadarTarget(SDWORD x, SDWORD y) { radarTarget.pos.x = x; radarTarget.pos.y = y; if ((x < 0) || (y < 0) || (x > world_coord(mapWidth - 1)) || (y > world_coord(mapHeight - 1))) { radarTarget.pos.z = world_coord(1) * ELEVATION_SCALE + CAMERA_PIVOT_HEIGHT; } else { radarTarget.pos.z = map_Height(x,y) + CAMERA_PIVOT_HEIGHT; } radarTarget.rot.direction = calcDirection(player.p.x, player.p.z, x, y); radarTarget.rot.pitch = 0; radarTarget.rot.roll = 0; }
/* Sets up the dummy target for the camera */ static void setUpRadarTarget(SDWORD x, SDWORD y) { radarTarget.pos.x = x; radarTarget.pos.y = y; if ((x < 0) || (y < 0) || (x > (SDWORD)((mapWidth - 1) * TILE_UNITS)) || (y > (SDWORD)((mapHeight - 1) * TILE_UNITS))) { radarTarget.pos.z = 128 * ELEVATION_SCALE + CAMERA_PIVOT_HEIGHT; } else { radarTarget.pos.z = map_Height(x,y) + CAMERA_PIVOT_HEIGHT; } radarTarget.rot.direction = calcDirection(player.p.x, player.p.z, x, y); radarTarget.rot.pitch = 0; radarTarget.rot.roll = 0; radarTarget.type = OBJ_TARGET; radarTarget.died = 0; }
void doBuildingLights( void ) { STRUCTURE *psStructure; UDWORD i; LIGHT light; for(i=0; i<MAX_PLAYERS; i++) { for(psStructure = apsStructLists[i]; psStructure; psStructure = psStructure->psNext) { light.range = psStructure->pStructureType->baseWidth * TILE_UNITS; light.position.x = psStructure->pos.x; light.position.z = psStructure->pos.y; light.position.y = map_Height(light.position.x,light.position.z); light.range = psStructure->pStructureType->baseWidth * TILE_UNITS; light.colour = LIGHT_WHITE; processLight(&light); } } }
/* Remove a Feature and free it's memory */ bool destroyFeature(FEATURE *psDel) { UDWORD widthScatter,breadthScatter,heightScatter, i; EFFECT_TYPE explosionSize; Vector3i pos; UDWORD width,breadth; UDWORD mapX,mapY; ASSERT_OR_RETURN(false, psDel != NULL, "Invalid feature pointer"); /* Only add if visible and damageable*/ if(psDel->visible[selectedPlayer] && psDel->psStats->damageable) { /* Set off a destruction effect */ /* First Explosions */ widthScatter = TILE_UNITS/2; breadthScatter = TILE_UNITS/2; heightScatter = TILE_UNITS/4; //set which explosion to use based on size of feature if (psDel->psStats->baseWidth < 2 && psDel->psStats->baseBreadth < 2) { explosionSize = EXPLOSION_TYPE_SMALL; } else if (psDel->psStats->baseWidth < 3 && psDel->psStats->baseBreadth < 3) { explosionSize = EXPLOSION_TYPE_MEDIUM; } else { explosionSize = EXPLOSION_TYPE_LARGE; } for(i=0; i<4; i++) { pos.x = psDel->pos.x + widthScatter - rand()%(2*widthScatter); pos.z = psDel->pos.y + breadthScatter - rand()%(2*breadthScatter); pos.y = psDel->pos.z + 32 + rand()%heightScatter; addEffect(&pos,EFFECT_EXPLOSION,explosionSize,false,NULL,0); } if(psDel->psStats->subType == FEAT_SKYSCRAPER) { pos.x = psDel->pos.x; pos.z = psDel->pos.y; pos.y = psDel->pos.z; addEffect(&pos,EFFECT_DESTRUCTION,DESTRUCTION_TYPE_SKYSCRAPER,true,psDel->sDisplay.imd,0); initPerimeterSmoke(psDel->sDisplay.imd, pos); shakeStart(); } /* Then a sequence of effects */ pos.x = psDel->pos.x; pos.z = psDel->pos.y; pos.y = map_Height(pos.x,pos.z); addEffect(&pos,EFFECT_DESTRUCTION,DESTRUCTION_TYPE_FEATURE,false,NULL,0); //play sound // ffs gj if(psDel->psStats->subType == FEAT_SKYSCRAPER) { audio_PlayStaticTrack( psDel->pos.x, psDel->pos.y, ID_SOUND_BUILDING_FALL ); } else { audio_PlayStaticTrack( psDel->pos.x, psDel->pos.y, ID_SOUND_EXPLOSION ); } } if (psDel->psStats->subType == FEAT_SKYSCRAPER) { // ----- Flip all the tiles under the skyscraper to a rubble tile // smoke effect should disguise this happening mapX = map_coord(psDel->pos.x) - psDel->psStats->baseWidth/2; mapY = map_coord(psDel->pos.y) - psDel->psStats->baseBreadth/2; for (width = 0; width < psDel->psStats->baseWidth; width++) { for (breadth = 0; breadth < psDel->psStats->baseBreadth; breadth++) { MAPTILE *psTile = mapTile(mapX+width,mapY+breadth); // stops water texture chnaging for underwateer festures if (terrainType(psTile) != TER_WATER) { if (terrainType(psTile) != TER_CLIFFFACE) { /* Clear feature bits */ psTile->texture = TileNumber_texture(psTile->texture) | RUBBLE_TILE; } else { /* This remains a blocking tile */ psTile->psObject = NULL; psTile->texture = TileNumber_texture(psTile->texture) | BLOCKING_RUBBLE_TILE; } } } } } removeFeature(psDel); return true; }
// free up a feature with no visual effects bool removeFeature(FEATURE *psDel) { int mapX, mapY, width, breadth, player; MESSAGE *psMessage; Vector3i pos; ASSERT_OR_RETURN(false, psDel != NULL, "Invalid feature pointer"); ASSERT_OR_RETURN(false, !psDel->died, "Feature already dead"); if(bMultiMessages && !ingame.localJoiningInProgress) { SendDestroyFeature(psDel); // inform other players of destruction return true; // Wait for our message before really destroying the feature. } //remove from the map data mapX = map_coord(psDel->pos.x) - psDel->psStats->baseWidth/2; mapY = map_coord(psDel->pos.y) - psDel->psStats->baseBreadth/2; for (width = 0; width < psDel->psStats->baseWidth; width++) { for (breadth = 0; breadth < psDel->psStats->baseBreadth; breadth++) { if (tileOnMap(mapX + width, mapY + breadth)) { MAPTILE *psTile = mapTile(mapX + width, mapY + breadth); if (psTile->psObject == (BASE_OBJECT *)psDel) { psTile->psObject = NULL; auxClearBlocking(mapX + width, mapY + breadth, FEATURE_BLOCKED | AIR_BLOCKED); } } } } if(psDel->psStats->subType == FEAT_GEN_ARTE) { pos.x = psDel->pos.x; pos.z = psDel->pos.y; pos.y = map_Height(pos.x,pos.z); addEffect(&pos,EFFECT_EXPLOSION,EXPLOSION_TYPE_DISCOVERY,false,NULL,0); scoreUpdateVar(WD_ARTEFACTS_FOUND); intRefreshScreen(); } if (psDel->psStats->subType == FEAT_GEN_ARTE || psDel->psStats->subType == FEAT_OIL_RESOURCE) { for (player = 0; player < MAX_PLAYERS; player++) { psMessage = findMessage((MSG_VIEWDATA *)psDel, MSG_PROXIMITY, player); while (psMessage) { removeMessage(psMessage, player); psMessage = findMessage((MSG_VIEWDATA *)psDel, MSG_PROXIMITY, player); } } } killFeature(psDel); return true; }
/* Calculate the acceleration that the camera spins around at */ static void updateCameraRotationAcceleration( UBYTE update ) { SDWORD worldAngle; float separation; SDWORD xConcern, yConcern, zConcern; bool bTooLow; PROPULSION_STATS *psPropStats; bool bGotFlying = false; SDWORD xPos = 0, yPos = 0, zPos = 0; bTooLow = false; if(trackingCamera.target->type == OBJ_DROID) { DROID *psDroid = (DROID*)trackingCamera.target; psPropStats = asPropulsionStats + psDroid->asBits[COMP_PROPULSION].nStat; if(psPropStats->propulsionType == PROPULSION_TYPE_LIFT) { int droidHeight, difHeight, droidMapHeight; bGotFlying = true; droidHeight = psDroid->pos.z; droidMapHeight = map_Height(psDroid->pos.x, psDroid->pos.y); difHeight = abs(droidHeight - droidMapHeight); if(difHeight < MIN_TRACK_HEIGHT) { bTooLow = true; } } } if(update & Y_UPDATE) { /* Presently only y rotation being calculated - but same idea for other axes */ /* Check what we're tracking */ if(getNumDroidsSelected()>2 && trackingCamera.target->type == OBJ_DROID) { unsigned group = trackingCamera.target->selected ? GROUP_SELECTED : trackingCamera.target->group; yConcern = getAverageTrackAngle(group, false); } else { yConcern = trackingCamera.target->rot.direction; } yConcern += DEG(180); while(trackingCamera.rotation.y < 0) { trackingCamera.rotation.y += DEG(360); } /* Which way are we facing? */ worldAngle = trackingCamera.rotation.y; separation = angleDelta(yConcern - worldAngle); /* Make new acceleration */ trackingCamera.rotAccel.y = ROT_ACCEL_CONSTANT * separation - ROT_VELOCITY_CONSTANT * trackingCamera.rotVel.y; } if(update & X_UPDATE) { if(trackingCamera.target->type == OBJ_DROID && !bGotFlying) { uint16_t pitch; unsigned group = trackingCamera.target->selected ? GROUP_SELECTED : trackingCamera.target->group; getTrackingConcerns(&xPos, &yPos, &zPos, GROUP_SELECTED, true); // FIXME Should this be group instead of GROUP_SELECTED? getBestPitchToEdgeOfGrid(xPos, zPos, DEG(180) - getAverageTrackAngle(group, true), &pitch); pitch = MAX(angleDelta(pitch), DEG(14)); xConcern = -pitch; } else { xConcern = trackingCamera.target->rot.pitch; xConcern += DEG(-16); } while(trackingCamera.rotation.x<0) { trackingCamera.rotation.x += DEG(360); } worldAngle = trackingCamera.rotation.x; separation = angleDelta(xConcern - worldAngle); /* Make new acceleration */ trackingCamera.rotAccel.x = /* Make this really slow */ ((ROT_ACCEL_CONSTANT)*separation - ROT_VELOCITY_CONSTANT*(float)trackingCamera.rotVel.x); } /* This looks a bit arse - looks like a flight sim */ if(update & Z_UPDATE) { if(bTooLow) { zConcern = 0; } else { zConcern = trackingCamera.target->rot.roll; } while(trackingCamera.rotation.z<0) { trackingCamera.rotation.z+=DEG(360); } worldAngle = trackingCamera.rotation.z; separation = (float) ((zConcern - worldAngle)); if(separation<DEG(-180)) { separation+=DEG(360); } else if(separation>DEG(180)) { separation-=DEG(360); } /* Make new acceleration */ trackingCamera.rotAccel.z = /* Make this really slow */ ((ROT_ACCEL_CONSTANT/1)*separation - ROT_VELOCITY_CONSTANT*(float)trackingCamera.rotVel.z); } }
/* 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; }
/* Moves one of the particles */ static void processParticle(ATPART *psPart) { SDWORD groundHeight; Vector3i pos; UDWORD x, y; MAPTILE *psTile; /* Only move if the game isn't paused */ if (!gamePaused()) { /* Move the particle - frame rate controlled */ psPart->position.x += graphicsTimeAdjustedIncrement(psPart->velocity.x); psPart->position.y += graphicsTimeAdjustedIncrement(psPart->velocity.y); psPart->position.z += graphicsTimeAdjustedIncrement(psPart->velocity.z); /* Wrap it around if it's gone off grid... */ testParticleWrap(psPart); /* If it's gone off the WORLD... */ if (psPart->position.x < 0 || psPart->position.z < 0 || psPart->position.x > ((mapWidth - 1)*TILE_UNITS) || psPart->position.z > ((mapHeight - 1)*TILE_UNITS)) { /* The kill it */ psPart->status = APS_INACTIVE; return; } /* What height is the ground under it? Only do if low enough...*/ if (psPart->position.y < 255 * ELEVATION_SCALE) { /* Get ground height */ groundHeight = map_Height(psPart->position.x, psPart->position.z); /* Are we below ground? */ if ((int)psPart->position.y < groundHeight || psPart->position.y < 0.f) { /* Kill it and return */ psPart->status = APS_INACTIVE; if (psPart->type == AP_RAIN) { x = map_coord(psPart->position.x); y = map_coord(psPart->position.z); psTile = mapTile(x, y); if (terrainType(psTile) == TER_WATER && TEST_TILE_VISIBLE(selectedPlayer, psTile)) { pos.x = psPart->position.x; pos.z = psPart->position.z; pos.y = groundHeight; effectSetSize(60); addEffect(&pos, EFFECT_EXPLOSION, EXPLOSION_TYPE_SPECIFIED, true, getImdFromIndex(MI_SPLASH), 0); } } return; } } if (psPart->type == AP_SNOW) { if (rand() % 30 == 1) { psPart->velocity.z = (float)SNOW_SPEED_DRIFT; } if (rand() % 30 == 1) { psPart->velocity.x = (float)SNOW_SPEED_DRIFT; } } } }
// //////////////////////////////////////////////////////////////////////////// // receive a check and update the local world state accordingly BOOL recvDroidCheck(NETQUEUE queue) { uint8_t count; int i; uint32_t synchTime; NETbeginDecode(queue, GAME_CHECK_DROID); // Get the number of droids to expect NETuint8_t(&count); NETuint32_t(&synchTime); // Get game time. for (i = 0; i < count; i++) { DROID * pD; PACKAGED_CHECK pc, pc2; Position precPos; NETPACKAGED_CHECK(&pc); // Find the droid in question if (!IdToDroid(pc.droidID, pc.player, &pD)) { NETlogEntry("Recvd Unknown droid info. val=player", SYNC_FLAG, pc.player); debug(LOG_SYNC, "Received checking info for an unknown (as yet) droid. player:%d ref:%d", pc.player, pc.droidID); continue; } syncDebugDroid(pD, '<'); if (pD->gameCheckDroid == NULL) { debug(LOG_SYNC, "We got a droid %u synch, but we couldn't find the droid!", pc.droidID); continue; // Can't synch, since we didn't save data to be able to calculate a delta. } pc2 = *(PACKAGED_CHECK *)pD->gameCheckDroid; // pc2 should be declared here, as const. if (pc2.gameTime != synchTime + MIN_DELAY_BETWEEN_DROID_SYNCHS) { debug(LOG_SYNC, "We got a droid %u synch, but we didn't choose the same droid to synch.", pc.droidID); ((PACKAGED_CHECK *)pD->gameCheckDroid)->gameTime = synchTime + MIN_DELAY_BETWEEN_DROID_SYNCHS; // Get droid synch time back in synch. continue; // Can't synch, since we didn't save data to be able to calculate a delta. } #define MERGECOPY(x, y, z) if (pc.y != pc2.y) { debug(LOG_SYNC, "Droid %u out of synch, changing "#x" from %"z" to %"z".", pc.droidID, x, pc.y); x = pc.y; } #define MERGEDELTA(x, y, z) if (pc.y != pc2.y) { debug(LOG_SYNC, "Droid %u out of synch, changing "#x" from %"z" to %"z".", pc.droidID, x, x + pc.y - pc2.y); x += pc.y - pc2.y; } // player not synched here... precPos = droidGetPrecisePosition(pD); MERGEDELTA(precPos.x, pos.x, "d"); MERGEDELTA(precPos.y, pos.y, "d"); MERGEDELTA(precPos.z, pos.z, "d"); droidSetPrecisePosition(pD, precPos); MERGEDELTA(pD->rot.direction, rot.direction, "d"); MERGEDELTA(pD->rot.pitch, rot.pitch, "d"); MERGEDELTA(pD->rot.roll, rot.roll, "d"); MERGEDELTA(pD->body, body, "u"); if (pD->body > pD->originalBody) { pD->body = pD->originalBody; debug(LOG_SYNC, "Droid %u body was too high after synch, reducing to %u.", pc.droidID, pD->body); } MERGEDELTA(pD->experience, experience, "u"); if (pc.pos.x != pc2.pos.x || pc.pos.y != pc2.pos.y) { // snap droid(if on ground) to terrain level at x,y. if ((asPropulsionStats + pD->asBits[COMP_PROPULSION].nStat)->propulsionType != PROPULSION_TYPE_LIFT) // if not airborne. { pD->pos.z = map_Height(pD->pos.x, pD->pos.y); } } // Doesn't cover all cases, but at least doesn't actively break stuff randomly. switch (pc.order) { case DORDER_MOVE: if (pc.order != pc2.order || pc.orderX != pc2.orderX || pc.orderY != pc2.orderY) { debug(LOG_SYNC, "Droid %u out of synch, changing order from %s to %s(%d, %d).", pc.droidID, getDroidOrderName(pc2.order), getDroidOrderName(pc.order), pc.orderX, pc.orderY); // reroute the droid. orderDroidLoc(pD, pc.order, pc.orderX, pc.orderY, ModeImmediate); } break; case DORDER_ATTACK: if (pc.order != pc2.order || pc.targetID != pc2.targetID) { BASE_OBJECT *obj = IdToPointer(pc.targetID, ANYPLAYER); if (obj != NULL) { debug(LOG_SYNC, "Droid %u out of synch, changing order from %s to %s(%u).", pc.droidID, getDroidOrderName(pc2.order), getDroidOrderName(pc.order), pc.targetID); // remote droid is attacking, not here tho! orderDroidObj(pD, pc.order, IdToPointer(pc.targetID, ANYPLAYER), ModeImmediate); } else { debug(LOG_SYNC, "Droid %u out of synch, would change order from %s to %s(%u), but can't find target.", pc.droidID, getDroidOrderName(pc2.order), getDroidOrderName(pc.order), pc.targetID); } } break; case DORDER_NONE: case DORDER_GUARD: if (pc.order != pc2.order) { DROID_ORDER_DATA sOrder; memset(&sOrder, 0, sizeof(DROID_ORDER_DATA)); sOrder.order = pc.order; debug(LOG_SYNC, "Droid %u out of synch, changing order from %s to %s.", pc.droidID, getDroidOrderName(pc2.order), getDroidOrderName(pc.order)); turnOffMultiMsg(true); moveStopDroid(pD); orderDroidBase(pD, &sOrder); turnOffMultiMsg(false); } break; default: break; // Don't know what to do, but at least won't be actively breaking anything. } MERGECOPY(pD->secondaryOrder, secondaryOrder, "u"); // The old code set this after changing orders, so doing that in case. #undef MERGECOPY #undef MERGEDELTA syncDebugDroid(pD, '>'); // ...and repeat! } NETend(); return true; }
/* Remove a Feature and free it's memory */ bool destroyFeature(FEATURE *psDel, unsigned impactTime) { UDWORD widthScatter, breadthScatter, heightScatter, i; EFFECT_TYPE explosionSize; Vector3i pos; ASSERT_OR_RETURN(false, psDel != NULL, "Invalid feature pointer"); ASSERT(gameTime - deltaGameTime < impactTime, "Expected %u < %u, gameTime = %u, bad impactTime", gameTime - deltaGameTime, impactTime, gameTime); /* Only add if visible and damageable*/ if (psDel->visible[selectedPlayer] && psDel->psStats->damageable) { /* Set off a destruction effect */ /* First Explosions */ widthScatter = TILE_UNITS / 2; breadthScatter = TILE_UNITS / 2; heightScatter = TILE_UNITS / 4; //set which explosion to use based on size of feature if (psDel->psStats->baseWidth < 2 && psDel->psStats->baseBreadth < 2) { explosionSize = EXPLOSION_TYPE_SMALL; } else if (psDel->psStats->baseWidth < 3 && psDel->psStats->baseBreadth < 3) { explosionSize = EXPLOSION_TYPE_MEDIUM; } else { explosionSize = EXPLOSION_TYPE_LARGE; } for (i = 0; i < 4; i++) { pos.x = psDel->pos.x + widthScatter - rand() % (2 * widthScatter); pos.z = psDel->pos.y + breadthScatter - rand() % (2 * breadthScatter); pos.y = psDel->pos.z + 32 + rand() % heightScatter; addEffect(&pos, EFFECT_EXPLOSION, explosionSize, false, NULL, 0, impactTime); } if (psDel->psStats->subType == FEAT_SKYSCRAPER) { pos.x = psDel->pos.x; pos.z = psDel->pos.y; pos.y = psDel->pos.z; addEffect(&pos, EFFECT_DESTRUCTION, DESTRUCTION_TYPE_SKYSCRAPER, true, psDel->sDisplay.imd, 0, impactTime); initPerimeterSmoke(psDel->sDisplay.imd, pos); shakeStart(250); // small shake } /* Then a sequence of effects */ pos.x = psDel->pos.x; pos.z = psDel->pos.y; pos.y = map_Height(pos.x, pos.z); addEffect(&pos, EFFECT_DESTRUCTION, DESTRUCTION_TYPE_FEATURE, false, NULL, 0, impactTime); //play sound // ffs gj if (psDel->psStats->subType == FEAT_SKYSCRAPER) { audio_PlayStaticTrack(psDel->pos.x, psDel->pos.y, ID_SOUND_BUILDING_FALL); } else { audio_PlayStaticTrack(psDel->pos.x, psDel->pos.y, ID_SOUND_EXPLOSION); } } if (psDel->psStats->subType == FEAT_SKYSCRAPER) { // ----- Flip all the tiles under the skyscraper to a rubble tile // smoke effect should disguise this happening StructureBounds b = getStructureBounds(psDel); 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); // stops water texture changing for underwater features if (terrainType(psTile) != TER_WATER) { if (terrainType(psTile) != TER_CLIFFFACE) { /* Clear feature bits */ psTile->texture = TileNumber_texture(psTile->texture) | RUBBLE_TILE; auxClearBlocking(b.map.x + width, b.map.y + breadth, AUXBITS_ALL); } else { /* This remains a blocking tile */ psTile->psObject = NULL; auxClearBlocking(b.map.x + width, b.map.y + breadth, AIR_BLOCKED); // Shouldn't remain blocking for air units, however. psTile->texture = TileNumber_texture(psTile->texture) | BLOCKING_RUBBLE_TILE; } } } } } removeFeature(psDel); psDel->died = impactTime; return true; }