summaryrefslogtreecommitdiff
path: root/src/uqm/battle.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/uqm/battle.c')
-rw-r--r--src/uqm/battle.c512
1 files changed, 512 insertions, 0 deletions
diff --git a/src/uqm/battle.c b/src/uqm/battle.c
new file mode 100644
index 0000000..f33e66d
--- /dev/null
+++ b/src/uqm/battle.c
@@ -0,0 +1,512 @@
+//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 "battle.h"
+
+#include "battlecontrols.h"
+#include "controls.h"
+#include "init.h"
+#include "element.h"
+#include "ship.h"
+#include "process.h"
+#include "tactrans.h"
+ // for flee_preprocess()
+#include "intel.h"
+#ifdef NETPLAY
+# include "supermelee/netplay/netmelee.h"
+# ifdef NETPLAY_CHECKSUM
+# include "supermelee/netplay/checksum.h"
+# endif
+# include "supermelee/netplay/notifyall.h"
+#endif
+#include "supermelee/pickmele.h"
+#include "resinst.h"
+#include "nameref.h"
+#include "setup.h"
+#include "settings.h"
+#include "sounds.h"
+#include "libs/async.h"
+#include "libs/graphics/gfx_common.h"
+#include "libs/log.h"
+#include "libs/mathlib.h"
+
+
+BYTE battle_counter[NUM_SIDES];
+ // The number of ships still available for battle to each side.
+ // A ship that has warped out is no longer available.
+BOOLEAN instantVictory;
+size_t battleInputOrder[NUM_SIDES];
+ // Indices of the sides in the order their input is processed.
+ // Network sides are last so that the sides will never be waiting
+ // on eachother, and games with a 0 frame delay are theoretically
+ // possible.
+#ifdef NETPLAY
+BattleFrameCounter battleFrameCount;
+ // Used for synchronisation purposes during netplay.
+#endif
+
+static BOOLEAN
+RunAwayAllowed (void)
+{
+ return (LOBYTE (GLOBAL (CurrentActivity)) == IN_ENCOUNTER
+ || LOBYTE (GLOBAL (CurrentActivity)) == IN_LAST_BATTLE)
+ && GET_GAME_STATE (STARBASE_AVAILABLE)
+ && !GET_GAME_STATE (BOMB_CARRIER);
+}
+
+static void
+DoRunAway (STARSHIP *StarShipPtr)
+{
+ ELEMENT *ElementPtr;
+
+ LockElement (StarShipPtr->hShip, &ElementPtr);
+ if (GetPrimType (&DisplayArray[ElementPtr->PrimIndex]) == STAMP_PRIM
+ && ElementPtr->life_span == NORMAL_LIFE
+ && !(ElementPtr->state_flags & FINITE_LIFE)
+ && ElementPtr->mass_points != MAX_SHIP_MASS * 10
+ && !(ElementPtr->state_flags & APPEARING))
+ {
+ battle_counter[0]--;
+
+ ElementPtr->turn_wait = 3;
+ ElementPtr->thrust_wait = 4;
+ ElementPtr->colorCycleIndex = 0;
+ ElementPtr->preprocess_func = flee_preprocess;
+ ElementPtr->mass_points = MAX_SHIP_MASS * 10;
+ ZeroVelocityComponents (&ElementPtr->velocity);
+ StarShipPtr->cur_status_flags &=
+ ~(SHIP_AT_MAX_SPEED | SHIP_BEYOND_MAX_SPEED);
+
+ SetPrimColor (&DisplayArray[ElementPtr->PrimIndex],
+ BUILD_COLOR (MAKE_RGB15 (0x0B, 0x00, 0x00), 0x2E));
+ // XXX: I think this is supposed to be the same as the
+ // first entry of the color cycle table in flee_preeprocess,
+ // but it is slightly different (0x0A as red value). - SvdB.
+ SetPrimType (&DisplayArray[ElementPtr->PrimIndex], STAMPFILL_PRIM);
+
+ StarShipPtr->ship_input_state = 0;
+ }
+ UnlockElement (StarShipPtr->hShip);
+}
+
+static void
+setupBattleInputOrder(void)
+{
+ size_t i;
+
+#ifndef NETPLAY
+ for (i = 0; i < NUM_SIDES; i++)
+ battleInputOrder[i] = i;
+#else
+ int j;
+
+ i = 0;
+ // First put the locally controlled players in the array.
+ for (j = 0; j < NUM_SIDES; j++) {
+ if (!(PlayerControl[j] & NETWORK_CONTROL)) {
+ battleInputOrder[i] = j;
+ i++;
+ }
+ }
+
+ // Next put the network controlled players in the array.
+ for (j = 0; j < NUM_SIDES; j++) {
+ if (PlayerControl[j] & NETWORK_CONTROL) {
+ battleInputOrder[i] = j;
+ i++;
+ }
+ }
+#endif
+}
+
+BATTLE_INPUT_STATE
+frameInputHuman (HumanInputContext *context, STARSHIP *StarShipPtr)
+{
+ (void) StarShipPtr;
+ return CurrentInputToBattleInput (context->playerNr);
+}
+
+static void
+ProcessInput (void)
+{
+ BOOLEAN CanRunAway;
+ size_t sideI;
+
+#ifdef NETPLAY
+ netInput ();
+#endif
+
+ CanRunAway = RunAwayAllowed ();
+
+ for (sideI = 0; sideI < NUM_SIDES; sideI++)
+ {
+ HSTARSHIP hBattleShip, hNextShip;
+ size_t cur_player = battleInputOrder[sideI];
+
+ for (hBattleShip = GetHeadLink (&race_q[cur_player]);
+ hBattleShip != 0; hBattleShip = hNextShip)
+ {
+ BATTLE_INPUT_STATE InputState;
+ STARSHIP *StarShipPtr;
+
+ StarShipPtr = LockStarShip (&race_q[cur_player], hBattleShip);
+ hNextShip = _GetSuccLink (StarShipPtr);
+
+ if (StarShipPtr->hShip)
+ {
+ // TODO: review and see if we have to do this every frame, or
+ // if we can do this once somewhere
+ StarShipPtr->control = PlayerControl[cur_player];
+
+ InputState = PlayerInput[cur_player]->handlers->frameInput (
+ PlayerInput[cur_player], StarShipPtr);
+
+#if CREATE_JOURNAL
+ JournalInput (InputState);
+#endif /* CREATE_JOURNAL */
+#ifdef NETPLAY
+ if (!(PlayerControl[cur_player] & NETWORK_CONTROL))
+ {
+ BattleInputBuffer *bib = getBattleInputBuffer(cur_player);
+ Netplay_NotifyAll_battleInput (InputState);
+ flushPacketQueues ();
+
+ BattleInputBuffer_push (bib, InputState);
+ // Add this input to the end of the buffer.
+ BattleInputBuffer_pop (bib, &InputState);
+ // Get the input from the front of the buffer.
+ }
+#endif
+
+ StarShipPtr->ship_input_state = 0;
+ if (StarShipPtr->RaceDescPtr->ship_info.crew_level)
+ {
+ if (InputState & BATTLE_LEFT)
+ StarShipPtr->ship_input_state |= LEFT;
+ else if (InputState & BATTLE_RIGHT)
+ StarShipPtr->ship_input_state |= RIGHT;
+ if (InputState & BATTLE_THRUST)
+ StarShipPtr->ship_input_state |= THRUST;
+ if (InputState & BATTLE_WEAPON)
+ StarShipPtr->ship_input_state |= WEAPON;
+ if (InputState & BATTLE_SPECIAL)
+ StarShipPtr->ship_input_state |= SPECIAL;
+
+ if (CanRunAway && cur_player == 0 &&
+ (InputState & BATTLE_ESCAPE))
+ DoRunAway (StarShipPtr);
+ }
+ }
+
+ UnlockStarShip (&race_q[cur_player], hBattleShip);
+ }
+ }
+
+#ifdef NETPLAY
+ flushPacketQueues ();
+#endif
+
+ if (GLOBAL (CurrentActivity) & (CHECK_LOAD | CHECK_ABORT))
+ GLOBAL (CurrentActivity) &= ~IN_BATTLE;
+}
+
+#if DEMO_MODE || CREATE_JOURNAL
+DWORD BattleSeed;
+#endif /* DEMO_MODE */
+
+static MUSIC_REF BattleRef;
+
+void
+BattleSong (BOOLEAN DoPlay)
+{
+ if (BattleRef == 0)
+ {
+ if (inHyperSpace ())
+ BattleRef = LoadMusic (HYPERSPACE_MUSIC);
+ else if (inQuasiSpace ())
+ BattleRef = LoadMusic (QUASISPACE_MUSIC);
+ else
+ BattleRef = LoadMusic (BATTLE_MUSIC);
+ }
+
+ if (DoPlay)
+ PlayMusic (BattleRef, TRUE, 1);
+}
+
+void
+FreeBattleSong (void)
+{
+ DestroyMusic (BattleRef);
+ BattleRef = 0;
+}
+
+static BOOLEAN
+DoBattle (BATTLE_STATE *bs)
+{
+ extern UWORD nth_frame;
+ RECT r;
+ BYTE battle_speed;
+
+ SetMenuSounds (MENU_SOUND_NONE, MENU_SOUND_NONE);
+
+#if defined (NETPLAY) && defined (NETPLAY_CHECKSUM)
+ if (getNumNetConnections() > 0 &&
+ battleFrameCount % NETPLAY_CHECKSUM_INTERVAL == 0)
+ {
+ crc_State state;
+ Checksum checksum;
+
+ crc_init(&state);
+ crc_processState (&state);
+ checksum = (Checksum) crc_finish (&state);
+
+ Netplay_NotifyAll_checksum ((uint32) battleFrameCount,
+ (uint32) checksum);
+ flushPacketQueues ();
+ addLocalChecksum (battleFrameCount, checksum);
+ }
+#endif
+ ProcessInput ();
+ // Also calls NetInput()
+#if defined (NETPLAY) && defined (NETPLAY_CHECKSUM)
+ if (getNumNetConnections() > 0)
+ {
+ size_t delay = getBattleInputDelay();
+
+ if (battleFrameCount >= delay
+ && (battleFrameCount - delay) % NETPLAY_CHECKSUM_INTERVAL == 0)
+ {
+ if (!(GLOBAL (CurrentActivity) & CHECK_ABORT))
+ {
+ if (!verifyChecksums (battleFrameCount - delay)) {
+ GLOBAL(CurrentActivity) |= CHECK_ABORT;
+ resetConnections (ResetReason_syncLoss);
+ }
+ }
+ }
+ }
+#endif
+
+ if (bs->first_time)
+ {
+ r.corner.x = SIS_ORG_X;
+ r.corner.y = SIS_ORG_Y;
+ r.extent.width = SIS_SCREEN_WIDTH;
+ r.extent.height = SIS_SCREEN_HEIGHT;
+ SetTransitionSource (&r);
+ }
+ BatchGraphics ();
+
+ // Call the callback function, if set
+ if (bs->frame_cb)
+ bs->frame_cb ();
+
+ RedrawQueue (TRUE);
+
+ if (bs->first_time)
+ {
+ bs->first_time = FALSE;
+ ScreenTransition (3, &r);
+ }
+ UnbatchGraphics ();
+ if ((!(GLOBAL (CurrentActivity) & IN_BATTLE)) ||
+ (GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD)))
+ {
+ return FALSE;
+ }
+
+ battle_speed = HIBYTE (nth_frame);
+ if (battle_speed == (BYTE)~0)
+ { // maximum speed, nothing rendered at all
+ Async_process ();
+ TaskSwitch ();
+ }
+ else
+ {
+ SleepThreadUntil (bs->NextTime
+ + BATTLE_FRAME_RATE / (battle_speed + 1));
+ bs->NextTime = GetTimeCounter ();
+ }
+
+ if ((GLOBAL (CurrentActivity) & IN_BATTLE) == 0)
+ return FALSE;
+
+#ifdef NETPLAY
+ battleFrameCount++;
+#endif
+ return TRUE;
+}
+
+#ifdef NETPLAY
+COUNT
+GetPlayerOrder (COUNT i)
+{
+ // Iff 'myTurn' is set on a connection, the local party will be
+ // processed first.
+ // If neither is network controlled, the top player (1) is handled
+ // first.
+ if (((PlayerControl[0] & NETWORK_CONTROL) &&
+ !NetConnection_getDiscriminant (netConnections[0])) ||
+ ((PlayerControl[1] & NETWORK_CONTROL) &&
+ NetConnection_getDiscriminant (netConnections[1])))
+ return i;
+ else
+ return 1 - i;
+}
+#endif
+
+// Let each player pick his ship.
+static BOOLEAN
+selectAllShips (SIZE num_ships)
+{
+ if (num_ships == 1) {
+ // HyperSpace in full game.
+ return GetNextStarShip (NULL, 0);
+ }
+
+#ifdef NETPLAY
+ if ((PlayerControl[0] & NETWORK_CONTROL) &&
+ (PlayerControl[1] & NETWORK_CONTROL))
+ {
+ log_add (log_Error, "Only one side at a time can be network "
+ "controlled.\n");
+ return FALSE;
+ }
+#endif
+
+ return GetInitialStarShips ();
+}
+
+BOOLEAN
+Battle (BattleFrameCallback *callback)
+{
+ SIZE num_ships;
+
+
+#if !(DEMO_MODE || CREATE_JOURNAL)
+ if (LOBYTE (GLOBAL (CurrentActivity)) != SUPER_MELEE) {
+ // In Supermelee, the RNG is already initialised.
+ TFB_SeedRandom (GetTimeCounter ());
+ }
+#else /* DEMO_MODE */
+ if (BattleSeed == 0)
+ BattleSeed = TFB_Random ();
+ TFB_SeedRandom (BattleSeed);
+ BattleSeed = TFB_Random (); /* get next battle seed */
+#endif /* DEMO_MODE */
+
+ BattleSong (FALSE);
+
+ num_ships = InitShips ();
+
+ if (instantVictory)
+ {
+ num_ships = 0;
+ battle_counter[0] = 1;
+ battle_counter[1] = 0;
+ instantVictory = FALSE;
+ }
+
+ if (num_ships)
+ {
+ BATTLE_STATE bs;
+
+ GLOBAL (CurrentActivity) |= IN_BATTLE;
+ battle_counter[0] = CountLinks (&race_q[0]);
+ battle_counter[1] = CountLinks (&race_q[1]);
+
+ if (optMeleeScale != TFB_SCALE_STEP)
+ SetGraphicScaleMode (optMeleeScale);
+
+ setupBattleInputOrder ();
+#ifdef NETPLAY
+ initBattleInputBuffers ();
+#ifdef NETPLAY_CHECKSUM
+ initChecksumBuffers ();
+#endif /* NETPLAY_CHECKSUM */
+ battleFrameCount = 0;
+ ResetWinnerStarShip ();
+ setBattleStateConnections (&bs);
+#endif /* NETPLAY */
+
+ if (!selectAllShips (num_ships)) {
+ GLOBAL (CurrentActivity) |= CHECK_ABORT;
+ goto AbortBattle;
+ }
+
+ BattleSong (TRUE);
+ bs.NextTime = 0;
+#ifdef NETPLAY
+ initBattleStateDataConnections ();
+ {
+ bool allOk = negotiateReadyConnections (true, NetState_inBattle);
+ if (!allOk) {
+ GLOBAL (CurrentActivity) |= CHECK_ABORT;
+ goto AbortBattle;
+ }
+ }
+#endif /* NETPLAY */
+ bs.InputFunc = DoBattle;
+ bs.frame_cb = callback;
+ bs.first_time = inHQSpace ();
+
+ DoInput (&bs, FALSE);
+
+AbortBattle:
+ if (LOBYTE (GLOBAL (CurrentActivity)) == SUPER_MELEE)
+ {
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ {
+ // Do not return to the main menu when a game is aborted,
+ // (just to the supermelee menu).
+#ifdef NETPLAY
+ waitResetConnections(NetState_inSetup);
+ // A connection may already be in inSetup (set from
+ // GetMeleeStarship). This is not a problem, although
+ // it will generate a warning in debug mode.
+#endif
+
+ GLOBAL (CurrentActivity) &= ~CHECK_ABORT;
+ }
+ else
+ {
+ // Show the result of the battle.
+ MeleeGameOver ();
+ }
+ }
+
+#ifdef NETPLAY
+ uninitBattleInputBuffers();
+#ifdef NETPLAY_CHECKSUM
+ uninitChecksumBuffers ();
+#endif /* NETPLAY_CHECKSUM */
+ setBattleStateConnections (NULL);
+#endif /* NETPLAY */
+
+ StopDitty ();
+ StopMusic ();
+ StopSound ();
+ }
+
+ UninitShips ();
+ FreeBattleSong ();
+
+
+ return (BOOLEAN) (num_ships < 0);
+}
+