Page Menu
Home
Phabricator (Chris)
Search
Configure Global Search
Log In
Files
F131988
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
110 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/Backend.cpp b/src/Backend.cpp
index 7febcf0..469487d 100644
--- a/src/Backend.cpp
+++ b/src/Backend.cpp
@@ -1,474 +1,476 @@
/***************************************************************************
Backend.cpp - description
-------------------
begin : Mon Dec 9 2002
copyright : (C) 2002 by upi
email : upi@apocalypse.rulez.org
***************************************************************************/
#include "common.h"
#include "Backend.h"
#include "Audio.h"
#include <string>
#include <stdarg.h>
#include "MszPerl.h"
/***************************************************************************
PUBLIC EXPORTED VARIABLES
***************************************************************************/
PerlInterpreter* my_perl;
Backend g_oBackend;
/***************************************************************************
PRIVATE VARIABLES (perl variable space)
***************************************************************************/
SV
*perl_bgx, *perl_bgy,
*perl_p1x, *perl_p1y, *perl_p1f, *perl_p1h,
*perl_p2x, *perl_p2y, *perl_p2f, *perl_p2h,
- *perl_over, *perl_ko;
+ *perl_gametick, *perl_over, *perl_ko;
SV
*perl_doodad_x, *perl_doodad_y,
*perl_doodad_t, *perl_doodad_f,
*perl_doodad_dir, *perl_doodad_gfxowner,
*perl_doodad_text;
SV
*perl_sound, *perl_Translated;
/***************************************************************************
TRANSLATION SERVICES
***************************************************************************/
const char* Translate( const char* a_pcText )
{
dSP ;
ENTER ;
SAVETMPS ;
PUSHMARK(SP) ;
XPUSHs(sv_2mortal(newSVpv(a_pcText, 0)));
PUTBACK ;
call_pv("Translate", G_DISCARD);
FREETMPS ;
LEAVE ;
if ( NULL == perl_Translated )
{
perl_Translated = get_sv("Translated", TRUE);
}
return SvPV_nolen( perl_Translated );
}
const char* TranslateUTF8( const char* a_pcText )
{
dSP ;
ENTER ;
SAVETMPS ;
PUSHMARK(SP) ;
XPUSHs(sv_2mortal(newSVpv(a_pcText, 0)));
PUTBACK ;
call_pv("Translate", G_DISCARD);
FREETMPS ;
LEAVE ;
if ( NULL == perl_Translated )
{
perl_Translated = get_sv("Translated", TRUE);
}
return SvPVutf8_nolen( perl_Translated );
}
/***************************************************************************
BACKEND CLASS IMPLEMENTATION
***************************************************************************/
#define PERLEVAL(A) eval_pv(A, TRUE);
#define PERLCALL(PROC,A,B) { \
dSP; \
ENTER; \
SAVETMPS; \
PUSHMARK(SP); \
XPUSHs(sv_2mortal(newSViv(A))); \
XPUSHs(sv_2mortal(newSViv(B))); \
PUTBACK ; \
\
call_pv( (PROC), G_DISCARD ); \
\
FREETMPS; \
LEAVE; \
}
Backend::Backend()
{
m_iBgX = m_iBgY = 0;
m_iNumDoodads = m_iNumSounds = 0;
for ( int i=0; i<2; ++i )
{
m_aoPlayers[i].m_iX = m_aoPlayers[i].m_iY = 0;
m_aoPlayers[i].m_iFrame = 0;
m_aoPlayers[i].m_iHitPoints = 0;
}
}
Backend::~Backend()
{
if ( NULL != my_perl )
{
perl_destruct( my_perl );
perl_free( my_perl );
my_perl = NULL;
}
}
bool Backend::Construct()
{
if ( my_perl != NULL )
{
// Already inited
return false;
}
perl_bgx = NULL;
perl_doodad_x = NULL;
std::string sFileName = DATADIR;
sFileName += "/script";
#ifndef _WINDOWS
chdir( sFileName.c_str() );
#endif
char *perl_argv[] = {"", "Backend.pl"};
my_perl = perl_alloc();
if ( my_perl == NULL )
{
return false;
}
perl_construct( my_perl );
if ( perl_parse( my_perl, NULL, 2, perl_argv, (char**)NULL ) )
{
char *error = SvPV_nolen(get_sv("@", FALSE));
fprintf( stderr, "%s", error );
return false;
}
if ( perl_run( my_perl ) )
{
char *error = SvPV_nolen(get_sv("@", FALSE));
fprintf( stderr, "%s", error );
return false;
}
return true;
}
const char* Backend::PerlEvalF( const char* a_pcFormat, ... )
{
va_list ap;
va_start( ap, a_pcFormat );
char acBuffer[1024];
vsnprintf( acBuffer, 1023, a_pcFormat, ap );
acBuffer[1023] = 0;
PERLEVAL(acBuffer);
const char *pcError = SvPV_nolen(get_sv("@", FALSE));
if ( pcError )
{
fprintf( stderr, "%s", pcError );
}
va_end( ap );
return pcError;
}
const char* Backend::GetPerlString( const char* acScalarName )
{
SV* poScalar = get_sv( acScalarName, FALSE );
if ( NULL == poScalar )
{
return "";
}
return SvPV_nolen( poScalar );
}
int Backend::GetPerlInt( const char* acScalarName )
{
SV* poScalar = get_sv( acScalarName, FALSE );
if ( NULL == poScalar )
{
return 0;
}
return SvIV( poScalar );
}
/** Returns the total number of registered fighters in the backend.
This may be more than the actual number of playable characters, as
some or many characters may not be ready or installed.
\see GetFighterID
*/
int Backend::GetNumberOfFighters()
{
PerlEvalF( "$::CppNumberOfFighters = scalar keys %::FighterStats;" );
return GetPerlInt( "CppNumberOfFighters" );
}
/** Returns the ID of a fighter. The index parameter should start from
zero, and be less than the value returned by GetNumberOfFighters().
\see GetNumberOfFighters
*/
FighterEnum Backend::GetFighterID( int a_iIndex )
{
PerlEvalF( "$::CppFighterID = (sort { $a - $b } keys %%::FighterStats)[%d];", a_iIndex );
return (FighterEnum) GetPerlInt( "CppFighterID" );
}
void Backend::AdvancePerl()
{
PERLEVAL("GameAdvance();");
}
void Backend::ReadFromPerl()
{
if ( perl_bgx == NULL )
{
+ perl_gametick = get_sv("gametick", TRUE);
perl_bgx = get_sv("bgx", TRUE);
perl_bgy = get_sv("bgy", TRUE);
perl_p1x = get_sv("p1x", TRUE);
perl_p1y = get_sv("p1y", TRUE);
perl_p1f = get_sv("p1f", TRUE);
perl_p1h = get_sv("p1h", TRUE);
perl_p2x = get_sv("p2x", TRUE);
perl_p2y = get_sv("p2y", TRUE);
perl_p2f = get_sv("p2f", TRUE);
perl_p2h = get_sv("p2h", TRUE);
perl_over= get_sv("over", TRUE);
perl_ko = get_sv("ko", TRUE);
}
+ m_iGameTick = SvIV( perl_gametick );
m_iBgX = SvIV( perl_bgx );
m_iBgY = SvIV( perl_bgy );
m_aoPlayers[0].m_iX = SvIV( perl_p1x );
m_aoPlayers[0].m_iY = SvIV( perl_p1y );
m_aoPlayers[0].m_iFrame = SvIV( perl_p1f );
m_aoPlayers[0].m_iHitPoints = SvIV( perl_p1h ) / 10;
m_aoPlayers[1].m_iX = SvIV( perl_p2x );
m_aoPlayers[1].m_iY = SvIV( perl_p2y );
m_aoPlayers[1].m_iFrame = SvIV( perl_p2f );
m_aoPlayers[1].m_iHitPoints = SvIV( perl_p2h ) / 10;
m_iGameOver = SvIV( perl_over );
m_bKO = SvIV( perl_ko );
// READ DOODAD DATA
if ( perl_doodad_x == NULL )
{
perl_doodad_x = get_sv("doodad_x", TRUE);
perl_doodad_y = get_sv("doodad_y", TRUE);
perl_doodad_t = get_sv("doodad_t", TRUE);
perl_doodad_f = get_sv("doodad_f", TRUE);
perl_doodad_dir = get_sv("doodad_dir", TRUE);
perl_doodad_gfxowner = get_sv("doodad_gfxowner", TRUE);
perl_doodad_text = get_sv("doodad_text", TRUE);
}
for ( m_iNumDoodads=0; m_iNumDoodads<MAXDOODADS; ++m_iNumDoodads )
{
PERLEVAL("GetNextDoodadData();");
SDoodad& oDoodad = m_aoDoodads[m_iNumDoodads];
oDoodad.m_iType = SvIV(perl_doodad_t);
if ( oDoodad.m_iType < 0 )
{
break;
}
oDoodad.m_iX = SvIV(perl_doodad_x);
oDoodad.m_iY = SvIV(perl_doodad_y);
oDoodad.m_iFrame = SvIV(perl_doodad_f);
oDoodad.m_iDir = SvIV(perl_doodad_dir);
oDoodad.m_iGfxOwner = SvIV(perl_doodad_gfxowner);
if ( oDoodad.m_iType == 0 )
{
oDoodad.m_sText = SvPV_nolen(perl_doodad_text);
}
else
{
oDoodad.m_sText = "";
}
}
// READ SOUND DATA
if ( perl_sound == NULL )
{
perl_sound = get_sv("sound", TRUE);
}
for ( m_iNumSounds=0; m_iNumSounds<MAXSOUNDS; ++m_iNumSounds )
{
PERLEVAL("GetNextSoundData();");
const char* pcSound = SvPV_nolen(perl_sound);
if ( NULL == pcSound
|| 0 == *pcSound )
{
break;
}
m_asSounds[ m_iNumSounds ] = pcSound;
//Audio->PlaySample( pcSound );
}
}
void Backend::PlaySounds()
{
for ( int i=0; i<m_iNumSounds; ++i )
{
Audio->PlaySample( m_asSounds[i].c_str() );
}
}
/***************************************************************************
PLAYBACK STRING CONVERSION ROUTINES
***************************************************************************/
void Backend::WriteToString( std::string& a_rsOutString )
{
char acBuffer[2048];
int iNumChars = sprintf( acBuffer, "%d %d %d %d %d %d %d %d %d %d %d ",
m_iBgX, m_iBgY,
m_aoPlayers[0].m_iX, m_aoPlayers[0].m_iY, m_aoPlayers[0].m_iFrame, m_aoPlayers[0].m_iHitPoints,
m_aoPlayers[1].m_iX, m_aoPlayers[1].m_iY, m_aoPlayers[1].m_iFrame, m_aoPlayers[1].m_iHitPoints,
m_iNumDoodads );
int i;
for ( i = 0; i<m_iNumDoodads; ++i )
{
SDoodad& roDoodad = m_aoDoodads[i];
iNumChars += sprintf( acBuffer+iNumChars, "%d %d %d %d %d %d %d %s ",
roDoodad.m_iX, roDoodad.m_iY, roDoodad.m_iType, roDoodad.m_iFrame,
roDoodad.m_iDir, roDoodad.m_iGfxOwner,
roDoodad.m_sText.size(), roDoodad.m_sText.c_str() );
}
iNumChars += sprintf( acBuffer+iNumChars, "%d ", m_iNumSounds );
for ( i = 0; i<m_iNumSounds; ++i )
{
iNumChars += sprintf( acBuffer+iNumChars, " %d %s",
m_asSounds[i].size(), m_asSounds[i].c_str() );
}
// debug( "Frame: '%s'\n", acBuffer );
a_rsOutString = acBuffer;
}
void Backend::ReadFromString( const std::string& a_rsString )
{
ReadFromString( a_rsString.c_str() );
}
void Backend::ReadFromString( const char* a_pcBuffer )
{
if ( strlen( a_pcBuffer ) < 10 )
{
m_iNumDoodads = m_iNumSounds = 0;
return;
}
int iNumMatches;
int iOffset, iTotal;
iNumMatches = sscanf( a_pcBuffer, "%d %d %d %d %d %d %d %d %d %d %d%n",
&m_iBgX, &m_iBgY,
&m_aoPlayers[0].m_iX, &m_aoPlayers[0].m_iY, &m_aoPlayers[0].m_iFrame, &m_aoPlayers[0].m_iHitPoints,
&m_aoPlayers[1].m_iX, &m_aoPlayers[1].m_iY, &m_aoPlayers[1].m_iFrame, &m_aoPlayers[1].m_iHitPoints,
&m_iNumDoodads, &iTotal );
if ( m_iNumDoodads > MAXDOODADS )
{
m_iNumDoodads = m_iNumSounds = 0;
return;
}
int i, j;
for ( i=0; i<m_iNumDoodads; ++i )
{
SDoodad& roDoodad = m_aoDoodads[i];
iNumMatches += sscanf( a_pcBuffer+iTotal, "%d %d %d %d %d %d %d %n",
&roDoodad.m_iX, &roDoodad.m_iY, &roDoodad.m_iType, &roDoodad.m_iFrame,
&roDoodad.m_iDir, &roDoodad.m_iGfxOwner,
&j, &iOffset );
iTotal += iOffset;
roDoodad.m_sText.assign( a_pcBuffer + iTotal, j );
iTotal += j;
}
iNumMatches += sscanf( a_pcBuffer + iTotal, "%d%n",
&m_iNumSounds, &iOffset );
if ( m_iNumSounds > MAXSOUNDS )
{
m_iNumSounds = 0;
return;
}
iTotal += iOffset;
for ( i=0; i<m_iNumSounds; ++i )
{
iNumMatches += sscanf( a_pcBuffer+iTotal, "%d %n",
&j, &iOffset );
iTotal += iOffset;
m_asSounds[i].assign( a_pcBuffer + iTotal, j );
iTotal += j;
}
}
diff --git a/src/Backend.h b/src/Backend.h
index 51586cf..4d5eb57 100644
--- a/src/Backend.h
+++ b/src/Backend.h
@@ -1,83 +1,84 @@
/***************************************************************************
Backend.h - description
-------------------
begin : Sun Dec 8 2002
copyright : (C) 2002 by upi
email : upi@apocalypse.rulez.org
***************************************************************************/
#ifndef BACKEND_H
#define BACKEND_H
#include <string>
#include "FighterEnum.h"
class RlePack;
#define MAXDOODADS 20
#define MAXSOUNDS 20
class Backend
{
public:
// Lifecycle
Backend();
~Backend();
bool Construct();
// Miscellaneous
const char* PerlEvalF( const char* a_pcFormat, ... );
const char* GetPerlString( const char* a_pcScalarName );
int GetPerlInt( const char* a_pcScalarName );
// Fighter enumeration
int GetNumberOfFighters();
FighterEnum GetFighterID( int a_iIndex );
// Game data
void AdvancePerl();
void ReadFromPerl();
void PlaySounds();
void WriteToString( std::string& a_rsOutString );
void ReadFromString( const std::string& a_rsString );
void ReadFromString( const char* a_pcBuffer );
public:
+ int m_iGameTick;
int m_iGameOver;
bool m_bKO;
int m_iBgX, m_iBgY;
int m_iNumDoodads;
int m_iNumSounds;
struct SPlayer
{
int m_iX, m_iY, m_iFrame, m_iHitPoints;
} m_aoPlayers[2];
struct SDoodad
{
int m_iX, m_iY, m_iType, m_iFrame;
int m_iDir, m_iGfxOwner;
std::string m_sText;
} m_aoDoodads[ MAXDOODADS ];
std::string m_asSounds[ MAXSOUNDS ];
};
extern Backend g_oBackend;
int DoGame( char* replay, bool isReplay, bool bDebug );
#endif
diff --git a/src/Background.cpp b/src/Background.cpp
index 673dd49..fd3b729 100644
--- a/src/Background.cpp
+++ b/src/Background.cpp
@@ -1,123 +1,141 @@
/***************************************************************************
Background.cpp - description
-------------------
begin : Sun Jan 11 2004
copyright : (C) 2004 by upi
email : upi@feel
***************************************************************************/
#include "Background.h"
#include "SDL.h"
#include "sge_surface.h"
#include "gfx.h"
#include "common.h"
#include <string>
#include <fstream>
+
+
+/* Calculating background distance:
+
+BW: background width
+SW: screen width (640)
+AW: arena width (1920)
+D: distance
+
+(AW - SW) * D = BW - SW
+
+D = (BW - SW) / (AW - SW)
+D = (BW - 640) / 1280
+
+*/
+
+
+
Background::Background()
{
m_bOK = false;
m_iNumber = 0;
}
Background::~Background()
{
Clear();
}
void Background::Clear()
{
for( LayerIterator it=m_aLayers.begin(); it!=m_aLayers.end(); ++it )
{
BackgroundLayer& roLayer = *it;
if ( roLayer.m_poSurface )
{
SDL_FreeSurface( roLayer.m_poSurface );
roLayer.m_poSurface = NULL;
}
}
m_aLayers.clear();
m_bOK = false;
m_iNumber = 0;
}
void Background::Load( int a_iBackgroundNumber )
{
char acFilename[FILENAME_MAX+1];
// 1. Try loading a description-based background.
sprintf( acFilename, "%s/gfx/level%d.desc", DATADIR, a_iBackgroundNumber );
std::ifstream oInput( acFilename );
if ( !oInput.is_open() )
{
// Description-based background not found. Try simple image-based
// background.
sprintf( acFilename, "level%d.png", a_iBackgroundNumber );
SDL_Surface* poImage = LoadBackground( acFilename, 64 );
if ( NULL == poImage )
{
// Couldn't load background.
return;
}
BackgroundLayer oLayer;
oLayer.m_poSurface = poImage;
oLayer.m_iXOffset = 0;
oLayer.m_iYOffset = 0;
oLayer.m_dDistance = 1.0;
m_aLayers.push_back( oLayer );
m_iNumber = a_iBackgroundNumber;
m_bOK = true;
return;
}
// 2. Parse description.
int iNumLayers;
oInput >> iNumLayers;
for ( int i=0; i<iNumLayers; ++i )
{
BackgroundLayer oLayer;
std::string sFilename;
oInput >> sFilename >> oLayer.m_iXOffset >> oLayer.m_iYOffset >> oLayer.m_dDistance;
oLayer.m_poSurface = LoadBackground( sFilename.c_str(), 64, 0 );
if ( NULL == oLayer.m_poSurface )
{
continue;
}
m_aLayers.push_back( oLayer );
}
m_bOK = m_aLayers.size() > 0;
m_iNumber = m_bOK ? a_iBackgroundNumber : 0;
}
bool Background::IsOK()
{
return m_bOK;
}
void Background::Draw( int a_iXPosition, int a_iYPosition )
{
for ( LayerIterator it = m_aLayers.begin(); it != m_aLayers.end(); ++it )
{
BackgroundLayer& roLayer = *it;
sge_Blit( roLayer.m_poSurface, gamescreen,
0, 0, // source position
roLayer.m_iXOffset - (int)( ((double)a_iXPosition) * roLayer.m_dDistance ),
roLayer.m_iYOffset - (int)( ((double)a_iYPosition) * roLayer.m_dDistance ),
SCREENWIDTH*3 + 100, SCREENHEIGHT + 100 );
}
}
diff --git a/src/Game.cpp b/src/Game.cpp
index 4abdb87..e37303c 100644
--- a/src/Game.cpp
+++ b/src/Game.cpp
@@ -1,1037 +1,1093 @@
/***************************************************************************
Game.cpp - description
-------------------
begin : Mon Sep 24 2001
copyright : (C) 2001 by upi
email : upi@apocalypse.rulez.org
***************************************************************************/
#include <string.h>
#include <stdio.h>
#include "SDL_image.h"
#include "sge_surface.h"
#include "sge_primitives.h"
#include "sge_bm_text.h"
#include "SDL_events.h"
#include "SDL_keysym.h"
#include <fstream>
#include "PlayerSelect.h"
#include "Background.h"
#include "common.h"
#include "gfx.h"
#include "Backend.h"
#include "RlePack.h"
#include "State.h"
#include "Game.h"
#include "Audio.h"
#include "MortalNetwork.h"
#include "MszPerl.h"
extern PerlInterpreter* my_perl;
int Game::mg_iBackgroundNumber = 1;
/*
GAME PALETTE
From To Num Desc
---------------------------------------
0 .. 63 64 Background
64 .. 111 48 Doodads
112 .. 175 64 1st Fighter
176 .. 239 64 2nd Fighter
240 .. 255 16 basic colors
*/
#define MAXFRAMESKIP 5
+/***************************************************************************
+ SFpsCounter CLASS
+***************************************************************************/
+
+
+
struct SFpsCounter
{
int m_iLastCheck; // Last second then Tick() was called
int m_iFrames; // The number of frames in this second so far
int m_iFps; // The number of frames in the last second
void Reset()
{
m_iLastCheck = m_iFrames = m_iFps = 0;
}
void Tick()
{
int iSecond = SDL_GetTicks() / 1000;
if ( iSecond > m_iLastCheck )
{
m_iLastCheck = iSecond;
m_iFps = m_iFrames;
m_iFrames = 0;
- fprintf( stderr, "%d ", m_iFps);
+// fprintf( stderr, "%d ", m_iFps);
}
++m_iFrames;
}
} oFpsCounter;
+/***************************************************************************
+ CKeyQueue CLASS
+***************************************************************************/
+
+
+CKeyQueue::CKeyQueue()
+{
+ Reset();
+}
+
+
+CKeyQueue::~CKeyQueue() {}
+
+
+void CKeyQueue::Reset()
+{
+ m_oKeys.clear();
+}
+
+
+void CKeyQueue::EnqueueKey( int a_iAtTime, int a_iPlayer, int a_iKey, bool a_bDown )
+{
+ debug( "EnqueueKey( %d, %d, %d, %d ) at %d\n", a_iAtTime, a_iPlayer, a_iKey, a_bDown, g_oBackend.m_iGameTick );
+ SEnqueuedKey oKey;
+ oKey.iTime = a_iAtTime;
+ oKey.iPlayer = a_iPlayer;
+ oKey.iKey = a_iKey;
+ oKey.bDown = a_bDown;
+
+ if ( m_oKeys.size() == 0 )
+ {
+ m_oKeys.push_front( oKey );
+ return;
+ }
+
+ // Try to find the appropriate time in the list
+ // We maintain the list so that it is sorted in a descending time
+ // order. This means that usually we enqueue keys at the front and
+ // dequeue them at the end, but sometimes a key is inserted in mid-queue.
+
+ TEnqueuedKeyList::iterator it;
+ for ( it=m_oKeys.begin(); it!=m_oKeys.end(); ++it )
+ {
+ if ( it->iTime <= a_iAtTime )
+ {
+ m_oKeys.insert( it, oKey );
+ return;
+ }
+ }
+
+ // a_iAtTime is smaller than any time in the queue, so it goes to the end.
+
+ m_oKeys.push_back( oKey );
+}
+
+
+/**
+If
+*/
+void CKeyQueue::DequeueKeys( int a_iToTime )
+{
+ while ( m_oKeys.size() > 0
+ && m_oKeys.back().iTime <= a_iToTime )
+ {
+ SEnqueuedKey& roKey = m_oKeys.back();
+ debug( "Dequeued key at %d tick: %d time, %d player, %d key, %d down\n", a_iToTime, roKey.iTime, roKey.iPlayer, roKey.iKey, roKey.bDown );
+ g_oBackend.PerlEvalF( roKey.bDown ? "KeyDown(%d,%d);" : "KeyUp(%d,%d);", roKey.iPlayer, roKey.iKey );
+ m_oKeys.pop_back();
+ }
+}
/***************************************************************************
GAME PUBLIC METHODS
***************************************************************************/
Game::Game( bool a_bIsReplay, bool a_bDebug)
{
m_bIsReplay = a_bIsReplay;
m_bDebug = a_bDebug;
m_enInitialGameMode = g_oState.m_enGameMode;
m_poBackground = new Background();
m_poBackground->Load(mg_iBackgroundNumber++);
if ( !m_poBackground->IsOK() )
{
m_poBackground->Load(1);
mg_iBackgroundNumber = 1;
}
m_poDoodads = LoadBackground( "Doodads.png", 48, 64 );
m_aiRoundsWonByPlayer[0] = m_aiRoundsWonByPlayer[1] = 0;
m_iNumberOfRounds = 0;
SDL_EnableUNICODE( 0 );
+ m_iEnqueueDelay = 10;
}
Game::~Game()
{
delete m_poBackground;
m_poBackground = NULL;
SDL_FreeSurface( m_poDoodads );
m_poDoodads = NULL;
}
/** Runs a whole game, with two or three rounds.
\retval 0 if player 1 has won.
\retval 1 if player 2 has won.
\retval -1 if the game was a draw.
*/
int Game::Run()
{
do
{
m_sReplayString = "";
m_aReplayOffsets.clear();
DoOneRound();
if ( g_oState.m_bQuitFlag
|| m_enInitialGameMode != g_oState.m_enGameMode )
{
return -1;
}
} while ( m_aiRoundsWonByPlayer[0] < 2
&& m_aiRoundsWonByPlayer[1] < 2
&& m_iNumberOfRounds < 3 );
if ( m_aiRoundsWonByPlayer[1] > m_aiRoundsWonByPlayer[0] ) return 1;
if ( m_aiRoundsWonByPlayer[1] < m_aiRoundsWonByPlayer[0] ) return 0;
return -1;
}
/** Returns the replay string of the last round.
*/
std::string& Game::GetReplay()
{
return m_sReplayString;
}
/***************************************************************************
GAME DRAWING METHODS
***************************************************************************/
/** Draws the hitpoint bars that are displayed on the top of the screen.
Also draws the fighter names below the bars.
Input variables:
\li g_oBackend.m_aoPlayers[x].m_iHitPoints
\li g_oPlayerSelect.GetFighterName
*/
void Game::DrawHitPointDisplay()
{
int hp1 = g_oBackend.m_aoPlayers[0].m_iHitPoints;// * 100 / g_oState.m_iHitPoints;
int hp2 = g_oBackend.m_aoPlayers[1].m_iHitPoints;// * 100 / g_oState.m_iHitPoints;
SDL_Rect src, dst;
src.y = 154;
src.h = 20;
dst.y = 15;
// Player 1, green part.
dst.x = 40;
src.x = 0;
src.w = hp1*2;
SDL_BlitSurface( m_poDoodads, &src, gamescreen, &dst );
// Player 1, red part.
dst.x += hp1*2;
src.x = (100 + hp1)*2;
src.w = (100-hp1)*2;
SDL_BlitSurface( m_poDoodads, &src, gamescreen, &dst );
// Player 2, red part.
dst.x = 400;
src.x = 200;
src.w = (100-hp2)*2;
SDL_BlitSurface( m_poDoodads, &src, gamescreen, &dst );
// Player 2, green part.
dst.x = 400 + (100-hp2)*2;
src.x = (100-hp2)*2;
src.w = hp2*2;
SDL_BlitSurface( m_poDoodads, &src, gamescreen, &dst );
// "Won" icon for Player 1
src.x = 0; src.y = 276; src.w = 32; src.h = 32;
if ( m_aiRoundsWonByPlayer[0] > 0 )
{
dst.x = 4; dst.y = 11;
SDL_BlitSurface( m_poDoodads, &src, gamescreen, &dst );
}
if ( m_aiRoundsWonByPlayer[1] > 0 )
{
dst.x = 604; dst.y = 11;
SDL_BlitSurface( m_poDoodads, &src, gamescreen, &dst );
}
int iTextX = 230 - g_oPlayerSelect.GetFighterNameWidth(0);
if ( iTextX < 5 ) iTextX = 5;
sge_BF_textout( gamescreen, fastFont, g_oPlayerSelect.GetFighterName(0),
iTextX, 38 );
iTextX = g_oPlayerSelect.GetFighterNameWidth(1);
iTextX = iTextX < (635-410) ? 410 : 635-iTextX;
sge_BF_textout( gamescreen, fastFont, g_oPlayerSelect.GetFighterName(1),
iTextX, 38 );
}
/** Draws the background, using the m_poBackground object.
*/
void Game::DrawBackground()
{
m_poBackground->Draw( g_oBackend.m_iBgX, g_oBackend.m_iBgY );
}
/** In debug mode, this method is used to draw the frame of the fighters.
\param a_sName The name of the polygon (in the perl namespace)
\param a_iColor The game color to draw the polygon with.
*/
void Game::DrawPoly( const char* a_pcName, int a_iColor )
{
AV *poList;
int n;
poList = get_av( a_pcName, FALSE );
if ( poList == NULL )
{
return;
}
n = av_len( poList ) + 1;
if ( n< 2 )
{
return;
}
for ( int i=0; i<n; i += 2 )
{
int j = (i+2) % n;
int x1 = SvIV( *av_fetch( poList, i, false) );
int y1 = SvIV( *av_fetch( poList, i+1, false) );
int x2 = SvIV( *av_fetch( poList, j, false) );
int y2 = SvIV( *av_fetch( poList, j+1, false) );
sge_Line( gamescreen, x1, y1, x2, y2, a_iColor ) ;
}
}
/** Draws every doodad that is currently defined in the backend.
*/
void Game::DrawDoodads()
{
for ( int i=0; i<g_oBackend.m_iNumDoodads; ++i )
{
Backend::SDoodad& roDoodad = g_oBackend.m_aoDoodads[i];
if ( 0 == roDoodad.m_iType )
{
// Handle text doodads
const char *s = roDoodad.m_sText.c_str();
int iWidth = sge_BF_TextSize(fastFont, s).w;
int iDoodadX = roDoodad.m_iX - iWidth/2;
if ( iDoodadX + iWidth > 640 ) iDoodadX = 640 - iWidth;
if ( iDoodadX < 0 ) iDoodadX = 0;
int iDoodadY = roDoodad.m_iY;
sge_BF_textout( gamescreen, fastFont, s, iDoodadX, iDoodadY );
continue;
}
if ( roDoodad.m_iGfxOwner < 2 )
{
g_oPlayerSelect.GetPlayerInfo(roDoodad.m_iGfxOwner).m_poPack->Draw(
roDoodad.m_iFrame, roDoodad.m_iX, roDoodad.m_iY, roDoodad.m_iDir < 1 );
continue;
}
SDL_Rect rsrc, rdst;
rdst.x = roDoodad.m_iX;
rdst.y = roDoodad.m_iY;
rsrc.x = 64 * roDoodad.m_iFrame;
rsrc.y = 0;
rsrc.w = 64;
rsrc.h = 64;
SDL_BlitSurface( m_poDoodads, &rsrc, gamescreen, &rdst );
//debug( "Doodad x: %d, y: %d, t: %d, f: %d\n", dx, dy, dt, df );
}
}
/** Draws the entire game screen:
\li First, the background.
\li The players.
\li The debug wireframes (if debugging is turned on)
\li The doodads.
\li The hitpoint display.
\li The gametime display.
\li The FPS display.
\li The "Round x" text during Ph_Start
Input:
\li m_enGamePhase
\li g_oBackend.m_iGameTime
\li m_iNumberOfRounds
\li oFpsCounter
*/
void Game::Draw()
{
#define GROUNDZERO 440
DrawBackground();
// DRAW THE SHADOWS
int i;
for ( i=0; i<2; ++i )
{
Backend::SPlayer& roPlayer = g_oBackend.m_aoPlayers[i];
int iFrame = roPlayer.m_iFrame;
if ( iFrame == 0 )
continue;
RlePack* poPack = g_oPlayerSelect.GetPlayerInfo(i).m_poPack;
int w = poPack->GetWidth( ABS(iFrame)-1 );
int h = poPack->GetHeight( ABS(iFrame)-1 );
h = GROUNDZERO - ( h + roPlayer.m_iY ); // Distance of feet from ground
if ( h < 0 ) h = 0;
if ( h > 500 ) h = 500;
h = 500 - h;
int h2 = 15 * h / 500;
h = ( w / 2 ) * h / 500;
sge_FilledEllipse( gamescreen,
g_oBackend.m_aoPlayers[i].m_iX + w/2, GROUNDZERO,
h, h2, C_BLACK );
}
for ( i=0; i<2; ++i )
{
Backend::SPlayer& roPlayer = g_oBackend.m_aoPlayers[i];
int iFrame = roPlayer.m_iFrame;
if ( iFrame == 0 )
continue;
RlePack* poPack = g_oPlayerSelect.GetPlayerInfo(i).m_poPack;
poPack->Draw( ABS(iFrame)-1, roPlayer.m_iX, roPlayer.m_iY, iFrame<0 );
}
if ( m_bDebug )
{
DrawPoly( "p1head", C_LIGHTRED );
DrawPoly( "p1body", C_LIGHTGREEN );
DrawPoly( "p1legs", C_LIGHTBLUE );
DrawPoly( "p1hit", C_YELLOW );
DrawPoly( "p2head", C_LIGHTRED );
DrawPoly( "p2body", C_LIGHTGREEN );
DrawPoly( "p2legs", C_LIGHTBLUE );
DrawPoly( "p2hit", C_YELLOW );
}
DrawDoodads();
DrawHitPointDisplay();
if ( Ph_NORMAL == m_enGamePhase )
{
char s[100];
sprintf( s, "%d", m_iGameTime ); // m_iGameTime is maintained by DoGame
DrawTextMSZ( s, inkFont, 320, 10, AlignHCenter, C_LIGHTCYAN, gamescreen, false );
}
else if ( Ph_START == m_enGamePhase )
{
char s[100];
const char* format = Translate( "Round %d" );
sprintf( s, format, m_iNumberOfRounds+1 );
DrawTextMSZ( s, inkFont, 320, 200, AlignHCenter, C_WHITE, gamescreen, false );
}
else if ( Ph_REWIND == m_enGamePhase )
{
DrawTextMSZ( "REW", inkFont, 320, 10, AlignHCenter, C_WHITE, gamescreen );
sge_BF_textout( gamescreen, fastFont, Translate("Press F1 to skip..."), 230, 450 );
}
else if ( Ph_SLOWFORWARD == m_enGamePhase )
{
DrawTextMSZ( "REPLAY", inkFont, 320, 10, AlignHCenter, C_WHITE, gamescreen );
sge_BF_textout( gamescreen, fastFont, Translate("Press F1 to skip..."), 230, 450 );
}
else if ( Ph_REPLAY == m_enGamePhase )
{
DrawTextMSZ( "DEMO", inkFont, 320, 10, AlignHCenter, C_WHITE, gamescreen );
}
if ( oFpsCounter.m_iFps > 0 )
{
sge_BF_textoutf( gamescreen, fastFont, 2, 2, "%d fps", oFpsCounter.m_iFps );
}
SDL_Flip( gamescreen );
}
/***************************************************************************
GAME PROTECTED METHODS
***************************************************************************/
bool Game::IsNetworkGame()
{
return SState::IN_NETWORK == g_oState.m_enGameMode;
}
/** Returns true if we control our own data, or false if the network supplies
us with game data. */
bool Game::IsMaster()
{
return !IsNetworkGame() || g_poNetwork->IsMaster();
}
/**
This method reads and updates the game's variables. In replay mode,
this is done by parsing the replay string. Otherwise the perl
backend is advanced the given number of steps.
Whichever the case, the variables will be available in g_oBackend.
Only the backend-driven variables are modified, the GamePhase and
GameTime remain unchanged; these are up for DoOneRound and friends
to modify.
*/
void Game::Advance( int a_iNumFrames )
{
if ( m_bIsReplay )
{
// Replay mode...
m_iFrame += a_iNumFrames;
- if ( m_iFrame >= ((int)m_aReplayOffsets.size())-1 ) m_iFrame = m_aReplayOffsets.size() - 2;
+ if ( m_iFrame >= ((int)m_aReplayOffsets.size())-1 ) m_iFrame = m_aReplayOffsets.size() - 2;
if ( m_iFrame <= 0 ) m_iFrame = 0;
std::string sFrameDesc = m_sReplayString.substr(
m_aReplayOffsets[m_iFrame],
m_aReplayOffsets[m_iFrame+1] - m_aReplayOffsets[m_iFrame] );
g_oBackend.ReadFromString( sFrameDesc );
return;
}
+ static std::string sFrameDesc;
+ int i;
+
if ( IsNetworkGame() )
{
- bool bMaster = g_poNetwork->IsMaster();
+ g_poNetwork->SendGameTick( g_oBackend.m_iGameTick );
g_poNetwork->Update();
- if (!bMaster)
+
+ int i = 0;
+
+ while ( g_poNetwork->GetGameTick() + m_iEnqueueDelay < g_oBackend.m_iGameTick + a_iNumFrames )
{
- // We don't run our own backend, just pull the data from the network.
- const char* pcRemoteBackend = g_poNetwork->GetLatestGameData();
- g_oBackend.ReadFromString( pcRemoteBackend );
- g_oBackend.PlaySounds();
- return;
+ ++i;
+ if ( i > 300 ) {
+ // Waited for too long..
+ g_poNetwork->Stop();
+ }
+ // The remote side is lagging behind.. Wait for it.
+ SDL_Delay( 10 );
+ g_poNetwork->Update();
+ if ( m_enInitialGameMode != g_oState.m_enGameMode ) {
+ return;
+ }
}
- }
- // OK. The trick here is to collect the sound information, and then
- // inject it into the last frame to send it to the remote side.
-
- static std::string sFrameDesc;
- int iSounds = 0;
- std::string asSounds[ MAXSOUNDS ];
- int i;
+ int iTime;
+ int iKey;
+ bool bPressed;
+ while ( g_poNetwork->GetKeystroke( iTime, iKey, bPressed ) )
+ {
+ debug( "Got GetKeystroke: %d, %d, %d at %d\n", iTime, iKey, bPressed, g_oBackend.m_iGameTick );
+ // g_oBackend.PerlEvalF( bPressed ? "KeyDown(%d,%d);" : "KeyUp(%d,%d);", 1, iKey );
+ m_oKeyQueue.EnqueueKey( iTime, IsMaster() ? 1 : 0, iKey, bPressed );
+ if ( iTime <= g_oBackend.m_iGameTick )
+ {
+ debug( "KEY ARRIVED TOO LATE!!!\n" );
+ }
+ }
+ }
while ( a_iNumFrames > 0 )
{
-- a_iNumFrames;
g_oBackend.AdvancePerl();
g_oBackend.ReadFromPerl();
g_oBackend.PlaySounds();
-
+ m_oKeyQueue.DequeueKeys( g_oBackend.m_iGameTick );
+
g_oBackend.WriteToString( sFrameDesc );
m_sReplayString += sFrameDesc;
m_sReplayString += '\n';
m_aReplayOffsets.push_back( m_sReplayString.size() );
-
- for ( i=0;
- IsNetworkGame() && i<g_oBackend.m_iNumSounds && iSounds<MAXSOUNDS;
- ++i, ++iSounds )
- {
- asSounds[iSounds] = g_oBackend.m_asSounds[i];
- }
- }
-
- if ( IsNetworkGame() && sFrameDesc.size() )
- {
- g_oBackend.m_iNumSounds = iSounds;
- for ( i=0; i<iSounds; ++i )
- {
- g_oBackend.m_asSounds[i] = asSounds[i];
- }
- g_oBackend.WriteToString( sFrameDesc );
- g_poNetwork->SendGameData( sFrameDesc.c_str() );
}
}
/** A helper method of ProcessEvents; it manages keypresses and releases of
-players.
+players. It is only called when keypresses are actually relevant for the
+backend (not during instant replay, etc).
*/
void Game::HandleKey( int a_iPlayer, int a_iKey, bool a_bDown )
{
- if ( IsMaster() )
- {
- if ( IsNetworkGame() )
- {
- a_iPlayer = 0;
- }
- g_oBackend.PerlEvalF( a_bDown ? "KeyDown(%d,%d);" : "KeyUp(%d,%d);", a_iPlayer, a_iKey );
- }
- else
+ int iCurrentTick = g_oBackend.m_iGameTick + m_iEnqueueDelay;
+
+ if ( IsNetworkGame() )
{
- g_poNetwork->SendKeystroke( a_iKey, a_bDown );
+ a_iPlayer = IsMaster() ? 0 : 1;
+
+ g_poNetwork->SendKeystroke( iCurrentTick, a_iKey, a_bDown );
}
+
+ m_oKeyQueue.EnqueueKey( iCurrentTick, a_iPlayer, a_iKey, a_bDown );
}
/** ProcessEvents reads events from the SDL event system.
Relevant key events are fed to the backend.
Esc brings up the menu.
Returns 1 on quit event (e.g. if the current game or replay should be aborted), 0 otherwise.
*/
int Game::ProcessEvents()
{
SDL_Event event;
while (SDL_PollEvent(&event))
{
switch (event.type)
{
case SDL_QUIT:
g_oState.m_bQuitFlag = true;
return 1;
case SDL_KEYDOWN:
{
if ( event.key.keysym.sym == SDLK_ESCAPE && !IsNetworkGame() )
{
SState::TGameMode enMode = g_oState.m_enGameMode;
::DoMenu();
return g_oState.m_enGameMode == enMode ? 0 : 1;
}
if ( event.key.keysym.sym == SDLK_F1 /*&& !IsNetworkGame()*/ )
{
// shortcut: abort the current round. This shall is here
// to ease testing, and will be removed from the final
// version.
return 1;
}
if ( Ph_NORMAL != m_enGamePhase &&
Ph_REPLAY != m_enGamePhase )
break;
for (int i=0; i<2; i++)
{
for (int j=0; j<9; j++ )
{
if (g_oState.m_aiPlayerKeys[i][j] == event.key.keysym.sym)
{
if (g_oState.m_enGameMode == SState::IN_DEMO)
{
g_oState.m_enGameMode = SState::IN_MULTI;
return 1;
}
HandleKey( i, j, true );
//g_oBackend.PerlEvalF( "KeyDown(%d,%d);", i, j );
return 0;
}
}
}
break;
}
case SDL_KEYUP:
{
if ( Ph_NORMAL != m_enGamePhase )
break;
for (int i=0; i<2; i++)
{
for (int j=0; j<9; j++ )
{
if (g_oState.m_aiPlayerKeys[i][j] == event.key.keysym.sym)
{
HandleKey( i, j, false );
- g_oBackend.PerlEvalF( "KeyUp(%d,%d);", i, j );
+ //g_oBackend.PerlEvalF( "KeyUp(%d,%d);", i, j );
return 0;
}
}
}
break;
}
} // End of switch
} // End of while SDL_PollEvent;
return 0;
}
void Game::HurryUp()
{
Audio->PlaySample( "aroooga.voc" );
DrawGradientText( "HURRY UP!", titleFont, 200, gamescreen );
SDL_Delay( 1000 );
Audio->PlaySample( "machine_start.voc" );
}
void Game::TimeUp()
{
DrawGradientText( "TIME IS UP!", titleFont, 200, gamescreen );
SDL_Delay( 1000 );
}
/** This methods starts and runs the "instant replay" mode that is done
at the end of a game round. This means doing phases Ph_REWIND and Ph_SLOWFORWARD.
Rewind will go back in time 200 ticks before the parameter a_iKoAt.
*/
void Game::InstantReplay( int a_iKoAt )
{
int iCurrentFrame = m_aReplayOffsets.size() - 200;
int iThisTick, iLastTick, iGameSpeed;
m_enGamePhase = Ph_REWIND;
iGameSpeed = 8;
iThisTick = SDL_GetTicks() / iGameSpeed;
iLastTick = iThisTick - 1;
while ( iCurrentFrame < (int)m_aReplayOffsets.size() - 150 )
{
// 1. Wait for the next tick (on extremely fast machines..)
while (iThisTick == iLastTick)
{
iThisTick = SDL_GetTicks() / iGameSpeed;
if ( iThisTick==iLastTick ) SDL_Delay(1);
}
// 2. Advance as many ticks as necessary..
int iNumTicks = iThisTick - iLastTick;
if ( iNumTicks > 10 ) iNumTicks = 10;
if ( iNumTicks < 0 ) iNumTicks = 0;
iCurrentFrame += ( Ph_REWIND == m_enGamePhase ) ? -iNumTicks : +iNumTicks ;
if ( Ph_REWIND == m_enGamePhase
&& ( iCurrentFrame < a_iKoAt - 200 || iCurrentFrame <= 0 )
)
{
m_enGamePhase = Ph_SLOWFORWARD;
iGameSpeed = 16;
SDL_Delay(500);
}
iLastTick = iThisTick;
if ( iCurrentFrame < 0 ) iCurrentFrame = 0;
- m_iFrame = iCurrentFrame;
+ m_iFrame = iCurrentFrame;
if ( m_iFrame >= ((int)m_aReplayOffsets.size())-1 ) m_iFrame = m_aReplayOffsets.size() - 2;
if ( m_iFrame <= 0 ) m_iFrame = 0;
std::string sFrameDesc = m_sReplayString.substr(
m_aReplayOffsets[m_iFrame],
m_aReplayOffsets[m_iFrame+1] - m_aReplayOffsets[m_iFrame] );
//debug( "PB: Frame %d ofs %d-%d; data: '%s'\n", m_iFrame,
// m_aReplayOffsets[m_iFrame], m_aReplayOffsets[m_iFrame+1], sFrameDesc.c_str() );
g_oBackend.ReadFromString( sFrameDesc );
if ( ProcessEvents() )
{
break;
}
oFpsCounter.Tick();
// 3. Draw the next game screen..
Draw();
if ( g_oState.m_bQuitFlag
|| SState::IN_DEMO == g_oState.m_enGameMode )
{
break;
}
}
}
/** This methods executes one round of gameplay.
The game progresses through phases Ph_START, Ph_NORMAL, and
Ph_KO. If a KO happened, it will invoke InstantReplay. At the end of
the round m_aiRoundsWonByPlayer[x] will be incremented depending on the
outcome. m_iNumberOfRounds will also increase by 1.
*/
void Game::DoOneRound()
{
m_enGamePhase = Ph_START;
- g_oBackend.PerlEvalF( "GameStart(%d,%d);", g_oState.m_iHitPoints, m_bDebug );
+ g_oBackend.PerlEvalF( "GameStart(%d,%d);",
+ IsMaster() ? g_oState.m_iHitPoints : g_poNetwork->GetGameParams().iHitPoints,
+ m_bDebug );
+ g_oBackend.ReadFromPerl();
if ( IsNetworkGame() )
{
g_poNetwork->SynchStartRound();
+ g_poNetwork->SendGameTick( g_oBackend.m_iGameTick-1 );
}
int iKoFrame = -1;
double dGameTime = 2 * 1000; // Only for the "greeting phase", the real gametime will be set after.
int iThisTick, iLastTick, iGameSpeed;
bool bHurryUp = false;
bool bReplayAfter = true;
- iGameSpeed = g_oState.m_iGameSpeed;
+ iGameSpeed = IsMaster() ? g_oState.m_iGameSpeed : g_poNetwork->GetGameParams().iGameSpeed;
iThisTick = SDL_GetTicks() / iGameSpeed;
iLastTick = iThisTick - 1;
+ m_oKeyQueue.Reset();
oFpsCounter.Reset();
// 1. DO THE NORMAL GAME ROUND (START, NORMAL, KO, TIMEUP)
while ( dGameTime >= 0 )
{
if ( m_enInitialGameMode != g_oState.m_enGameMode )
{
return;
}
// 1. Wait for the next tick (on extremely fast machines..)
while (iThisTick == iLastTick)
{
iThisTick = SDL_GetTicks() / iGameSpeed;
if ( iThisTick==iLastTick ) SDL_Delay(1);
}
// 2. Advance as many ticks as necessary..
int iNumTicks = iThisTick - iLastTick;
if ( iNumTicks > MAXFRAMESKIP ) iNumTicks = MAXFRAMESKIP;
Advance( iNumTicks );
dGameTime -= iNumTicks * iGameSpeed;
// 3. Check for state transitions and game time.
// START -> NORMAL
// NORMAL -> KO
// NORMAL -> TIMEUP
// bHurryUp flag can be set during NORMAL phase
- if ( IsMaster() )
+ if ( Ph_START == m_enGamePhase ) // Check for the end of the START phase
{
- if ( Ph_START == m_enGamePhase ) // Check for the end of the START phase
+ if ( dGameTime <= 0 )
{
- if ( dGameTime <= 0 )
- {
- m_enGamePhase = Ph_NORMAL;
- dGameTime = g_oState.m_iGameTime * 1000;
- }
+ m_enGamePhase = Ph_NORMAL;
+ dGameTime = (IsMaster() ? g_oState.m_iGameTime : g_poNetwork->GetGameParams().iGameTime) * 1000;
}
- else if ( Ph_NORMAL == m_enGamePhase ) // Check for the end of the NORMAL phase
+ }
+ else if ( Ph_NORMAL == m_enGamePhase ) // Check for the end of the NORMAL phase
+ {
+ if ( dGameTime < 10 * 1000
+ && !bHurryUp )
{
- if ( dGameTime < 10 * 1000
- && !bHurryUp )
- {
- bHurryUp = true;
- g_poNetwork->SendHurryup( 1 );
- HurryUp();
- iGameSpeed = iGameSpeed * 3 / 4;
- }
- if ( g_oBackend.m_bKO )
- {
- m_enGamePhase = Ph_KO;
- dGameTime = 10 * 1000;
- iKoFrame = m_aReplayOffsets.size();
- }
- else if ( dGameTime <= 0 )
- {
- m_enGamePhase = Ph_TIMEUP;
- g_poNetwork->SendHurryup( 2 );
- TimeUp();
- break;
- }
+ bHurryUp = true;
+ g_poNetwork->SendHurryup( 1 );
+ HurryUp();
+ iGameSpeed = iGameSpeed * 3 / 4;
}
-
- m_iGameTime = (int) ((dGameTime + 500.0) / 1000.0);
-
- if ( IsNetworkGame() )
+ if ( g_oBackend.m_bKO )
{
- g_poNetwork->SendGameTime( m_iGameTime, m_enGamePhase );
+ m_enGamePhase = Ph_KO;
+ dGameTime = 10 * 1000;
+ iKoFrame = m_aReplayOffsets.size();
}
- }
- else
- {
- m_iGameTime = g_poNetwork->GetGameTime();
- m_enGamePhase = (TGamePhaseEnum) g_poNetwork->GetGamePhase();
- dGameTime = 1000.0; // ignored.
- switch (g_poNetwork->GetHurryup() )
+ else if ( dGameTime <= 0 )
{
- case 1: HurryUp(); break;
- case 2: TimeUp(); break;
+ m_enGamePhase = Ph_TIMEUP;
+ g_poNetwork->SendHurryup( 2 );
+ TimeUp();
+ break;
}
}
+
+ m_iGameTime = (int) ((dGameTime + 500.0) / 1000.0);
iLastTick = iThisTick;
+ // ProcessEvents will read keyboard/gamepad input
+ // It will also transmit them to the remote side in a network game.
+
if ( ProcessEvents() || g_oState.m_bQuitFlag )
{
bReplayAfter = false;
break;
}
-
- if ( IsNetworkGame() && IsMaster() )
- {
- int iKey;
- bool bPressed;
- while ( g_poNetwork->GetKeystroke( iKey, bPressed ) )
- {
- debug( "Got GetKeystroke: %d, %d\n", iKey, bPressed );
- g_oBackend.PerlEvalF( bPressed ? "KeyDown(%d,%d);" : "KeyUp(%d,%d);", 1, iKey );
- }
- }
oFpsCounter.Tick();
// 3. Draw the next game screen..
Draw();
// 4. Check 'end of round' condition.
if ( !IsMaster() )
{
if ( g_poNetwork->IsRoundOver() )
{
break;
}
}
else if ( g_oBackend.m_iGameOver )
{
break;
}
}
int p1h = g_oBackend.m_aoPlayers[0].m_iHitPoints;
int p2h = g_oBackend.m_aoPlayers[1].m_iHitPoints;
// 3. DO THE REPLAY (IF THERE WAS A KO)
if ( iKoFrame>0 && bReplayAfter && !IsNetworkGame() )
{
InstantReplay( iKoFrame );
}
// 4. END OF ROUND
debug( "Game over; p1h = %d; p2h = %d\n", p1h, p2h );
if ( IsMaster() )
{
int iWhoWon = -1;
if ( p1h > p2h ) { ++m_aiRoundsWonByPlayer[0]; iWhoWon = 0; }
if ( p2h > p1h ) { ++m_aiRoundsWonByPlayer[1]; iWhoWon = 1; }
if ( IsNetworkGame() )
{
+ g_poNetwork->SendGameTick( g_oBackend.m_iGameTick + m_iEnqueueDelay * 100 );
g_poNetwork->SendRoundOver( iWhoWon, m_iNumberOfRounds > 0 );
}
}
else
{
int iWhoWon = g_poNetwork->GetWhoWon();
if ( iWhoWon>=0 )
{
++m_aiRoundsWonByPlayer[iWhoWon];
}
}
++m_iNumberOfRounds;
}
void Game::DoReplay( const char* a_pcReplayFile )
{
std::ifstream oInput( a_pcReplayFile );
int iPlayer1, iPlayer2;
oInput >> iPlayer1 >> iPlayer2;
std::string sLine;
std::getline( oInput, sLine );
g_oPlayerSelect.SetPlayer( 0, (FighterEnum) iPlayer1 );
g_oPlayerSelect.SetPlayer( 1, (FighterEnum) iPlayer2 );
int iThisTick, iLastTick, iGameSpeed;
m_enGamePhase = Ph_REPLAY;
iGameSpeed = 12;
iThisTick = SDL_GetTicks() / iGameSpeed;
iLastTick = iThisTick - 1;
while ( !oInput.eof() )
{
// 1. Wait for the next tick (on extremely fast machines..)
while (iThisTick == iLastTick)
{
iThisTick = SDL_GetTicks() / iGameSpeed;
if ( iThisTick==iLastTick ) SDL_Delay(1);
}
// 2. Advance as many ticks as necessary..
int iNumTicks = iThisTick - iLastTick;
if ( iNumTicks > 5 ) iNumTicks = 5;
for ( int i=0; i< iNumTicks; ++i )
{
std::getline( oInput, sLine );
}
if ( 0 == sLine.size() )
{
break;
}
iLastTick = iThisTick;
g_oBackend.ReadFromString( sLine );
if ( ProcessEvents() )
{
break;
}
oFpsCounter.Tick();
// 3. Draw the next game screen..
Draw();
if ( g_oState.m_bQuitFlag )
{
break;
}
}
}
/** Public static function.
Other parts of OpenMortal need not include "Game.h" so long as they have
the definition of this method (defined in "common.h"). The method runs
a cycle of the game (either a normal game, or replay).
In replay mode, DoReplay() is called, and the replay file is required.
In normal mode, Run() is called. The replay file is recorded, if it is not NULL.
*/
int DoGame( char* a_pcReplayFile, bool a_bIsReplay, bool a_bDebug )
{
Game oGame( a_bIsReplay, a_bDebug );
if ( a_bIsReplay )
{
if ( NULL == a_pcReplayFile )
{
return 0;
}
oGame.DoReplay( a_pcReplayFile );
return 0;
}
else
{
int iRetval = oGame.Run();
if ( NULL != a_pcReplayFile )
{
std::ofstream oOutput( a_pcReplayFile );
oOutput <<
g_oPlayerSelect.GetPlayerInfo(0).m_enFighter << ' ' <<
g_oPlayerSelect.GetPlayerInfo(1).m_enFighter << '\n' << oGame.GetReplay();
}
return iRetval;
}
}
diff --git a/src/Game.h b/src/Game.h
index 8e02915..fd2f5c4 100644
--- a/src/Game.h
+++ b/src/Game.h
@@ -1,81 +1,134 @@
/***************************************************************************
Game.h - description
-------------------
begin : Mon Aug 27 2003
copyright : (C) 2003 by upi
email : upi@apocalypse.rulez.org
***************************************************************************/
#ifndef GAME_H
#define GAME_H
#include <string>
#include <vector>
+#include <list>
struct SDL_Surface;
class Background;
+
+
+/**
+CKeyQueue is used to introduce a certain amount of negative or positive
+lag to keystrokes.
+
+The keys are sent to the backend and the remote site set to a future time.
+The time is measured in the backend's game ticks. So if the current key
+is pressed at game time 80, and enqueued to game time 90, there will be an
+artificial lag of 10 game ticks.
+
+The actual length of game ticks depends on the game speed (set in the menu),
+for a normal game its 1000/80 = 12.5 ms. This artificial lag is useful for
+network games.
+*/
+
+class CKeyQueue
+{
+public:
+ CKeyQueue();
+ ~CKeyQueue();
+
+ void Reset();
+ void EnqueueKey( int a_iAtTime, int a_iPlayer, int a_iKey, bool a_bDown );
+ void DequeueKeys( int a_iToTime );
+
+protected:
+ struct SEnqueuedKey
+ {
+ int iTime;
+ int iPlayer;
+ int iKey;
+ bool bDown;
+ };
+
+ typedef std::list<SEnqueuedKey> TEnqueuedKeyList;
+ TEnqueuedKeyList m_oKeys;
+};
+
+
+
+
+/**
+The Game class is for running the frontend of a game.
+
+This involves reading the game state data from a source (be it a replay
+file or the backend), handling the keystrokes and network, etc.
+*/
+
class Game
{
public:
Game( bool a_bIsReplay, bool a_bDebug );
~Game();
int Run();
std::string& GetReplay();
void DoReplay( const char* a_pcReplayFile );
protected:
void Draw();
void DrawHitPointDisplay();
void DrawBackground();
void DrawDoodads();
void DrawPoly( const char* a_pcName, int a_iColor );
void DoOneRound();
void Advance( int a_iNumFrames );
int ProcessEvents();
void HandleKey( int a_iPlayer, int a_iKey, bool a_bDown );
void HurryUp();
void TimeUp();
void InstantReplay( int a_iKoAt );
bool IsNetworkGame();
bool IsMaster();
+ void ReadKeysFromNetwork();
protected:
static int mg_iBackgroundNumber;
bool m_bIsReplay;
bool m_bDebug;
Background* m_poBackground;
SDL_Surface* m_poDoodads;
int m_aiRoundsWonByPlayer[2];
int m_iNumberOfRounds;
int m_iFrame;
int m_iGameTime;
+ CKeyQueue m_oKeyQueue;
+ int m_iEnqueueDelay;
std::string m_sReplayString;
std::vector<int> m_aReplayOffsets;
enum TGamePhaseEnum // This enum assumes its values during DoOneRound
{
Ph_START, // "Round X" displayed, fighters getting ready
Ph_NORMAL, // During the fight
Ph_TIMEUP, // Time is up, no KO, no replay.
Ph_KO, // There is a KO, forward until the guy is down
Ph_REWIND, // There was a KO, rewinding until before the KO
Ph_SLOWFORWARD, // Playing back the KO
Ph_REPLAY, // Replay mode
} m_enGamePhase;
SState::TGameMode m_enInitialGameMode; // must make sure it's still the same.
};
#endif
diff --git a/src/Makefile.am b/src/Makefile.am
index 1522a8c..c05aa30 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,29 +1,29 @@
## Process this file with automake to produce Makefile.in
bin_PROGRAMS = openmortal
openmortal_SOURCES = Audio.cpp Backend.cpp common.cpp \
Demo.cpp FighterStats.cpp FlyingChars.cpp Game.cpp gfx.cpp main.cpp \
menu.cpp PlayerSelect.cpp RlePack.cpp sge_bm_text.cpp \
sge_primitives.cpp sge_surface.cpp sge_tt_text.cpp State.cpp \
GameOver.cpp Background.cpp MortalNetworkImpl.cpp Chooser.cpp \
- TextArea.cpp OnlineChat.cpp
+ TextArea.cpp OnlineChat.cpp Joystick.cpp
EXTRA_DIST = Audio.h Backend.h common.h Demo.h FlyingChars.h gfx.h \
menu.h RlePack.h sge_bm_text.h sge_config.h sge_internal.h \
sge_primitives.h sge_surface.h sge_tt_text.h State.h \
Game.h FighterEnum.h PlayerSelect.h MszPerl.h \
Audio.cpp Backend.cpp common.cpp \
Demo.cpp FighterStats.cpp FlyingChars.cpp Game.cpp gfx.cpp main.cpp \
menu.cpp PlayerSelect.cpp RlePack.cpp sge_bm_text.cpp \
sge_primitives.cpp sge_surface.cpp sge_tt_text.cpp State.cpp \
GameOver.cpp Background.h MortalNetwork.h MortalNetworkImpl.h \
- FighterStats.h Chooser.h TextArea.h OnlineChat.h
+ FighterStats.h Chooser.h TextArea.h OnlineChat.h Joystick.h Event.h
CXXFLAGS= @CXXFLAGS@ -DDATADIR=\"${pkgdatadir}\" -Wall
# set the include path found by configure
#INCLUDES= $(all_includes)
# the library search path.
#msz_LDFLAGS = $(all_libraries)
diff --git a/src/Makefile.in b/src/Makefile.in
index bf0caf6..3c177bb 100644
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -1,366 +1,366 @@
# Makefile.in generated automatically by automake 1.4-p6 from Makefile.am
# Copyright (C) 1994, 1995-8, 1999, 2001 Free Software Foundation, Inc.
# This Makefile.in is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
# with or without modifications, as long as this notice is preserved.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE.
SHELL = @SHELL@
srcdir = @srcdir@
top_srcdir = @top_srcdir@
VPATH = @srcdir@
prefix = @prefix@
exec_prefix = @exec_prefix@
bindir = @bindir@
sbindir = @sbindir@
libexecdir = @libexecdir@
datadir = @datadir@
sysconfdir = @sysconfdir@
sharedstatedir = @sharedstatedir@
localstatedir = @localstatedir@
libdir = @libdir@
infodir = @infodir@
mandir = @mandir@
includedir = @includedir@
oldincludedir = /usr/include
DESTDIR =
pkgdatadir = $(datadir)/@PACKAGE@
pkglibdir = $(libdir)/@PACKAGE@
pkgincludedir = $(includedir)/@PACKAGE@
top_builddir = ..
ACLOCAL = @ACLOCAL@
AUTOCONF = @AUTOCONF@
AUTOMAKE = @AUTOMAKE@
AUTOHEADER = @AUTOHEADER@
INSTALL = @INSTALL@
INSTALL_PROGRAM = @INSTALL_PROGRAM@ $(AM_INSTALL_PROGRAM_FLAGS)
INSTALL_DATA = @INSTALL_DATA@
INSTALL_SCRIPT = @INSTALL_SCRIPT@
transform = @program_transform_name@
NORMAL_INSTALL = :
PRE_INSTALL = :
POST_INSTALL = :
NORMAL_UNINSTALL = :
PRE_UNINSTALL = :
POST_UNINSTALL = :
build_alias = @build_alias@
build_triplet = @build@
host_alias = @host_alias@
host_triplet = @host@
target_alias = @target_alias@
target_triplet = @target@
AUTODIRS = @AUTODIRS@
CXX = @CXX@
FT2_CFLAGS = @FT2_CFLAGS@
FT2_CONFIG = @FT2_CONFIG@
FT2_LIBS = @FT2_LIBS@
MAKEINFO = @MAKEINFO@
PACKAGE = @PACKAGE@
PERL = @PERL@
SDL_CFLAGS = @SDL_CFLAGS@
SDL_CONFIG = @SDL_CONFIG@
SDL_LIBS = @SDL_LIBS@
VERSION = @VERSION@
perl_embed_ccflags = @perl_embed_ccflags@
perl_embed_ldflags = @perl_embed_ldflags@
bin_PROGRAMS = openmortal
-openmortal_SOURCES = Audio.cpp Backend.cpp common.cpp Demo.cpp FighterStats.cpp FlyingChars.cpp Game.cpp gfx.cpp main.cpp menu.cpp PlayerSelect.cpp RlePack.cpp sge_bm_text.cpp sge_primitives.cpp sge_surface.cpp sge_tt_text.cpp State.cpp GameOver.cpp Background.cpp MortalNetworkImpl.cpp Chooser.cpp TextArea.cpp OnlineChat.cpp
+openmortal_SOURCES = Audio.cpp Backend.cpp common.cpp Demo.cpp FighterStats.cpp FlyingChars.cpp Game.cpp gfx.cpp main.cpp menu.cpp PlayerSelect.cpp RlePack.cpp sge_bm_text.cpp sge_primitives.cpp sge_surface.cpp sge_tt_text.cpp State.cpp GameOver.cpp Background.cpp MortalNetworkImpl.cpp Chooser.cpp TextArea.cpp OnlineChat.cpp Joystick.cpp
-EXTRA_DIST = Audio.h Backend.h common.h Demo.h FlyingChars.h gfx.h menu.h RlePack.h sge_bm_text.h sge_config.h sge_internal.h sge_primitives.h sge_surface.h sge_tt_text.h State.h Game.h FighterEnum.h PlayerSelect.h MszPerl.h Audio.cpp Backend.cpp common.cpp Demo.cpp FighterStats.cpp FlyingChars.cpp Game.cpp gfx.cpp main.cpp menu.cpp PlayerSelect.cpp RlePack.cpp sge_bm_text.cpp sge_primitives.cpp sge_surface.cpp sge_tt_text.cpp State.cpp GameOver.cpp Background.h MortalNetwork.h MortalNetworkImpl.h FighterStats.h Chooser.h TextArea.h OnlineChat.h
+EXTRA_DIST = Audio.h Backend.h common.h Demo.h FlyingChars.h gfx.h menu.h RlePack.h sge_bm_text.h sge_config.h sge_internal.h sge_primitives.h sge_surface.h sge_tt_text.h State.h Game.h FighterEnum.h PlayerSelect.h MszPerl.h Audio.cpp Backend.cpp common.cpp Demo.cpp FighterStats.cpp FlyingChars.cpp Game.cpp gfx.cpp main.cpp menu.cpp PlayerSelect.cpp RlePack.cpp sge_bm_text.cpp sge_primitives.cpp sge_surface.cpp sge_tt_text.cpp State.cpp GameOver.cpp Background.h MortalNetwork.h MortalNetworkImpl.h FighterStats.h Chooser.h TextArea.h OnlineChat.h Joystick.h Event.h
CXXFLAGS = @CXXFLAGS@ -DDATADIR=\"${pkgdatadir}\" -Wall
mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs
CONFIG_HEADER = ../config.h
CONFIG_CLEAN_FILES =
PROGRAMS = $(bin_PROGRAMS)
DEFS = @DEFS@ -I. -I$(srcdir) -I..
CPPFLAGS = @CPPFLAGS@
LDFLAGS = @LDFLAGS@
LIBS = @LIBS@
openmortal_OBJECTS = Audio.o Backend.o common.o Demo.o FighterStats.o \
FlyingChars.o Game.o gfx.o main.o menu.o PlayerSelect.o RlePack.o \
sge_bm_text.o sge_primitives.o sge_surface.o sge_tt_text.o State.o \
GameOver.o Background.o MortalNetworkImpl.o Chooser.o TextArea.o \
-OnlineChat.o
+OnlineChat.o Joystick.o
openmortal_LDADD = $(LDADD)
openmortal_DEPENDENCIES =
openmortal_LDFLAGS =
CXXCOMPILE = $(CXX) $(DEFS) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS)
CXXLD = $(CXX)
CXXLINK = $(CXXLD) $(AM_CXXFLAGS) $(CXXFLAGS) $(LDFLAGS) -o $@
DIST_COMMON = Makefile.am Makefile.in
DISTFILES = $(DIST_COMMON) $(SOURCES) $(HEADERS) $(TEXINFOS) $(EXTRA_DIST)
TAR = gtar
GZIP_ENV = --best
DEP_FILES = .deps/Audio.P .deps/Backend.P .deps/Background.P \
.deps/Chooser.P .deps/Demo.P .deps/FighterStats.P .deps/FlyingChars.P \
-.deps/Game.P .deps/GameOver.P .deps/MortalNetworkImpl.P \
-.deps/OnlineChat.P .deps/PlayerSelect.P .deps/RlePack.P .deps/State.P \
-.deps/TextArea.P .deps/common.P .deps/gfx.P .deps/main.P .deps/menu.P \
-.deps/sge_bm_text.P .deps/sge_primitives.P .deps/sge_surface.P \
-.deps/sge_tt_text.P
+.deps/Game.P .deps/GameOver.P .deps/Joystick.P \
+.deps/MortalNetworkImpl.P .deps/OnlineChat.P .deps/PlayerSelect.P \
+.deps/RlePack.P .deps/State.P .deps/TextArea.P .deps/common.P \
+.deps/gfx.P .deps/main.P .deps/menu.P .deps/sge_bm_text.P \
+.deps/sge_primitives.P .deps/sge_surface.P .deps/sge_tt_text.P
SOURCES = $(openmortal_SOURCES)
OBJECTS = $(openmortal_OBJECTS)
all: all-redirect
.SUFFIXES:
.SUFFIXES: .S .c .cpp .o .s
$(srcdir)/Makefile.in: Makefile.am $(top_srcdir)/configure.in $(ACLOCAL_M4)
cd $(top_srcdir) && $(AUTOMAKE) --gnu src/Makefile
Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status $(BUILT_SOURCES)
cd $(top_builddir) \
&& CONFIG_FILES=$(subdir)/$@ CONFIG_HEADERS= $(SHELL) ./config.status
mostlyclean-binPROGRAMS:
clean-binPROGRAMS:
-test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS)
distclean-binPROGRAMS:
maintainer-clean-binPROGRAMS:
install-binPROGRAMS: $(bin_PROGRAMS)
@$(NORMAL_INSTALL)
$(mkinstalldirs) $(DESTDIR)$(bindir)
@list='$(bin_PROGRAMS)'; for p in $$list; do \
if test -f $$p; then \
echo " $(INSTALL_PROGRAM) $$p $(DESTDIR)$(bindir)/`echo $$p|sed 's/$(EXEEXT)$$//'|sed '$(transform)'|sed 's/$$/$(EXEEXT)/'`"; \
$(INSTALL_PROGRAM) $$p $(DESTDIR)$(bindir)/`echo $$p|sed 's/$(EXEEXT)$$//'|sed '$(transform)'|sed 's/$$/$(EXEEXT)/'`; \
else :; fi; \
done
uninstall-binPROGRAMS:
@$(NORMAL_UNINSTALL)
list='$(bin_PROGRAMS)'; for p in $$list; do \
rm -f $(DESTDIR)$(bindir)/`echo $$p|sed 's/$(EXEEXT)$$//'|sed '$(transform)'|sed 's/$$/$(EXEEXT)/'`; \
done
.s.o:
$(COMPILE) -c $<
.S.o:
$(COMPILE) -c $<
mostlyclean-compile:
-rm -f *.o core *.core
clean-compile:
distclean-compile:
-rm -f *.tab.c
maintainer-clean-compile:
openmortal: $(openmortal_OBJECTS) $(openmortal_DEPENDENCIES)
@rm -f openmortal
$(CXXLINK) $(openmortal_LDFLAGS) $(openmortal_OBJECTS) $(openmortal_LDADD) $(LIBS)
.cpp.o:
$(CXXCOMPILE) -c $<
tags: TAGS
ID: $(HEADERS) $(SOURCES) $(LISP)
list='$(SOURCES) $(HEADERS)'; \
unique=`for i in $$list; do echo $$i; done | \
awk ' { files[$$0] = 1; } \
END { for (i in files) print i; }'`; \
here=`pwd` && cd $(srcdir) \
&& mkid -f$$here/ID $$unique $(LISP)
TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) $(LISP)
tags=; \
here=`pwd`; \
list='$(SOURCES) $(HEADERS)'; \
unique=`for i in $$list; do echo $$i; done | \
awk ' { files[$$0] = 1; } \
END { for (i in files) print i; }'`; \
test -z "$(ETAGS_ARGS)$$unique$(LISP)$$tags" \
|| (cd $(srcdir) && etags $(ETAGS_ARGS) $$tags $$unique $(LISP) -o $$here/TAGS)
mostlyclean-tags:
clean-tags:
distclean-tags:
-rm -f TAGS ID
maintainer-clean-tags:
distdir = $(top_builddir)/$(PACKAGE)-$(VERSION)/$(subdir)
subdir = src
distdir: $(DISTFILES)
here=`cd $(top_builddir) && pwd`; \
top_distdir=`cd $(top_distdir) && pwd`; \
distdir=`cd $(distdir) && pwd`; \
cd $(top_srcdir) \
&& $(AUTOMAKE) --include-deps --build-dir=$$here --srcdir-name=$(top_srcdir) --output-dir=$$top_distdir --gnu src/Makefile
@for file in $(DISTFILES); do \
d=$(srcdir); \
if test -d $$d/$$file; then \
cp -pr $$d/$$file $(distdir)/$$file; \
else \
test -f $(distdir)/$$file \
|| ln $$d/$$file $(distdir)/$$file 2> /dev/null \
|| cp -p $$d/$$file $(distdir)/$$file || :; \
fi; \
done
DEPS_MAGIC := $(shell mkdir .deps > /dev/null 2>&1 || :)
-include $(DEP_FILES)
mostlyclean-depend:
clean-depend:
distclean-depend:
-rm -rf .deps
maintainer-clean-depend:
%.o: %.c
@echo '$(COMPILE) -c $<'; \
$(COMPILE) -Wp,-MD,.deps/$(*F).pp -c $<
@-cp .deps/$(*F).pp .deps/$(*F).P; \
tr ' ' '\012' < .deps/$(*F).pp \
| sed -e 's/^\\$$//' -e '/^$$/ d' -e '/:$$/ d' -e 's/$$/ :/' \
>> .deps/$(*F).P; \
rm .deps/$(*F).pp
%.lo: %.c
@echo '$(LTCOMPILE) -c $<'; \
$(LTCOMPILE) -Wp,-MD,.deps/$(*F).pp -c $<
@-sed -e 's/^\([^:]*\)\.o[ ]*:/\1.lo \1.o :/' \
< .deps/$(*F).pp > .deps/$(*F).P; \
tr ' ' '\012' < .deps/$(*F).pp \
| sed -e 's/^\\$$//' -e '/^$$/ d' -e '/:$$/ d' -e 's/$$/ :/' \
>> .deps/$(*F).P; \
rm -f .deps/$(*F).pp
%.o: %.cpp
@echo '$(CXXCOMPILE) -c $<'; \
$(CXXCOMPILE) -Wp,-MD,.deps/$(*F).pp -c $<
@-cp .deps/$(*F).pp .deps/$(*F).P; \
tr ' ' '\012' < .deps/$(*F).pp \
| sed -e 's/^\\$$//' -e '/^$$/ d' -e '/:$$/ d' -e 's/$$/ :/' \
>> .deps/$(*F).P; \
rm .deps/$(*F).pp
%.lo: %.cpp
@echo '$(LTCXXCOMPILE) -c $<'; \
$(LTCXXCOMPILE) -Wp,-MD,.deps/$(*F).pp -c $<
@-sed -e 's/^\([^:]*\)\.o[ ]*:/\1.lo \1.o :/' \
< .deps/$(*F).pp > .deps/$(*F).P; \
tr ' ' '\012' < .deps/$(*F).pp \
| sed -e 's/^\\$$//' -e '/^$$/ d' -e '/:$$/ d' -e 's/$$/ :/' \
>> .deps/$(*F).P; \
rm -f .deps/$(*F).pp
info-am:
info: info-am
dvi-am:
dvi: dvi-am
check-am: all-am
check: check-am
installcheck-am:
installcheck: installcheck-am
install-exec-am: install-binPROGRAMS
install-exec: install-exec-am
install-data-am:
install-data: install-data-am
install-am: all-am
@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
install: install-am
uninstall-am: uninstall-binPROGRAMS
uninstall: uninstall-am
all-am: Makefile $(PROGRAMS)
all-redirect: all-am
install-strip:
$(MAKE) $(AM_MAKEFLAGS) AM_INSTALL_PROGRAM_FLAGS=-s install
installdirs:
$(mkinstalldirs) $(DESTDIR)$(bindir)
mostlyclean-generic:
clean-generic:
distclean-generic:
-rm -f Makefile $(CONFIG_CLEAN_FILES)
-rm -f config.cache config.log stamp-h stamp-h[0-9]*
maintainer-clean-generic:
mostlyclean-am: mostlyclean-binPROGRAMS mostlyclean-compile \
mostlyclean-tags mostlyclean-depend mostlyclean-generic
mostlyclean: mostlyclean-am
clean-am: clean-binPROGRAMS clean-compile clean-tags clean-depend \
clean-generic mostlyclean-am
clean: clean-am
distclean-am: distclean-binPROGRAMS distclean-compile distclean-tags \
distclean-depend distclean-generic clean-am
distclean: distclean-am
maintainer-clean-am: maintainer-clean-binPROGRAMS \
maintainer-clean-compile maintainer-clean-tags \
maintainer-clean-depend maintainer-clean-generic \
distclean-am
@echo "This command is intended for maintainers to use;"
@echo "it deletes files that may require special tools to rebuild."
maintainer-clean: maintainer-clean-am
.PHONY: mostlyclean-binPROGRAMS distclean-binPROGRAMS clean-binPROGRAMS \
maintainer-clean-binPROGRAMS uninstall-binPROGRAMS install-binPROGRAMS \
mostlyclean-compile distclean-compile clean-compile \
maintainer-clean-compile tags mostlyclean-tags distclean-tags \
clean-tags maintainer-clean-tags distdir mostlyclean-depend \
distclean-depend clean-depend maintainer-clean-depend info-am info \
dvi-am dvi check check-am installcheck-am installcheck install-exec-am \
install-exec install-data-am install-data install-am install \
uninstall-am uninstall all-redirect all-am all installdirs \
mostlyclean-generic distclean-generic clean-generic \
maintainer-clean-generic clean mostlyclean distclean maintainer-clean
# set the include path found by configure
#INCLUDES= $(all_includes)
# the library search path.
#msz_LDFLAGS = $(all_libraries)
# Tell versions [3.59,3.63) of GNU make to not export all variables.
# Otherwise a system limit (for SysV at least) may be exceeded.
.NOEXPORT:
diff --git a/src/MortalNetwork.h b/src/MortalNetwork.h
index e9183e9..988d47a 100644
--- a/src/MortalNetwork.h
+++ b/src/MortalNetwork.h
@@ -1,121 +1,130 @@
/***************************************************************************
MortalNetwork.h - description
-------------------
begin : Sun Jan 25 2004
copyright : (C) 2004 by upi
email : upi@feel
***************************************************************************/
#ifndef MORTALNETWORK_H
#define MORTALNETWORK_H
#include "FighterEnum.h"
+#include "SDL/SDL_types.h"
/** Mortal Network messages:
TYPICAL MESSAGE FLOW:
<connection is established>
Introduction messages are sent (version checking, usernames)
<players go to the character selection screen>
1. F <number> messages go both ways as players choose their characters.
2. R message goes in both direction when players have finished choosing.
<both sides go to the game screen>
In odd rounds, the "server" if the "master" and the "client" is the "slave"
In even rounds, the "client" if the "master" and the "server" is the "slave"
Both the master and the slave send an S message to synchronize the game start.
The master sends G <text> messages to update the backend on the slave side.
The slave sends K <number> <bool> messages to communicate keystrokes to the master side.
The master sends O <number> <bool> message when the round is over.
Back to game start synchronization.
<both sides go to final judgement - may disconnect the game>
<both sides go back to the character selection screen>
OTHERS:
Msgs can be send on the character selection screen with M <text>.
The connection can be broken at any time. IsConnectionAlive() must be called
periodically.
The "server" is always appears as player 1, the "client" is always player 2.
However, they both use the "Player 1" keys.
*/
class CMortalNetwork
{
+public:
+ struct SGameParams
+ {
+ Uint32 iGameTime;
+ Uint32 iGameSpeed;
+ Uint32 iHitPoints;
+ };
+
public:
static void Create();
// Connection's lifecycle
virtual bool Start( const char* a_pcServerName ) = 0; // Accept connection, or connect to given server
virtual void Stop() = 0; // Disconnect
virtual bool IsConnectionAlive() = 0; // Is the connection still good?
virtual void Update() = 0; // Read network traffic. Might get disconnected...
virtual const char* GetLastError() = 0;
virtual bool IsMaster() = 0; // Am I Master or Slave?
// Msg related methods
virtual const char* GetRemoteUsername() = 0; // This is the name that is passed upon connection.
virtual void SendMsg( const char* a_rsMsg ) = 0; // Prompt the user for a line of chat text
virtual bool IsMsgAvailable() = 0; // Returns true is a chatline has arrived
virtual const char* GetMsg() = 0; // The next chatline, or NULL if there are no more.
// Charater Selection methods
virtual bool IsRemoteFighterAvailable( FighterEnum a_enFighter ) = 0; // Does the other computer have fighter X installed?
virtual FighterEnum GetRemoteFighter() = 0; // Returns the latest fighter chosen by the remote side.
virtual bool IsRemoteSideReady() = 0; // The other player is finished choosing.
virtual void SendFighter( FighterEnum a_enFighter ) = 0; // Let the other side know that I switched to fighter X.
virtual void SendReady() = 0; // Let the other side know that I am ready.
virtual void SendGameParams( int a_iGameSpeed, int a_iGameTime, int a_iHitPoints ) = 0;
+ virtual SGameParams GetGameParams() = 0;
// Game methods
virtual bool SynchStartRound() = 0;
virtual void SendGameData( const char* a_pcGameData ) = 0;
virtual const char* GetLatestGameData() = 0;
- virtual void SendKeystroke( int a_iKey, bool a_bPressed ) = 0;
- virtual bool GetKeystroke( int& a_riOutKey, bool& a_rbPressed ) = 0;
+ virtual void SendKeystroke( int a_iTime, int a_iKey, bool a_bPressed ) = 0;
+ virtual bool GetKeystroke( int& a_riOutTime, int& a_riOutKey, bool& a_rbPressed ) = 0;
+
+ virtual void SendGameTick( int a_iGameTick ) = 0;
+ virtual int GetGameTick() = 0;
- virtual void SendGameTime( int a_iGameTime, int a_iGamePhase ) = 0;
- virtual int GetGameTime() = 0;
- virtual int GetGamePhase() = 0;
-
virtual void SendHurryup( int a_iHurryUpCode ) = 0;
virtual int GetHurryup() = 0;
-
+
virtual void SendRoundOver( int a_iWhoWon, bool a_bGameOver ) = 0;
virtual bool IsRoundOver() = 0;
virtual bool IsGameOver() = 0;
virtual int GetWhoWon() = 0;
};
extern CMortalNetwork* g_poNetwork;
#endif // MORTALNETWORK_H
diff --git a/src/MortalNetworkImpl.cpp b/src/MortalNetworkImpl.cpp
index bbf77da..af0c216 100644
--- a/src/MortalNetworkImpl.cpp
+++ b/src/MortalNetworkImpl.cpp
@@ -1,934 +1,933 @@
/***************************************************************************
MortalNetworkImpl.cpp - description
-------------------
begin : Sun Jan 25 2004
copyright : (C) 2004 by upi
email : upi@feel
***************************************************************************/
#include "MortalNetworkImpl.h"
#include "State.h"
#include "PlayerSelect.h"
#include "common.h"
#include "config.h"
#define MORTALNETWORKPORT 0x3A22
#define MAXSTRINGLENGTH 900
// Some graphics routines, defined in menu.cpp
void MortalNetworkResetMessages( bool a_bClear );
void MortalNetworkMessage( const char* format, ... );
bool MortalNetworkCheckKey();
const char* GetGameTimeString( int a_iValue );
const char* GetGameSpeedString( int a_iValue );
const char* GetHitPointsString( int a_iValue );
CMortalNetwork* g_poNetwork = NULL;
void CMortalNetwork::Create() // static
{
if ( NULL == g_poNetwork )
{
g_poNetwork = new CMortalNetworkImpl;
}
}
CMortalNetworkImpl::CMortalNetworkImpl()
{
m_enState = NS_DISCONNECTED;
m_bServer = false;
m_bMaster = false;
m_poSocket = NULL;
m_enRemoteFighter = UNKNOWN;
m_bRemoteReady = false;
m_bRoundOver = false;
m_iWhoWon = -1;
m_bGameOver = false;
- m_iGameTime = 0;
- m_iGamePhase = 0;
+ m_iGameTick = 0;
m_iHurryupCode = 0;
m_iIncomingBufferSize = 0;
if(SDLNet_Init()==-1)
{
m_bNetworkAvailable = false;
m_sLastError = SDLNet_GetError();
debug ( "Error opening SDLNet: %s\n", m_sLastError.c_str() );
}
m_bNetworkAvailable = true;
}
CMortalNetworkImpl::~CMortalNetworkImpl()
{
Stop();
}
bool CMortalNetworkImpl::Start( const char* a_pcServerName )
{
#define RETURNNOERROR { \
debug( "%s\n", m_sLastError.c_str() ); \
return false; }
#define RETURNWITHERROR { \
m_sLastError = SDLNet_GetError(); \
debug( "%s\n", m_sLastError.c_str() ); \
return false; }
#define RETURNWITHADDITIONALERROR { \
m_sLastError += SDLNet_GetError(); \
debug( "%s\n", m_sLastError.c_str() ); \
return false; }
if ( !m_bNetworkAvailable )
{
return false;
}
debug( "CMortalNetworkImpl::Start( %s )\n", a_pcServerName ? a_pcServerName : "NULL" );
IPaddress oAddress;
if ( a_pcServerName )
{
MortalNetworkMessage( Translate("Resolving hostname (%s)..."), a_pcServerName );
}
int iResult = SDLNet_ResolveHost( &oAddress, (char*) a_pcServerName, MORTALNETWORKPORT );
if ( iResult )
{
m_sLastError = Translate( "Couldn't resolve host." );
RETURNNOERROR;
}
debug( "IP Address of server is 0x%x\n", oAddress.host );
if ( a_pcServerName )
{
Uint32 ipaddr=SDL_SwapBE32(oAddress.host);
MortalNetworkMessage("Connecting to %d.%d.%d.%d port %d",
ipaddr>>24, (ipaddr>>16)&0xff, (ipaddr>>8)&0xff, ipaddr&0xff, MORTALNETWORKPORT );
}
if ( !a_pcServerName )
{
// SERVER-MODE CONNECTION
m_poSocket = SDLNet_TCP_Open( &oAddress );
if ( NULL == m_poSocket ) RETURNWITHERROR;
// Wait for connection ...
MortalNetworkMessage ( Translate("Waiting for connection... (press any key to abort)") );
MortalNetworkMessage( Translate("You must have port 14882 open for this to work.") );
TCPsocket poClient;
while ( 1 )
{
poClient = SDLNet_TCP_Accept( m_poSocket );
if ( poClient ) break;
if (MortalNetworkCheckKey()) break;;
SDL_Delay( 100 );
}
SDLNet_TCP_Close( m_poSocket );
if ( NULL == poClient )
{
m_sLastError = "No connection.";
return false;
}
IPaddress* poRemoteAddress = SDLNet_TCP_GetPeerAddress(poClient);
if ( !poRemoteAddress )
{
RETURNWITHERROR;
}
Uint32 ipaddr=SDL_SwapBE32(poRemoteAddress->host);
MortalNetworkMessage("Accepted connection from %d.%d.%d.%d port %d",
ipaddr>>24, (ipaddr>>16)&0xff, (ipaddr>>8)&0xff, ipaddr&0xff, MORTALNETWORKPORT);
// Set the client socket as our socket, and drop the server socket.
m_poSocket = poClient;
}
else
{
// CLIENT-MODE CONNECTION
MortalNetworkMessage ( Translate("Waiting for connection... (press any key to abort)") );
while (1)
{
m_poSocket = SDLNet_TCP_Open( &oAddress );
if ( m_poSocket ) break;
if ( MortalNetworkCheckKey() ) break;
SDL_Delay( 100 );
}
if ( NULL == m_poSocket )
{
RETURNWITHERROR;
}
}
// CONNECTION ESTABLISHED. SEND INTRO PACKETS
MortalNetworkMessage( Translate("Connection established.") );
struct SIntroPackage
{
char cID;
char acVersion[10];
} oIntroPackage, oRemotePackage;
oIntroPackage.cID = 'I';
strncpy( oIntroPackage.acVersion, VERSION, 10 );
oIntroPackage.acVersion[9] = 0;
debug( "Sending intro package... " );
int iRetval = SDLNet_TCP_Send( m_poSocket, &oIntroPackage, sizeof( oIntroPackage ) );
if ( iRetval < (int) sizeof( oIntroPackage ) )
{
RETURNWITHERROR;
}
iRetval = SDLNet_TCP_Recv( m_poSocket, &oRemotePackage, sizeof( oRemotePackage ) );
if ( iRetval <= 0 )
{
RETURNWITHERROR;
}
if ( iRetval < (int) sizeof( oRemotePackage )
|| oRemotePackage.cID != 'I'
|| strncmp( oRemotePackage.acVersion, VERSION, 9 ) )
{
m_sLastError = Translate( "The remote side has a different version of OpenMortal running." );
RETURNNOERROR;
}
MortalNetworkMessage( Translate("Life is good.") );
m_enState = NS_CHARACTER_SELECTION;
m_bServer = NULL == a_pcServerName;
m_bMaster = m_bServer;
m_sLastError = "";
m_asMsgs.clear();
m_enRemoteFighter = UNKNOWN;
m_bRemoteReady = false;
m_sLatestGameData = "";
m_aiKeystrokes.clear();
+ m_aiKeystrokes.clear();
m_abKeystrokes.clear();
- m_iGameTime = 0;
- m_iGamePhase = 0;
+ m_iGameTick = 0;
m_iHurryupCode = 0;
m_iIncomingBufferSize = 0;
m_aiAvailableRemoteFighters.clear();
m_oGameParams.iGameTime = m_oGameParams.iGameSpeed = m_oGameParams.iHitPoints = 0;
m_sRemoteUserName = "HIM";
m_poSocketSet = SDLNet_AllocSocketSet( 1 );
SDLNet_TCP_AddSocket( m_poSocketSet, m_poSocket ); // Check for errors?
SendRawData( 'U', g_oState.m_acNick, strlen(g_oState.m_acNick)+1 );
return true;
}
void CMortalNetworkImpl::Stop()
{
if ( NS_DISCONNECTED == m_enState )
{
return;
}
g_oState.m_enGameMode = SState::IN_DEMO;
m_enState = NS_DISCONNECTED;
SDLNet_FreeSocketSet( m_poSocketSet );
SDLNet_TCP_Close( m_poSocket );
}
bool CMortalNetworkImpl::IsConnectionAlive()
{
return ( NS_DISCONNECTED != m_enState );
}
#define DISCONNECTONCOMMUNICATIONERROR { \
m_sLastError = Translate("Communication error. Disconnecting."); \
Stop(); \
return; }
#define DISCONNECTWITH(A) { \
m_sLastError = Translate("Communication error. Disconnecting."); \
Stop(); \
return(A); }
#define CHECKCONNECTION if ( NS_DISCONNECTED == m_enState ) return;
void CMortalNetworkImpl::Update()
{
CHECKCONNECTION;
// 1. CHECK FOR STUFF TO READ
int iRetval = SDLNet_CheckSockets( m_poSocketSet, 0 );
if ( iRetval <= 0 )
{
return;
}
// 2. APPEND AT MOST 1024 bytes TO THE END OF THE INCOMING BUFFER
iRetval = SDLNet_TCP_Recv( m_poSocket, m_acIncomingBuffer + m_iIncomingBufferSize, 1024 );
if ( iRetval <= 0 )
{
m_sLastError = SDLNet_GetError();
Stop();
return;
}
m_iIncomingBufferSize += iRetval;
// 3. CONSUME THE INCOMING BUFFER.
// We always make sure the incoming buffer starts with a package header.
int iOffset = 0;
while ( iOffset < m_iIncomingBufferSize )
{
// 3.1. Check if we have enough data to receive the package.
if ( m_iIncomingBufferSize - iOffset < 4 )
{
// Not enough space left for a full header.
debug( "Not enough space left for a full header (%d).\n", m_iIncomingBufferSize-iOffset );
break;
}
unsigned int iLengthOfPackage = SDL_SwapBE16(*((Uint16*)(m_acIncomingBuffer + iOffset + 1)));
if ( iLengthOfPackage > 1000 )
{
debug( "Maximum package size exceeded.\n" );
DISCONNECTONCOMMUNICATIONERROR;
}
- debug( "Receiving stuff.. %c type, %d package length, offset %d in buffer, %d bytes in buffer\n",
- m_acIncomingBuffer[iOffset], iLengthOfPackage, iOffset, m_iIncomingBufferSize );
+// debug( "Receiving stuff.. %c type, %d package length, offset %d in buffer, %d bytes in buffer\n",
+// m_acIncomingBuffer[iOffset], iLengthOfPackage, iOffset, m_iIncomingBufferSize );
if ( iOffset + 4 + (int)iLengthOfPackage > m_iIncomingBufferSize )
{
// Not enough space left for the actual package.
debug( "Not enough space left for the actual package.\n" );
break;
}
// 3.2. Receive the data.
switch ( m_acIncomingBuffer[iOffset] )
{
case 'M': ReceiveMsg( m_acIncomingBuffer+iOffset+4, iLengthOfPackage ); break;
case 'F': ReceiveFighter( m_acIncomingBuffer+iOffset+4, iLengthOfPackage ); break;
case 'R': ReceiveReady( m_acIncomingBuffer+iOffset+4, iLengthOfPackage ); break;
case 'S': m_bSynchQueryResponse=true; break;
case 'G': ReceiveGameData( m_acIncomingBuffer+iOffset+4, iLengthOfPackage ); break;
case 'K': ReceiveKeystroke( m_acIncomingBuffer+iOffset+4, iLengthOfPackage ); break;
case 'O': ReceiveRoundOver( m_acIncomingBuffer+iOffset+4, iLengthOfPackage ); break;
- case 'T': ReceiveGameTime( m_acIncomingBuffer+iOffset+4, iLengthOfPackage ); break;
+ case 'T': ReceiveGameTick( m_acIncomingBuffer+iOffset+4, iLengthOfPackage ); break;
case 'H': ReceiveHurryup( m_acIncomingBuffer+iOffset+4, iLengthOfPackage ); break;
case 'A': ReceiveRemoteFighterAvailable( m_acIncomingBuffer+iOffset+4, iLengthOfPackage ); break;
case 'Q': ReceiveRemoteFighterQuery( m_acIncomingBuffer+iOffset+4, iLengthOfPackage ); break;
case 'P': ReceiveGameParams( m_acIncomingBuffer+iOffset+4, iLengthOfPackage ); break;
case 'U': ReceiveRemoteUserName( m_acIncomingBuffer+iOffset+4, iLengthOfPackage ); break;
default:
{
debug( "Bad ID: %c (%d)\n", m_acIncomingBuffer[iOffset], m_acIncomingBuffer[iOffset] );
DISCONNECTONCOMMUNICATIONERROR;
}
}
if ( !IsConnectionAlive() )
{
return;
}
iOffset += iLengthOfPackage + 4;
}
// 4. MOVE LEFTOVER DATA TO THE BEGINNING OF THE INCOMING BUFFER
// The leftover data starts at iOffset, and is (m_iIncomingBufferSize-iOffset) long.
memmove( m_acIncomingBuffer, m_acIncomingBuffer + iOffset, m_iIncomingBufferSize-iOffset );
m_iIncomingBufferSize -= iOffset;
}
const char* CMortalNetworkImpl::GetLastError()
{
return m_sLastError.c_str();
}
bool CMortalNetworkImpl::IsMaster()
{
return m_bMaster;
}
void CMortalNetworkImpl::ReceiveRemoteUserName( void* a_pData, int a_iLength )
{
if ( a_iLength < 1 || a_iLength > MAXSTRINGLENGTH ) DISCONNECTONCOMMUNICATIONERROR;
char* acData = (char*) a_pData;
acData[ a_iLength-1 ] = 0; // Last char should be 0, just making sure..
m_sRemoteUserName = acData;
}
const char* CMortalNetworkImpl::GetRemoteUsername()
{
return m_sRemoteUserName.c_str();
}
/*************************************************************************
MSG RELATED METHODS
*************************************************************************/
/** All sent data must go through this method. It ensures the well-formed
header for the data.
The header itself looks like this:
ID char
Length Uint16
Reserved char
This is followed by as many bytes as the Length is.
*/
void CMortalNetworkImpl::SendRawData( char a_cID, const void* a_pData, int a_iLength )
{
CHECKCONNECTION;
char acBuffer[4];
acBuffer[0] = a_cID;
*((Uint16*)(acBuffer+1)) = SDL_SwapBE16( a_iLength );
acBuffer[3] = 0;
int iRetval = SDLNet_TCP_Send( m_poSocket, &acBuffer, 4 );
if ( iRetval != 4 ) DISCONNECTONCOMMUNICATIONERROR;
if ( a_iLength )
{
iRetval = SDLNet_TCP_Send( m_poSocket, (void*) a_pData, a_iLength );
if (iRetval != a_iLength ) DISCONNECTONCOMMUNICATIONERROR;
}
}
void CMortalNetworkImpl::SendMsg( const char* a_pcMsg )
{
CHECKCONNECTION;
int iMsgLen = strlen( a_pcMsg ) + 1;
if ( iMsgLen > MAXSTRINGLENGTH )
{
// Will not be 0 terminated if exceeds length!
iMsgLen = MAXSTRINGLENGTH;
}
SendRawData( 'M', a_pcMsg, iMsgLen );
}
void CMortalNetworkImpl::ReceiveMsg( void* a_pData, int a_iLength )
{
if ( a_iLength < 1 || a_iLength > MAXSTRINGLENGTH ) DISCONNECTONCOMMUNICATIONERROR;
char* pcData = (char*) a_pData;
pcData[ a_iLength-1 ] = 0; // Last char should be 0, just making sure..
std::string sMsg = "<" + m_sRemoteUserName + "> " + pcData;
m_asMsgs.push_back( sMsg );
}
bool CMortalNetworkImpl::IsMsgAvailable()
{
return m_asMsgs.size() > 0;
}
const char* CMortalNetworkImpl::GetMsg()
{
static std::string sLastMsg;
if ( IsMsgAvailable() )
{
sLastMsg = m_asMsgs.front();
m_asMsgs.pop_front();
return sLastMsg.c_str();
}
return NULL;
}
/*************************************************************************
CHARACTER SELECTION RELATED METHODS
*************************************************************************/
/** Unfortunately not all STL are created equal... Some do not have the
find algorithm. So, here is our feeble implementation... */
template<class InputIterator, class EqualityComparable>
InputIterator MszFind(InputIterator first, InputIterator last, const EqualityComparable& value )
{
while ( first != last
&& *first != value )
{
++first;
}
return first;
}
bool CMortalNetworkImpl::IsRemoteFighterAvailable( FighterEnum a_enFighter )
{
if ( 0 == a_enFighter )
{
return false;
}
// Check if we already have it cached.
if ( MszFind( m_aiAvailableRemoteFighters.begin(), m_aiAvailableRemoteFighters.end(), a_enFighter ) != m_aiAvailableRemoteFighters.end() )
{
return true;
}
if ( MszFind( m_aiAvailableRemoteFighters.begin(), m_aiAvailableRemoteFighters.end(), -a_enFighter ) != m_aiAvailableRemoteFighters.end() )
{
return false;
}
Uint32 iFighter = SDL_SwapBE32( a_enFighter );
SendRawData( 'Q', &iFighter, sizeof(Uint32) );
for ( int i=0; i<100; ++i )
{
SDL_Delay(10);
Update();
if ( MszFind( m_aiAvailableRemoteFighters.begin(), m_aiAvailableRemoteFighters.end(), a_enFighter ) != m_aiAvailableRemoteFighters.end() )
{
return true;
}
if ( MszFind( m_aiAvailableRemoteFighters.begin(), m_aiAvailableRemoteFighters.end(), -a_enFighter ) != m_aiAvailableRemoteFighters.end() )
{
return false;
}
}
m_aiAvailableRemoteFighters.push_front( -a_enFighter );
return false;
}
void CMortalNetworkImpl::ReceiveRemoteFighterQuery( void* a_pData, int a_iLength )
{
if ( a_iLength != sizeof(Uint32) ) DISCONNECTONCOMMUNICATIONERROR;
FighterEnum iFighter = (FighterEnum) SDL_SwapBE32( *((Uint32*)a_pData) );
bool bAvailable = g_oPlayerSelect.IsLocalFighterAvailable( iFighter );
Uint32 iResponse = bAvailable ? iFighter : iFighter + 100000;
debug( "ReceiveRemoteFighterQuery: %d -> %d\n", iFighter, iResponse );
iResponse = SDL_SwapBE32( iResponse );
SendRawData( 'A', &iResponse, sizeof(Uint32) );
}
void CMortalNetworkImpl::ReceiveRemoteFighterAvailable( void* a_pData, int a_iLength )
{
if ( a_iLength != sizeof(Uint32) ) DISCONNECTONCOMMUNICATIONERROR;
Uint32 iFighter = SDL_SwapBE32( *((Uint32*)a_pData) );
debug( "ReceiveRemoteFighterAvailable: %d\n", iFighter );
if ( iFighter >= 100000 )
{
m_aiAvailableRemoteFighters.push_front( -(iFighter-100000) );
}
else
{
m_aiAvailableRemoteFighters.push_front( iFighter );
}
}
void CMortalNetworkImpl::SendFighter( FighterEnum a_enFighter )
{
CHECKCONNECTION;
Uint32 iFighter = SDL_SwapBE32( a_enFighter );
SendRawData( 'F', &iFighter, sizeof (iFighter) );
}
void CMortalNetworkImpl::ReceiveFighter( void* a_pcData, int a_iLength )
{
Uint32 iFighter;
if ( a_iLength != sizeof(iFighter) ) DISCONNECTONCOMMUNICATIONERROR;
iFighter = *((Uint32*)a_pcData);
m_enRemoteFighter = (FighterEnum) SDL_SwapBE32( iFighter );
debug( "ReceiveFighter: %d\n", m_enRemoteFighter );
}
FighterEnum CMortalNetworkImpl::GetRemoteFighter()
{
return m_enRemoteFighter;
}
void CMortalNetworkImpl::SendReady()
{
CHECKCONNECTION;
SendRawData( 'R', NULL, 0 );
}
void CMortalNetworkImpl::ReceiveReady( void* a_pData, int a_iLength )
{
if ( a_iLength != 0 ) DISCONNECTONCOMMUNICATIONERROR;
m_bRemoteReady = true;
}
bool CMortalNetworkImpl::IsRemoteSideReady()
{
return m_bRemoteReady;
}
void CMortalNetworkImpl::SendGameParams( int a_iGameSpeed, int a_iGameTime, int a_iHitPoints )
{
CHECKCONNECTION;
if ( (int)m_oGameParams.iGameSpeed == a_iGameSpeed
&& (int)m_oGameParams.iGameTime == a_iGameTime
&& (int)m_oGameParams.iHitPoints == a_iHitPoints )
{
// Nothing to update.
return;
}
m_oGameParams.iGameSpeed = a_iGameSpeed;
m_oGameParams.iGameTime = a_iGameTime;
m_oGameParams.iHitPoints = a_iHitPoints;
SGameParams oPackage;
oPackage.iGameSpeed = SDL_SwapBE32( a_iGameSpeed );
oPackage.iGameTime = SDL_SwapBE32( a_iGameTime );
oPackage.iHitPoints = SDL_SwapBE32( a_iHitPoints );
SendRawData( 'P', &oPackage, sizeof(SGameParams) );
}
void CMortalNetworkImpl::ReceiveGameParams( void* a_pData, int a_iLength )
{
if ( a_iLength != sizeof(SGameParams) ) DISCONNECTONCOMMUNICATIONERROR;
SGameParams* poPackage = (SGameParams*) a_pData;
if ( m_oGameParams.iGameSpeed != SDL_SwapBE32( poPackage->iGameSpeed ) )
{
m_oGameParams.iGameSpeed = SDL_SwapBE32( poPackage->iGameSpeed );
m_asMsgs.push_back( std::string("*** ") + GetGameSpeedString( m_oGameParams.iGameSpeed ) );
}
if ( m_oGameParams.iGameTime != SDL_SwapBE32( poPackage->iGameTime ) )
{
m_oGameParams.iGameTime = SDL_SwapBE32( poPackage->iGameTime );
m_asMsgs.push_back( std::string("*** ") + GetGameTimeString( m_oGameParams.iGameTime) );
}
if ( m_oGameParams.iHitPoints != SDL_SwapBE32( poPackage->iHitPoints ) )
{
m_oGameParams.iHitPoints = SDL_SwapBE32( poPackage->iHitPoints );
m_asMsgs.push_back( std::string("*** ") + GetHitPointsString( m_oGameParams.iHitPoints ) );
}
}
+CMortalNetworkImpl::SGameParams CMortalNetworkImpl::GetGameParams()
+{
+ return m_oGameParams;
+}
/*************************************************************************
GAME RELATED METHODS
*************************************************************************/
bool CMortalNetworkImpl::SynchStartRound()
{
debug( "SynchStartRound STARTED.\n" );
m_bSynchQueryResponse = false;
// run until both sides manage to get a SYNCH
int i=0;
while ( !m_bSynchQueryResponse )
{
SendRawData('S', NULL, 0);
if ( !IsConnectionAlive() ) break;
Update();
SDL_Delay(100);
if ( !IsConnectionAlive() ) break;
++i;
if ( i == 10 )
{
debug( "Synch is slow...\n" );
MortalNetworkResetMessages( true );
MortalNetworkMessage( "Synching with remote side..." );
MortalNetworkMessage( "Press any key to disconnect." );
}
if ( i > 10
&& MortalNetworkCheckKey() )
{
Stop();
return false;
}
}
if ( IsConnectionAlive() )
{
m_enState = NS_IN_GAME;
m_bRoundOver = false;
m_bGameOver = false;
m_bRemoteReady = false;
m_bSynchQueryResponse = false;
m_iHurryupCode = -1;
}
return IsConnectionAlive();
debug( "SynchStartRound FINISHED.\n" );
}
void CMortalNetworkImpl::SendGameData( const char* a_pcGameData )
{
CHECKCONNECTION;
int iMsgLen = strlen( a_pcGameData ) + 1;
if ( iMsgLen > MAXSTRINGLENGTH )
{
// Will not be 0 terminated if exceeds length!
iMsgLen = MAXSTRINGLENGTH;
}
SendRawData( 'G', a_pcGameData, iMsgLen );
}
void CMortalNetworkImpl::ReceiveGameData( void* a_pData, int a_iLength )
{
if ( a_iLength < 1 || a_iLength > MAXSTRINGLENGTH ) DISCONNECTONCOMMUNICATIONERROR;
char* pcData = (char*) a_pData;
pcData[ a_iLength-1 ] = 0; // Last char should be 0, just making sure..
if ( pcData[0] )
{
m_sLatestGameData = pcData;
}
}
const char* CMortalNetworkImpl::GetLatestGameData()
{
return m_sLatestGameData.c_str();
}
struct SKeystrokePackage
{
+ Uint32 iTime;
char cKey;
bool bPressed;
};
-void CMortalNetworkImpl::SendKeystroke( int a_iKey, bool a_bPressed )
+void CMortalNetworkImpl::SendKeystroke( int a_iTime, int a_iKey, bool a_bPressed )
{
CHECKCONNECTION;
SKeystrokePackage oPackage;
+ oPackage.iTime = SDL_SwapBE32( a_iTime );
oPackage.cKey = a_iKey;
oPackage.bPressed = a_bPressed;
SendRawData( 'K', &oPackage, sizeof( oPackage) );
}
void CMortalNetworkImpl::ReceiveKeystroke( void* a_pData, int a_iLength )
{
if ( a_iLength != (int)sizeof(SKeystrokePackage) ) DISCONNECTONCOMMUNICATIONERROR;
SKeystrokePackage* poPackage = (SKeystrokePackage*) a_pData;
-
+
+ m_aiKeyTimes.push_back( SDL_SwapBE32(poPackage->iTime) );
m_aiKeystrokes.push_back( poPackage->cKey );
m_abKeystrokes.push_back( poPackage->bPressed );
}
-bool CMortalNetworkImpl::GetKeystroke( int& a_riOutKey, bool& a_rbOutPressed )
+bool CMortalNetworkImpl::GetKeystroke( int& a_riOutTime, int& a_riOutKey, bool& a_rbOutPressed )
{
if ( m_aiKeystrokes.size() == 0 )
{
return false;
}
+ a_riOutTime = m_aiKeyTimes.front();
a_riOutKey = m_aiKeystrokes.front();
a_rbOutPressed = m_abKeystrokes.front();
+ m_aiKeyTimes.pop_front();
m_aiKeystrokes.pop_front();
m_abKeystrokes.pop_front();
- debug( "GetKeystroke: %d, %d\n", a_riOutKey, a_rbOutPressed );
+ // debug( "GetKeystroke: %d, %d\n", a_riOutKey, a_rbOutPressed );
return true;
}
-struct SGameTimePackage
+struct SGameTickPackage
{
- int iGameTime;
- int iGamePhase;
+ Uint32 iGameTick;
};
-void CMortalNetworkImpl::SendGameTime( int a_iGameTime, int a_iGamePhase )
+void CMortalNetworkImpl::SendGameTick( int a_iGameTick )
{
CHECKCONNECTION;
+
+ if ( a_iGameTick < 0 ) a_iGameTick = 0;
- if ( a_iGameTime == m_iGameTime
- && a_iGamePhase == m_iGamePhase )
- {
- return; // Nothing to update, the other side already knows.
- }
-
- SGameTimePackage oPackage;
- m_iGameTime = a_iGameTime;
- m_iGamePhase = a_iGamePhase;
- oPackage.iGameTime = SDL_SwapBE32( a_iGameTime );
- oPackage.iGamePhase = SDL_SwapBE32( a_iGamePhase );
+ SGameTickPackage oPackage;
+ oPackage.iGameTick = SDL_SwapBE32( a_iGameTick );
- SendRawData( 'T', &oPackage, sizeof(SGameTimePackage) );
+ SendRawData( 'T', &oPackage, sizeof(SGameTickPackage) );
}
-void CMortalNetworkImpl::ReceiveGameTime( void* a_pData, int a_iLength )
+void CMortalNetworkImpl::ReceiveGameTick( void* a_pData, int a_iLength )
{
- if ( a_iLength != sizeof(SGameTimePackage) ) DISCONNECTONCOMMUNICATIONERROR;
- SGameTimePackage* poPackage = (SGameTimePackage*) a_pData;
+ if ( a_iLength != sizeof(SGameTickPackage) ) DISCONNECTONCOMMUNICATIONERROR;
+ SGameTickPackage* poPackage = (SGameTickPackage*) a_pData;
- m_iGameTime = SDL_SwapBE32( poPackage->iGameTime );
- m_iGamePhase = SDL_SwapBE32( poPackage->iGamePhase );
+ m_iGameTick = SDL_SwapBE32( poPackage->iGameTick );
}
-int CMortalNetworkImpl::GetGameTime()
+int CMortalNetworkImpl::GetGameTick()
{
- return m_iGameTime;
+ return m_iGameTick;
}
-int CMortalNetworkImpl::GetGamePhase()
-{
- return m_iGamePhase;
-}
void CMortalNetworkImpl::SendHurryup( int a_iHurryUpCode )
{
CHECKCONNECTION;
int iPackage = SDL_SwapBE32( a_iHurryUpCode );
SendRawData( 'H', &iPackage, sizeof(int) );
}
void CMortalNetworkImpl::ReceiveHurryup( void* a_pData, int a_iLength )
{
if ( a_iLength != sizeof(int) ) DISCONNECTONCOMMUNICATIONERROR;
m_iHurryupCode = SDL_SwapBE32( *((int*)a_pData) );
}
int CMortalNetworkImpl::GetHurryup()
{
int iRetval = m_iHurryupCode;
m_iHurryupCode = -1;
return iRetval;
}
struct SRoundOrder
{
int iWhoWon;
bool bGameOver;
};
void CMortalNetworkImpl::SendRoundOver( int a_iWhoWon, bool a_bGameOver )
{
CHECKCONNECTION;
SRoundOrder oPackage;
oPackage.iWhoWon = a_iWhoWon;
oPackage.bGameOver = a_bGameOver;
SendRawData( 'O', &oPackage, sizeof(SRoundOrder) );
if ( a_bGameOver )
{
m_enState = NS_CHARACTER_SELECTION;
}
+
+ debug ( "SendRoundOver: %d, %d\n", a_iWhoWon, a_bGameOver );
}
void CMortalNetworkImpl::ReceiveRoundOver( void* a_pData, int a_iLength )
{
if ( a_iLength != sizeof(SRoundOrder) ) DISCONNECTONCOMMUNICATIONERROR;
SRoundOrder* poPackage = (SRoundOrder*) a_pData;
m_iWhoWon = poPackage->iWhoWon;
m_bGameOver = poPackage->bGameOver;
m_bRoundOver = true;
+
+ debug ( "ReceiveRoundOver: %d, %d\n", m_iWhoWon, m_bGameOver );
}
bool CMortalNetworkImpl::IsRoundOver()
{
return m_bRoundOver;
}
bool CMortalNetworkImpl::IsGameOver()
{
return m_bGameOver;
}
int CMortalNetworkImpl::GetWhoWon()
{
return m_iWhoWon;
}
diff --git a/src/MortalNetworkImpl.h b/src/MortalNetworkImpl.h
index 2007e49..841de74 100644
--- a/src/MortalNetworkImpl.h
+++ b/src/MortalNetworkImpl.h
@@ -1,170 +1,165 @@
/***************************************************************************
MortalNetworkImpl.h - description
-------------------
begin : Sun Jan 25 2004
copyright : (C) 2004 by upi
email : upi@feel
***************************************************************************/
#ifndef MORTALNETWORKIMPL_H
#define MORTALNETWORKIMPL_H
#include "MortalNetwork.h"
#include "SDL_net.h"
#include <string>
#include <list>
typedef std::list<int> TIntList;
typedef std::list<std::string> TStringList;
/**
Implementation of the CMortalNetwork interface.
SUMMARY OF MESSAGES:
I <version> <username> - Introduction sent both ways on connection.
U <text> - Remote user's name
M <text> - Incoming Msg text.
S - Ready for the next round (synch).
G <text> - Update on the game backend data.
T <number> <number> - Update the game time and game phase.
K <number> <bool> - Key # up/down
H <number> - Hurryup and other special messages
O <number> <bool> - The round is over (who won, are there more rounds).
F <number> - I have switched to fighter X.
R - I have chosen a fighter.
Q <number> - Is fighter X available?
A <number> - Fighter A is available.
P <number> x3 - Game parameters
*/
class CMortalNetworkImpl: public CMortalNetwork
{
public:
CMortalNetworkImpl();
virtual ~CMortalNetworkImpl();
// Connection's lifecycle
bool Start( const char* a_pcServerName );
void Stop(); // Disconnect
bool IsConnectionAlive(); // Is the connection still good?
void Update(); // Read network traffic. Might get disconnected...
const char* GetLastError();
bool IsMaster(); // Am I Master or Slave?
// Msg related methods
const char* GetRemoteUsername(); // This is the name that is passed upon connection.
void SendMsg( const char* a_pcMsg ); // Prompt the user for a line of chat text
bool IsMsgAvailable(); // Returns true is a chatline has arrived
const char* GetMsg(); // The next chatline, or NULL if there are no more.
// Charater Selection methods
bool IsRemoteFighterAvailable( FighterEnum a_enFighter ); // Does the other computer have fighter X installed?
FighterEnum GetRemoteFighter(); // Returns the latest fighter chosen by the remote side.
bool IsRemoteSideReady(); // The other player is finished choosing.
void SendFighter( FighterEnum a_enFighter ); // Let the other side know that I switched to fighter X.
void SendReady(); // Let the other side know that I am ready.
void SendGameParams( int a_iGameSpeed, int a_iGameTime, int a_iHitPoints );
+ SGameParams GetGameParams();
// Game methods
bool SynchStartRound();
void SendGameData( const char* a_pcGameData );
const char* GetLatestGameData();
- void SendKeystroke( int a_iKey, bool a_bPressed );
- bool GetKeystroke( int& a_riOutKey, bool& a_rbPressed );
+ void SendKeystroke( int a_iTime, int a_iKey, bool a_bPressed );
+ bool GetKeystroke( int& a_riOutTime, int& a_riOutKey, bool& a_rbPressed );
- void SendGameTime( int a_iGameTime, int a_iGamePhase );
- int GetGameTime();
- int GetGamePhase();
+ void SendGameTick( int a_iGameTick );
+ int GetGameTick();
void SendHurryup( int a_iHurryUpCode );
int GetHurryup() ;
void SendRoundOver( int a_iWhoWon, bool a_bGameOver );
int GetWhoWon();
bool IsRoundOver();
bool IsGameOver();
protected:
void SendRawData( char a_cID, const void* a_pData, int a_iLength );
void ReceiveMsg( void* a_pData, int a_iLength );
void ReceiveRemoteUserName( void* a_pData, int a_iLength );
void ReceiveGameData( void* a_pData, int a_iLength );
void ReceiveKeystroke( void* a_pData, int a_iLength );
void ReceiveFighter( void* a_pData, int a_iLength );
void ReceiveReady( void* a_pData, int a_iLength );
void ReceiveRoundOver( void* a_pData, int a_iLength );
- void ReceiveGameTime( void* a_pData, int a_iLength );
+ void ReceiveGameTick( void* a_pData, int a_iLength );
void ReceiveHurryup( void* a_pData, int a_iLength );
void ReceiveRemoteFighterAvailable( void* a_pData, int a_iLength );
void ReceiveRemoteFighterQuery( void* a_pData, int a_iLength );
void ReceiveGameParams( void* a_pData, int a_iLength );
protected:
enum TNetworkState
{
NS_DISCONNECTED,
NS_CHARACTER_SELECTION,
NS_IN_GAME,
};
// Network METADATA
bool m_bNetworkAvailable; ///< Is the networking API initialized correctly?
TNetworkState m_enState; ///< The current state
bool m_bServer; ///< We are the server side.
bool m_bMaster; ///< We are the master side. (Initially the server side)
TCPsocket m_poSocket; ///< The TCP/IP network socket.
SDLNet_SocketSet m_poSocketSet; ///< SDLNet construct for watching the socket.
char m_acIncomingBuffer[2048]; ///< Received data goes here.
int m_iIncomingBufferSize; ///< How much of the buffer is filled?
std::string m_sLastError; ///< The last error message from SDLNet
TStringList m_asMsgs; ///< Incoming chatlines
// GAME DATA
std::string m_sRemoteUserName;
TIntList m_aiAvailableRemoteFighters;
FighterEnum m_enRemoteFighter;
bool m_bRemoteReady;
- struct SGameParams
- {
- Uint32 iGameTime;
- Uint32 iGameSpeed;
- Uint32 iHitPoints;
- } m_oGameParams;
+ SGameParams m_oGameParams;
std::string m_sLatestGameData;
+ TIntList m_aiKeyTimes;
TIntList m_aiKeystrokes;
TIntList m_abKeystrokes;
bool m_bRoundOver;
int m_iWhoWon;
bool m_bGameOver;
- int m_iGameTime;
- int m_iGamePhase;
+ int m_iGameTick;
int m_iHurryupCode;
// REMOTE QUERY RESPONSES
bool m_bSynchQueryResponse;
};
#endif // MORTALNETWORKIMPL_H
diff --git a/src/common.cpp b/src/common.cpp
index 6b42d35..7459317 100644
--- a/src/common.cpp
+++ b/src/common.cpp
@@ -1,23 +1,121 @@
/***************************************************************************
common.cpp - description
-------------------
begin : Fri Aug 24 2001
copyright : (C) 2001 by upi
email : upi@apocalypse.rulez.org
***************************************************************************/
#include <stdio.h>
#include <stdarg.h>
#include "common.h"
+#include "State.h"
+#include "Joystick.h"
+#include "SDL/SDL.h"
+#include "Event.h"
SDL_Surface* gamescreen = NULL;
void debug( const char* format, ... )
{
va_list ap;
va_start( ap, format );
vfprintf( stderr, format, ap );
va_end( ap );
}
+
+
+
+bool FindPlayerKey( SDLKey a_enKey, int& a_riOutPlayer, int& a_riOutKey )
+{
+ for (int i=0; i<2; i++)
+ {
+ for (int j=0; j<9; j++ )
+ {
+ if (g_oState.m_aiPlayerKeys[i][j] == a_enKey )
+ {
+ a_riOutPlayer = i;
+ a_riOutKey = j;
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+
+
+bool TranslateEvent( const SDL_Event* a_poInEvent, SMortalEvent* a_poOutEvent )
+{
+ a_poOutEvent->m_enType = Me_NOTHING;
+
+ switch ( a_poInEvent->type )
+ {
+ case SDL_QUIT:
+ a_poOutEvent->m_enType = Me_QUIT;
+ g_oState.m_bQuitFlag = true;
+ return true;
+
+ case SDL_KEYDOWN:
+ {
+ SDLKey enKey = a_poInEvent->key.keysym.sym;
+ if ( enKey == SDLK_ESCAPE )
+ {
+ a_poOutEvent->m_enType = Me_MENU;
+ return true;
+ }
+ if ( enKey == SDLK_F1 )
+ {
+ a_poOutEvent->m_enType = Me_SKIP;
+ return true;
+ }
+
+ // Check the player keys
+ int iPlayer;
+ int iKey;
+ bool bFound = FindPlayerKey( enKey, iPlayer, iKey );
+
+ if ( bFound )
+ {
+ a_poOutEvent->m_enType = Me_PLAYERKEYDOWN;
+ a_poOutEvent->m_iPlayer = iPlayer;
+ a_poOutEvent->m_iKey = iKey;
+ return true;
+ }
+ break;
+ }
+
+ case SDL_KEYUP:
+ {
+ SDLKey enKey = a_poInEvent->key.keysym.sym;
+ int iPlayer;
+ int iKey;
+ bool bFound = FindPlayerKey( enKey, iPlayer, iKey );
+
+ if ( bFound )
+ {
+ a_poOutEvent->m_enType = Me_PLAYERKEYUP;
+ a_poOutEvent->m_iPlayer = iPlayer;
+ a_poOutEvent->m_iKey = iKey;
+ return true;
+ }
+ break;
+ }
+
+ case SDL_JOYAXISMOTION:
+ case SDL_JOYBALLMOTION:
+ case SDL_JOYHATMOTION:
+ case SDL_JOYBUTTONDOWN:
+ case SDL_JOYBUTTONUP:
+ {
+ return g_oJoystick.TranslateEvent( a_poInEvent, a_poOutEvent );
+ }
+
+
+ } // switch
+
+ return false;
+}
+
diff --git a/src/main.cpp b/src/main.cpp
index fc43f5a..afb58ae 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,396 +1,408 @@
/***************************************************************************
main.cpp - description
-------------------
begin : Wed Aug 22 10:18:47 CEST 2001
copyright : (C) 2001 by upi
email : upi@apocalypse.rulez.org
***************************************************************************/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "PlayerSelect.h"
#include "SDL_video.h"
#include "sge_tt_text.h"
#include "sge_bm_text.h"
#include "sge_surface.h"
#include "SDL.h"
#include "SDL_image.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <string>
#include "common.h"
#include "gfx.h"
#include "Audio.h"
#include "RlePack.h"
#include "Backend.h"
#include "State.h"
#include "FighterStats.h"
#include "MortalNetwork.h"
#ifdef _WINDOWS
#undef DATADIR // GRRR.. windows keyword...
#include <windows.h>
#define DATADIR "../data"
#endif
_sge_TTFont* inkFont;
_sge_TTFont* impactFont;
_sge_TTFont* titleFont;
_sge_TTFont* chatFont;
sge_bmpFont* fastFont;
sge_bmpFont* creditsFont;
sge_bmpFont* storyFont;
SDL_Color Colors[] =
{
{ 0, 0, 0, 0 }, { 0, 0, 42, 0 }, { 0, 42, 0, 0 }, { 0, 42, 42, 0 },
{ 42, 0, 0, 0 }, { 42, 0, 42, 0 }, { 63, 42, 0, 0 }, { 42, 42, 42, 0 },
{ 21, 21, 21, 0 }, { 21, 21, 63, 0 }, { 21, 63, 21, 0 }, { 21, 63, 63, 0 },
{ 63, 21, 21, 0 }, { 63, 21, 63, 0 }, { 63, 63, 21, 0 }, { 63, 63, 63, 0 }
};
void Complain( const char* a_pcError )
{
#ifdef _WINDOWS
::MessageBoxA( 0, a_pcError, "OpenMortal", MB_ICONEXCLAMATION );
#else
fprintf( stderr, "%s", a_pcError );
#endif
}
_sge_TTFont* LoadTTF( const char* a_pcFilename, int a_iSize )
{
std::string sPath = std::string(DATADIR) + "/fonts/" + a_pcFilename;
_sge_TTFont* poFont = sge_TTF_OpenFont( sPath.c_str(), a_iSize );
if ( NULL == poFont )
{
Complain( ("Couldn't load font: " + sPath).c_str() );
}
return poFont;
}
sge_bmpFont* LoadBMPFont( const char* a_pcFilename )
{
std::string sPath = std::string(DATADIR) + "/fonts/" + a_pcFilename;
sge_bmpFont* poFont = sge_BF_OpenFont( sPath.c_str(), SGE_BFSFONT | SGE_BFTRANSP );
if ( NULL == poFont )
{
Complain( ("Couldn't load font: " + sPath).c_str() );
}
return poFont;
}
int init( int iFlags )
{
if (SDL_Init(SDL_INIT_VIDEO /*| SDL_INIT_AUDIO*/) < 0)
{
fprintf(stderr, "Failed to initialize SDL: %s\n", SDL_GetError());
return -1;
}
atexit(SDL_Quit);
int d = SDL_VideoModeOK(640, 480, 8, iFlags);
if (d == 0)
{
fprintf(stderr, "requested video mode not available\n");
// return -1;
}
gamescreen = SDL_SetVideoMode(640, 480, 8, iFlags);
if (gamescreen == NULL)
{
fprintf(stderr, "failed to set video mode: %s\n", SDL_GetError());
return -1;
}
SDL_WM_SetCaption( "OpenMortal", "OpenMortal" );
std::string sPath = std::string(DATADIR) + "/gfx/icon.png";
SDL_WM_SetIcon(IMG_Load(sPath.c_str()), NULL);
SDL_ShowCursor( SDL_DISABLE );
for ( int i=0; i<16; ++i ) { Colors[i].r *=4; Colors[i].g *=4; Colors[i].b *=4; }
SDL_SetColors( gamescreen, Colors, 256-16, 16 );
if ( sge_TTF_Init() )
{
fprintf(stderr, "couldn't start ttf engine: %s\n", SDL_GetError());
return -1;
}
sge_TTF_AAOff();
inkFont = LoadTTF( "aardvark.ttf", 20 );
if ( !inkFont ) return -1;
impactFont = LoadTTF( "bradybun.ttf", 20 ); // gooddogc.ttf, 20
if ( !impactFont ) return -1;
titleFont = LoadTTF( "deadgrit.ttf", 48 ); // deadgrit.ttf, 48
if ( !titleFont ) return -1;
chatFont = LoadTTF( "thin.ttf", 20 ); // deadgrit.ttf, 48
if ( !chatFont ) return -1;
fastFont = LoadBMPFont( "brandybun3.png" );
if ( !fastFont ) return -1;
creditsFont = LoadBMPFont( "CreditsFont2.png" );//"fangfont.png" );
if ( !creditsFont ) return -1;
storyFont = LoadBMPFont( "glossyfont.png" );
if ( !storyFont ) return -1;
return 0;
}
int init2()
{
if ( !g_oBackend.Construct() )
{
fprintf(stderr, "couldn't start backend.\n" );
return -1;
}
return 0;
}
int DrawMainScreen()
{
SDL_Surface* background = LoadBackground( "Mortal.png", 240 );
DrawTextMSZ( VERSION, inkFont, 540, 430, UseShadow | AlignHCenter, C_WHITE, background, false );
SDL_Rect r;
r.x = r.y = 0;
std::string sStaffFilename = DATADIR;
sStaffFilename += "/characters/STAFF.DAT";
RlePack pack( sStaffFilename.c_str(), 240 );
pack.ApplyPalette();
//SDL_SetColors( gamescreen, pack.getPalette(), 0, 240 );
SDL_BlitSurface( background, NULL, gamescreen, &r );
SDL_Flip( gamescreen );
char* filename[15] = {
"Jacint.pl", "Jozsi.pl", "Agent.pl", "Mrsmith.pl",
"Sleepy.pl", "Tejszin.pl",
"UPi.pl", "Zoli.pl", "Ulmar.pl", "Bence.pl",
"Descant.pl", "Grizli.pl", "Sirpi.pl", "Macy.pl", "Cumi.pl" };
int x[14] = {
0, 26, 67, 125, 159, 209,
249, 289, 358, 397, 451, 489, 532, 161 };
int y[14] = {
5, 4, 5, 5, 5, 7,
4, 0, 7, 5, 5, 6, 5, 243 };
int i;
+ g_oBackend.PerlEvalF( "eval( \"require '%s/characters/Kinga.pl';\" )", DATADIR );
g_oBackend.PerlEvalF( "eval( \"require '%s/characters/Ambrus.pl';\" )", DATADIR );
g_oBackend.PerlEvalF( "eval( \"require '%s/characters/Dani.pl';\" )", DATADIR );
for ( i=0; i<15; ++i )
{
pack.Draw( i, x[i], y[i], false );
SDL_Flip( gamescreen );
if ( filename[i] != NULL )
{
debug( "Loading fighter %s", filename[i] );
g_oBackend.PerlEvalF( "eval( \"require '%s/characters/%s';\" )", DATADIR, filename[i] );
}
}
int retval = 0;
i = 0;
SDL_FreeSurface( background );
return retval;
}
+
+
+int InitJoystick();
+
+
+
+
int main(int argc, char *argv[])
{
srand( (unsigned int)time(NULL) );
if ( 0 != init2() )
{
fprintf( stderr, "init2() failed." );
return -1;
}
g_oState.m_pcArgv0 = argv[0];
g_oState.Load();
CMortalNetwork::Create();
bool bDebug = false;
int iFlags = SDL_SWSURFACE | SDL_HWPALETTE;
+
if ( g_oState.m_bFullscreen )
{
iFlags |= SDL_FULLSCREEN;
}
int i;
for ( i=1; i<argc; ++i )
{
if ( !strcmp(argv[i], "-debug") )
{
bDebug = true;
}
else if ( !strcmp(argv[i], "-fullscreen") )
{
iFlags |= SDL_FULLSCREEN;
}
else if ( !strcmp(argv[i], "-hwsurface") )
{
iFlags |= SDL_HWSURFACE;
}
else if ( !strcmp(argv[i], "-doublebuf") )
{
iFlags |= SDL_DOUBLEBUF;
}
else if ( !strcmp(argv[i], "-anyformat") )
{
iFlags |= SDL_ANYFORMAT;
}
else
{
printf( "Usage: %s [-debug] [-fullscreen] [-hwsurface] [-doublebuf] [-anyformat]\n", argv[0] );
return 0;
}
}
if (init( iFlags )<0)
{
return -1;
}
+ // InitJoystick();
+
g_oState.SetLanguage( g_oState.m_acLanguage );
new MszAudio;
// Audio->LoadMusic( "Last_Ninja_-_The_Wilderness.mid", "DemoMusic" );
Audio->LoadMusic( "ride.mod", "DemoMusic" );
Audio->PlayMusic( "DemoMusic" );
Audio->LoadMusic( "2nd_pm.s3m", "GameMusic" );
DrawMainScreen();
+
g_oPlayerSelect.SetPlayer( 0, ZOLI );
g_oPlayerSelect.SetPlayer( 1, SIRPI );
/*
int nextFighter = 0;
int describeOrder[ (int)LASTFIGHTER ];
for ( i=0; i<(int)LASTFIGHTER; ++i ) describeOrder[i] = i;
for ( i=0; i<100; ++i )
{
int j = rand() % (int)LASTFIGHTER;
int k = rand() % (int)LASTFIGHTER;
int l;
l = describeOrder[j];
describeOrder[j] = describeOrder[k];
describeOrder[k] = l;
}
*/
/*
{
int iGameNumber=0;
char acReplayFile[1024];
for ( i=0; i<15; ++i )
{
sprintf( acReplayFile, DATADIR "/msz%i.replay", i );
DrawTextMSZ( acReplayFile, impactFont, 10, 10, 0, C_WHITE, gamescreen );
SDL_Delay(5000 );
DoGame( acReplayFile, true, bDebug );
}
}
*/
bool bNetworkGame = false;
while ( 1 )
{
if ( g_oState.m_bQuitFlag ) break;
switch ( g_oState.m_enGameMode )
{
case SState::IN_DEMO:
DoDemos();
continue;
case SState::IN_CHAT:
DoOnlineChat();
continue;
default:
break; // Handled below.
}
// Remaining are game modes: IN_SINGLE, IN_MULTI, IN_NETWORK
Audio->PlaySample( "car_start.voc" );
Audio->PlayMusic( "GameMusic" );
bNetworkGame = false;
while ( g_oState.m_enGameMode != SState::IN_DEMO
&& g_oState.m_enGameMode != SState::IN_CHAT
&& !g_oState.m_bQuitFlag )
{
bNetworkGame = SState::IN_NETWORK == g_oState.m_enGameMode;
g_oPlayerSelect.DoPlayerSelect();
if ( g_oState.m_bQuitFlag || g_oState.m_enGameMode == SState::IN_DEMO ) break;
//sprintf( acReplayFile, "/tmp/msz%d.replay", ++iGameNumber );
int iGameResult = DoGame( NULL, false, bDebug );
//int iGameResult = DoGame( acReplayFile, false, bDebug );
//DoGame( acReplayFile, true, bDebug );
if ( g_oState.m_bQuitFlag || g_oState.m_enGameMode == SState::IN_DEMO ) break;
debug ( "iGameResult = %d\n", iGameResult );
if ( iGameResult >= 0 && !bNetworkGame )
{
GameOver( iGameResult );
FighterStatsDemo oDemo( g_oPlayerSelect.GetPlayerInfo( iGameResult ).m_enFighter );
oDemo.Run();
}
if ( g_oState.m_bQuitFlag || g_oState.m_enGameMode == SState::IN_DEMO ) break;
}
if ( bNetworkGame && !g_oState.m_bQuitFlag )
{
DrawTextMSZ( "Connection closed.", inkFont, 320, 210, AlignHCenter | UseShadow, C_WHITE, gamescreen );
DrawTextMSZ( g_poNetwork->GetLastError(), impactFont, 320, 250, AlignHCenter | UseShadow, C_WHITE, gamescreen );
GetKey();
}
if ( g_oState.m_bQuitFlag ) break;
Audio->PlayMusic( "DemoMusic" );
}
g_oState.Save();
SDL_Quit();
return EXIT_SUCCESS;
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Tue, Jun 16, 12:25 AM (2 w, 1 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
70862
Default Alt Text
(110 KB)
Attached To
Mode
R76 OpenMortal
Attached
Detach File
Event Timeline