diff options
Diffstat (limited to 'src/uqm/ships/shofixti/shofixti.c')
-rw-r--r-- | src/uqm/ships/shofixti/shofixti.c | 521 |
1 files changed, 521 insertions, 0 deletions
diff --git a/src/uqm/ships/shofixti/shofixti.c b/src/uqm/ships/shofixti/shofixti.c new file mode 100644 index 0000000..03de57e --- /dev/null +++ b/src/uqm/ships/shofixti/shofixti.c @@ -0,0 +1,521 @@ +//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 "shofixti.h" +#include "resinst.h" + +#include "uqm/globdata.h" +#include "uqm/tactrans.h" +#include "libs/mathlib.h" + +// Core characteristics +#define MAX_CREW 6 +#define MAX_ENERGY 4 +#define ENERGY_REGENERATION 1 +#define ENERGY_WAIT 9 +#define MAX_THRUST 35 +#define THRUST_INCREMENT 5 +#define TURN_WAIT 1 +#define THRUST_WAIT 0 +#define WEAPON_WAIT 3 +#define SPECIAL_WAIT 0 +#define SHIP_MASS 1 + +// Dart Gun +#define WEAPON_ENERGY_COST 1 +#define SHOFIXTI_OFFSET 15 +#define MISSILE_OFFSET 1 +#define MISSILE_SPEED DISPLAY_TO_WORLD (24) +#define MISSILE_LIFE 10 +#define MISSILE_HITS 1 +#define MISSILE_DAMAGE 1 + +// Glory Device +#define SPECIAL_ENERGY_COST 0 +#define DESTRUCT_RANGE 180 +#define MAX_DESTRUCTION (DESTRUCT_RANGE / 10) + +// Full game: Tanaka/Katana's damaged ships +#define NUM_LIMPETS 3 + +static RACE_DESC shofixti_desc = +{ + { /* SHIP_INFO */ + FIRES_FORE, + 5, /* Super Melee cost */ + MAX_CREW, MAX_CREW, + MAX_ENERGY, MAX_ENERGY, + SHOFIXTI_RACE_STRINGS, + SHOFIXTI_ICON_MASK_PMAP_ANIM, + SHOFIXTI_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, + }, + { + { + SHOFIXTI_BIG_MASK_PMAP_ANIM, + SHOFIXTI_MED_MASK_PMAP_ANIM, + SHOFIXTI_SML_MASK_PMAP_ANIM, + }, + { + DART_BIG_MASK_PMAP_ANIM, + DART_MED_MASK_PMAP_ANIM, + DART_SML_MASK_PMAP_ANIM, + }, + { + DESTRUCT_BIG_MASK_ANIM, + DESTRUCT_MED_MASK_ANIM, + DESTRUCT_SML_MASK_ANIM, + }, + { + SHOFIXTI_CAPTAIN_MASK_PMAP_ANIM, + NULL, NULL, NULL, NULL, NULL + }, + SHOFIXTI_VICTORY_SONG, + SHOFIXTI_SHIP_SOUNDS, + { NULL, NULL, NULL }, + { NULL, NULL, NULL }, + { NULL, NULL, NULL }, + NULL, NULL + }, + { + 0, + MISSILE_SPEED * MISSILE_LIFE, + NULL, + }, + (UNINIT_FUNC *) NULL, + (PREPROCESS_FUNC *) NULL, + (POSTPROCESS_FUNC *) NULL, + (INIT_WEAPON_FUNC *) NULL, + 0, + 0, /* CodeRef */ +}; + +static COUNT +initialize_standard_missile (ELEMENT *ShipPtr, HELEMENT MissileArray[]) +{ + + 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 = MissileBlock.index = StarShipPtr->ShipFacing; + MissileBlock.sender = ShipPtr->playerNr; + MissileBlock.flags = IGNORE_SIMILAR; + MissileBlock.pixoffs = SHOFIXTI_OFFSET; + MissileBlock.speed = MISSILE_SPEED; + MissileBlock.hit_points = MISSILE_HITS; + MissileBlock.damage = MISSILE_DAMAGE; + MissileBlock.life = MISSILE_LIFE; + MissileBlock.preprocess_func = NULL; + MissileBlock.blast_offs = MISSILE_OFFSET; + MissileArray[0] = initialize_missile (&MissileBlock); + + return (1); +} + +static void +destruct_preprocess (ELEMENT *ElementPtr) +{ +#define DESTRUCT_SWITCH ((NUM_EXPLOSION_FRAMES * 3) - 3) + PRIMITIVE *lpPrim; + + // ship_death() set the ship element's life_span to + // (NUM_EXPLOSION_FRAMES * 3) + lpPrim = &(GLOBAL (DisplayArray))[ElementPtr->PrimIndex]; + ElementPtr->state_flags |= CHANGING; + if (ElementPtr->life_span > DESTRUCT_SWITCH) + { + // First, stamp-fill the ship's own element with changing colors + // for 3 frames. No explosion element yet. + SetPrimType (lpPrim, STAMPFILL_PRIM); + if (ElementPtr->life_span == DESTRUCT_SWITCH + 2) + SetPrimColor (lpPrim, + BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x0A), 0x0E)); + else + SetPrimColor (lpPrim, + BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x1F), 0x0F)); + } + else if (ElementPtr->life_span < DESTRUCT_SWITCH) + { + // Stamp-fill the explosion element with cycling colors for the + // remainder of the glory explosion frames. + Color color = GetPrimColor (lpPrim); + + ElementPtr->next.image.frame = + IncFrameIndex (ElementPtr->current.image.frame); + if (sameColor (color, + BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x1F), 0x0F))) + SetPrimColor (lpPrim, + BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x0A), 0x0E)); + else if (sameColor (color, + BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x0A), 0x0E))) + SetPrimColor (lpPrim, + BUILD_COLOR (MAKE_RGB15 (0x1F, 0x0A, 0x0A), 0x0C)); + else if (sameColor (color, + BUILD_COLOR (MAKE_RGB15 (0x1F, 0x0A, 0x0A), 0x0C))) + SetPrimColor (lpPrim, + BUILD_COLOR (MAKE_RGB15 (0x14, 0x0A, 0x00), 0x06)); + else if (sameColor (color, + BUILD_COLOR (MAKE_RGB15 (0x14, 0x0A, 0x00), 0x06))) + SetPrimColor (lpPrim, + BUILD_COLOR (MAKE_RGB15 (0x14, 0x00, 0x00), 0x04)); + } + else + { + HELEMENT hDestruct; + + SetPrimType (lpPrim, NO_PRIM); + // The ship's own element will not be drawn anymore but will remain + // alive all through the glory explosion. + ElementPtr->preprocess_func = NULL; + + // Spawn a separate glory explosion element. + // XXX: Why? Why not keep using the ship's element? + // Is it because of conflicting state_flags, hit_points or + // mass_points? + hDestruct = AllocElement (); + if (hDestruct) + { + ELEMENT *DestructPtr; + STARSHIP *StarShipPtr; + + GetElementStarShip (ElementPtr, &StarShipPtr); + + PutElement (hDestruct); + LockElement (hDestruct, &DestructPtr); + SetElementStarShip (DestructPtr, StarShipPtr); + DestructPtr->hit_points = 0; + DestructPtr->mass_points = 0; + DestructPtr->playerNr = NEUTRAL_PLAYER_NUM; + DestructPtr->state_flags = APPEARING | FINITE_LIFE | NONSOLID; + SetPrimType (&(GLOBAL (DisplayArray))[DestructPtr->PrimIndex], + STAMPFILL_PRIM); + SetPrimColor (&(GLOBAL (DisplayArray))[DestructPtr->PrimIndex], + BUILD_COLOR (MAKE_RGB15 (0x1F, 0x1F, 0x1F), 0x0F)); + DestructPtr->current.image.farray = + StarShipPtr->RaceDescPtr->ship_data.special; + DestructPtr->current.image.frame = + StarShipPtr->RaceDescPtr->ship_data.special[0]; + DestructPtr->life_span = GetFrameCount ( + DestructPtr->current.image.frame); + DestructPtr->current.location = ElementPtr->current.location; + DestructPtr->preprocess_func = destruct_preprocess; + DestructPtr->postprocess_func = NULL; + DestructPtr->death_func = NULL; + ZeroVelocityComponents (&DestructPtr->velocity); + UnlockElement (hDestruct); + } + } +} + +/* In order to detect any Orz Marines that have boarded the ship + when it self-destructs, we'll need to see some Orz functions */ +#include "../orz/orz.h" +#define ORZ_MARINE(ptr) (ptr->preprocess_func == intruder_preprocess && \ + ptr->collision_func == marine_collision) + +static void +self_destruct_kill_objects (ELEMENT *ElementPtr) +{ + // This is called during PostProcessQueue(), close to or at the end, + // for the temporary destruct element to apply the effects of glory + // explosion. The effects are not seen until the next frame. + HELEMENT hElement, hNextElement; + + for (hElement = GetHeadElement (); hElement != 0; hElement = hNextElement) + { + ELEMENT *ObjPtr; + SIZE delta_x, delta_y; + DWORD dist; + + LockElement (hElement, &ObjPtr); + hNextElement = GetSuccElement (ObjPtr); + + if (!CollidingElement (ObjPtr) && !ORZ_MARINE (ObjPtr)) + { + UnlockElement (hElement); + continue; + } + + delta_x = ObjPtr->next.location.x - ElementPtr->next.location.x; + if (delta_x < 0) + delta_x = -delta_x; + delta_y = ObjPtr->next.location.y - ElementPtr->next.location.y; + if (delta_y < 0) + delta_y = -delta_y; + delta_x = WORLD_TO_DISPLAY (delta_x); + delta_y = WORLD_TO_DISPLAY (delta_y); + dist = delta_x * delta_x + delta_y * delta_y; + if (delta_x <= DESTRUCT_RANGE && delta_y <= DESTRUCT_RANGE + && dist <= DESTRUCT_RANGE * DESTRUCT_RANGE) + { + int destruction = 1 + MAX_DESTRUCTION * + (DESTRUCT_RANGE - square_root (dist)) / DESTRUCT_RANGE; + + // XXX: Why not simply call do_damage()? + if (ObjPtr->state_flags & PLAYER_SHIP) + { + if (!DeltaCrew (ObjPtr, -destruction)) + ObjPtr->life_span = 0; + } + else if (!GRAVITY_MASS (ObjPtr->mass_points)) + { + if (destruction < ObjPtr->hit_points) + ObjPtr->hit_points -= destruction; + else + { + ObjPtr->hit_points = 0; + ObjPtr->life_span = 0; + } + } + } + + UnlockElement (hElement); + } +} + +// This function is called when the ship dies via Glory Device. +// The generic ship_death() function is not called for the ship in this case. +static void +shofixti_destruct_death (ELEMENT *ShipPtr) +{ + STARSHIP *StarShip; + STARSHIP *winner; + + GetElementStarShip (ShipPtr, &StarShip); + + StopAllBattleMusic (); + + StartShipExplosion (ShipPtr, false); + // We process the explosion ourselves because it is different + ShipPtr->preprocess_func = destruct_preprocess; + + PlaySound (SetAbsSoundIndex (StarShip->RaceDescPtr->ship_data.ship_sounds, + 1), CalcSoundPosition (ShipPtr), ShipPtr, GAME_SOUND_PRIORITY + 1); + + winner = GetWinnerStarShip (); + if (winner == NULL) + { // No winner determined yet + winner = FindAliveStarShip (ShipPtr); + if (winner == NULL) + { // No ships left alive after the Glory Device thus Shofixti wins + winner = StarShip; + } + SetWinnerStarShip (winner); + } + else if (winner == StarShip) + { // This ship is the winner + // It may have self-destructed before the ditty started playing, + // and in that case, there should be no ditty + StarShip->cur_status_flags &= ~PLAY_VICTORY_DITTY; + } + RecordShipDeath (ShipPtr); +} + +static void +self_destruct (ELEMENT *ElementPtr) +{ + STARSHIP *StarShipPtr; + HELEMENT hDestruct; + + GetElementStarShip (ElementPtr, &StarShipPtr); + + // Spawn a temporary element, which dies in this same frame, in order + // to defer the effects of the glory explosion. + // It will be the last element (or one of the last) for which the + // death_func() will be called from PostProcessQueue() in this frame. + // XXX: Why at the end? Why not just do it now? + hDestruct = AllocElement (); + if (hDestruct) + { + ELEMENT *DestructPtr; + + LockElement (hDestruct, &DestructPtr); + DestructPtr->playerNr = ElementPtr->playerNr; + DestructPtr->state_flags = APPEARING | NONSOLID | FINITE_LIFE; + DestructPtr->next.location = ElementPtr->next.location; + DestructPtr->life_span = 0; + DestructPtr->pParent = ElementPtr->pParent; + DestructPtr->hTarget = 0; + + DestructPtr->death_func = self_destruct_kill_objects; + + UnlockElement (hDestruct); + + PutElement (hDestruct); + } + + // Must kill off the remaining crew ourselves + DeltaCrew (ElementPtr, -(int)ElementPtr->crew_level); + + ElementPtr->state_flags |= NONSOLID; + ElementPtr->life_span = 0; + // The ship is now dead. It's death_func, i.e. shofixti_destruct_death(), + // will be called the next frame. + ElementPtr->death_func = shofixti_destruct_death; +} + +static void +shofixti_intelligence (ELEMENT *ShipPtr, EVALUATE_DESC *ObjectsOfConcern, + COUNT ConcernCounter) +{ + STARSHIP *StarShipPtr; + + ship_intelligence (ShipPtr, ObjectsOfConcern, ConcernCounter); + + GetElementStarShip (ShipPtr, &StarShipPtr); + if (StarShipPtr->special_counter != 0) + return; + + if (StarShipPtr->ship_input_state & SPECIAL) + StarShipPtr->ship_input_state &= ~SPECIAL; + else + { + EVALUATE_DESC *lpWeaponEvalDesc; + EVALUATE_DESC *lpShipEvalDesc; + + lpWeaponEvalDesc = &ObjectsOfConcern[ENEMY_WEAPON_INDEX]; + lpShipEvalDesc = &ObjectsOfConcern[ENEMY_SHIP_INDEX]; + if (StarShipPtr->RaceDescPtr->ship_data.special[0] + && (GetFrameCount (StarShipPtr->RaceDescPtr->ship_data. + captain_control.special) + - GetFrameIndex (StarShipPtr->RaceDescPtr->ship_data. + captain_control.special) > 5 + || (lpShipEvalDesc->ObjectPtr != NULL + && lpShipEvalDesc->which_turn <= 4) + || (lpWeaponEvalDesc->ObjectPtr != NULL + /* means IMMEDIATE WEAPON */ + && (((lpWeaponEvalDesc->ObjectPtr->state_flags & PLAYER_SHIP) + && ShipPtr->crew_level == 1) + || (PlotIntercept (lpWeaponEvalDesc->ObjectPtr, ShipPtr, 2, 0) + && lpWeaponEvalDesc->ObjectPtr->mass_points >= + ShipPtr->crew_level + && (TFB_Random () & 1)))))) + StarShipPtr->ship_input_state |= SPECIAL; + } +} + +static void +shofixti_postprocess (ELEMENT *ElementPtr) +{ + STARSHIP *StarShipPtr; + + GetElementStarShip (ElementPtr, &StarShipPtr); + if ((StarShipPtr->cur_status_flags + ^ StarShipPtr->old_status_flags) & SPECIAL) + { + StarShipPtr->RaceDescPtr->ship_data.captain_control.special = + IncFrameIndex (StarShipPtr->RaceDescPtr->ship_data. + captain_control.special); + if (GetFrameCount (StarShipPtr->RaceDescPtr->ship_data. + captain_control.special) + - GetFrameIndex (StarShipPtr->RaceDescPtr->ship_data. + captain_control.special) == 3) + self_destruct (ElementPtr); + } +} + +RACE_DESC* +init_shofixti (void) +{ + RACE_DESC *RaceDescPtr; + // The caller of this func will copy the struct + static RACE_DESC new_shofixti_desc; + + shofixti_desc.postprocess_func = shofixti_postprocess; + shofixti_desc.init_weapon_func = initialize_standard_missile; + shofixti_desc.cyborg_control.intelligence_func = shofixti_intelligence; + + new_shofixti_desc = shofixti_desc; + if (LOBYTE (GLOBAL (CurrentActivity)) == IN_ENCOUNTER + && !GET_GAME_STATE (SHOFIXTI_RECRUITED)) + { + // Tanaka/Katana flies in a damaged ship. + COUNT i; + + new_shofixti_desc.ship_data.ship_rsc[0] = OLDSHOF_BIG_MASK_PMAP_ANIM; + new_shofixti_desc.ship_data.ship_rsc[1] = OLDSHOF_MED_MASK_PMAP_ANIM; + new_shofixti_desc.ship_data.ship_rsc[2] = OLDSHOF_SML_MASK_PMAP_ANIM; + new_shofixti_desc.ship_data.special_rsc[0] = NULL_RESOURCE; + new_shofixti_desc.ship_data.special_rsc[1] = NULL_RESOURCE; + new_shofixti_desc.ship_data.special_rsc[2] = NULL_RESOURCE; + new_shofixti_desc.ship_data.captain_control.captain_rsc = + OLDSHOF_CAPTAIN_MASK_PMAP_ANIM; + + /* Weapon doesn't work as well */ + new_shofixti_desc.characteristics.weapon_wait = 10; + + /* Simulate VUX limpets */ + for (i = 0; i < NUM_LIMPETS; ++i) + { + if (++new_shofixti_desc.characteristics.turn_wait == 0) + --new_shofixti_desc.characteristics.turn_wait; + if (++new_shofixti_desc.characteristics.thrust_wait == 0) + --new_shofixti_desc.characteristics.thrust_wait; + +/* This should be the same as MIN_THRUST_INCREMENT in vux.c */ +#define MIN_THRUST_INCREMENT DISPLAY_TO_WORLD (1) + + if (new_shofixti_desc.characteristics.thrust_increment <= + MIN_THRUST_INCREMENT) + { + new_shofixti_desc.characteristics.max_thrust = + new_shofixti_desc.characteristics.thrust_increment << 1; + } + else + { + COUNT num_thrusts; + + num_thrusts = new_shofixti_desc.characteristics.max_thrust / + new_shofixti_desc.characteristics.thrust_increment; + --new_shofixti_desc.characteristics.thrust_increment; + new_shofixti_desc.characteristics.max_thrust = + new_shofixti_desc.characteristics.thrust_increment * + num_thrusts; + } + } + } + + RaceDescPtr = &new_shofixti_desc; + + return (RaceDescPtr); +} + |