/* 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 EglFSWaylandInput::pointerAxisChanged(quint32 time, const Qt::Orientation &orientation, qreal value) { EglFSWaylandWindow *window = EglFSWaylandWindow::fromSurface( m_seat->pointer()->focusSurface()); QPoint angleDelta(0, 0); if (orientation == Qt::Horizontal) angleDelta.setX(value * -12); else angleDelta.setY(value * -12); QWindowSystemInterface::handleWheelEvent(window ? window->window() : Q_NULLPTR, time, m_mouseLocalPos, m_mouseGlobalPos, QPoint(), angleDelta); }
// Render a composite droid given a DROID_TEMPLATE structure. // void displayComponentButtonTemplate(DROID_TEMPLATE *psTemplate, const Vector3i *Rotation, const Vector3i *Position, int scale) { const glm::mat4 matrix = setMatrix(Position, Rotation, scale); // Decide how to sort it. leftFirst = angleDelta(DEG(Rotation->y)) < 0; DROID Droid(0, selectedPlayer); memset(Droid.asBits, 0, sizeof(Droid.asBits)); droidSetBits(psTemplate, &Droid); Droid.pos = Vector3i(0, 0, 0); Droid.rot = Vector3i(0, 0, 0); //draw multi component object as a button object displayCompObj(&Droid, true, matrix); }
// Render a composite droid given a DROID_TEMPLATE structure. // void displayComponentButtonTemplate(DROID_TEMPLATE *psTemplate, Vector3i *Rotation, Vector3i *Position, bool RotXYZ, SDWORD scale) { setMatrix(Position, Rotation, RotXYZ); pie_MatScale(scale / 100.f); // Decide how to sort it. leftFirst = angleDelta(DEG(Rotation->y)) < 0; DROID Droid(0, selectedPlayer); memset(Droid.asBits, 0, sizeof(Droid.asBits)); droidSetBits(psTemplate,&Droid); Droid.pos = Vector3i(0, 0, 0); //draw multi component object as a button object displayCompObj(&Droid, true); unsetMatrix(); }
/* 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; }
/* 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; }
// multiple turrets display removed the pointless mountRotation void displayComponentObject(DROID *psDroid, const glm::mat4 &viewMatrix) { Vector3i position, rotation; Spacetime st = interpolateObjectSpacetime(psDroid, graphicsTime); leftFirst = angleDelta(player.r.y - st.rot.direction) <= 0; /* Get the real position */ position.x = st.pos.x - player.p.x; position.z = -(st.pos.y - player.p.z); position.y = st.pos.z; if (isTransporter(psDroid)) { position.y += bobTransporterHeight(); } /* Get all the pitch,roll,yaw info */ rotation.y = -st.rot.direction; rotation.x = st.rot.pitch; rotation.z = st.rot.roll; /* Translate origin */ /* Rotate for droid */ glm::mat4 modelMatrix = glm::translate(glm::vec3(position)) * glm::rotate(UNDEG(rotation.y), glm::vec3(0.f, 1.f, 0.f)) * glm::rotate(UNDEG(rotation.x), glm::vec3(1.f, 0.f, 0.f)) * glm::rotate(UNDEG(rotation.z), glm::vec3(0.f, 0.f, 1.f)); if (graphicsTime - psDroid->timeLastHit < GAME_TICKS_PER_SEC && psDroid->lastHitWeapon == WSC_ELECTRONIC) { modelMatrix *= objectShimmy((BASE_OBJECT *) psDroid); } // now check if the projected circle is within the screen boundaries if(!clipDroidOnScreen(psDroid, viewMatrix * modelMatrix)) { return; } if (psDroid->lastHitWeapon == WSC_EMP && graphicsTime - psDroid->timeLastHit < EMP_DISABLE_TIME) { Vector3i position; //add an effect on the droid position.x = st.pos.x + DROID_EMP_SPREAD; position.y = st.pos.z + rand() % 8; position.z = st.pos.y + DROID_EMP_SPREAD; effectGiveAuxVar(90 + rand() % 20); addEffect(&position, EFFECT_EXPLOSION, EXPLOSION_TYPE_PLASMA, false, nullptr, 0); } if (psDroid->visible[selectedPlayer] == UBYTE_MAX) { //ingame not button object //should render 3 mounted weapons now if (displayCompObj(psDroid, false, viewMatrix * modelMatrix)) { // did draw something to the screen - update the framenumber psDroid->sDisplay.frameNumber = frameGetFrameNumber(); } } else { int frame = graphicsTime / BLIP_ANIM_DURATION + psDroid->id % 8192; // de-sync the blip effect, but don't overflow the int if (pie_Draw3DShape(getImdFromIndex(MI_BLIP), frame, 0, WZCOL_WHITE, pie_ADDITIVE, psDroid->visible[selectedPlayer] / 2, viewMatrix * modelMatrix)) { psDroid->sDisplay.frameNumber = frameGetFrameNumber(); } } }
static inline uint16_t interpolateAngle(uint16_t v1, uint16_t v2, uint32_t t1, uint32_t t2, uint32_t t) { const int numer = t - t1, denom = t2 - t1; return v1 + angleDelta(v2 - v1) * numer / denom; }
// multiple turrets display removed the pointless mountRotation void displayComponentObject(DROID *psDroid) { Vector3i position, rotation; Spacetime st = interpolateObjectSpacetime(psDroid, graphicsTime); leftFirst = angleDelta(player.r.y - st.rot.direction) <= 0; /* Push the matrix */ pie_MatBegin(true); /* Get the real position */ position.x = st.pos.x - player.p.x; position.z = -(st.pos.y - player.p.z); position.y = st.pos.z; if (isTransporter(psDroid)) { position.y += bobTransporterHeight(); } /* Get all the pitch,roll,yaw info */ rotation.y = -st.rot.direction; rotation.x = st.rot.pitch; rotation.z = st.rot.roll; /* Translate origin */ pie_TRANSLATE(position.x, position.y, position.z); /* Rotate for droid */ pie_MatRotY(rotation.y); pie_MatRotX(rotation.x); pie_MatRotZ(rotation.z); if (graphicsTime - psDroid->timeLastHit < GAME_TICKS_PER_SEC && psDroid->lastHitWeapon == WSC_ELECTRONIC) { objectShimmy((BASE_OBJECT *) psDroid); } if (psDroid->lastHitWeapon == WSC_EMP && graphicsTime - psDroid->timeLastHit < EMP_DISABLE_TIME) { Vector3i position; //add an effect on the droid position.x = st.pos.x + DROID_EMP_SPREAD; position.y = st.pos.z + rand() % 8; position.z = st.pos.y + DROID_EMP_SPREAD; effectGiveAuxVar(90 + rand() % 20); addEffect(&position, EFFECT_EXPLOSION, EXPLOSION_TYPE_PLASMA, false, NULL, 0); } if (psDroid->visible[selectedPlayer] == UBYTE_MAX) { //ingame not button object //should render 3 mounted weapons now displayCompObj(psDroid, false); } else { int frame = graphicsTime / BLIP_ANIM_DURATION + psDroid->id % 8192; // de-sync the blip effect, but don't overflow the int pie_Draw3DShape(getImdFromIndex(MI_BLIP), frame, 0, WZCOL_WHITE, pie_ADDITIVE, psDroid->visible[selectedPlayer] / 2); } pie_MatEnd(); }
// multiple turrets display removed the pointless mountRotation void displayComponentObject(DROID *psDroid) { Vector3i position, rotation; int32_t xShift,zShift; SDWORD frame; PROPULSION_STATS *psPropStats; UDWORD tileX,tileY; MAPTILE *psTile; SPACETIME st = interpolateObjectSpacetime((SIMPLE_OBJECT *)psDroid, graphicsTime); psPropStats = asPropulsionStats + psDroid->asBits[COMP_PROPULSION].nStat; leftFirst = angleDelta(player.r.y - st.rot.direction) <= 0; /* Push the matrix */ pie_MatBegin(); /* Get internal tile units coordinates */ xShift = map_round(player.p.x); zShift = map_round(player.p.z); /* Mask out to tile_units resolution */ pie_TRANSLATE(xShift,0,-zShift); /* Get the real position */ position.x = (st.pos.x - player.p.x) - terrainMidX*TILE_UNITS; position.z = terrainMidY*TILE_UNITS - (st.pos.y - player.p.z); position.y = st.pos.z; if(psDroid->droidType == DROID_TRANSPORTER) { position.y += bobTransporterHeight(); } /* Get all the pitch,roll,yaw info */ rotation.y = -st.rot.direction; rotation.x = st.rot.pitch; rotation.z = st.rot.roll; /* Translate origin */ pie_TRANSLATE(position.x,position.y,position.z); /* Rotate for droid */ pie_MatRotY(rotation.y); pie_MatRotX(rotation.x); pie_MatRotZ(rotation.z); if( (gameTime-psDroid->timeLastHit < GAME_TICKS_PER_SEC) && psDroid->lastHitWeapon == WSC_ELECTRONIC) { objectShimmy( (BASE_OBJECT*) psDroid ); } if (psDroid->lastHitWeapon == WSC_EMP && (gameTime - psDroid->timeLastHit < EMP_DISABLE_TIME)) { Vector3i position; //add an effect on the droid position.x = st.pos.x + DROID_EMP_SPREAD; position.y = st.pos.z + rand()%8; position.z = st.pos.y + DROID_EMP_SPREAD; effectGiveAuxVar(90+rand()%20); addEffect(&position,EFFECT_EXPLOSION,EXPLOSION_TYPE_PLASMA,false,NULL,0); } if ((psDroid->visible[selectedPlayer] == UBYTE_MAX) || demoGetStatus()) { //ingame not button object //should render 3 mounted weapons now displayCompObj(psDroid, false); } else { // make sure it's not over water. tileX = st.pos.x/TILE_UNITS; tileY = st.pos.y/TILE_UNITS; // double check it's on map if ( tileX < mapWidth && tileY < mapHeight ) { psTile = mapTile(tileX,tileY); if (terrainType(psTile) != TER_WATER) { frame = gameTime/BLIP_ANIM_DURATION + psDroid->id; //visible[selectedPlayer]; pie_Draw3DShape(getImdFromIndex(MI_BLIP), frame, 0, WZCOL_WHITE, WZCOL_BLACK, pie_ADDITIVE, psDroid->visible[selectedPlayer] / 2); /* set up all the screen coords stuff - need to REMOVE FROM THIS LOOP */ } } } pie_MatEnd(); }