summaryrefslogtreecommitdiff
path: root/src/uqm/tactrans.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/uqm/tactrans.c')
-rw-r--r--src/uqm/tactrans.c1032
1 files changed, 1032 insertions, 0 deletions
diff --git a/src/uqm/tactrans.c b/src/uqm/tactrans.c
new file mode 100644
index 0000000..4e2b896
--- /dev/null
+++ b/src/uqm/tactrans.c
@@ -0,0 +1,1032 @@
+//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 "tactrans.h"
+
+#include "battlecontrols.h"
+#include "build.h"
+#include "collide.h"
+#include "globdata.h"
+#include "element.h"
+#include "ship.h"
+#include "status.h"
+#include "battle.h"
+#include "init.h"
+#include "supermelee/pickmele.h"
+#ifdef NETPLAY
+# include "supermelee/netplay/netmelee.h"
+# include "supermelee/netplay/netmisc.h"
+# include "supermelee/netplay/notify.h"
+# include "supermelee/netplay/proto/ready.h"
+# include "supermelee/netplay/packet.h"
+# include "supermelee/netplay/packetq.h"
+#endif
+#include "races.h"
+#include "encount.h"
+#include "settings.h"
+#include "sounds.h"
+#include "libs/mathlib.h"
+
+
+static void cleanup_dead_ship (ELEMENT *ElementPtr);
+
+static BOOLEAN dittyIsPlaying;
+static STARSHIP *winnerStarShip;
+ // Indicates which ship is the winner of the current battle.
+ // The winner will be last to pick the next ship.
+
+
+BOOLEAN
+OpponentAlive (STARSHIP *TestStarShipPtr)
+{
+ HELEMENT hElement, hSuccElement;
+
+ for (hElement = GetHeadElement (); hElement; hElement = hSuccElement)
+ {
+ ELEMENT *ElementPtr;
+ STARSHIP *StarShipPtr;
+
+ LockElement (hElement, &ElementPtr);
+ hSuccElement = GetSuccElement (ElementPtr);
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ UnlockElement (hElement);
+
+ if (StarShipPtr && StarShipPtr != TestStarShipPtr
+ && StarShipPtr->RaceDescPtr->ship_info.crew_level == 0)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+PlayDitty (STARSHIP *ship)
+{
+ PlayMusic (ship->RaceDescPtr->ship_data.victory_ditty, FALSE, 3);
+ dittyIsPlaying = TRUE;
+}
+
+void
+StopDitty (void)
+{
+ if (dittyIsPlaying)
+ StopMusic ();
+ dittyIsPlaying = FALSE;
+}
+
+static BOOLEAN
+DittyPlaying (void)
+{
+ if (!dittyIsPlaying)
+ return FALSE;
+
+ dittyIsPlaying = PLRPlaying ((MUSIC_REF)~0);
+ return dittyIsPlaying;
+}
+
+void
+ResetWinnerStarShip (void)
+{
+ winnerStarShip = NULL;
+}
+
+#ifdef NETPLAY
+static void
+readyToEnd2Callback (NetConnection *conn, void *arg)
+{
+ NetConnection_setState (conn, NetState_endingBattle2);
+ (void) arg;
+}
+
+static void
+readyToEndCallback (NetConnection *conn, void *arg)
+{
+ // This callback function gets called from inside the function that
+ // updates the frame counter, but this is not a problem as the
+ // ending frame count will at least be 1 greater than the current
+ // frame count.
+
+ BattleStateData *battleStateData;
+ battleStateData = (BattleStateData *) NetConnection_getStateData(conn);
+
+#ifdef NETPLAY_DEBUG
+ fprintf (stderr, "Both sides are ready to end the battle; starting "
+ "end-of-battle synchronisation.\n");
+#endif
+ NetConnection_setState (conn, NetState_endingBattle);
+ if (battleFrameCount + 1 > battleStateData->endFrameCount)
+ battleStateData->endFrameCount = battleFrameCount + 1;
+ Netplay_Notify_frameCount (conn, battleFrameCount + 1);
+ // The +1 is to ensure that after the remote side receives the
+ // frame count it will still receive one more frame data packet,
+ // so it will know in advance when the last frame data packet
+ // will come so it won't block. It also ensures that the
+ // local frame counter won't go past the sent number, which
+ // could happen when the function triggering the call to this
+ // function is the frame update function which might update
+ // the frame counter one more time.
+ flushPacketQueue (conn);
+#ifdef NETPLAY_DEBUG
+ fprintf (stderr, "NETPLAY: [%d] ==> Sent battleFrameCount %d.\n",
+ NetConnection_getPlayerNr(conn), battleFrameCount + 1);
+#endif
+ Netplay_localReady(conn, readyToEnd2Callback, NULL, false);
+ (void) arg;
+}
+
+/*
+ * When one player's ship dies, there's a delay before the next ship
+ * can be chosen. This time depends on the time the ditty is playing
+ * and may differ for each side.
+ * To synchronise the time, the following protocol is followed:
+ * 1. (NetState_inBattle) The Ready protocol is used to let either
+ * party know when they're ready to stop the battle.
+ * 2. (NetState_endingBattle) Each party sends the frame number of when
+ * it wants to end the battle, and continues until that point, where
+ * it waits until it has received the frame number of the other party.
+ * 3. After a player has both sent and received a frame count, the
+ * simulation continues for each party, until the maximum of both
+ * frame counts has been achieved.
+ * 4. The Ready protocol is used to let each side signal that it has
+ * reached the target frame count.
+ * 5. The battle ends.
+ */
+static bool
+readyForBattleEndPlayer (NetConnection *conn)
+{
+ BattleStateData *battleStateData;
+ battleStateData = (BattleStateData *) NetConnection_getStateData(conn);
+
+ if (NetConnection_getState (conn) == NetState_interBattle ||
+ NetConnection_getState (conn) == NetState_inSetup)
+ {
+ // This connection is already ready. The entire synchronisation
+ // protocol has already been done for this connection.
+ return true;
+ }
+
+ if (NetConnection_getState (conn) == NetState_inBattle)
+ {
+ if (Netplay_isLocalReady(conn))
+ {
+ // We've already sent notice that we are ready, but we're
+ // still waiting for the other side to say it's ready too.
+ return false;
+ }
+
+ // We haven't yet told the other side we're ready. We do so now.
+ Netplay_localReady (conn, readyToEndCallback, NULL, true);
+ // This may set the state to endingBattle.
+
+ if (NetConnection_getState (conn) == NetState_inBattle)
+ return false;
+ }
+
+ assert (NetConnection_getState (conn) == NetState_endingBattle ||
+ NetConnection_getState (conn) == NetState_endingBattle2);
+
+ // Keep the simulation going as long as the target frame count
+ // hasn't been reached yet. Note that if the connection state is
+ // NetState_endingBattle, then we haven't yet received the
+ // remote frame count, so the target frame count may still rise.
+ if (battleFrameCount < battleStateData->endFrameCount)
+ return false;
+
+ if (NetConnection_getState (conn) == NetState_endingBattle)
+ {
+ // We have reached the target frame count, but we don't know
+ // the remote target frame count yet. So we wait until it has
+ // come in.
+ waitReady (conn);
+ // TODO: check whether all connections are still connected.
+ assert (NetConnection_getState (conn) == NetState_endingBattle2);
+
+ // Continue the simulation if the battleFrameCount has gone up.
+ if (battleFrameCount < battleStateData->endFrameCount)
+ return false;
+ }
+
+ // We are ready and wait for the other party to become ready too.
+ negotiateReady (conn, true, NetState_interBattle);
+
+ return true;
+}
+#endif
+
+bool
+battleEndReadyHuman (HumanInputContext *context)
+{
+ (void) context;
+ return true;
+}
+
+bool
+battleEndReadyComputer (ComputerInputContext *context)
+{
+ (void) context;
+ return true;
+}
+
+#ifdef NETPLAY
+bool
+battleEndReadyNetwork (NetworkInputContext *context)
+{
+ return readyForBattleEndPlayer (netConnections[context->playerNr]);
+}
+#endif
+
+// Returns true iff this side is ready to end the battle.
+static inline bool
+readyForBattleEnd (void)
+{
+#ifndef NETPLAY
+#if DEMO_MODE
+ // In Demo mode, the saved journal should be replayed with frame
+ // accuracy. PLRPlaying () isn't consistent enough.
+ return true;
+#else /* !DEMO_MODE */
+ return !DittyPlaying ();
+#endif /* !DEMO_MODE */
+#else /* defined (NETPLAY) */
+ int playerI;
+
+ if (DittyPlaying ())
+ return false;
+
+ for (playerI = 0; playerI < NUM_PLAYERS; playerI++)
+ if (!PlayerInput[playerI]->handlers->battleEndReady (
+ PlayerInput[playerI]))
+ return false;
+
+ return true;
+#endif /* defined (NETPLAY) */
+}
+
+static void
+preprocess_dead_ship (ELEMENT *DeadShipPtr)
+{
+ ProcessSound ((SOUND)~0, NULL);
+ (void)DeadShipPtr; // unused argument
+}
+
+void
+cleanup_dead_ship (ELEMENT *DeadShipPtr)
+{
+ STARSHIP *DeadStarShipPtr;
+
+ ProcessSound ((SOUND)~0, NULL);
+
+ GetElementStarShip (DeadShipPtr, &DeadStarShipPtr);
+ {
+ // Ship explosion has finished, or ship has just warped out
+ // if DeadStarShipPtr->crew_level != 0
+ BOOLEAN MusicStarted;
+ HELEMENT hElement, hSuccElement;
+
+ /* Record crew left after the battle */
+ DeadStarShipPtr->crew_level =
+ DeadStarShipPtr->RaceDescPtr->ship_info.crew_level;
+
+ MusicStarted = FALSE;
+
+ for (hElement = GetHeadElement (); hElement; hElement = hSuccElement)
+ {
+ ELEMENT *ElementPtr;
+ STARSHIP *StarShipPtr;
+
+ LockElement (hElement, &ElementPtr);
+ hSuccElement = GetSuccElement (ElementPtr);
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ // Get the STARSHIP that this ELEMENT belongs to.
+
+ if (StarShipPtr == DeadStarShipPtr)
+ {
+ // This element belongs to the dead ship; it may be the
+ // ship's own element.
+ SetElementStarShip (ElementPtr, 0);
+
+ if (!(ElementPtr->state_flags & CREW_OBJECT)
+ || ElementPtr->preprocess_func != crew_preprocess)
+ {
+ // Set the element up for deletion.
+ SetPrimType (&DisplayArray[ElementPtr->PrimIndex],
+ NO_PRIM);
+ ElementPtr->life_span = 0;
+ ElementPtr->state_flags =
+ NONSOLID | DISAPPEARING | FINITE_LIFE;
+ ElementPtr->preprocess_func = 0;
+ ElementPtr->postprocess_func = 0;
+ ElementPtr->death_func = 0;
+ ElementPtr->collision_func = 0;
+ }
+ }
+
+ if (StarShipPtr
+ && (StarShipPtr->cur_status_flags & PLAY_VICTORY_DITTY))
+ {
+ // StarShipPtr points to the remaining ship.
+ MusicStarted = TRUE;
+ PlayDitty (StarShipPtr);
+ StarShipPtr->cur_status_flags &= ~PLAY_VICTORY_DITTY;
+ }
+
+ UnlockElement (hElement);
+ }
+
+#define MIN_DITTY_FRAME_COUNT ((ONE_SECOND * 3) / BATTLE_FRAME_RATE)
+ // The ship will be "alive" for at least 2 more frames to make sure
+ // the elements it owns (set up for deletion above) expire first.
+ // Ditty does NOT play in the following circumstances:
+ // * The winning ship dies before the loser finishes exploding
+ // * At the moment the losing ship dies, the winner has started
+ // the warp out sequence
+ DeadShipPtr->life_span = MusicStarted ? MIN_DITTY_FRAME_COUNT : 1;
+ if (DeadStarShipPtr == winnerStarShip)
+ { // This ship died but won the battle. We need to keep it alive
+ // longer than the dead opponent ship so that the winning player
+ // picks last.
+ DeadShipPtr->life_span = MIN_DITTY_FRAME_COUNT + 1;
+ }
+ DeadShipPtr->death_func = new_ship;
+ DeadShipPtr->preprocess_func = preprocess_dead_ship;
+ DeadShipPtr->state_flags &= ~DISAPPEARING;
+ // XXX: this increment was originally done by another piece of code
+ // just below this one. I am almost sure it is not needed, but it
+ // keeps the original framecount.
+ ++DeadShipPtr->life_span;
+ SetElementStarShip (DeadShipPtr, DeadStarShipPtr);
+ }
+}
+
+static void
+setMinShipLifeSpan (ELEMENT *ship, COUNT life_span)
+{
+ if (ship->death_func == new_ship)
+ { // The ship has finished exploding or warping out, and now
+ // we can work with the remaining element
+ assert (ship->state_flags & FINITE_LIFE);
+ assert (!(ship->state_flags & DISAPPEARING));
+ if (ship->life_span < life_span)
+ ship->life_span = life_span;
+ }
+}
+
+static void
+setMinStarShipLifeSpan (STARSHIP *starShip, COUNT life_span)
+{
+ ELEMENT *ship;
+
+ LockElement (starShip->hShip, &ship);
+ setMinShipLifeSpan (ship, life_span);
+ UnlockElement (starShip->hShip);
+}
+
+static void
+checkOtherShipLifeSpan (ELEMENT *deadShip)
+{
+ STARSHIP *deadStarShip;
+
+ GetElementStarShip (deadShip, &deadStarShip);
+
+ if (winnerStarShip != NULL && deadStarShip != winnerStarShip
+ && winnerStarShip->RaceDescPtr->ship_info.crew_level == 0)
+ { // The opponent ship also died but won anyway (e.g. Glory device)
+ // We need to keep the opponent ship alive longer so that the
+ // winning player picks last.
+ setMinStarShipLifeSpan (winnerStarShip, deadShip->life_span + 1);
+ }
+ else if (winnerStarShip == NULL)
+ { // Both died at the same time, or the loser has already expired
+ HELEMENT hElement, hNextElement;
+
+ // Find the other dead ship(s) and keep them alive for at least as
+ // long as this ship.
+ for (hElement = GetHeadElement (); hElement; hElement = hNextElement)
+ {
+ ELEMENT *element;
+ STARSHIP *starShip;
+
+ LockElement (hElement, &element);
+ hNextElement = GetSuccElement (element);
+ GetElementStarShip (element, &starShip);
+
+ if (starShip != NULL && element != deadShip
+ && starShip->RaceDescPtr->ship_info.crew_level == 0)
+ { // This is another dead ship
+ setMinShipLifeSpan (element, deadShip->life_span);
+ }
+
+ UnlockElement (hElement);
+ }
+ }
+}
+
+// This function is called when dead ship element's life_span reaches 0
+void
+new_ship (ELEMENT *DeadShipPtr)
+{
+ STARSHIP *DeadStarShipPtr;
+
+ GetElementStarShip (DeadShipPtr, &DeadStarShipPtr);
+
+ if (!readyForBattleEnd ())
+ {
+ DeadShipPtr->state_flags &= ~DISAPPEARING;
+ ++DeadShipPtr->life_span;
+
+ // Keep the winner alive longer, or in a simultaneous destruction
+ // tie, keep the other dead ship alive so that readyForBattleEnd()
+ // is called for only one ship at a time.
+ // When a ship has been destroyed, each side of a network
+ // connection waits until the other side is ready.
+ // When two ships die at the same time, this is handled for one
+ // ship after the other.
+ checkOtherShipLifeSpan (DeadShipPtr);
+ return;
+ }
+
+ // Once a ship is being picked, we do not care about the winner anymore
+ winnerStarShip = NULL;
+
+ {
+ BOOLEAN RestartMusic;
+
+ StopDitty ();
+ StopMusic ();
+ StopSound ();
+
+ SetElementStarShip (DeadShipPtr, 0);
+ RestartMusic = OpponentAlive (DeadStarShipPtr);
+
+ free_ship (DeadStarShipPtr->RaceDescPtr, TRUE, TRUE);
+ DeadStarShipPtr->RaceDescPtr = 0;
+
+ // Graphics are batched while the draw queue is processed,
+ // but we are going to draw the ship selection box now
+ UnbatchGraphics ();
+
+#ifdef NETPLAY
+ initBattleStateDataConnections ();
+ {
+ bool allOk =
+ negotiateReadyConnections (true, NetState_interBattle);
+ // We are already in NetState_interBattle, but all
+ // sides just need to pass this checkpoint before
+ // going on.
+ if (!allOk)
+ {
+ // Some network connection has been reset.
+ GLOBAL (CurrentActivity) &= ~IN_BATTLE;
+ BatchGraphics ();
+ return;
+ }
+ }
+#endif /* NETPLAY */
+
+ if (!FleetIsInfinite (DeadStarShipPtr->playerNr))
+ { // This may be a dead ship (crew_level == 0) or a warped out ship
+ UpdateShipFragCrew (DeadStarShipPtr);
+ // Deactivate the ship (cannot be selected)
+ DeadStarShipPtr->SpeciesID = NO_ID;
+ }
+
+ if (GetNextStarShip (DeadStarShipPtr, DeadStarShipPtr->playerNr))
+ {
+#ifdef NETPLAY
+ {
+ bool allOk =
+ negotiateReadyConnections (true, NetState_inBattle);
+ if (!allOk)
+ {
+ // Some network connection has been reset.
+ GLOBAL (CurrentActivity) &= ~IN_BATTLE;
+ BatchGraphics ();
+ return;
+ }
+ }
+#endif
+ if (RestartMusic)
+ BattleSong (TRUE);
+ }
+ else if (battle_counter[0] == 0 || battle_counter[1] == 0)
+ {
+ // One player is out of ships. The battle is over.
+ GLOBAL (CurrentActivity) &= ~IN_BATTLE;
+ }
+#ifdef NETPLAY
+ else
+ {
+ // Battle has been aborted.
+ GLOBAL (CurrentActivity) |= CHECK_ABORT;
+ }
+#endif
+ BatchGraphics ();
+ }
+}
+
+static void
+explosion_preprocess (ELEMENT *ShipPtr)
+{
+ BYTE i;
+
+ i = (NUM_EXPLOSION_FRAMES * 3) - ShipPtr->life_span;
+ switch (i)
+ {
+ case 25:
+ ShipPtr->preprocess_func = NULL;
+ case 0:
+ case 1:
+ case 2:
+ case 20:
+ case 21:
+ case 22:
+ case 23:
+ case 24:
+ i = 1;
+ break;
+ case 3:
+ case 4:
+ case 5:
+ case 18:
+ case 19:
+ i = 2;
+ break;
+ case 15:
+ SetPrimType (&DisplayArray[ShipPtr->PrimIndex], NO_PRIM);
+ ShipPtr->state_flags |= CHANGING;
+ default:
+ i = 3;
+ break;
+ }
+
+ do
+ {
+ HELEMENT hElement;
+
+ hElement = AllocElement ();
+ if (hElement)
+ {
+ COUNT angle, dist;
+ DWORD rand_val;
+ ELEMENT *ElementPtr;
+ extern FRAME explosion[];
+
+ PutElement (hElement);
+ LockElement (hElement, &ElementPtr);
+ ElementPtr->playerNr = NEUTRAL_PLAYER_NUM;
+ ElementPtr->state_flags = APPEARING | FINITE_LIFE | NONSOLID;
+ ElementPtr->life_span = 9;
+ SetPrimType (&DisplayArray[ElementPtr->PrimIndex], STAMP_PRIM);
+ ElementPtr->current.image.farray = explosion;
+ ElementPtr->current.image.frame = explosion[0];
+ rand_val = TFB_Random ();
+ angle = LOBYTE (HIWORD (rand_val));
+ dist = DISPLAY_TO_WORLD (LOBYTE (LOWORD (rand_val)) % 8);
+ if (HIBYTE (LOWORD (rand_val)) < 256 * 1 / 3)
+ dist += DISPLAY_TO_WORLD (8);
+ ElementPtr->current.location.x =
+ ShipPtr->current.location.x + COSINE (angle, dist);
+ ElementPtr->current.location.y =
+ ShipPtr->current.location.y + SINE (angle, dist);
+ ElementPtr->preprocess_func = animation_preprocess;
+ rand_val = TFB_Random ();
+ angle = LOBYTE (LOWORD (rand_val));
+ dist = WORLD_TO_VELOCITY (
+ DISPLAY_TO_WORLD (HIBYTE (LOWORD (rand_val)) % 5));
+ SetVelocityComponents (&ElementPtr->velocity,
+ COSINE (angle, dist), SINE (angle, dist));
+ UnlockElement (hElement);
+ }
+ } while (--i);
+}
+
+void
+StopAllBattleMusic (void)
+{
+ StopDitty ();
+ StopMusic ();
+}
+
+STARSHIP *
+FindAliveStarShip (ELEMENT *deadShip)
+{
+ STARSHIP *aliveShip = NULL;
+ HELEMENT hElement, hNextElement;
+
+ // Find the remaining ship, if any, and see if it is still alive.
+ for (hElement = GetHeadElement (); hElement; hElement = hNextElement)
+ {
+ ELEMENT *ElementPtr;
+
+ LockElement (hElement, &ElementPtr);
+ if ((ElementPtr->state_flags & PLAYER_SHIP)
+ && ElementPtr != deadShip
+ /* and not running away */
+ && ElementPtr->mass_points <= MAX_SHIP_MASS + 1)
+ {
+ GetElementStarShip (ElementPtr, &aliveShip);
+ assert (aliveShip != NULL);
+ if (aliveShip->RaceDescPtr->ship_info.crew_level == 0
+ /* reincarnating Pkunk is not actually dead */
+ && ElementPtr->mass_points != MAX_SHIP_MASS + 1)
+ {
+ aliveShip = NULL;
+ }
+
+ UnlockElement (hElement);
+ break;
+ }
+ hNextElement = GetSuccElement (ElementPtr);
+ UnlockElement (hElement);
+ }
+
+ return aliveShip;
+}
+
+STARSHIP *
+GetWinnerStarShip (void)
+{
+ return winnerStarShip;
+}
+
+void
+SetWinnerStarShip (STARSHIP *winner)
+{
+ if (winner == NULL)
+ return; // nothing to do
+
+ winner->cur_status_flags |= PLAY_VICTORY_DITTY;
+
+ // The winner is set once per battle. If both ships die, this function is
+ // called twice, once for each ship. We need to preserve the winner
+ // determined on the first call.
+ if (winnerStarShip == NULL)
+ winnerStarShip = winner;
+}
+
+void
+RecordShipDeath (ELEMENT *deadShip)
+{
+ STARSHIP *deadStarShip;
+
+ GetElementStarShip (deadShip, &deadStarShip);
+ assert (deadStarShip != NULL);
+
+ if (deadShip->mass_points <= MAX_SHIP_MASS)
+ { // Not running away.
+ // When a ship tries to run away, it is (dis)counted in DoRunAway(),
+ // so when it dies while running away, we will not count it again
+ assert (deadStarShip->playerNr >= 0);
+ battle_counter[deadStarShip->playerNr]--;
+ }
+
+ if (LOBYTE (GLOBAL (CurrentActivity)) == SUPER_MELEE)
+ MeleeShipDeath (deadStarShip);
+}
+
+void
+StartShipExplosion (ELEMENT *ShipPtr, bool playSound)
+{
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+
+ ZeroVelocityComponents (&ShipPtr->velocity);
+
+ DeltaEnergy (ShipPtr,
+ -(SIZE)StarShipPtr->RaceDescPtr->ship_info.energy_level);
+
+ ShipPtr->life_span = NUM_EXPLOSION_FRAMES * 3;
+ ShipPtr->state_flags &= ~DISAPPEARING;
+ ShipPtr->state_flags |= FINITE_LIFE | NONSOLID;
+ ShipPtr->preprocess_func = explosion_preprocess;
+ ShipPtr->postprocess_func = PostProcessStatus;
+ ShipPtr->death_func = cleanup_dead_ship;
+ ShipPtr->hTarget = 0;
+
+ if (playSound)
+ {
+ PlaySound (SetAbsSoundIndex (GameSounds, SHIP_EXPLODES),
+ CalcSoundPosition (ShipPtr), ShipPtr, GAME_SOUND_PRIORITY + 1);
+ }
+}
+
+void
+ship_death (ELEMENT *ShipPtr)
+{
+ STARSHIP *StarShipPtr;
+ STARSHIP *winner;
+
+ GetElementStarShip (ShipPtr, &StarShipPtr);
+
+ StopAllBattleMusic ();
+
+ // If the winning ship dies before the ditty starts, do not play it.
+ // e.g. a ship can die after the opponent begins exploding but
+ // before the explosion is over.
+ StarShipPtr->cur_status_flags &= ~PLAY_VICTORY_DITTY;
+
+ StartShipExplosion (ShipPtr, true);
+
+ winner = FindAliveStarShip (ShipPtr);
+ SetWinnerStarShip (winner);
+ RecordShipDeath (ShipPtr);
+}
+
+#define START_ION_COLOR BUILD_COLOR (MAKE_RGB15 (0x1F, 0x15, 0x00), 0x7A)
+
+// Called from the death_func of an element for an ion trail pixel, or a
+// ship shadow (when warping in/out).
+static void
+cycle_ion_trail (ELEMENT *ElementPtr)
+{
+ static const Color colorTab[] =
+ {
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x15, 0x00), 0x7a),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x11, 0x00), 0x7b),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x0E, 0x00), 0x7c),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x0A, 0x00), 0x7d),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x07, 0x00), 0x7e),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x03, 0x00), 0x7f),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x00, 0x00), 0x2a),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1B, 0x00, 0x00), 0x2b),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x17, 0x00, 0x00), 0x2c),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x13, 0x00, 0x00), 0x2d),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x0F, 0x00, 0x00), 0x2e),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x0B, 0x00, 0x00), 0x2f),
+ };
+ const size_t colorTabCount = sizeof colorTab / sizeof colorTab[0];
+
+ assert (!(ElementPtr->state_flags & PLAYER_SHIP));
+
+ ElementPtr->colorCycleIndex++;
+ if (ElementPtr->colorCycleIndex != colorTabCount)
+ {
+ ElementPtr->life_span = ElementPtr->thrust_wait;
+ // Reset the life span.
+
+ SetPrimColor (&DisplayArray[ElementPtr->PrimIndex],
+ colorTab[ElementPtr->colorCycleIndex]);
+
+ ElementPtr->state_flags &= ~DISAPPEARING;
+ ElementPtr->state_flags |= CHANGING;
+ } // else, the element disappears.
+}
+
+void
+spawn_ion_trail (ELEMENT *ElementPtr)
+{
+ HELEMENT hIonElement;
+
+ assert (ElementPtr->state_flags & PLAYER_SHIP);
+
+ hIonElement = AllocElement ();
+ if (hIonElement)
+ {
+#define ION_LIFE 1
+ COUNT angle;
+ RECT r;
+ ELEMENT *IonElementPtr;
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ angle = FACING_TO_ANGLE (StarShipPtr->ShipFacing) + HALF_CIRCLE;
+ GetFrameRect (StarShipPtr->RaceDescPtr->ship_data.ship[0], &r);
+ r.extent.height = DISPLAY_TO_WORLD (r.extent.height + r.corner.y);
+
+ InsertElement (hIonElement, GetHeadElement ());
+ LockElement (hIonElement, &IonElementPtr);
+ IonElementPtr->playerNr = NEUTRAL_PLAYER_NUM;
+ IonElementPtr->state_flags = APPEARING | FINITE_LIFE | NONSOLID;
+ IonElementPtr->thrust_wait = ION_LIFE;
+ IonElementPtr->life_span = IonElementPtr->thrust_wait;
+ // When the element "dies", in the death_func
+ // 'cycle_ion_trail', it is given new life a number of
+ // times, by setting life_span to thrust_wait.
+ SetPrimType (&DisplayArray[IonElementPtr->PrimIndex], POINT_PRIM);
+ SetPrimColor (&DisplayArray[IonElementPtr->PrimIndex],
+ START_ION_COLOR);
+ IonElementPtr->colorCycleIndex = 0;
+ IonElementPtr->current.image.frame =
+ DecFrameIndex (stars_in_space);
+ IonElementPtr->current.image.farray = &stars_in_space;
+ IonElementPtr->current.location = ElementPtr->current.location;
+ IonElementPtr->current.location.x +=
+ (COORD)COSINE (angle, r.extent.height);
+ IonElementPtr->current.location.y +=
+ (COORD)SINE (angle, r.extent.height);
+ IonElementPtr->death_func = cycle_ion_trail;
+
+ SetElementStarShip (IonElementPtr, StarShipPtr);
+
+ {
+ /* normally done during preprocess, but because
+ * object is being inserted at head rather than
+ * appended after tail it may never get preprocessed.
+ */
+ IonElementPtr->next = IonElementPtr->current;
+ --IonElementPtr->life_span;
+ IonElementPtr->state_flags |= PRE_PROCESS;
+ }
+
+ UnlockElement (hIonElement);
+ }
+}
+
+// Preprocess function for spawning a ship into or out of battle.
+// Used when a new ship warps in, or a ship escapes by warping out, but not
+// when a Pkunk ship is reborn.
+void
+ship_transition (ELEMENT *ElementPtr)
+{
+ if (ElementPtr->state_flags & PLAYER_SHIP)
+ {
+ if (ElementPtr->state_flags & APPEARING)
+ {
+ ElementPtr->life_span = HYPERJUMP_LIFE;
+ ElementPtr->preprocess_func = ship_transition;
+ ElementPtr->postprocess_func = NULL;
+ SetPrimType (&DisplayArray[ElementPtr->PrimIndex], NO_PRIM);
+ ElementPtr->state_flags |= NONSOLID | FINITE_LIFE | CHANGING;
+ }
+ else if (ElementPtr->life_span < HYPERJUMP_LIFE)
+ {
+ if (ElementPtr->life_span == NORMAL_LIFE
+ && ElementPtr->crew_level)
+ {
+ ElementPtr->current.image.frame =
+ ElementPtr->next.image.frame =
+ SetEquFrameIndex (
+ ElementPtr->current.image.farray[0],
+ ElementPtr->current.image.frame);
+ SetPrimType (&DisplayArray[ElementPtr->PrimIndex], STAMP_PRIM);
+ InitIntersectStartPoint (ElementPtr);
+ InitIntersectEndPoint (ElementPtr);
+ InitIntersectFrame (ElementPtr);
+ ZeroVelocityComponents (&ElementPtr->velocity);
+ ElementPtr->state_flags &= ~(NONSOLID | FINITE_LIFE);
+ ElementPtr->state_flags |= CHANGING;
+
+ ElementPtr->preprocess_func = ship_preprocess;
+ ElementPtr->postprocess_func = ship_postprocess;
+ }
+
+ return;
+ }
+ }
+
+ {
+ HELEMENT hShipImage;
+ ELEMENT *ShipImagePtr;
+ STARSHIP *StarShipPtr;
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ LockElement (StarShipPtr->hShip, &ShipImagePtr);
+
+ if (!(ShipImagePtr->state_flags & NONSOLID))
+ {
+ ElementPtr->preprocess_func = NULL;
+ }
+ else if ((hShipImage = AllocElement ()))
+ {
+#define TRANSITION_SPEED DISPLAY_TO_WORLD (40)
+#define TRANSITION_LIFE 1
+ COUNT angle;
+
+ PutElement (hShipImage);
+
+ angle = FACING_TO_ANGLE (StarShipPtr->ShipFacing);
+
+ LockElement (hShipImage, &ShipImagePtr);
+ ShipImagePtr->playerNr = NEUTRAL_PLAYER_NUM;
+ ShipImagePtr->state_flags = APPEARING | FINITE_LIFE | NONSOLID;
+ ShipImagePtr->thrust_wait = TRANSITION_LIFE;
+ ShipImagePtr->life_span = ShipImagePtr->thrust_wait;
+ // When the element "dies", in the death_func
+ // 'cycle_ion_trail', it is given new life a number of
+ // times, by setting life_span to thrust_wait.
+ SetPrimType (&DisplayArray[ShipImagePtr->PrimIndex],
+ STAMPFILL_PRIM);
+ SetPrimColor (&DisplayArray[ShipImagePtr->PrimIndex],
+ START_ION_COLOR);
+ ShipImagePtr->colorCycleIndex = 0;
+ ShipImagePtr->current.image = ElementPtr->current.image;
+ ShipImagePtr->current.location = ElementPtr->current.location;
+ if (!(ElementPtr->state_flags & PLAYER_SHIP))
+ {
+ ShipImagePtr->current.location.x +=
+ COSINE (angle, TRANSITION_SPEED);
+ ShipImagePtr->current.location.y +=
+ SINE (angle, TRANSITION_SPEED);
+ ElementPtr->preprocess_func = NULL;
+ }
+ else if (ElementPtr->crew_level)
+ {
+ ShipImagePtr->current.location.x -=
+ COSINE (angle, TRANSITION_SPEED)
+ * (ElementPtr->life_span - 1);
+ ShipImagePtr->current.location.y -=
+ SINE (angle, TRANSITION_SPEED)
+ * (ElementPtr->life_span - 1);
+
+ ShipImagePtr->current.location.x =
+ WRAP_X (ShipImagePtr->current.location.x);
+ ShipImagePtr->current.location.y =
+ WRAP_Y (ShipImagePtr->current.location.y);
+ }
+ ShipImagePtr->preprocess_func = ship_transition;
+ ShipImagePtr->death_func = cycle_ion_trail;
+ SetElementStarShip (ShipImagePtr, StarShipPtr);
+
+ UnlockElement (hShipImage);
+ }
+
+ UnlockElement (StarShipPtr->hShip);
+ }
+}
+
+void
+flee_preprocess (ELEMENT *ElementPtr)
+{
+ STARSHIP *StarShipPtr;
+
+ if (--ElementPtr->turn_wait == 0)
+ {
+ static const Color colorTab[] =
+ {
+ BUILD_COLOR (MAKE_RGB15_INIT (0x0A, 0x00, 0x00), 0x2E),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x0E, 0x00, 0x00), 0x2D),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x13, 0x00, 0x00), 0x2C),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x17, 0x00, 0x00), 0x2B),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1B, 0x00, 0x00), 0x2A),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x00, 0x00), 0x29),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x04, 0x04), 0x28),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x0A, 0x0A), 0x27),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x0F, 0x0F), 0x26),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x13, 0x13), 0x25),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x19, 0x19), 0x24),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x13, 0x13), 0x25),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x0F, 0x0F), 0x26),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x0A, 0x0A), 0x27),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x04, 0x04), 0x28),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1F, 0x00, 0x00), 0x29),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1B, 0x00, 0x00), 0x2A),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x17, 0x00, 0x00), 0x2B),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x13, 0x00, 0x00), 0x2C),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x0E, 0x00, 0x00), 0x2D),
+ };
+ const size_t colorTabCount = sizeof colorTab / sizeof colorTab[0];
+
+ ElementPtr->colorCycleIndex++;
+ if (ElementPtr->colorCycleIndex == colorTabCount)
+ ElementPtr->colorCycleIndex = 0;
+
+ SetPrimColor (&DisplayArray[ElementPtr->PrimIndex],
+ colorTab[ElementPtr->colorCycleIndex]);
+
+ if (ElementPtr->colorCycleIndex == 0)
+ --ElementPtr->thrust_wait;
+
+ ElementPtr->turn_wait = ElementPtr->thrust_wait;
+ if (ElementPtr->turn_wait)
+ {
+ ElementPtr->turn_wait = ((ElementPtr->turn_wait - 1) >> 1) + 1;
+ }
+ else if (ElementPtr->colorCycleIndex != (colorTabCount / 2))
+ {
+ ElementPtr->turn_wait = 1;
+ }
+ else
+ {
+ ElementPtr->death_func = cleanup_dead_ship;
+ ElementPtr->crew_level = 0;
+
+ ElementPtr->life_span = HYPERJUMP_LIFE + 1;
+ ElementPtr->preprocess_func = ship_transition;
+ ElementPtr->postprocess_func = NULL;
+ SetPrimType (&DisplayArray[ElementPtr->PrimIndex], NO_PRIM);
+ ElementPtr->state_flags |= NONSOLID | FINITE_LIFE | CHANGING;
+ }
+ }
+
+ GetElementStarShip (ElementPtr, &StarShipPtr);
+ StarShipPtr->cur_status_flags &=
+ ~(LEFT | RIGHT | THRUST | WEAPON | SPECIAL);
+ // Ignore control input when fleeing.
+ PreProcessStatus (ElementPtr);
+}