aboutsummaryrefslogtreecommitdiff
path: root/src/parsec/e_record.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/parsec/e_record.cpp')
-rw-r--r--src/parsec/e_record.cpp1788
1 files changed, 1788 insertions, 0 deletions
diff --git a/src/parsec/e_record.cpp b/src/parsec/e_record.cpp
new file mode 100644
index 0000000..81f71e9
--- /dev/null
+++ b/src/parsec/e_record.cpp
@@ -0,0 +1,1788 @@
+/*
+ * PARSEC - Action Commands Recording
+ *
+ * $Author: uberlinuxguy $ - $Date: 2004/09/26 03:43:34 $
+ *
+ * Orginally written by:
+ * Copyright (c) Clemens Beer <cbx@parsec.org> 2002
+ * Copyright (c) Markus Hadwiger <msh@parsec.org> 1997-1999
+ *
+ * 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
+ */
+
+// C library
+#include <math.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+// compilation flags/debug support
+#include "config.h"
+#include "debug.h"
+
+// general definitions
+#include "general.h"
+#include "objstruc.h"
+
+// global externals
+#include "globals.h"
+
+// subsystem headers
+#include "net_defs.h"
+#include "inp_defs.h"
+
+// mathematics header
+#include "utl_math.h"
+
+// local module header
+#include "e_record.h"
+
+// proprietary module headers
+#include "con_act.h"
+#include "con_aux.h"
+#include "con_ext.h"
+#include "con_main.h"
+#include "net_game.h"
+#include "net_util.h"
+#include "obj_creg.h"
+#include "obj_ctrl.h"
+#include "obj_cust.h"
+
+
+// flags
+//#define SAVE_OBJNUM_ON_SHIPLISTSAVE
+#define STRIP_BINARY_PACKETS
+
+
+
+// file name constants for packet recording session files ---------------------
+//
+static char rpacket_extension[] = ".prp";
+static char rpacket_basename[] = "rpack";
+
+
+// here camera information is stored at the right time to be used later on ----
+//
+static Camera RecordShipViewCamera;
+
+
+// flag whether absolute matrices should be recorded --------------------------
+//
+//static int RecordAbsolutePosition = TRUE;
+#define RecordAbsolutePosition (!AUX_DISABLE_ABSOLUTE_RECORDING)
+
+
+// flag whether wait command has already been inserted in the current frame ---
+//
+static int idlewait_inserted = FALSE;
+
+
+// save wait command using the current idle duration --------------------------
+//
+INLINE
+void SaveWaitCommand()
+{
+ //NOTE:
+ // this function is always called before recording
+ // a command that depends on correct timing. it
+ // ensures that the command will be played back
+ // at the original relative time.
+
+ // write idle wait only if not already done
+ if ( !idlewait_inserted ) {
+
+ // write idle wait
+ ASSERT( RecordingFp != NULL );
+ fprintf( RecordingFp, "%s %d\n", ACMSTR( ACM_IDLEWAIT ), IdleDuration );
+
+ // reset duration timer
+ IdleDuration = 0;
+
+ // set flag
+ idlewait_inserted = TRUE;
+ }
+}
+
+
+// write list of commands able to recreate the current state ------------------
+//
+void Save_StateInfo( FILE *fp )
+{
+ ASSERT( fp != NULL );
+
+ Save_PrepRestore( fp );
+ Save_Camera( fp );
+ Save_PseudoStars( fp );
+ Save_ObjListShips( fp );
+ Save_ObjListLasers( fp );
+ Save_ObjListMissls( fp );
+ Save_ObjListExtras( fp );
+ Save_ObjListCustom( fp );
+ Save_MyShipState( fp );
+ Save_RemoteState( fp );
+}
+
+
+// write prepare restore command ----------------------------------------------
+//
+void Save_PrepRestore( FILE *fp )
+{
+ ASSERT( fp != NULL );
+
+ fprintf( fp, "%s %d\n", ACMSTR( ACM_PREPRESTORE ), MyShip->ObjectClass );
+}
+
+
+// save state of local ship (MyShip) ------------------------------------------
+//
+void Save_MyShipState( FILE *fp )
+{
+ //NOTE:
+ // this function saves all commands restoring
+ // the properties of the local ship.
+
+ ASSERT( fp != NULL );
+
+ //NOTE:
+ // it is crucial that "ac.weaponsactive" is executed after
+ // the available weapons and energy have already been set,
+ // since they will be checked on whether the weapons activation
+ // is actually possible. (see CON_ACT::AcMyShip_WeaponsActive())
+
+ fprintf( fp, "%s %d\n", ACMSTR( ACM_MS_CURDAMAGE ), MyShip->CurDamage );
+ fprintf( fp, "%s %d\n", ACMSTR( ACM_MS_MAXDAMAGE ), MyShip->MaxDamage );
+ fprintf( fp, "%s %d\n", ACMSTR( ACM_MS_CURENERGY ), MyShip->CurEnergy );
+ fprintf( fp, "%s %d\n", ACMSTR( ACM_MS_MAXENERGY ), MyShip->MaxEnergy );
+ fprintf( fp, "%s %d\n", ACMSTR( ACM_MS_CURSPEED ), MyShip->CurSpeed );
+ fprintf( fp, "%s %d\n", ACMSTR( ACM_MS_MAXSPEED ), MyShip->MaxSpeed );
+ fprintf( fp, "%s %d\n", ACMSTR( ACM_MS_NUMMISSLS ), MyShip->NumMissls );
+ fprintf( fp, "%s %d\n", ACMSTR( ACM_MS_MAXNUMMISSLS ), MyShip->MaxNumMissls );
+ fprintf( fp, "%s %d\n", ACMSTR( ACM_MS_NUMHOMMISSLS ), MyShip->NumHomMissls );
+ fprintf( fp, "%s %d\n", ACMSTR( ACM_MS_MAXNUMHOMMISSLS ), MyShip->MaxNumHomMissls );
+ fprintf( fp, "%s %d\n", ACMSTR( ACM_MS_NUMPARTMISSLS ), MyShip->NumPartMissls );
+ fprintf( fp, "%s %d\n", ACMSTR( ACM_MS_MAXNUMPARTMISSLS ), MyShip->MaxNumPartMissls);
+ fprintf( fp, "%s %d\n", ACMSTR( ACM_MS_NUMMINES ), MyShip->NumMines );
+ fprintf( fp, "%s %d\n", ACMSTR( ACM_MS_MAXNUMMINES ), MyShip->MaxNumMines );
+ fprintf( fp, "%s %d\n", ACMSTR( ACM_MS_WEAPONS ), MyShip->Weapons );
+ fprintf( fp, "%s %d\n", ACMSTR( ACM_MS_WEAPONSACTIVE ), MyShip->WeaponsActive );
+ fprintf( fp, "%s %d\n", ACMSTR( ACM_MS_SPECIALS ), MyShip->Specials );
+ fprintf( fp, "%s %d\n", ACMSTR( ACM_GL_SELECTEDLASER ), SelectedLaser );
+ fprintf( fp, "%s %d\n", ACMSTR( ACM_GL_SELECTEDMISSILE ), SelectedMissile );
+ fprintf( fp, "%s %d\n", ACMSTR( ACM_GL_CURGUN ), CurGun );
+ fprintf( fp, "%s %d\n", ACMSTR( ACM_GL_CURLAUNCHER ), CurLauncher );
+ fprintf( fp, "%s %d\n", ACMSTR( ACM_GL_PANEL3CONTROL ), AUX_HUD_PANEL_3_CONTROL );
+ fprintf( fp, "%s %d\n", ACMSTR( ACM_GL_PANEL4CONTROL ), AUX_HUD_PANEL_4_CONTROL );
+ fprintf( fp, "%s %d\n", ACMSTR( ACM_GL_TARGETOBJNUMBER ), TargetObjNumber );
+}
+
+
+// save global maximum number of remote players (NET_GLOB::CurMaxPlayers) -----
+//
+PRIVATE
+void SaveCurMaxPlayers( FILE *fp )
+{
+ ASSERT( fp != NULL );
+
+ fprintf( fp, "%s %d\n", ACMSTR( ACM_REMOTEMAXPLAYERS ), CurMaxPlayers );
+}
+
+
+// save remote player's state -------------------------------------------------
+//
+PRIVATE
+void SavePlayerState( FILE *fp, int id, int status, int objclass, int killstat )
+{
+ ASSERT( fp != NULL );
+
+ fprintf( fp, "%s %d %d %d %d\n", ACMSTR( ACM_REMOTEPLAYER ),
+ id, status, objclass, killstat );
+}
+
+
+// save remote player's matrix ------------------------------------------------
+//
+PRIVATE
+void SavePlayerMatrix( FILE *fp, ShipObject *obj )
+{
+ ASSERT( fp != NULL );
+ ASSERT( obj != NULL );
+
+ // entire matrix in one command
+ fprintf( fp, "%s %f %f %f %f %f %f %f %f %f %f %f %f\n",
+ ACMSTR( ACM_REMOTEMATRIX ),
+ GEOMV_TO_FLOAT( obj->ObjPosition[ 0 ][ 0 ] ),
+ GEOMV_TO_FLOAT( obj->ObjPosition[ 0 ][ 1 ] ),
+ GEOMV_TO_FLOAT( obj->ObjPosition[ 0 ][ 2 ] ),
+ GEOMV_TO_FLOAT( obj->ObjPosition[ 0 ][ 3 ] ),
+ GEOMV_TO_FLOAT( obj->ObjPosition[ 1 ][ 0 ] ),
+ GEOMV_TO_FLOAT( obj->ObjPosition[ 1 ][ 1 ] ),
+ GEOMV_TO_FLOAT( obj->ObjPosition[ 1 ][ 2 ] ),
+ GEOMV_TO_FLOAT( obj->ObjPosition[ 1 ][ 3 ] ),
+ GEOMV_TO_FLOAT( obj->ObjPosition[ 2 ][ 0 ] ),
+ GEOMV_TO_FLOAT( obj->ObjPosition[ 2 ][ 1 ] ),
+ GEOMV_TO_FLOAT( obj->ObjPosition[ 2 ][ 2 ] ),
+ GEOMV_TO_FLOAT( obj->ObjPosition[ 2 ][ 3 ] ) );
+}
+
+
+// save remote player's weapons state -----------------------------------------
+//
+PRIVATE
+void SavePlayerWeaponsActive( FILE *fp, ShipObject *obj )
+{
+ ASSERT( fp != NULL );
+ ASSERT( obj != NULL );
+
+ fprintf( fp, "%s %d\n", ACMSTR( ACM_REMOTEWEAPONSACTIVE ),
+ obj->WeaponsActive );
+}
+
+
+// save remote player's name --------------------------------------------------
+//
+PRIVATE
+void SavePlayerName( FILE *fp, char *name )
+{
+ ASSERT( fp != NULL );
+ ASSERT( name != NULL );
+
+ // save only non-empty names
+ if ( name[ 0 ] != '\0' ) {
+ fprintf( fp, "%s %s\n", ACMSTR( ACM_REMOTENAME ), name );
+ }
+}
+
+
+// save state of all remote players -------------------------------------------
+//
+void Save_RemoteState( FILE *fp )
+{
+ ASSERT( fp != NULL );
+
+ // prevent remote state access when
+ // network game not active
+ if ( !NetConnected )
+ return;
+
+ // save maximum number of players to allow
+ // restoration/checking on replay
+ SaveCurMaxPlayers( fp );
+
+ // process all players
+ for ( int id = 0; id < MAX_NET_PROTO_PLAYERS; id++ ) {
+
+ ShipObject *obj = (ShipObject *) Player_Ship[ id ];
+ ASSERT( ( obj == NULL ) || OBJECT_TYPE_SHIP( obj ) );
+
+ //NOTE:
+ // if ( obj == MyShip ) information about
+ // the local player will be stored. the
+ // meaning of this information is different
+ // from the normal meaning of the fields.
+
+ // determine player ship object class
+ int objclass = ( obj != NULL ) ? obj->ObjectClass : 0;
+
+ // if local player use <objclass> field as <num-of-remote-players>
+ if ( obj == MyShip ) {
+ ASSERT( NumRemPlayers > 0 );
+ // negative objclass denotes local player
+ objclass = -NumRemPlayers;
+ }
+
+ // save player state
+ SavePlayerState( fp, id, Player_Status[ id ],
+ objclass, Player_KillStat[ id ] );
+
+ // save player matrix and weapons state (only for remote players)
+ if ( ( obj != NULL ) && ( obj != MyShip ) ) {
+ SavePlayerMatrix( fp, obj );
+ SavePlayerWeaponsActive( fp, obj );
+ }
+
+ // save player name
+ SavePlayerName( fp, Player_Name[ id ] );
+ }
+}
+
+
+// record state of local player (invoked after state change) ------------------
+//
+void Record_LocalPlayerState()
+{
+ ASSERT( NetConnected );
+
+ if ( RecordingActive && NetConnected ) {
+
+ ASSERT( RecordingFp != NULL );
+ ASSERT( NumRemPlayers > 0 );
+
+ ASSERT( Player_Ship[ LocalPlayerId ] == MyShip );
+ ASSERT( Player_ShipId[ LocalPlayerId ] == SHIPID_LOCALPLAYER );
+
+ // save player state
+ SaveWaitCommand();
+ SavePlayerState( RecordingFp, LocalPlayerId, Player_Status[ LocalPlayerId ],
+ -NumRemPlayers, Player_KillStat[ LocalPlayerId ] );
+
+ // save player matrix on join
+ if ( Player_Status[ LocalPlayerId ] == PLAYER_JOINED )
+ SavePlayerMatrix( RecordingFp, MyShip );
+
+ // save player name
+ SavePlayerName( RecordingFp, Player_Name[ LocalPlayerId ] );
+ }
+}
+
+
+// record local join ----------------------------------------------------------
+//
+void Record_Join()
+{
+ ASSERT( NetConnected );
+ ASSERT( NetJoined );
+ ASSERT( Player_Status[ LocalPlayerId ] == PLAYER_JOINED );
+
+ if ( RecordingActive && NetConnected ) {
+
+ ASSERT( RecordingFp != NULL );
+ ASSERT( NumRemPlayers > 0 );
+
+ ASSERT( Player_Ship[ LocalPlayerId ] == MyShip );
+ ASSERT( Player_ShipId[ LocalPlayerId ] == SHIPID_LOCALPLAYER );
+
+ // save join command, matrix, and name
+ SaveWaitCommand();
+ fprintf( RecordingFp, "%s %d %d %d\n", ACMSTR( ACM_REMOTEJOIN ),
+ LocalPlayerId, 0, NumRemPlayers );
+ SavePlayerMatrix( RecordingFp, MyShip );
+ SavePlayerName( RecordingFp, Player_Name[ LocalPlayerId ] );
+ }
+}
+
+
+// record local unjoin --------------------------------------------------------
+//
+void Record_Unjoin( byte flag )
+{
+ ASSERT( NetConnected );
+ ASSERT( !NetJoined );
+ ASSERT( Player_Status[ LocalPlayerId ] == PLAYER_CONNECTED );
+
+ if ( RecordingActive && NetConnected ) {
+
+ ASSERT( RecordingFp != NULL );
+ ASSERT( NumRemPlayers > 0 );
+
+ ASSERT( Player_Ship[ LocalPlayerId ] == MyShip );
+ ASSERT( Player_ShipId[ LocalPlayerId ] == SHIPID_LOCALPLAYER );
+
+ // save unjoin command
+ SaveWaitCommand();
+ fprintf( RecordingFp, "%s %d %d %d\n", ACMSTR( ACM_REMOTEUNJOIN ),
+ LocalPlayerId, flag, NumRemPlayers );
+ }
+}
+
+
+// record state changes of specific myship variables (used by CON_INT.C) ------
+//
+void Record_MyShipStateWeapons()
+{
+ if ( RecordingActive ) {
+ ASSERT( RecordingFp != NULL );
+ fprintf( RecordingFp, "%s %d\n", ACMSTR( ACM_MS_WEAPONS ), MyShip->Weapons );
+ }
+}
+
+void Record_MyShipStateSpecials()
+{
+ if ( RecordingActive ) {
+ ASSERT( RecordingFp != NULL );
+ fprintf( RecordingFp, "%s %d\n", ACMSTR( ACM_MS_SPECIALS ), MyShip->Specials );
+ }
+}
+
+void Record_MyShipStateCurDamage()
+{
+ if ( RecordingActive ) {
+ ASSERT( RecordingFp != NULL );
+ fprintf( RecordingFp, "%s %d\n", ACMSTR( ACM_MS_CURDAMAGE ), MyShip->CurDamage );
+ }
+}
+
+void Record_MyShipStateCurEnergy()
+{
+ if ( RecordingActive ) {
+ ASSERT( RecordingFp != NULL );
+ fprintf( RecordingFp, "%s %d\n", ACMSTR( ACM_MS_CURENERGY ), MyShip->CurEnergy );
+ }
+}
+
+void Record_MyShipStateCurSpeed()
+{
+ if ( RecordingActive ) {
+ ASSERT( RecordingFp != NULL );
+ fprintf( RecordingFp, "%s %d\n", ACMSTR( ACM_MS_CURSPEED ), MyShip->CurSpeed );
+ }
+}
+
+void Record_MyShipStateNumMissls()
+{
+ if ( RecordingActive ) {
+ ASSERT( RecordingFp != NULL );
+ fprintf( RecordingFp, "%s %d\n", ACMSTR( ACM_MS_NUMMISSLS ), MyShip->NumMissls );
+ }
+}
+
+void Record_MyShipStateNumHomMissls()
+{
+ if ( RecordingActive ) {
+ ASSERT( RecordingFp != NULL );
+ fprintf( RecordingFp, "%s %d\n", ACMSTR( ACM_MS_NUMHOMMISSLS ), MyShip->NumHomMissls );
+ }
+}
+
+void Record_MyShipStateNumPartMissls()
+{
+ if ( RecordingActive ) {
+ ASSERT( RecordingFp != NULL );
+ fprintf( RecordingFp, "%s %d\n", ACMSTR( ACM_MS_NUMPARTMISSLS ), MyShip->NumPartMissls );
+ }
+}
+
+void Record_MyShipStateNumMines()
+{
+ if ( RecordingActive ) {
+ ASSERT( RecordingFp != NULL );
+ fprintf( RecordingFp, "%s %d\n", ACMSTR( ACM_MS_NUMMINES ), MyShip->NumMines );
+ }
+}
+
+void Record_MyShipStateMaxDamage()
+{
+ if ( RecordingActive ) {
+ ASSERT( RecordingFp != NULL );
+ fprintf( RecordingFp, "%s %d\n", ACMSTR( ACM_MS_MAXDAMAGE ), MyShip->MaxDamage );
+ }
+}
+
+void Record_MyShipStateMaxEnergy()
+{
+ if ( RecordingActive ) {
+ ASSERT( RecordingFp != NULL );
+ fprintf( RecordingFp, "%s %d\n", ACMSTR( ACM_MS_MAXENERGY ), MyShip->MaxEnergy );
+ }
+}
+
+void Record_MyShipStateMaxSpeed()
+{
+ if ( RecordingActive ) {
+ ASSERT( RecordingFp != NULL );
+ fprintf( RecordingFp, "%s %d\n", ACMSTR( ACM_MS_MAXSPEED ), MyShip->MaxSpeed );
+ }
+}
+
+void Record_MyShipStateMaxNumMissls()
+{
+ if ( RecordingActive ) {
+ ASSERT( RecordingFp != NULL );
+ fprintf( RecordingFp, "%s %d\n", ACMSTR( ACM_MS_MAXNUMMISSLS ), MyShip->MaxNumMissls );
+ }
+}
+
+void Record_MyShipStateMaxNumHomMissls()
+{
+ if ( RecordingActive ) {
+ ASSERT( RecordingFp != NULL );
+ fprintf( RecordingFp, "%s %d\n", ACMSTR( ACM_MS_MAXNUMHOMMISSLS ), MyShip->MaxNumHomMissls );
+ }
+}
+
+void Record_MyShipStateMaxNumPartMissls()
+{
+ if ( RecordingActive ) {
+ ASSERT( RecordingFp != NULL );
+ fprintf( RecordingFp, "%s %d\n", ACMSTR( ACM_MS_MAXNUMPARTMISSLS ), MyShip->MaxNumPartMissls );
+ }
+}
+
+void Record_MyShipStateMaxNumMines()
+{
+ if ( RecordingActive ) {
+ ASSERT( RecordingFp != NULL );
+ fprintf( RecordingFp, "%s %d\n", ACMSTR( ACM_MS_MAXNUMMINES ), MyShip->MaxNumMines );
+ }
+}
+
+
+// save position matrix of object ---------------------------------------------
+//
+PRIVATE
+void SaveObjectMatrix( FILE *fp, GenObject *obj )
+{
+ //NOTE:
+ // between an "ac.creatobj" command and the "ac.objmatrxcol<x>"
+ // commands generated by this function no commands except
+ // "ac.setobjid" and "ac.setobjhostid" are allowed. this is
+ // crucial for delayed object creation. furthermore, there
+ // MUST NOT be any "ac.creatobj" commands that are not followed
+ // by these four "ac.objmatrxcol<x>" commands.
+ // (see CON_ACT::AcCreateObject() etc.)
+
+ //NOTE:
+ // the output of this function could look like this:
+ //
+ // ac.objmatrxcol1 768 0.571397 -0.623530 0.533586
+ // ac.objmatrxcol2 768 0.769223 0.633509 -0.083435
+ // ac.objmatrxcol3 768 -0.286007 0.458122 0.841620
+ // ac.objmatrxcol4 768 8.604858 305.986023 -0.425659
+ //
+ // 768 (0x0300 == EXTRA_LIST_NO) identifies the object-list
+ // where the position matrix should be modified on execution.
+ // (the object at the head of the list will be modified.)
+
+ ASSERT( fp != NULL );
+ ASSERT( obj != NULL );
+
+ fprintf( fp, "%s %d %f %f %f\n", ACMSTR( ACM_OBJMATRXCOL1 ),
+ obj->ObjectType & TYPELISTMASK,
+ GEOMV_TO_FLOAT( obj->ObjPosition[ 0 ][ 0 ] ),
+ GEOMV_TO_FLOAT( obj->ObjPosition[ 1 ][ 0 ] ),
+ GEOMV_TO_FLOAT( obj->ObjPosition[ 2 ][ 0 ] ) );
+ fprintf( fp, "%s %d %f %f %f\n", ACMSTR( ACM_OBJMATRXCOL2 ),
+ obj->ObjectType & TYPELISTMASK,
+ GEOMV_TO_FLOAT( obj->ObjPosition[ 0 ][ 1 ] ),
+ GEOMV_TO_FLOAT( obj->ObjPosition[ 1 ][ 1 ] ),
+ GEOMV_TO_FLOAT( obj->ObjPosition[ 2 ][ 1 ] ) );
+ fprintf( fp, "%s %d %f %f %f\n", ACMSTR( ACM_OBJMATRXCOL3 ),
+ obj->ObjectType & TYPELISTMASK,
+ GEOMV_TO_FLOAT( obj->ObjPosition[ 0 ][ 2 ] ),
+ GEOMV_TO_FLOAT( obj->ObjPosition[ 1 ][ 2 ] ),
+ GEOMV_TO_FLOAT( obj->ObjPosition[ 2 ][ 2 ] ) );
+ fprintf( fp, "%s %d %f %f %f\n", ACMSTR( ACM_OBJMATRXCOL4 ),
+ obj->ObjectType & TYPELISTMASK,
+ GEOMV_TO_FLOAT( obj->ObjPosition[ 0 ][ 3 ] ),
+ GEOMV_TO_FLOAT( obj->ObjPosition[ 1 ][ 3 ] ),
+ GEOMV_TO_FLOAT( obj->ObjPosition[ 2 ][ 3 ] ) );
+}
+
+
+// write create event for laser object ----------------------------------------
+//
+PRIVATE
+void SaveLaserCreation( LaserObject *laserpo, FILE *fp )
+{
+ ASSERT( laserpo != NULL );
+ ASSERT( fp != NULL );
+
+ fprintf( fp, "%s %d\n", ACMSTR( ACM_CREATEOBJECT ),
+ laserpo->ObjectClass );
+ SaveObjectMatrix( fp, laserpo );
+ fprintf( fp, "%s %d\n", ACMSTR( ACM_LASER_SPEED ),
+ laserpo->Speed );
+ fprintf( fp, "%s %f %f %f\n", ACMSTR( ACM_LASER_DIRVEC ),
+ GEOMV_TO_FLOAT( laserpo->DirectionVec.X ),
+ GEOMV_TO_FLOAT( laserpo->DirectionVec.Y ),
+ GEOMV_TO_FLOAT( laserpo->DirectionVec.Z ) );
+ fprintf( fp, "%s %d\n", ACMSTR( ACM_LASER_OWNER ),
+ laserpo->Owner );
+}
+
+
+// save current energy value --------------------------------------------------
+//
+PRIVATE
+void SaveCurEnergy( FILE *fp )
+{
+ ASSERT( fp != NULL );
+
+ fprintf( fp, "%s %d\n", ACMSTR( ACM_MS_CURENERGY ), MyShip->CurEnergy );
+}
+
+
+// record create event for laser object if recording active -------------------
+//
+void Record_LaserCreation( LaserObject *laserpo )
+{
+ ASSERT( laserpo != NULL );
+
+ if ( RecordingActive ) {
+ ASSERT( RecordingFp != NULL );
+ SaveWaitCommand();
+ SaveLaserCreation( laserpo, RecordingFp );
+ SaveCurEnergy( RecordingFp );
+ }
+}
+
+
+// write create event for missile object --------------------------------------
+//
+PRIVATE
+void SaveMissileCreation( MissileObject *missilepo, FILE *fp )
+{
+ ASSERT( missilepo != NULL );
+ ASSERT( fp != NULL );
+
+ fprintf( fp, "%s %d\n", ACMSTR( ACM_CREATEOBJECT ),
+ missilepo->ObjectClass );
+ SaveObjectMatrix( fp, missilepo );
+ fprintf( fp, "%s %d\n", ACMSTR( ACM_MISSL_SPEED ),
+ missilepo->Speed );
+ fprintf( fp, "%s %f %f %f\n", ACMSTR( ACM_MISSL_DIRVEC ),
+ GEOMV_TO_FLOAT( missilepo->DirectionVec.X ),
+ GEOMV_TO_FLOAT( missilepo->DirectionVec.Y ),
+ GEOMV_TO_FLOAT( missilepo->DirectionVec.Z ) );
+ fprintf( fp, "%s %d\n", ACMSTR( ACM_MISSL_OWNER ),
+ missilepo->Owner );
+}
+
+
+// save current number of missiles --------------------------------------------
+//
+PRIVATE
+void SaveNumMissls( FILE *fp )
+{
+ ASSERT( fp != NULL );
+
+ fprintf( fp, "%s %d\n", ACMSTR( ACM_MS_NUMMISSLS ), MyShip->NumMissls );
+}
+
+
+// record create event for missile object if recording active -----------------
+//
+void Record_MissileCreation( MissileObject *missilepo )
+{
+ ASSERT( missilepo != NULL );
+
+ if ( RecordingActive ) {
+ ASSERT( RecordingFp != NULL );
+ SaveWaitCommand();
+ SaveMissileCreation( missilepo, RecordingFp );
+ SaveNumMissls( RecordingFp );
+ }
+}
+
+
+// save current number of homing missiles -------------------------------------
+//
+PRIVATE
+void SaveNumHomMissls( FILE *fp )
+{
+ ASSERT( fp != NULL );
+
+ fprintf( fp, "%s %d\n", ACMSTR( ACM_MS_NUMHOMMISSLS ), MyShip->NumHomMissls );
+}
+
+
+// record create event for missile object with target if recording active -----
+//
+void Record_TargetMissileCreation( TargetMissileObject *missilepo )
+{
+ ASSERT( missilepo != NULL );
+
+ if ( RecordingActive ) {
+ ASSERT( RecordingFp != NULL );
+ SaveWaitCommand();
+ SaveMissileCreation( missilepo, RecordingFp );
+ fprintf( RecordingFp, "%s %d\n", ACMSTR( ACM_MISSL_TARGET ),
+ missilepo->TargetObjNumber );
+ SaveNumHomMissls( RecordingFp );
+ }
+}
+
+
+// write create event for mine object -----------------------------------------
+//
+PRIVATE
+void SaveMineCreation( MineObject *minepo, FILE *fp )
+{
+ ASSERT( minepo != NULL );
+ ASSERT( fp != NULL );
+
+ fprintf( fp, "%s %d\n", ACMSTR( ACM_CREATEOBJECT ),
+ minepo->ObjectClass );
+ SaveObjectMatrix( fp, minepo );
+ fprintf( fp, "%s %d\n", ACMSTR( ACM_MINE_OWNER ),
+ minepo->Owner );
+}
+
+
+// save current number of mines -----------------------------------------------
+//
+PRIVATE
+void SaveNumMines( FILE *fp )
+{
+ ASSERT( fp != NULL );
+
+ fprintf( fp, "%s %d\n", ACMSTR( ACM_MS_NUMMINES ), MyShip->NumMines );
+}
+
+
+// record create event for mine object if recording active --------------------
+//
+void Record_MineCreation( MineObject *minepo )
+{
+ ASSERT( minepo != NULL );
+
+ if ( RecordingActive ) {
+ ASSERT( RecordingFp != NULL );
+ SaveWaitCommand();
+ SaveMineCreation( minepo, RecordingFp );
+ SaveNumMines( RecordingFp );
+ }
+}
+
+
+// write create event for extra object ----------------------------------------
+//
+PRIVATE
+void SaveExtraCreation( ExtraObject *extrapo, FILE *fp )
+{
+ ASSERT( extrapo != NULL );
+ ASSERT( fp != NULL );
+
+ fprintf( fp, "%s %d\n", ACMSTR( ACM_CREATEEXTRA ),
+ extrapo->ObjectClass );
+ SaveObjectMatrix( fp, extrapo );
+}
+
+
+// record create event for extra object if recording active -------------------
+//
+void Record_ExtraCreation( ExtraObject *extrapo )
+{
+ ASSERT( extrapo != NULL );
+
+ if ( RecordingActive ) {
+ ASSERT( RecordingFp != NULL );
+ SaveWaitCommand();
+ SaveExtraCreation( extrapo, RecordingFp );
+ }
+}
+
+
+// write create event for custom object ---------------------------------------
+//
+PRIVATE
+void SaveCustomCreation( CustomObject *custompo, FILE *fp )
+{
+ ASSERT( custompo != NULL );
+ ASSERT( fp != NULL );
+
+ // virtual objects must not be saved here
+ if ( custompo->ObjectClass != CLASS_ID_INVALID ) {
+
+ // check whether this custom type is persistent
+ int custtype_flags = OBJ_GetCustomTypeFlags( custompo->ObjectType );
+ if ( !( custtype_flags & CUSTOM_TYPE_NOT_PERSISTANT ) ) {
+ fprintf( fp, "%s %d\n", ACMSTR( ACM_CREATEOBJECT ), custompo->ObjectClass );
+ SaveObjectMatrix( fp, custompo );
+ }
+ }
+}
+
+
+// record create event for custom object if recording active ------------------
+//
+void Record_CustomCreation( CustomObject *custompo )
+{
+ ASSERT( custompo != NULL );
+
+ if ( RecordingActive ) {
+
+ // check whether this custom type is persistent
+ int custtype_flags = OBJ_GetCustomTypeFlags( custompo->ObjectType );
+ if ( !( custtype_flags & CUSTOM_TYPE_NOT_PERSISTANT ) ) {
+ ASSERT( RecordingFp != NULL );
+ SaveWaitCommand();
+ SaveCustomCreation( custompo, RecordingFp );
+ }
+ }
+}
+
+
+// record create event for energy field if recording active -------------------
+//
+void Record_EnergyFieldCreation( Vertex3& origin )
+{
+ if ( RecordingActive ) {
+ ASSERT( RecordingFp != NULL );
+ SaveWaitCommand();
+ fprintf( RecordingFp, "%s %f %f %f\n", ACMSTR( ACM_ENERGYFIELD ),
+ GEOMV_TO_FLOAT( origin.X ),
+ GEOMV_TO_FLOAT( origin.Y ),
+ GEOMV_TO_FLOAT( origin.Z ) );
+ }
+}
+
+
+// record create event for spreadfire particles if recording active -----------
+//
+void Record_SpreadFireFiring()
+{
+ if ( RecordingActive ) {
+ ASSERT( RecordingFp != NULL );
+ SaveWaitCommand();
+ fprintf( RecordingFp, "%s\n", ACMSTR( ACM_SPREADFIRE ) );
+ }
+}
+
+
+// record activation event of lightning beams if recording active -------------
+//
+void Record_LightningActivation()
+{
+ if ( RecordingActive ) {
+ ASSERT( RecordingFp != NULL );
+ SaveWaitCommand();
+ fprintf( RecordingFp, "%s\n", ACMSTR( ACM_LIGHTNINGON ) );
+ }
+}
+
+
+// record deactivation event of lightning beams if recording active -----------
+//
+void Record_LightningDeactivation()
+{
+ if ( RecordingActive ) {
+ ASSERT( RecordingFp != NULL );
+ SaveWaitCommand();
+ fprintf( RecordingFp, "%s\n", ACMSTR( ACM_LIGHTNINGOFF ) );
+ }
+}
+
+
+// record activation event of helix cannon if recording active ----------------
+//
+void Record_HelixActivation()
+{
+ if ( RecordingActive ) {
+ ASSERT( RecordingFp != NULL );
+ SaveWaitCommand();
+ fprintf( RecordingFp, "%s\n", ACMSTR( ACM_HELIXON ) );
+ }
+}
+
+
+// record deactivation event of helix cannon if recording active --------------
+//
+void Record_HelixDeactivation()
+{
+ if ( RecordingActive ) {
+ ASSERT( RecordingFp != NULL );
+ SaveWaitCommand();
+ fprintf( RecordingFp, "%s\n", ACMSTR( ACM_HELIXOFF ) );
+ }
+}
+
+
+// record activation event of photon cannon if recording active ---------------
+//
+void Record_PhotonActivation()
+{
+ if ( RecordingActive ) {
+ ASSERT( RecordingFp != NULL );
+ SaveWaitCommand();
+ fprintf( RecordingFp, "%s\n", ACMSTR( ACM_PHOTONON ) );
+ }
+}
+
+
+// record deactivation event of photon cannon if recording active -------------
+//
+void Record_PhotonDeactivation()
+{
+ if ( RecordingActive ) {
+ ASSERT( RecordingFp != NULL );
+ SaveWaitCommand();
+ fprintf( RecordingFp, "%s\n", ACMSTR( ACM_PHOTONOFF ) );
+ }
+}
+
+
+// record activation event of emp if recording active -------------------------
+//
+void Record_EmpActivation()
+{
+ if ( RecordingActive ) {
+ ASSERT( RecordingFp != NULL );
+ SaveWaitCommand();
+ fprintf( RecordingFp, "%s\n", ACMSTR( ACM_EMPON ) );
+ }
+}
+
+
+// record deactivation event of emp if recording active -------------
+//
+void Record_EmpDeactivation()
+{
+ if ( RecordingActive ) {
+ ASSERT( RecordingFp != NULL );
+ SaveWaitCommand();
+ fprintf( RecordingFp, "%s\n", ACMSTR( ACM_EMPOFF ) );
+ }
+}
+
+
+// record emp creation if recording active ------------------------------------
+//
+void Record_EmpCreation()
+{
+ if ( RecordingActive ) {
+ ASSERT( RecordingFp != NULL );
+ SaveWaitCommand();
+ fprintf( RecordingFp, "%s\n", ACMSTR( ACM_EMP ) );
+ }
+}
+
+
+// save shiplist in reversed order --------------------------------------------
+//
+PRIVATE
+void RecursiveShipListSave( FILE *fp, ShipObject *scanpo )
+{
+ ASSERT( fp != NULL );
+
+ if ( scanpo == NULL )
+ return;
+
+ RecursiveShipListSave( fp, (ShipObject *) scanpo->NextObj );
+
+ // in network game ships of remote players
+ // should not be saved here
+ if ( NetConnected ) {
+
+ //NOTE:
+ // inactive players and the local player need
+ // not be explicitly excluded here.
+
+ // check all players
+ for ( int id = 0; id < MAX_NET_PROTO_PLAYERS; id++ )
+ if ( Player_Ship[ id ] == scanpo )
+ return;
+ }
+
+ fprintf( fp, "%s %d\n", ACMSTR( ACM_CREATEOBJECT ),
+ scanpo->ObjectClass );
+
+#ifdef SAVE_OBJNUM_ON_SHIPLISTSAVE
+
+ fprintf( fp, "%s %d\n", ACMSTR( ACM_SETOBJECTID ),
+ scanpo->ObjectNumber );
+#endif
+
+ fprintf( fp, "%s %d\n", ACMSTR( ACM_SETOBJECTHOSTID ),
+ scanpo->HostObjNumber );
+
+ SaveObjectMatrix( fp, scanpo );
+}
+
+
+// save all objects contained in ship objects list ----------------------------
+//
+void Save_ObjListShips( FILE *fp )
+{
+ ASSERT( fp != NULL );
+
+ // save recursively to achieve same ordering on
+ // restoration as in the original
+ RecursiveShipListSave( fp, FetchFirstShip() );
+}
+
+
+// save all objects contained in laser objects list ----------------------------
+//
+void Save_ObjListLasers( FILE *fp )
+{
+ ASSERT( fp != NULL );
+
+ LaserObject *scanpo = FetchFirstLaser();
+ for ( ; scanpo; scanpo = (LaserObject *) scanpo->NextObj ) {
+ SaveLaserCreation( scanpo, fp );
+ }
+}
+
+
+// save all objects contained in missile objects list -------------------------
+//
+void Save_ObjListMissls( FILE *fp )
+{
+ ASSERT( fp != NULL );
+
+ MissileObject *scanpo = FetchFirstMissile();
+ for ( ; scanpo; scanpo = (MissileObject *) scanpo->NextObj ) {
+ SaveMissileCreation( scanpo, fp );
+ }
+}
+
+
+// save all objects contained in extra objects list ---------------------------
+//
+void Save_ObjListExtras( FILE *fp )
+{
+ ASSERT( fp != NULL );
+
+ ExtraObject *scanpo = FetchFirstExtra();
+ for ( ; scanpo; scanpo = (ExtraObject *) scanpo->NextObj ) {
+ if ( scanpo->ObjectType == MINE1TYPE )
+ SaveMineCreation( (MineObject *) scanpo, fp );
+ else
+ SaveExtraCreation( scanpo, fp );
+ }
+}
+
+
+// save all objects contained in custom objects list --------------------------
+//
+void Save_ObjListCustom( FILE *fp )
+{
+ ASSERT( fp != NULL );
+
+ CustomObject *scanpo = FetchFirstCustom();
+ for ( ; scanpo; scanpo = (CustomObject *) scanpo->NextObj ) {
+ SaveCustomCreation( scanpo, fp );
+ }
+}
+
+
+// save command to simulate depression of key ---------------------------------
+//
+PRIVATE
+void SaveKeyPress( int indx )
+{
+ ASSERT( ( indx >= 0 ) && ( (dword)indx < NUM_GAMEFUNC_KEYS ) );
+
+ if ( ( indx >= 0 ) && ( (dword)indx < NUM_GAMEFUNC_KEYS ) ) {
+ ASSERT( RecordingFp != NULL );
+ SaveWaitCommand();
+ fprintf( RecordingFp, "%s %d\n", ACMSTR( ACM_SIMULKEYPRESS ), indx );
+ }
+}
+
+
+// save command to simulate depression of key ---------------------------------
+//
+PRIVATE
+void SaveKeyRelease( int indx )
+{
+ ASSERT( ( indx >= 0 ) && ( (dword)indx < NUM_GAMEFUNC_KEYS ) );
+
+ if ( ( indx >= 0 ) && ( (dword)indx < NUM_GAMEFUNC_KEYS ) ) {
+ ASSERT( RecordingFp != NULL );
+ SaveWaitCommand();
+ fprintf( RecordingFp, "%s %d\n", ACMSTR( ACM_SIMULKEYRELEASE ), indx );
+ }
+}
+
+
+// record firing of weapons if recording active -------------------------------
+//
+void REC_RecordFiring()
+{
+ //NOTE:
+ // this function is called by INP_USER::INP_UserCheckFiring()
+ // to record firing of weapons literally (not just by recording
+ // the resulting object creations).
+ //
+ // NOT USED AT THE MOMENT.
+
+ if ( RecordingActive ) {
+ }
+}
+
+
+// record key depressions of specific keys if recording active ----------------
+//
+void REC_RecordKeys()
+{
+ //NOTE:
+ // this function is called by INP_USER::INP_UserProcessInput()
+ // to record special keystrokes literally (that is, where not
+ // just their results are recorded).
+
+ if ( RecordingActive ) {
+
+ //NOTE:
+ // if the object camera is deactivated by pressing
+ // <ESC> instead of the activation key, this will
+ // also be recorded here, because this keydown event
+ // will be translated by G_MAIN::SpecialKeyFunctions().
+
+ size_t msize = sizeof(DepressedKeys->key_Escape);
+
+ // activate/deactive object camera
+ if ( DepressedKeys->key_ToggleObjCamera ) {
+ SaveKeyPress( offsetof( keyfunc_s, key_ToggleObjCamera ) / msize );
+ }
+
+ // cycle weapons
+ if ( DepressedKeys->key_NextWeapon ) {
+ SaveKeyPress( offsetof( keyfunc_s, key_NextWeapon ) / msize );
+ }
+
+ // cycle missiles
+ if ( DepressedKeys->key_NextMissile ) {
+ SaveKeyPress( offsetof( keyfunc_s, key_NextMissile ) / msize );
+ }
+
+ // cycle targets
+ if ( DepressedKeys->key_NextTarget ) {
+ SaveKeyPress( offsetof( keyfunc_s, key_NextTarget ) / msize );
+ }
+
+ //NOTE:
+ // if the object camera is active, zooming in and out
+ // have to be handled separately, since they are not
+ // tied to the local ship's speed.
+
+ if ( ObjCameraActive ) {
+
+static int acc_active = FALSE;
+ if ( DepressedKeys->key_Accelerate ) {
+ SaveKeyPress( offsetof( keyfunc_s, key_Accelerate ) / msize );
+ acc_active = TRUE;
+ } else if ( acc_active ) {
+ SaveKeyRelease( offsetof( keyfunc_s, key_Accelerate ) / msize );
+ acc_active = FALSE;
+ }
+
+static int dec_active = FALSE;
+ if ( DepressedKeys->key_Decelerate ) {
+ SaveKeyPress( offsetof( keyfunc_s, key_Decelerate ) / msize );
+ dec_active = TRUE;
+ } else if ( dec_active ) {
+ SaveKeyRelease( offsetof( keyfunc_s, key_Decelerate ) / msize );
+ dec_active = FALSE;
+ }
+ }
+
+ }
+}
+
+// variable to holde the stride in packets ( either 546 or 1000 bytes ) -------
+//
+static int prev_session = -1;
+static size_t packet_read_stride = RECORD_PACKET_SIZE;
+
+// create filename for remote packet data -------------------------------------
+//
+PRIVATE
+void CreatePacketName( char *packetname, int session, int packet, int write )
+{
+ ASSERT( packetname != NULL );
+ ASSERT( session >= 0 );
+ ASSERT( packet >= 0 );
+
+ if ( AUX_RECORD_PACKETS_INTO_SESSION ) {
+
+ //NOTE:
+ // each recording session is stored into its own file.
+ // the filename for session 5 would be "RPACK005.PRP".
+ // this function also returns the offset in this file
+ // at which the packet may be found/should be stored.
+ // (packets are simply stored as array in the file.)
+
+ strcpy( packetname, rpacket_basename );
+ DIG3_TO_STR( packetname + strlen( rpacket_basename ), session );
+ packetname[ strlen( rpacket_basename ) + 3 ] = 0;
+ strcat( packetname, rpacket_extension );
+
+ } else {
+
+ //NOTE:
+ // all packets of a recording session are stored into
+ // a file named like the generated console script.
+ // so, if the console script's name is "FLIGHT.CON",
+ // the corresponding packets will be stored in
+ // "FLIGHT.PRP".
+
+ // just use the current recording's name
+ strcpy( packetname, write ? packet_record_name : packet_replay_name );
+ strcat( packetname, rpacket_extension );
+ }
+}
+
+// load previously saved remote packet ----------------------------------------
+//
+PRIVATE
+int LoadPacket( NetPacketExternal* ext_gamepacket, int session, int packet )
+{
+ ASSERT( ext_gamepacket != NULL );
+ ASSERT( session >= 0 );
+ ASSERT( packet >= 0 );
+
+ // create name of packet file
+ char packetname[ PATH_MAX + 1 ];
+ CreatePacketName( packetname, session, packet, FALSE );
+
+ // search paths
+ FILE *fp = fopen( packetname, "rb" );
+ if ( fp == NULL ) {
+ char pathname[ PATH_MAX + 1 ];
+ strcpy( pathname, REFCON_COMMANDS_DIR );
+ strcat( pathname, packetname );
+ fp = fopen( pathname, "rb" );
+ if ( fp == NULL ) {
+ strcpy( pathname, STDCON_COMMANDS_DIR );
+ strcat( pathname, packetname );
+ fp = fopen( pathname, "rb" );
+ if ( fp == NULL ) {
+ strcpy( pathname, RECORD_COMMANDS_DIR );
+ strcat( pathname, packetname );
+ fp = fopen( pathname, "rb" );
+ }
+ }
+ }
+
+ // load packet data
+ if ( fp != NULL ) {
+
+ // check whether this session contains packets with IPX length
+ if ( prev_session != session ) {
+ ASSERT( packet == 0 );
+
+ // only read signature
+ fseek( fp, 0, SEEK_SET );
+ size_t bytesread = fread( ext_gamepacket, 1, PACKET_SIGNATURE_SIZE, fp );
+ if ( bytesread != PACKET_SIGNATURE_SIZE ) {
+ return FALSE;
+ }
+
+ // check for < 0198 signature
+ extern const char net_game_signature[];
+ if ( strncmp( ext_gamepacket->Signature, net_game_signature, PACKET_SIGNATURE_SIZE ) == 0 ) {
+ // set packet read stride to IPX length, this was used in all demos prior to 0198
+ packet_read_stride = NET_IPX_DATA_LENGTH;
+ } else {
+ packet_read_stride = RECORD_PACKET_SIZE;
+ }
+
+ prev_session = session;
+ }
+
+ // calculate seek position based on packet# and stride
+ long seekpos = ( packet * packet_read_stride );
+
+ fseek( fp, seekpos, SEEK_SET );
+ size_t bytesread = fread( ext_gamepacket, 1, packet_read_stride, fp );
+ fclose( fp );
+ int readok = ( bytesread == packet_read_stride );
+ if ( !readok )
+ MSGOUT( "E_RECORD::LoadPacket(): read error." );
+
+ return readok;
+ }
+
+ return FALSE;
+}
+
+
+// save entire remote packet as binary file -----------------------------------
+//
+PRIVATE
+int SavePacket( NetPacketExternal* ext_gamepacket, int session, int packet )
+{
+ ASSERT( ext_gamepacket != NULL );
+ ASSERT( session >= 0 );
+ ASSERT( packet >= 0 );
+
+ // create name of packet file
+ char packetname[ PATH_MAX + 1 ];
+ CreatePacketName( packetname, session, packet, TRUE );
+
+ // recorded packets always have a stride of RECORD_PACKET_SIZE
+ long seekpos = ( packet * RECORD_PACKET_SIZE );
+
+ // save packet data
+ FILE *fp = fopen( packetname, ( seekpos > 0 ) ? "ab" : "wb" );
+ if ( fp != NULL ) {
+ fseek( fp, seekpos, SEEK_SET );
+ size_t byteswritten = fwrite( ext_gamepacket, 1, RECORD_PACKET_SIZE, fp );
+ fclose( fp );
+ int writeok = ( byteswritten == RECORD_PACKET_SIZE );
+ if ( !writeok )
+ MSGOUT( "SavePacket(): write error." );
+ return writeok;
+ }
+
+ return FALSE;
+}
+
+// return whether we are currently recording packets --------------------------
+//
+int REC_IsRecordingPackets()
+{
+ return ( RecordingActive && RecordRemotePackets );
+}
+
+
+// this gets called whenever a valid remote packet is received ----------------
+//
+void REC_RecordRemotePacket( NetPacketExternal* ext_gamepacket )
+{
+ //NOTE:
+ // this function is called by the low-level network code
+ // whenever a packet is inserted into the list of valid
+ // received packets. for example NET_UDP::ChainPacket().
+
+ ASSERT( ext_gamepacket != NULL );
+
+ if ( RecordingActive && RecordRemotePackets ) {
+
+ ASSERT( RecordingFp != NULL );
+
+ // save packet data
+ if ( !SavePacket( ext_gamepacket, RemoteRecSessionId, RemoteRecPacketId ) ) {
+ MSGOUT( "remote packet %d.%d not saved.", RemoteRecSessionId, RemoteRecPacketId );
+ fprintf( RecordingFp, ";*** packet %d.%d not saved.\n", RemoteRecSessionId, RemoteRecPacketId );
+ return;
+ }
+
+ // save batch command
+ SaveWaitCommand();
+ fprintf( RecordingFp, "%s %d %d\n", ACMSTR( ACM_PACKET ), RemoteRecSessionId, RemoteRecPacketId );
+
+#ifdef PACKETINFO_WRITING_AVAILABLE
+
+ if ( AUX_RECORD_VERBOSE_PACKETS )
+ NETs_WritePacketInfo( RecordingFp, ext_gamepacket );
+#endif
+ // increment packet number
+ RemoteRecPacketId++;
+ }
+}
+
+
+// read and replay remote packet from session file ----------------------------
+//
+void REC_PlayRemotePacket( int session, int packetno )
+{
+ //NOTE:
+ // this function is called by CON_ACT::AcPlayRemotePacket()
+ // to play back a remote packet identified by its session
+ // and packet id. the packet is read from the corresponding
+ // session file by this function.
+
+ ASSERT( session >= 0 );
+ ASSERT( packetno >= 0 );
+
+ //NOTE:
+ // simulation will not be enabled for script replay if actual
+ // network not available. (in contrast to binary replay.)
+
+ if ( !NetConnected ) {
+ MSGOUT( "REC_PlayRemotePacket(): no net (packet %d.%d).", session, packetno );
+ return;
+ }
+ ASSERT( NetConnected == NETWORK_GAME_ON );
+
+ NetPacketExternal* ext_gamepacket = (NetPacketExternal*) ALLOCMEM( RECORD_PACKET_SIZE );
+
+ if ( ext_gamepacket != NULL ) {
+ if ( LoadPacket( ext_gamepacket, session, packetno ) ) {
+
+ // reuse binary playback
+ REC_PlayRemotePacketBin( ext_gamepacket );
+
+ } else {
+ MSGOUT( "remote packet %d.%d not found (replay).", session, packetno );
+ }
+ }
+
+ //NOTE:
+ // free the external packet, as the NETs_HandleInPacket_DEMO in REC_PlayRemotePacketBin already
+ // makes a copy of the packet
+ FREEMEM( ext_gamepacket );
+}
+
+
+// determines whether packet signature should also be stripped ----------------
+//
+#define STRIP_HEAD_SIZE PACKET_SIGNATURE_SIZE // strip it
+//#define STRIP_HEAD_SIZE 0 // don't strip
+
+
+// replay remote packet from compiled demo ------------------------------------
+//
+size_t REC_PlayRemotePacketBin( NetPacketExternal* ext_gamepacket )
+{
+ //NOTE:
+ // this function is called by CON_ACT::ExecBinCommands() to
+ // play back a remote packet coming from a binary demo file.
+ // Also reused by E_RECORD::REC_PlayRemotePacket
+
+ ASSERT( NetConnected );
+ ASSERT( ext_gamepacket != NULL );
+
+ NetPacket* gamepacket = (NetPacket*)ALLOCMEM( NET_MAX_NETPACKET_INTERNAL_LEN );
+
+ size_t psize = 0;
+
+ // translate from external DEMO to internal, return size of external packet
+ if ( NETs_HandleInPacket_DEMO( ext_gamepacket, gamepacket, &psize ) ) {
+
+ // chain gamepacket as received
+ NETs_InsertVirtualPacket( gamepacket );
+ }
+
+ //NOTE:
+ // gamepacket MUST not be freed here, as this is done by e.g. NET_UDP::ReleasePacketFromChain
+ // NO FREEMEM( gamepacket );
+
+ ASSERT( psize > 0 );
+ return psize;
+}
+
+
+// fetch remote packet according to session and packet ids --------------------
+//
+size_t REC_FetchRemotePacket( NetPacketExternal* ext_gamepacket, int session, int packetno )
+{
+ //NOTE:
+ // this function is called by CON_ACT::WriteActionCommand() to
+ // fetch a remote packet for insertion into a binary demo file.
+
+ ASSERT( ext_gamepacket != NULL );
+ ASSERT( session >= 0 );
+ ASSERT( packetno >= 0 );
+
+ if ( ext_gamepacket != NULL ) {
+ if ( LoadPacket( ext_gamepacket, session, packetno ) ) {
+
+#ifdef STRIP_BINARY_PACKETS
+
+ //FIXME: DEMO packet header compression should be done when converting
+ // from normal packets to DEMO packets in NETs_HandleOutPacket_DEMO
+
+ // return only used part of packet ( strip excess zero bytes )
+ size_t psize = NETs_NetPacketExternal_DEMO_GetSize( ext_gamepacket );
+ return psize;
+#else
+ // return entire packet
+ return RECORD_PACKET_SIZE;
+#endif
+ } else {
+ MSGOUT( "remote packet %d.%d not found (compile).", session, packetno );
+ }
+ }
+
+ return 0;
+}
+
+
+// this gets called whenever a tcp message from the gameserver is received ----
+//
+void REC_RecordServerMessage( char *message )
+{
+ ASSERT( message != NULL );
+
+ if ( RecordingActive ) {
+
+ ASSERT( RecordingFp != NULL );
+
+ // save batch command
+ if ( message[ 0 ] != '\0' ) {
+ fprintf( RecordingFp, "%s %s\n", ACMSTR( ACM_SERVERMSG ), message );
+ }
+ }
+}
+
+
+// save matrices to record later on -------------------------------------------
+//
+void REC_InitMatrices()
+{
+ //NOTE:
+ // this function is used to remember certain matrices that
+ // have to be saved in this state but will already have
+ // been modified when they are actually saved to file.
+
+//FIXME:
+//need this also for demo cutting
+// if ( RecordingActive ) {
+ memcpy( &RecordShipViewCamera, &ShipViewCamera, sizeof( Camera ) );
+// }
+}
+
+
+// write command to reestablish current rotation of camera --------------------
+//
+PRIVATE
+void WriteCameraRotation( FILE *fp )
+{
+ //NOTE:
+ // this function is also used by REC_RecordActions()
+ // if absolute recording is turned on.
+
+ ASSERT( fp != NULL );
+
+ fprintf( fp, "%s %f %f %f\n", ACMSTR( ACM_CAMROT1 ),
+ GEOMV_TO_FLOAT( RecordShipViewCamera[ 0 ][ 0 ] ),
+ GEOMV_TO_FLOAT( RecordShipViewCamera[ 1 ][ 0 ] ),
+ GEOMV_TO_FLOAT( RecordShipViewCamera[ 2 ][ 0 ] ) );
+ fprintf( fp, "%s %f %f %f\n", ACMSTR( ACM_CAMROT2 ),
+ GEOMV_TO_FLOAT( RecordShipViewCamera[ 0 ][ 1 ] ),
+ GEOMV_TO_FLOAT( RecordShipViewCamera[ 1 ][ 1 ] ),
+ GEOMV_TO_FLOAT( RecordShipViewCamera[ 2 ][ 1 ] ) );
+ fprintf( fp, "%s %f %f %f\n", ACMSTR( ACM_CAMROT3 ),
+ GEOMV_TO_FLOAT( RecordShipViewCamera[ 0 ][ 2 ] ),
+ GEOMV_TO_FLOAT( RecordShipViewCamera[ 1 ][ 2 ] ),
+ GEOMV_TO_FLOAT( RecordShipViewCamera[ 2 ][ 2 ] ) );
+}
+
+
+// write command to reestablish current origin of camera ----------------------
+//
+PRIVATE
+void WriteCameraOrigin( FILE *fp )
+{
+ //NOTE:
+ // this function is also used by REC_RecordActions()
+ // if absolute recording is turned on.
+
+ ASSERT( fp != NULL );
+
+ fprintf( fp, "%s %f %f %f\n", ACMSTR( ACM_CAMORIGIN ),
+ GEOMV_TO_FLOAT( RecordShipViewCamera[ 0 ][ 3 ] ),
+ GEOMV_TO_FLOAT( RecordShipViewCamera[ 1 ][ 3 ] ),
+ GEOMV_TO_FLOAT( RecordShipViewCamera[ 2 ][ 3 ] ) );
+}
+
+
+// save camera position and orientation ---------------------------------------
+//
+void Save_Camera( FILE *fp )
+{
+ ASSERT( fp != NULL );
+
+ WriteCameraRotation( fp );
+ WriteCameraOrigin( fp );
+}
+
+
+// save pseudo star positions -------------------------------------------------
+//
+void Save_PseudoStars( FILE *fp )
+{
+ ASSERT( fp != NULL );
+
+ for ( int sid = 0; sid < NumPseudoStars; sid++ ) {
+
+ fprintf( fp, "%s %f %f %f\n", ACMSTR( ACM_ADDPSTAR ),
+ GEOMV_TO_FLOAT( PseudoStars[ sid ].X ),
+ GEOMV_TO_FLOAT( PseudoStars[ sid ].Y ),
+ GEOMV_TO_FLOAT( PseudoStars[ sid ].Z ) );
+ }
+}
+
+
+// use same commands for camera and object camera -----------------------------
+//
+#define ACM_OBJCAMPITCH ACM_PITCH
+#define ACM_OBJCAMYAW ACM_YAW
+#define ACM_OBJCAMROLL ACM_ROLL
+
+
+// write action command if pitch changed --------------------------------------
+//
+INLINE
+int CheckPitch( int write )
+{
+ int command = ObjCameraActive ? ACM_OBJCAMPITCH : ACM_PITCH;
+
+ if ( LastPitch != RecPitch ) {
+ if ( write ) {
+ LastPitch = RecPitch;
+ fprintf( RecordingFp, "%s %d\n", ACMSTR( command ),
+ -LastPitch );
+ }
+ return 1;
+ }
+
+ return 0;
+}
+
+
+// write action command if yaw changed ----------------------------------------
+//
+INLINE
+int CheckYaw( int write )
+{
+ int command = ObjCameraActive ? ACM_OBJCAMYAW : ACM_YAW;
+
+ if ( LastYaw != RecYaw ) {
+ if ( write ) {
+ LastYaw = RecYaw;
+ fprintf( RecordingFp, "%s %d\n", ACMSTR( command ),
+ -LastYaw );
+ }
+ return 1;
+ }
+
+ return 0;
+}
+
+
+// write action command if roll changed ---------------------------------------
+//
+INLINE
+int CheckRoll( int write )
+{
+ int command = ObjCameraActive ? ACM_OBJCAMROLL : ACM_ROLL;
+
+ if ( LastRoll != RecRoll ) {
+ if ( write ) {
+ LastRoll = RecRoll;
+ fprintf( RecordingFp, "%s %d\n", ACMSTR( command ),
+ -LastRoll );
+ }
+ return 1;
+ }
+
+ return 0;
+}
+
+
+// write action command if horizontal slide changed ---------------------------
+//
+INLINE
+int CheckSlideHorz( int write )
+{
+ if ( !ObjCameraActive ) {
+
+ if ( LastSlideHorz != RecSlideHorz ) {
+ if ( write ) {
+ LastSlideHorz = RecSlideHorz;
+ fprintf( RecordingFp, "%s %f\n", ACMSTR( ACM_SLIDEX ),
+ GEOMV_TO_FLOAT( LastSlideHorz ) );
+ }
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+
+// write action command if horizontal slide changed ---------------------------
+//
+INLINE
+int CheckSlideVert( int write )
+{
+ if ( !ObjCameraActive ) {
+
+ if ( LastSlideVert != RecSlideVert ) {
+ if ( write ) {
+ LastSlideVert = RecSlideVert;
+ fprintf( RecordingFp, "%s %f\n", ACMSTR( ACM_SLIDEY ),
+ GEOMV_TO_FLOAT( LastSlideVert ) );
+ }
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+
+// write action command if speed changed --------------------------------------
+//
+INLINE
+int CheckSpeed( int write )
+{
+ if ( LastSpeed != MyShip->CurSpeed ) {
+ if ( write ) {
+ LastSpeed = MyShip->CurSpeed;
+ fprintf( RecordingFp, "%s %d\n", ACMSTR( ACM_MOVE ),
+ LastSpeed );
+ }
+ return 1;
+ }
+
+ return 0;
+}
+
+
+// record changes into batch file if recording active -------------------------
+//
+void REC_RecordActions()
+{
+ //NOTE:
+ // this function is called from within the gameloop
+ // (G_MAIN::GameLoop()) to record the user's actions
+ // (motion) since the last frame.
+
+ if ( !RecordingActive )
+ return;
+
+ ASSERT( RecordingFp != NULL );
+
+ int rotchange = 0;
+
+ // check if pitch/yaw/roll have changed
+ // since they were last saved (just check)
+ rotchange += CheckPitch( FALSE );
+ rotchange += CheckYaw( FALSE );
+ rotchange += CheckRoll( FALSE );
+
+ // check if absolute values should be recorded regardless of change
+ if ( RecordAbsolutePosition && !ObjCameraActive ) {
+ // if nothing at all going on still don't record anything
+ if ( ( RecPitch != 0 ) || ( RecYaw != 0 ) || ( RecRoll != 0 ) )
+ rotchange += 1;
+ }
+
+ int poschange = 0;
+
+ // check if position has changed
+ // since it was last saved
+ poschange += CheckSlideHorz( FALSE );
+ poschange += CheckSlideVert( FALSE );
+ poschange += CheckSpeed( FALSE );
+
+ if ( rotchange || poschange ) {
+
+ // write idle wait if not already done
+ SaveWaitCommand();
+
+ // save pitch/yaw/roll to file if they have
+ // changed and then remember them internally
+ CheckPitch( TRUE );
+ CheckYaw( TRUE );
+ CheckRoll( TRUE );
+
+ // save absolute values to file if desired
+ if ( RecordAbsolutePosition && !ObjCameraActive ) {
+ WriteCameraRotation( RecordingFp );
+ WriteCameraOrigin( RecordingFp );
+ }
+
+ // save position changes to file
+ CheckSlideHorz( TRUE );
+ CheckSlideVert( TRUE );
+ CheckSpeed( TRUE );
+ }
+
+ // clear flag
+ idlewait_inserted = FALSE;
+
+ // increase duration count
+ IdleDuration += CurScreenRefFrames;
+}
+
+
+