//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 "melnorme.h" #include "resinst.h" #include "uqm/globdata.h" #include "libs/mathlib.h" // Core characteristics #define MAX_CREW 20 #define MAX_ENERGY MAX_ENERGY_SIZE #define ENERGY_REGENERATION 1 #define ENERGY_WAIT 4 #define MAX_THRUST 36 #define THRUST_INCREMENT 6 #define THRUST_WAIT 4 #define TURN_WAIT 4 #define SHIP_MASS 7 // Blaster Pulse #define WEAPON_ENERGY_COST 5 #define WEAPON_WAIT 1 #define MELNORME_OFFSET 24 #define LEVEL_COUNTER 72 #define MAX_PUMP 4 #define PUMPUP_SPEED DISPLAY_TO_WORLD (45) #define PUMPUP_LIFE 10 #define PUMPUP_DAMAGE 2 #define MIN_PUMPITUDE_ANIMS 3 #define NUM_PUMP_ANIMS 5 #define REVERSE_DIR (BYTE)(1 << 7) // Confusion Pulse #define SPECIAL_ENERGY_COST 20 #define SPECIAL_WAIT 20 #define CMISSILE_SPEED DISPLAY_TO_WORLD (30) #define CMISSILE_LIFE 20 #define CMISSILE_HITS 200 #define CMISSILE_DAMAGE 0 #define CMISSILE_OFFSET 4 static RACE_DESC melnorme_desc = { { /* SHIP_INFO */ FIRES_FORE, 18, /* Super Melee cost */ MAX_CREW, MAX_CREW, MAX_ENERGY, MAX_ENERGY, MELNORME_RACE_STRINGS, MELNORME_ICON_MASK_PMAP_ANIM, MELNORME_MICON_MASK_PMAP_ANIM, NULL, NULL, NULL }, { /* FLEET_STUFF */ INFINITE_RADIUS, /* Initial sphere of influence radius */ { /* Known location (center of SoI) */ MAX_X_UNIVERSE >> 1, MAX_Y_UNIVERSE >> 1, }, }, { MAX_THRUST, THRUST_INCREMENT, ENERGY_REGENERATION, WEAPON_ENERGY_COST, SPECIAL_ENERGY_COST, ENERGY_WAIT, TURN_WAIT, THRUST_WAIT, WEAPON_WAIT, SPECIAL_WAIT, SHIP_MASS, }, { { MELNORME_BIG_MASK_PMAP_ANIM, MELNORME_MED_MASK_PMAP_ANIM, MELNORME_SML_MASK_PMAP_ANIM, }, { PUMPUP_BIG_MASK_PMAP_ANIM, PUMPUP_MED_MASK_PMAP_ANIM, PUMPUP_SML_MASK_PMAP_ANIM, }, { CONFUSE_BIG_MASK_PMAP_ANIM, CONFUSE_MED_MASK_PMAP_ANIM, CONFUSE_SML_MASK_PMAP_ANIM, }, { MELNORME_CAPTAIN_MASK_PMAP_ANIM, NULL, NULL, NULL, NULL, NULL }, MELNORME_VICTORY_SONG, MELNORME_SHIP_SOUNDS, { NULL, NULL, NULL }, { NULL, NULL, NULL }, { NULL, NULL, NULL }, NULL, NULL }, { 0, PUMPUP_SPEED * PUMPUP_LIFE, NULL, }, (UNINIT_FUNC *) NULL, (PREPROCESS_FUNC *) NULL, (POSTPROCESS_FUNC *) NULL, (INIT_WEAPON_FUNC *) NULL, 0, 0, /* CodeRef */ }; static void pump_up_preprocess (ELEMENT *ElementPtr) { if (--ElementPtr->thrust_wait & 1) { COUNT frame_index; frame_index = GetFrameIndex (ElementPtr->current.image.frame); if (((ElementPtr->turn_wait & REVERSE_DIR) && (frame_index % NUM_PUMP_ANIMS) != 0) || (!(ElementPtr->turn_wait & REVERSE_DIR) && ((frame_index + 1) % NUM_PUMP_ANIMS) == 0)) { --frame_index; ElementPtr->turn_wait |= REVERSE_DIR; } else { ++frame_index; ElementPtr->turn_wait &= ~REVERSE_DIR; } ElementPtr->next.image.frame = SetAbsFrameIndex ( ElementPtr->current.image.frame, frame_index); ElementPtr->state_flags |= CHANGING; } } static COUNT initialize_pump_up (ELEMENT *ShipPtr, HELEMENT PumpUpArray[]); static void pump_up_postprocess (ELEMENT *ElementPtr) { if (ElementPtr->state_flags & APPEARING) { ZeroVelocityComponents (&ElementPtr->velocity); } else { HELEMENT hPumpUp; ELEMENT *EPtr; ELEMENT *ShipPtr; STARSHIP *StarShipPtr; GetElementStarShip (ElementPtr, &StarShipPtr); LockElement (StarShipPtr->hShip, &ShipPtr); initialize_pump_up (ShipPtr, &hPumpUp); DeltaEnergy (ShipPtr, 0); UnlockElement (StarShipPtr->hShip); LockElement (hPumpUp, &EPtr); EPtr->current.image.frame = ElementPtr->current.image.frame; EPtr->turn_wait = ElementPtr->turn_wait; EPtr->thrust_wait = ElementPtr->thrust_wait; if (--EPtr->thrust_wait == 0) { if ((EPtr->turn_wait & ~REVERSE_DIR) < MAX_PUMP - 1) { ++EPtr->turn_wait; EPtr->current.image.frame = SetRelFrameIndex ( EPtr->current.image.frame, NUM_PUMP_ANIMS); ProcessSound (SetAbsSoundIndex ( StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 2), EPtr); } EPtr->thrust_wait = LEVEL_COUNTER; } EPtr->mass_points = EPtr->hit_points = (PUMPUP_DAMAGE << (ElementPtr->turn_wait & ~REVERSE_DIR)); SetElementStarShip (EPtr, StarShipPtr); if (EPtr->thrust_wait & 1) { COUNT frame_index; frame_index = GetFrameIndex (EPtr->current.image.frame); if (((EPtr->turn_wait & REVERSE_DIR) && (frame_index % NUM_PUMP_ANIMS) != 0) || (!(EPtr->turn_wait & REVERSE_DIR) && ((frame_index + 1) % NUM_PUMP_ANIMS) == 0)) { --frame_index; EPtr->turn_wait |= REVERSE_DIR; } else { ++frame_index; EPtr->turn_wait &= ~REVERSE_DIR; } EPtr->current.image.frame = SetAbsFrameIndex ( EPtr->current.image.frame, frame_index); } if (StarShipPtr->cur_status_flags & StarShipPtr->old_status_flags & WEAPON) { StarShipPtr->weapon_counter = WEAPON_WAIT; } else { COUNT angle; EPtr->life_span = PUMPUP_LIFE; EPtr->preprocess_func = pump_up_preprocess; EPtr->postprocess_func = 0; angle = FACING_TO_ANGLE (StarShipPtr->ShipFacing); SetVelocityComponents (&EPtr->velocity, COSINE (angle, WORLD_TO_VELOCITY (PUMPUP_SPEED)), SINE (angle, WORLD_TO_VELOCITY (PUMPUP_SPEED))); ProcessSound (SetAbsSoundIndex ( StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 3), EPtr); } UnlockElement (hPumpUp); PutElement (hPumpUp); SetPrimType (&(GLOBAL (DisplayArray))[ElementPtr->PrimIndex], NO_PRIM); ElementPtr->state_flags |= NONSOLID; } } 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 pump_up_collision (ELEMENT *ElementPtr0, POINT *pPt0, ELEMENT *ElementPtr1, POINT *pPt1) { RECT r; BYTE old_thrust_wait; HELEMENT hBlastElement; GetFrameRect (ElementPtr0->next.image.frame, &r); old_thrust_wait = ElementPtr0->thrust_wait; ElementPtr0->blast_offset = r.extent.width >> 1; hBlastElement = weapon_collision (ElementPtr0, pPt0, ElementPtr1, pPt1); ElementPtr0->thrust_wait = old_thrust_wait; if (hBlastElement) { ELEMENT *BlastElementPtr; LockElement (hBlastElement, &BlastElementPtr); BlastElementPtr->life_span = MIN_PUMPITUDE_ANIMS + (ElementPtr0->turn_wait & ~REVERSE_DIR); 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], MAX_PUMP * NUM_PUMP_ANIMS); UnlockElement (hBlastElement); } } static COUNT initialize_pump_up (ELEMENT *ShipPtr, HELEMENT PumpUpArray[]) { 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 = MELNORME_OFFSET; MissileBlock.speed = DISPLAY_TO_WORLD (MELNORME_OFFSET); MissileBlock.hit_points = PUMPUP_DAMAGE; MissileBlock.damage = PUMPUP_DAMAGE; MissileBlock.life = 2; MissileBlock.preprocess_func = 0; MissileBlock.blast_offs = 0; PumpUpArray[0] = initialize_missile (&MissileBlock); if (PumpUpArray[0]) { ELEMENT *PumpUpPtr; LockElement (PumpUpArray[0], &PumpUpPtr); PumpUpPtr->postprocess_func = pump_up_postprocess; PumpUpPtr->collision_func = pump_up_collision; PumpUpPtr->thrust_wait = LEVEL_COUNTER; UnlockElement (PumpUpArray[0]); } return (1); } static void confuse_preprocess (ELEMENT *ElementPtr) { if (!(ElementPtr->state_flags & NONSOLID)) { ElementPtr->next.image.frame = SetAbsFrameIndex ( ElementPtr->current.image.frame, (GetFrameIndex (ElementPtr->current.image.frame) + 1) & 7); ElementPtr->state_flags |= CHANGING; } else if (ElementPtr->hTarget == 0) { ElementPtr->life_span = 0; ElementPtr->state_flags |= DISAPPEARING; } else { ELEMENT *eptr; LockElement (ElementPtr->hTarget, &eptr); ElementPtr->next.location = eptr->next.location; if (ElementPtr->turn_wait) { HELEMENT hEffect; STARSHIP *StarShipPtr; if (GetFrameIndex (ElementPtr->next.image.frame = IncFrameIndex (ElementPtr->current.image.frame)) == 0) ElementPtr->next.image.frame = SetRelFrameIndex (ElementPtr->next.image.frame, -8); GetElementStarShip (eptr, &StarShipPtr); StarShipPtr->ship_input_state = (StarShipPtr->ship_input_state & ~(LEFT | RIGHT | SPECIAL)) | ElementPtr->turn_wait; hEffect = AllocElement (); if (hEffect) { LockElement (hEffect, &eptr); eptr->playerNr = ElementPtr->playerNr; eptr->state_flags = FINITE_LIFE | NONSOLID | CHANGING; eptr->life_span = 1; eptr->current = eptr->next = ElementPtr->next; eptr->preprocess_func = confuse_preprocess; SetPrimType (&(GLOBAL (DisplayArray))[eptr->PrimIndex], STAMP_PRIM); GetElementStarShip (ElementPtr, &StarShipPtr); SetElementStarShip (eptr, StarShipPtr); eptr->hTarget = ElementPtr->hTarget; UnlockElement (hEffect); PutElement (hEffect); } } UnlockElement (ElementPtr->hTarget); } } static void confusion_collision (ELEMENT *ElementPtr0, POINT *pPt0, ELEMENT *ElementPtr1, POINT *pPt1) { if (ElementPtr1->state_flags & PLAYER_SHIP) { HELEMENT hConfusionElement, hNextElement; ELEMENT *ConfusionPtr; STARSHIP *StarShipPtr; GetElementStarShip (ElementPtr0, &StarShipPtr); for (hConfusionElement = GetHeadElement (); hConfusionElement; hConfusionElement = hNextElement) { LockElement (hConfusionElement, &ConfusionPtr); if (elementsOfSamePlayer (ConfusionPtr, ElementPtr0) && ConfusionPtr->current.image.farray == StarShipPtr->RaceDescPtr->ship_data.special && (ConfusionPtr->state_flags & NONSOLID)) { UnlockElement (hConfusionElement); break; } hNextElement = GetSuccElement (ConfusionPtr); UnlockElement (hConfusionElement); } if (hConfusionElement || (hConfusionElement = AllocElement ())) { LockElement (hConfusionElement, &ConfusionPtr); if (ConfusionPtr->state_flags == 0) /* not allocated before */ { InsertElement (hConfusionElement, GetHeadElement ()); ConfusionPtr->current = ElementPtr0->next; ConfusionPtr->current.image.frame = SetAbsFrameIndex ( ConfusionPtr->current.image.frame, 8 ); ConfusionPtr->next = ConfusionPtr->current; ConfusionPtr->playerNr = ElementPtr0->playerNr; ConfusionPtr->state_flags = FINITE_LIFE | NONSOLID | CHANGING; ConfusionPtr->preprocess_func = confuse_preprocess; SetPrimType ( &(GLOBAL (DisplayArray))[ConfusionPtr->PrimIndex], NO_PRIM ); SetElementStarShip (ConfusionPtr, StarShipPtr); GetElementStarShip (ElementPtr1, &StarShipPtr); ConfusionPtr->hTarget = StarShipPtr->hShip; } ConfusionPtr->life_span = 400; ConfusionPtr->turn_wait = (BYTE)(1 << ((BYTE)TFB_Random () & 1)); /* LEFT or RIGHT */ UnlockElement (hConfusionElement); } ElementPtr0->hit_points = 0; ElementPtr0->life_span = 0; ElementPtr0->state_flags |= DISAPPEARING | COLLISION | NONSOLID; } (void) pPt0; /* Satisfying compiler (unused parameter) */ (void) pPt1; /* Satisfying compiler (unused parameter) */ } static COUNT initialize_confusion (ELEMENT *ShipPtr, HELEMENT ConfusionArray[]) { STARSHIP *StarShipPtr; MISSILE_BLOCK ConfusionBlock; GetElementStarShip (ShipPtr, &StarShipPtr); ConfusionBlock.cx = ShipPtr->next.location.x; ConfusionBlock.cy = ShipPtr->next.location.y; ConfusionBlock.farray = StarShipPtr->RaceDescPtr->ship_data.special; ConfusionBlock.index = 0; ConfusionBlock.face = StarShipPtr->ShipFacing; ConfusionBlock.sender = ShipPtr->playerNr; ConfusionBlock.flags = IGNORE_SIMILAR; ConfusionBlock.pixoffs = MELNORME_OFFSET; ConfusionBlock.speed = CMISSILE_SPEED; ConfusionBlock.hit_points = CMISSILE_HITS; ConfusionBlock.damage = CMISSILE_DAMAGE; ConfusionBlock.life = CMISSILE_LIFE; ConfusionBlock.preprocess_func = confuse_preprocess; ConfusionBlock.blast_offs = CMISSILE_OFFSET; ConfusionArray[0] = initialize_missile (&ConfusionBlock); if (ConfusionArray[0]) { ELEMENT *CMissilePtr; LockElement (ConfusionArray[0], &CMissilePtr); CMissilePtr->collision_func = confusion_collision; SetElementStarShip (CMissilePtr, StarShipPtr); UnlockElement (ConfusionArray[0]); } return (1); } static COUNT initialize_test_pump_up (ELEMENT *ShipPtr, HELEMENT PumpUpArray[]) { STARSHIP *StarShipPtr; MISSILE_BLOCK MissileBlock; //ELEMENT *PumpUpPtr; 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 = MELNORME_OFFSET; MissileBlock.speed = PUMPUP_SPEED; MissileBlock.hit_points = PUMPUP_DAMAGE; MissileBlock.damage = PUMPUP_DAMAGE; MissileBlock.life = PUMPUP_LIFE; MissileBlock.preprocess_func = 0; MissileBlock.blast_offs = 0; PumpUpArray[0] = initialize_missile (&MissileBlock); return (1); } static void melnorme_intelligence (ELEMENT *ShipPtr, EVALUATE_DESC *ObjectsOfConcern, COUNT ConcernCounter) { BYTE old_count; STARSHIP *StarShipPtr; EVALUATE_DESC *lpEvalDesc; GetElementStarShip (ShipPtr, &StarShipPtr); StarShipPtr->RaceDescPtr->init_weapon_func = initialize_test_pump_up; old_count = StarShipPtr->weapon_counter; if (StarShipPtr->weapon_counter == WEAPON_WAIT) StarShipPtr->weapon_counter = 0; lpEvalDesc = &ObjectsOfConcern[ENEMY_SHIP_INDEX]; if (lpEvalDesc->ObjectPtr) { if (StarShipPtr->RaceDescPtr->ship_info.energy_level < SPECIAL_ENERGY_COST + WEAPON_ENERGY_COST && !(StarShipPtr->old_status_flags & WEAPON)) lpEvalDesc->MoveState = ENTICE; else { STARSHIP *EnemyStarShipPtr; GetElementStarShip (lpEvalDesc->ObjectPtr, &EnemyStarShipPtr); if (!(EnemyStarShipPtr->RaceDescPtr->ship_info.ship_flags & IMMEDIATE_WEAPON)) lpEvalDesc->MoveState = PURSUE; } } ship_intelligence (ShipPtr, ObjectsOfConcern, ConcernCounter); if (StarShipPtr->weapon_counter == 0 && (old_count != 0 || ((StarShipPtr->special_counter || StarShipPtr->RaceDescPtr->ship_info.energy_level >= SPECIAL_ENERGY_COST + WEAPON_ENERGY_COST) && !(StarShipPtr->ship_input_state & WEAPON)))) StarShipPtr->ship_input_state ^= WEAPON; StarShipPtr->ship_input_state &= ~SPECIAL; if (StarShipPtr->special_counter == 0 && StarShipPtr->RaceDescPtr->ship_info.energy_level >= SPECIAL_ENERGY_COST) { BYTE old_input_state; old_input_state = StarShipPtr->ship_input_state; StarShipPtr->RaceDescPtr->init_weapon_func = initialize_confusion; ++ShipPtr->turn_wait; ++ShipPtr->thrust_wait; ship_intelligence (ShipPtr, ObjectsOfConcern, ENEMY_SHIP_INDEX + 1); --ShipPtr->thrust_wait; --ShipPtr->turn_wait; if (StarShipPtr->ship_input_state & WEAPON) { StarShipPtr->ship_input_state &= ~WEAPON; StarShipPtr->ship_input_state |= SPECIAL; } StarShipPtr->ship_input_state = (unsigned char)(old_input_state | (StarShipPtr->ship_input_state & SPECIAL)); } StarShipPtr->weapon_counter = old_count; StarShipPtr->RaceDescPtr->init_weapon_func = initialize_pump_up; } static void melnorme_postprocess (ELEMENT *ElementPtr) { STARSHIP *StarShipPtr; GetElementStarShip (ElementPtr, &StarShipPtr); if ((StarShipPtr->cur_status_flags & SPECIAL) && StarShipPtr->special_counter == 0 && DeltaEnergy (ElementPtr, -SPECIAL_ENERGY_COST)) { HELEMENT Confusion; initialize_confusion (ElementPtr, &Confusion); if (Confusion) { ELEMENT *CMissilePtr; LockElement (Confusion, &CMissilePtr); ProcessSound (SetAbsSoundIndex ( StarShipPtr->RaceDescPtr->ship_data.ship_sounds, 1), CMissilePtr); UnlockElement (Confusion); PutElement (Confusion); StarShipPtr->special_counter = StarShipPtr->RaceDescPtr->characteristics.special_wait; } } } RACE_DESC* init_melnorme (void) { RACE_DESC *RaceDescPtr; melnorme_desc.postprocess_func = melnorme_postprocess; melnorme_desc.init_weapon_func = initialize_pump_up; melnorme_desc.cyborg_control.intelligence_func = melnorme_intelligence; RaceDescPtr = &melnorme_desc; return (RaceDescPtr); }