summaryrefslogtreecommitdiff
path: root/src/uqm/ships/shofixti/shofixti.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/uqm/ships/shofixti/shofixti.c')
-rw-r--r--src/uqm/ships/shofixti/shofixti.c521
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);
+}
+