//Copyright Paul Reiche, Fred Ford. 1992-2002 /* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "ship.h" #include "build.h" #include "collide.h" #include "colors.h" #include "globdata.h" #include "status.h" #include "hyper.h" #include "tactrans.h" #include "pickship.h" #include "intel.h" #include "init.h" #include "battle.h" #include "supermelee/pickmele.h" #include "races.h" #include "setup.h" #include "sounds.h" #include "libs/mathlib.h" void animation_preprocess (ELEMENT *ElementPtr) { if (ElementPtr->turn_wait > 0) --ElementPtr->turn_wait; else { ElementPtr->next.image.frame = IncFrameIndex (ElementPtr->current.image.frame); ElementPtr->state_flags |= CHANGING; ElementPtr->turn_wait = ElementPtr->next_turn; } } STATUS_FLAGS inertial_thrust (ELEMENT *ElementPtr) { #define MAX_ALLOWED_SPEED WORLD_TO_VELOCITY (DISPLAY_TO_WORLD (18)) #define MAX_ALLOWED_SPEED_SQR ((DWORD)MAX_ALLOWED_SPEED * MAX_ALLOWED_SPEED) COUNT CurrentAngle, TravelAngle; COUNT max_thrust, thrust_increment; VELOCITY_DESC *VelocityPtr; STARSHIP *StarShipPtr; VelocityPtr = &ElementPtr->velocity; GetElementStarShip (ElementPtr, &StarShipPtr); CurrentAngle = FACING_TO_ANGLE (StarShipPtr->ShipFacing); TravelAngle = GetVelocityTravelAngle (VelocityPtr); thrust_increment = StarShipPtr->RaceDescPtr->characteristics.thrust_increment; max_thrust = StarShipPtr->RaceDescPtr->characteristics.max_thrust; if (thrust_increment == max_thrust) { // inertialess acceleration (Skiff) SetVelocityVector (VelocityPtr, max_thrust, StarShipPtr->ShipFacing); return (SHIP_AT_MAX_SPEED); } else if (TravelAngle == CurrentAngle && (StarShipPtr->cur_status_flags & (SHIP_AT_MAX_SPEED | SHIP_BEYOND_MAX_SPEED)) && !(StarShipPtr->cur_status_flags & SHIP_IN_GRAVITY_WELL)) { // already maxed-out acceleration return (StarShipPtr->cur_status_flags & (SHIP_AT_MAX_SPEED | SHIP_BEYOND_MAX_SPEED)); } else { SIZE delta_x, delta_y; SIZE cur_delta_x, cur_delta_y; DWORD desired_speed, max_speed; DWORD current_speed; thrust_increment = WORLD_TO_VELOCITY (thrust_increment); GetCurrentVelocityComponents (VelocityPtr, &cur_delta_x, &cur_delta_y); current_speed = VelocitySquared (cur_delta_x, cur_delta_y); delta_x = cur_delta_x + COSINE (CurrentAngle, thrust_increment); delta_y = cur_delta_y + SINE (CurrentAngle, thrust_increment); desired_speed = VelocitySquared (delta_x, delta_y); max_speed = VelocitySquared (WORLD_TO_VELOCITY (max_thrust), 0); if (desired_speed <= max_speed) { // normal acceleration SetVelocityComponents (VelocityPtr, delta_x, delta_y); } else if (((StarShipPtr->cur_status_flags & SHIP_IN_GRAVITY_WELL) && desired_speed <= MAX_ALLOWED_SPEED_SQR) || desired_speed < current_speed) { // acceleration in a gravity well within max allowed // deceleration after gravity whip SetVelocityComponents (VelocityPtr, delta_x, delta_y); return (SHIP_AT_MAX_SPEED | SHIP_BEYOND_MAX_SPEED); } else if (TravelAngle == CurrentAngle) { // normal max acceleration, same vector if (current_speed <= max_speed) SetVelocityVector (VelocityPtr, max_thrust, StarShipPtr->ShipFacing); return (SHIP_AT_MAX_SPEED); } else { // maxed-out acceleration at an angle to current travel vector // thrusting at an angle while at max velocity only changes // the travel vector, but does not really change the velocity VELOCITY_DESC v = *VelocityPtr; DeltaVelocityComponents (&v, COSINE (CurrentAngle, thrust_increment >> 1) - COSINE (TravelAngle, thrust_increment), SINE (CurrentAngle, thrust_increment >> 1) - SINE (TravelAngle, thrust_increment)); GetCurrentVelocityComponents (&v, &cur_delta_x, &cur_delta_y); desired_speed = VelocitySquared (cur_delta_x, cur_delta_y); if (desired_speed > max_speed) { if (desired_speed < current_speed) *VelocityPtr = v; return (SHIP_AT_MAX_SPEED | SHIP_BEYOND_MAX_SPEED); } *VelocityPtr = v; } return (0); } } void ship_preprocess (ELEMENT *ElementPtr) { STATUS_FLAGS cur_status_flags; STARSHIP *StarShipPtr; RACE_DESC *RDPtr; GetElementStarShip (ElementPtr, &StarShipPtr); RDPtr = StarShipPtr->RaceDescPtr; cur_status_flags = StarShipPtr->cur_status_flags & ~(LEFT | RIGHT | THRUST | WEAPON | SPECIAL); if (!(ElementPtr->state_flags & APPEARING)) { cur_status_flags |= StarShipPtr->ship_input_state & (LEFT | RIGHT | THRUST | WEAPON | SPECIAL); } else { // Preprocessing for the first time ElementPtr->crew_level = RDPtr->ship_info.crew_level; if (ElementPtr->playerNr == NPC_PLAYER_NUM && LOBYTE (GLOBAL (CurrentActivity)) == IN_LAST_BATTLE) { // Sa-Matra STAMP s; CONTEXT OldContext; OldContext = SetContext (StatusContext); s.origin.x = s.origin.y = 0; s.frame = RDPtr->ship_data.captain_control.background; DrawStamp (&s); DestroyDrawable (ReleaseDrawable (s.frame)); RDPtr->ship_data.captain_control.background = 0; SetContext (OldContext); } else if (LOBYTE (GLOBAL (CurrentActivity)) <= IN_ENCOUNTER) { CONTEXT OldContext; InitShipStatus (&RDPtr->ship_info, StarShipPtr, NULL); OldContext = SetContext (StatusContext); DrawCaptainsWindow (StarShipPtr); SetContext (OldContext); if (RDPtr->preprocess_func) (*RDPtr->preprocess_func) (ElementPtr); // XXX: Hack: Pkunk sets hTarget!=0 when it reincarnates. In that // case there is no ship_transition() but a Phoenix transition // instead. if (ElementPtr->hTarget == 0) { ship_transition (ElementPtr); } else { ElementPtr->hTarget = 0; if (!PLRPlaying ((MUSIC_REF)~0) && OpponentAlive (StarShipPtr)) BattleSong (TRUE); } return; } else { ElementPtr->current.location.x = LOG_SPACE_WIDTH >> 1; ElementPtr->current.location.y = LOG_SPACE_HEIGHT >> 1; ElementPtr->next.location = ElementPtr->current.location; InitIntersectStartPoint (ElementPtr); InitIntersectEndPoint (ElementPtr); if (hyper_transition (ElementPtr)) return; } } StarShipPtr->cur_status_flags = cur_status_flags; if (StarShipPtr->energy_counter) --StarShipPtr->energy_counter; else if (RDPtr->ship_info.energy_level < (BYTE)RDPtr->ship_info.max_energy || (SBYTE)RDPtr->characteristics.energy_regeneration < 0) DeltaEnergy (ElementPtr, (SBYTE)RDPtr->characteristics.energy_regeneration); if (RDPtr->preprocess_func) { (*RDPtr->preprocess_func) (ElementPtr); cur_status_flags = StarShipPtr->cur_status_flags; } if (ElementPtr->turn_wait) --ElementPtr->turn_wait; else if (cur_status_flags & (LEFT | RIGHT)) { if (cur_status_flags & LEFT) StarShipPtr->ShipFacing = NORMALIZE_FACING (StarShipPtr->ShipFacing - 1); else StarShipPtr->ShipFacing = NORMALIZE_FACING (StarShipPtr->ShipFacing + 1); ElementPtr->next.image.frame = SetAbsFrameIndex (ElementPtr->next.image.frame, StarShipPtr->ShipFacing); ElementPtr->state_flags |= CHANGING; ElementPtr->turn_wait = RDPtr->characteristics.turn_wait; } if (ElementPtr->thrust_wait) --ElementPtr->thrust_wait; else if (cur_status_flags & THRUST) { STATUS_FLAGS thrust_status; thrust_status = inertial_thrust (ElementPtr); StarShipPtr->cur_status_flags &= ~(SHIP_AT_MAX_SPEED | SHIP_BEYOND_MAX_SPEED | SHIP_IN_GRAVITY_WELL); StarShipPtr->cur_status_flags |= thrust_status; ElementPtr->thrust_wait = RDPtr->characteristics.thrust_wait; if (!OBJECT_CLOAKED (ElementPtr) && LOBYTE (GLOBAL (CurrentActivity)) <= IN_ENCOUNTER) { spawn_ion_trail (ElementPtr); } } if (LOBYTE (GLOBAL (CurrentActivity)) <= IN_ENCOUNTER) PreProcessStatus (ElementPtr); } void ship_postprocess (ELEMENT *ElementPtr) { STARSHIP *StarShipPtr; RACE_DESC *RDPtr; if (ElementPtr->crew_level == 0) return; GetElementStarShip (ElementPtr, &StarShipPtr); RDPtr = StarShipPtr->RaceDescPtr; if (StarShipPtr->weapon_counter) --StarShipPtr->weapon_counter; else if ((StarShipPtr->cur_status_flags & WEAPON) && DeltaEnergy (ElementPtr, -RDPtr->characteristics.weapon_energy_cost)) { COUNT num_weapons; HELEMENT Weapon[6]; num_weapons = (*RDPtr->init_weapon_func) (ElementPtr, Weapon); if (num_weapons) { HELEMENT *WeaponPtr; STARSHIP *StarShipPtr; BOOLEAN played_sfx = FALSE; GetElementStarShip (ElementPtr, &StarShipPtr); WeaponPtr = &Weapon[0]; do { HELEMENT w; w = *WeaponPtr++; if (w) { ELEMENT *EPtr; LockElement (w, &EPtr); SetElementStarShip (EPtr, StarShipPtr); if (!played_sfx) { ProcessSound (RDPtr->ship_data.ship_sounds, EPtr); played_sfx = TRUE; } UnlockElement (w); PutElement (w); } } while (--num_weapons); if (!played_sfx) ProcessSound (RDPtr->ship_data.ship_sounds, ElementPtr); } StarShipPtr->weapon_counter = RDPtr->characteristics.weapon_wait; } if (StarShipPtr->special_counter) --StarShipPtr->special_counter; if (RDPtr->postprocess_func) (*RDPtr->postprocess_func) (ElementPtr); if (LOBYTE (GLOBAL (CurrentActivity)) <= IN_ENCOUNTER) PostProcessStatus (ElementPtr); } void collision (ELEMENT *ElementPtr0, POINT *pPt0, ELEMENT *ElementPtr1, POINT *pPt1) { if (!(ElementPtr1->state_flags & FINITE_LIFE)) { ElementPtr0->state_flags |= COLLISION; if (GRAVITY_MASS (ElementPtr1->mass_points)) { // Collision with a planet. SIZE damage; damage = ElementPtr0->hit_points >> 2; if (damage == 0) damage = 1; do_damage (ElementPtr0, damage); damage = TARGET_DAMAGED_FOR_1_PT + (damage >> 1); if (damage > TARGET_DAMAGED_FOR_6_PLUS_PT) damage = TARGET_DAMAGED_FOR_6_PLUS_PT; ProcessSound (SetAbsSoundIndex (GameSounds, damage), ElementPtr0); } } (void) pPt0; /* Satisfying compiler (unused parameter) */ (void) pPt1; /* Satisfying compiler (unused parameter) */ } static BOOLEAN spawn_ship (STARSHIP *StarShipPtr) { HELEMENT hShip; RACE_DESC *RDPtr; RDPtr = load_ship (StarShipPtr->SpeciesID, TRUE); if (!RDPtr) return FALSE; StarShipPtr->RaceDescPtr = RDPtr; StarShipPtr->ship_input_state = 0; StarShipPtr->cur_status_flags = 0; StarShipPtr->old_status_flags = 0; if (LOBYTE (GLOBAL (CurrentActivity)) == IN_ENCOUNTER || LOBYTE (GLOBAL (CurrentActivity)) == IN_LAST_BATTLE) { if (StarShipPtr->crew_level == 0) { // SIS, already handled from sis_ship.c. // RDPtr->ship_info.crew_level = GLOBAL_SIS (CrewEnlisted); } else RDPtr->ship_info.crew_level = StarShipPtr->crew_level; if (RDPtr->ship_info.crew_level > RDPtr->ship_info.max_crew) RDPtr->ship_info.crew_level = RDPtr->ship_info.max_crew; } StarShipPtr->energy_counter = 0; StarShipPtr->weapon_counter = 0; StarShipPtr->special_counter = 0; hShip = StarShipPtr->hShip; if (hShip == 0) { hShip = AllocElement (); if (hShip != 0) InsertElement (hShip, GetHeadElement ()); } StarShipPtr->hShip = hShip; if (StarShipPtr->hShip != 0) { // Construct an ELEMENT for the STARSHIP ELEMENT *ShipElementPtr; LockElement (hShip, &ShipElementPtr); ShipElementPtr->playerNr = StarShipPtr->playerNr; ShipElementPtr->crew_level = 0; ShipElementPtr->mass_points = RDPtr->characteristics.ship_mass; ShipElementPtr->state_flags = APPEARING | PLAYER_SHIP | IGNORE_SIMILAR; ShipElementPtr->turn_wait = 0; ShipElementPtr->thrust_wait = 0; ShipElementPtr->life_span = NORMAL_LIFE; ShipElementPtr->colorCycleIndex = 0; SetPrimType (&DisplayArray[ShipElementPtr->PrimIndex], STAMP_PRIM); ShipElementPtr->current.image.farray = RDPtr->ship_data.ship; if (ShipElementPtr->playerNr == NPC_PLAYER_NUM && LOBYTE (GLOBAL (CurrentActivity)) == IN_LAST_BATTLE) { // This is the Sa-Matra StarShipPtr->ShipFacing = 0; ShipElementPtr->current.image.frame = SetAbsFrameIndex (RDPtr->ship_data.ship[0], StarShipPtr->ShipFacing); ShipElementPtr->current.location.x = LOG_SPACE_WIDTH >> 1; ShipElementPtr->current.location.y = LOG_SPACE_HEIGHT >> 1; ++ShipElementPtr->life_span; } else { StarShipPtr->ShipFacing = NORMALIZE_FACING (TFB_Random ()); if (inHQSpace ()) { // Only one ship is ever spawned in HyperSpace -- flagship COUNT facing = GLOBAL (ShipFacing); // XXX: Solar system reentry test depends on ShipFacing != 0 if (facing > 0) --facing; // XXX: This appears to set the facing to a random value // for when the ship returns from an encounter back // to HyperSpace. However, it is overwritten later // in sis.c. See also r1614. //GLOBAL (ShipFacing) = StarShipPtr->ShipFacing + 1; StarShipPtr->ShipFacing = facing; } ShipElementPtr->current.image.frame = SetAbsFrameIndex (RDPtr->ship_data.ship[0], StarShipPtr->ShipFacing); do { ShipElementPtr->current.location.x = WRAP_X (DISPLAY_ALIGN_X (TFB_Random ())); ShipElementPtr->current.location.y = WRAP_Y (DISPLAY_ALIGN_Y (TFB_Random ())); } while (CalculateGravity (ShipElementPtr) || TimeSpaceMatterConflict (ShipElementPtr)); } ShipElementPtr->preprocess_func = ship_preprocess; ShipElementPtr->postprocess_func = ship_postprocess; ShipElementPtr->death_func = ship_death; ShipElementPtr->collision_func = collision; ZeroVelocityComponents (&ShipElementPtr->velocity); SetElementStarShip (ShipElementPtr, StarShipPtr); ShipElementPtr->hTarget = 0; UnlockElement (hShip); } return (hShip != 0); } // Select a new ship and spawn it. BOOLEAN GetNextStarShip (STARSHIP *LastStarShipPtr, COUNT which_side) { HSTARSHIP hBattleShip; hBattleShip = GetEncounterStarShip (LastStarShipPtr, which_side); if (hBattleShip) { STARSHIP *StarShipPtr; StarShipPtr = LockStarShip (&race_q[which_side], hBattleShip); if (LastStarShipPtr) { if (StarShipPtr == LastStarShipPtr) { // Ship has been recycled (on infinite ship worlds). LastStarShipPtr = 0; } else StarShipPtr->hShip = LastStarShipPtr->hShip; } if (!spawn_ship (StarShipPtr)) { UnlockStarShip (&race_q[which_side], hBattleShip); return (FALSE); } UnlockStarShip (&race_q[which_side], hBattleShip); } if (LastStarShipPtr) LastStarShipPtr->hShip = 0; return (hBattleShip != 0); } BOOLEAN GetInitialStarShips (void) { if (LOBYTE (GLOBAL (CurrentActivity)) == SUPER_MELEE) { HSTARSHIP ships[NUM_PLAYERS]; COUNT i; if (!GetInitialMeleeStarShips (ships)) return FALSE; for (i = 0; i < NUM_PLAYERS; i++) { STARSHIP *StarShipPtr; COUNT playerI = GetPlayerOrder (i); StarShipPtr = LockStarShip (&race_q[playerI], ships[playerI]); if (!spawn_ship (StarShipPtr)) { UnlockStarShip (&race_q[playerI], ships[playerI]); return FALSE; } UnlockStarShip (&race_q[playerI], ships[playerI]); } return TRUE; } else { int i; for (i = NUM_PLAYERS; i > 0; --i) { if (!GetNextStarShip (NULL, i - 1)) return FALSE; } return TRUE; } }