//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 "chenjesu.h" #include "resinst.h" #include "uqm/globdata.h" #include "libs/mathlib.h" // Core characteristics #define MAX_CREW 36 #define MAX_ENERGY 30 #define ENERGY_REGENERATION 1 #define ENERGY_WAIT 4 #define MAX_THRUST /* DISPLAY_TO_WORLD (7) */ 27 #define THRUST_INCREMENT /* DISPLAY_TO_WORLD (2) */ 3 #define THRUST_WAIT 4 #define TURN_WAIT 6 #define SHIP_MASS 10 // Photon Shard #define WEAPON_ENERGY_COST 5 #define WEAPON_WAIT 0 #define CHENJESU_OFFSET 16 #define MISSILE_OFFSET 0 #define MISSILE_SPEED DISPLAY_TO_WORLD (16) #define MISSILE_LIFE 90 /* actually, it's as long as you hold the button down. */ #define MISSILE_HITS 10 #define MISSILE_DAMAGE 6 #define NUM_SPARKLES 8 // Shrapnel #define FRAGMENT_OFFSET 2 #define NUM_FRAGMENTS 8 #define FRAGMENT_LIFE 10 #define FRAGMENT_SPEED MISSILE_SPEED #define FRAGMENT_RANGE (FRAGMENT_LIFE * FRAGMENT_SPEED) /* This bit is for the cyborg only. */ #define FRAGMENT_HITS 1 #define FRAGMENT_DAMAGE 2 // DOGI #define SPECIAL_ENERGY_COST MAX_ENERGY #define SPECIAL_WAIT 0 #define DOGGY_OFFSET 18 #define DOGGY_SPEED DISPLAY_TO_WORLD (8) #define ENERGY_DRAIN 10 #define MAX_DOGGIES 4 #define DOGGY_HITS 3 #define DOGGY_MASS 4 static RACE_DESC chenjesu_desc = { { /* SHIP_INFO */ FIRES_FORE | SEEKING_SPECIAL | SEEKING_WEAPON, 28, /* Super Melee cost */ MAX_CREW, MAX_CREW, MAX_ENERGY, MAX_ENERGY, CHENJESU_RACE_STRINGS, CHENJESU_ICON_MASK_PMAP_ANIM, CHENJESU_MICON_MASK_PMAP_ANIM, NULL, NULL, NULL }, { /* FLEET_STUFF */ 0, /* Initial sphere of influence radius */ { /* Known location (center of SoI) */ 0, 0, }, }, { MAX_THRUST, THRUST_INCREMENT, ENERGY_REGENERATION, WEAPON_ENERGY_COST, SPECIAL_ENERGY_COST, ENERGY_WAIT, TURN_WAIT, THRUST_WAIT, WEAPON_WAIT, SPECIAL_WAIT, SHIP_MASS, }, { { CHENJESU_BIG_MASK_PMAP_ANIM, CHENJESU_MED_MASK_PMAP_ANIM, CHENJESU_SML_MASK_PMAP_ANIM, }, { SPARK_BIG_MASK_PMAP_ANIM, SPARK_MED_MASK_PMAP_ANIM, SPARK_SML_MASK_PMAP_ANIM, }, { DOGGY_BIG_MASK_PMAP_ANIM, DOGGY_MED_MASK_PMAP_ANIM, DOGGY_SML_MASK_PMAP_ANIM, }, { CHENJESU_CAPTAIN_MASK_PMAP_ANIM, NULL, NULL, NULL, NULL, NULL }, CHENJESU_VICTORY_SONG, CHENJESU_SHIP_SOUNDS, { NULL, NULL, NULL }, { NULL, NULL, NULL }, { NULL, NULL, NULL }, NULL, NULL }, { 0, LONG_RANGE_WEAPON, NULL, }, (UNINIT_FUNC *) NULL, (PREPROCESS_FUNC *) NULL, (POSTPROCESS_FUNC *) NULL, (INIT_WEAPON_FUNC *) NULL, 0, 0, /* CodeRef */ }; static void crystal_postprocess (ELEMENT *ElementPtr) { STARSHIP *StarShipPtr; MISSILE_BLOCK MissileBlock; GetElementStarShip (ElementPtr, &StarShipPtr); MissileBlock.cx = ElementPtr->next.location.x; MissileBlock.cy = ElementPtr->next.location.y; MissileBlock.farray = StarShipPtr->RaceDescPtr->ship_data.weapon; MissileBlock.index = 1; MissileBlock.sender = ElementPtr->playerNr; MissileBlock.flags = IGNORE_SIMILAR; MissileBlock.pixoffs = 0; MissileBlock.speed = FRAGMENT_SPEED; MissileBlock.hit_points = FRAGMENT_HITS; MissileBlock.damage = FRAGMENT_DAMAGE; MissileBlock.life = FRAGMENT_LIFE; MissileBlock.preprocess_func = NULL; MissileBlock.blast_offs = FRAGMENT_OFFSET; for (MissileBlock.face = 0; MissileBlock.face < ANGLE_TO_FACING (FULL_CIRCLE); MissileBlock.face += (ANGLE_TO_FACING (FULL_CIRCLE) / NUM_FRAGMENTS)) { HELEMENT hFragment; hFragment = initialize_missile (&MissileBlock); if (hFragment) { ELEMENT *FragPtr; LockElement (hFragment, &FragPtr); SetElementStarShip (FragPtr, StarShipPtr); UnlockElement (hFragment); PutElement (hFragment); } } ProcessSound (SetAbsSoundIndex ( /* CRYSTAL_FRAGMENTS */ StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 1), ElementPtr); } static void crystal_preprocess (ELEMENT *ElementPtr) { STARSHIP *StarShipPtr; GetElementStarShip (ElementPtr, &StarShipPtr); if (StarShipPtr->cur_status_flags & WEAPON) ++ElementPtr->life_span; /* keep it going while key pressed */ else { ElementPtr->life_span = 1; ElementPtr->postprocess_func = crystal_postprocess; } } static void animate (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; } } static void crystal_collision (ELEMENT *ElementPtr0, POINT *pPt0, ELEMENT *ElementPtr1, POINT *pPt1) { HELEMENT hBlastElement; hBlastElement = weapon_collision (ElementPtr0, pPt0, ElementPtr1, pPt1); if (hBlastElement) { ELEMENT *BlastElementPtr; LockElement (hBlastElement, &BlastElementPtr); BlastElementPtr->current.location = ElementPtr1->current.location; BlastElementPtr->life_span = NUM_SPARKLES; BlastElementPtr->turn_wait = BlastElementPtr->next_turn = 0; { BlastElementPtr->preprocess_func = animate; } BlastElementPtr->current.image.farray = ElementPtr0->next.image.farray; BlastElementPtr->current.image.frame = SetAbsFrameIndex (BlastElementPtr->current.image.farray[0], 2); /* skip stones */ UnlockElement (hBlastElement); } } static void doggy_preprocess (ELEMENT *ElementPtr) { STARSHIP *StarShipPtr; GetElementStarShip (ElementPtr, &StarShipPtr); ++StarShipPtr->special_counter; if (ElementPtr->thrust_wait > 0) /* could be non-zero after a collision */ --ElementPtr->thrust_wait; else { COUNT facing, orig_facing; SIZE delta_facing; facing = orig_facing = NORMALIZE_FACING (ANGLE_TO_FACING ( GetVelocityTravelAngle (&ElementPtr->velocity) )); if ((delta_facing = TrackShip (ElementPtr, &facing)) < 0) facing = NORMALIZE_FACING (TFB_Random ()); else { ELEMENT *ShipPtr; LockElement (ElementPtr->hTarget, &ShipPtr); facing = NORMALIZE_FACING (ANGLE_TO_FACING ( ARCTAN (ShipPtr->current.location.x - ElementPtr->current.location.x, ShipPtr->current.location.y - ElementPtr->current.location.y) )); delta_facing = NORMALIZE_FACING (facing - GetFrameIndex (ShipPtr->current.image.frame)); UnlockElement (ElementPtr->hTarget); if (delta_facing > ANGLE_TO_FACING (HALF_CIRCLE - OCTANT) && delta_facing < ANGLE_TO_FACING (HALF_CIRCLE + OCTANT)) { if (delta_facing >= ANGLE_TO_FACING (HALF_CIRCLE)) facing -= ANGLE_TO_FACING (QUADRANT); else facing += ANGLE_TO_FACING (QUADRANT); } facing = NORMALIZE_FACING (facing); } if (facing != orig_facing) SetVelocityVector (&ElementPtr->velocity, DOGGY_SPEED, facing); } } static void doggy_death (ELEMENT *ElementPtr) { STARSHIP *StarShipPtr; GetElementStarShip (ElementPtr, &StarShipPtr); ProcessSound (SetAbsSoundIndex ( /* DOGGY_DIES */ StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 3), ElementPtr); ElementPtr->state_flags &= ~DISAPPEARING; ElementPtr->state_flags |= NONSOLID | FINITE_LIFE; ElementPtr->life_span = 6; { ElementPtr->preprocess_func = animate; } ElementPtr->death_func = NULL; ElementPtr->collision_func = NULL; ZeroVelocityComponents (&ElementPtr->velocity); ElementPtr->turn_wait = ElementPtr->next_turn = 0; } static void doggy_collision (ELEMENT *ElementPtr0, POINT *pPt0, ELEMENT *ElementPtr1, POINT *pPt1) { collision (ElementPtr0, pPt0, ElementPtr1, pPt1); if ((ElementPtr1->state_flags & PLAYER_SHIP) && !elementsOfSamePlayer (ElementPtr0, ElementPtr1)) { STARSHIP *StarShipPtr; GetElementStarShip (ElementPtr0, &StarShipPtr); ProcessSound (SetAbsSoundIndex ( /* DOGGY_STEALS_ENERGY */ StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 2), ElementPtr0); GetElementStarShip (ElementPtr1, &StarShipPtr); if (StarShipPtr->RaceDescPtr->ship_info.energy_level < ENERGY_DRAIN) DeltaEnergy (ElementPtr1, -StarShipPtr->RaceDescPtr->ship_info.energy_level); else DeltaEnergy (ElementPtr1, -ENERGY_DRAIN); } if (ElementPtr0->thrust_wait <= COLLISION_THRUST_WAIT) ElementPtr0->thrust_wait += COLLISION_THRUST_WAIT << 1; } static void spawn_doggy (ELEMENT *ElementPtr) { HELEMENT hDoggyElement; if ((hDoggyElement = AllocElement ()) != 0) { COUNT angle; ELEMENT *DoggyElementPtr; STARSHIP *StarShipPtr; ElementPtr->state_flags |= DEFY_PHYSICS; PutElement (hDoggyElement); LockElement (hDoggyElement, &DoggyElementPtr); DoggyElementPtr->hit_points = DOGGY_HITS; DoggyElementPtr->mass_points = DOGGY_MASS; DoggyElementPtr->thrust_wait = 0; DoggyElementPtr->playerNr = ElementPtr->playerNr; DoggyElementPtr->state_flags = APPEARING; DoggyElementPtr->life_span = NORMAL_LIFE; SetPrimType (&(GLOBAL (DisplayArray))[DoggyElementPtr->PrimIndex], STAMP_PRIM); { DoggyElementPtr->preprocess_func = doggy_preprocess; DoggyElementPtr->postprocess_func = NULL; DoggyElementPtr->collision_func = doggy_collision; DoggyElementPtr->death_func = doggy_death; } GetElementStarShip (ElementPtr, &StarShipPtr); angle = FACING_TO_ANGLE (StarShipPtr->ShipFacing) + HALF_CIRCLE; DoggyElementPtr->current.location.x = ElementPtr->next.location.x + COSINE (angle, DISPLAY_TO_WORLD (CHENJESU_OFFSET + DOGGY_OFFSET)); DoggyElementPtr->current.location.y = ElementPtr->next.location.y + SINE (angle, DISPLAY_TO_WORLD (CHENJESU_OFFSET + DOGGY_OFFSET)); DoggyElementPtr->current.image.farray = StarShipPtr->RaceDescPtr->ship_data.special; DoggyElementPtr->current.image.frame = StarShipPtr->RaceDescPtr->ship_data.special[0]; SetVelocityVector (&DoggyElementPtr->velocity, DOGGY_SPEED, NORMALIZE_FACING (ANGLE_TO_FACING (angle))); SetElementStarShip (DoggyElementPtr, StarShipPtr); ProcessSound (SetAbsSoundIndex ( /* RELEASE_DOGGY */ StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 4), DoggyElementPtr); UnlockElement (hDoggyElement); } } static void chenjesu_intelligence (ELEMENT *ShipPtr, EVALUATE_DESC *ObjectsOfConcern, COUNT ConcernCounter) { EVALUATE_DESC *lpEvalDesc; STARSHIP *StarShipPtr; GetElementStarShip (ShipPtr, &StarShipPtr); StarShipPtr->ship_input_state &= ~SPECIAL; lpEvalDesc = &ObjectsOfConcern[ENEMY_SHIP_INDEX]; if (lpEvalDesc->ObjectPtr) { STARSHIP *EnemyStarShipPtr; GetElementStarShip (lpEvalDesc->ObjectPtr, &EnemyStarShipPtr); if ((lpEvalDesc->which_turn <= 16 && MANEUVERABILITY ( &EnemyStarShipPtr->RaceDescPtr->cyborg_control ) >= MEDIUM_SHIP) || (MANEUVERABILITY ( &EnemyStarShipPtr->RaceDescPtr->cyborg_control ) <= SLOW_SHIP && WEAPON_RANGE ( &EnemyStarShipPtr->RaceDescPtr->cyborg_control ) >= LONG_RANGE_WEAPON * 3 / 4 && (EnemyStarShipPtr->RaceDescPtr->ship_info.ship_flags & SEEKING_WEAPON))) lpEvalDesc->MoveState = PURSUE; } if (StarShipPtr->special_counter == 1 && ObjectsOfConcern[ENEMY_WEAPON_INDEX].ObjectPtr && ObjectsOfConcern[ENEMY_WEAPON_INDEX].MoveState == ENTICE && ObjectsOfConcern[ENEMY_WEAPON_INDEX].which_turn <= 8) { lpEvalDesc = &ObjectsOfConcern[ENEMY_WEAPON_INDEX]; } ship_intelligence (ShipPtr, ObjectsOfConcern, ConcernCounter); if (lpEvalDesc->ObjectPtr) { HELEMENT h, hNext; ELEMENT *CrystalPtr; h = (StarShipPtr->old_status_flags & WEAPON) ? GetTailElement () : (HELEMENT)0; for (; h; h = hNext) { LockElement (h, &CrystalPtr); hNext = GetPredElement (CrystalPtr); if (!(CrystalPtr->state_flags & NONSOLID) && CrystalPtr->next.image.farray == StarShipPtr->RaceDescPtr->ship_data.weapon && CrystalPtr->preprocess_func && CrystalPtr->life_span > 0 && elementsOfSamePlayer (CrystalPtr, ShipPtr)) { if (ObjectsOfConcern[ENEMY_SHIP_INDEX].ObjectPtr) { COUNT which_turn; if ((which_turn = PlotIntercept (CrystalPtr, ObjectsOfConcern[ENEMY_SHIP_INDEX].ObjectPtr, CrystalPtr->life_span, FRAGMENT_RANGE / 2)) == 0 || (which_turn == 1 && PlotIntercept (CrystalPtr, ObjectsOfConcern[ENEMY_SHIP_INDEX].ObjectPtr, CrystalPtr->life_span, 0) == 0)) StarShipPtr->ship_input_state &= ~WEAPON; else if (StarShipPtr->weapon_counter == 0) { StarShipPtr->ship_input_state |= WEAPON; lpEvalDesc = &ObjectsOfConcern[ENEMY_SHIP_INDEX]; } UnlockElement (h); break; } hNext = 0; } UnlockElement (h); } if (h == 0) { if (StarShipPtr->old_status_flags & WEAPON) { StarShipPtr->ship_input_state &= ~WEAPON; if (lpEvalDesc == &ObjectsOfConcern[ENEMY_WEAPON_INDEX]) StarShipPtr->weapon_counter = 3; } else if (StarShipPtr->weapon_counter == 0 && ship_weapons (ShipPtr, lpEvalDesc->ObjectPtr, FRAGMENT_RANGE / 2)) StarShipPtr->ship_input_state |= WEAPON; } } if (StarShipPtr->special_counter < MAX_DOGGIES) { if (lpEvalDesc->ObjectPtr && StarShipPtr->RaceDescPtr->ship_info.energy_level <= SPECIAL_ENERGY_COST && !(StarShipPtr->ship_input_state & WEAPON)) StarShipPtr->ship_input_state |= SPECIAL; } } static COUNT initialize_crystal (ELEMENT *ShipPtr, HELEMENT CrystalArray[]) { STARSHIP *StarShipPtr; MISSILE_BLOCK MissileBlock; GetElementStarShip (ShipPtr, &StarShipPtr); MissileBlock.cx = ShipPtr->next.location.x; MissileBlock.cy = ShipPtr->next.location.y; MissileBlock.farray = StarShipPtr->RaceDescPtr->ship_data.weapon; MissileBlock.face = StarShipPtr->ShipFacing; MissileBlock.index = 0; MissileBlock.sender = ShipPtr->playerNr; MissileBlock.flags = IGNORE_SIMILAR; MissileBlock.pixoffs = CHENJESU_OFFSET; MissileBlock.speed = MISSILE_SPEED; MissileBlock.hit_points = MISSILE_HITS; MissileBlock.damage = MISSILE_DAMAGE; MissileBlock.life = MISSILE_LIFE; MissileBlock.preprocess_func = crystal_preprocess; MissileBlock.blast_offs = MISSILE_OFFSET; CrystalArray[0] = initialize_missile (&MissileBlock); if (CrystalArray[0]) { ELEMENT *CrystalPtr; LockElement (CrystalArray[0], &CrystalPtr); CrystalPtr->collision_func = crystal_collision; UnlockElement (CrystalArray[0]); } return (1); } static void chenjesu_postprocess (ELEMENT *ElementPtr) { STARSHIP *StarShipPtr; GetElementStarShip (ElementPtr, &StarShipPtr); if ((StarShipPtr->cur_status_flags & SPECIAL) && StarShipPtr->special_counter < MAX_DOGGIES && DeltaEnergy (ElementPtr, -SPECIAL_ENERGY_COST)) { spawn_doggy (ElementPtr); } StarShipPtr->special_counter = 1; /* say there is one doggy, because ship_postprocess will * decrement special_counter */ } static void chenjesu_preprocess (ELEMENT *ElementPtr) { STARSHIP *StarShipPtr; GetElementStarShip (ElementPtr, &StarShipPtr); if (StarShipPtr->special_counter > 1) /* only when STANDARD * computer opponent */ StarShipPtr->special_counter += MAX_DOGGIES; if (StarShipPtr->cur_status_flags & StarShipPtr->old_status_flags & WEAPON) ++StarShipPtr->weapon_counter; } RACE_DESC* init_chenjesu (void) { RACE_DESC *RaceDescPtr; chenjesu_desc.preprocess_func = chenjesu_preprocess; chenjesu_desc.postprocess_func = chenjesu_postprocess; chenjesu_desc.init_weapon_func = initialize_crystal; chenjesu_desc.cyborg_control.intelligence_func = chenjesu_intelligence; RaceDescPtr = &chenjesu_desc; return (RaceDescPtr); }