Page MenuHomePhabricator (Chris)

No OneTemporary

Authored By
Unknown
Size
125 KB
Referenced Files
None
Subscribers
None
diff --git a/src/Game.cpp b/src/Game.cpp
index 21967ed..e30ae39 100644
--- a/src/Game.cpp
+++ b/src/Game.cpp
@@ -1,1211 +1,1211 @@
/***************************************************************************
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 "Event.h"
#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 CGame::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
***************************************************************************/
/**
\ingroup GameLogic
*/
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);
}
++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
***************************************************************************/
CGame::CGame( bool a_bIsReplay, bool a_bWide, bool a_bDebug)
{
m_bIsReplay = a_bIsReplay;
m_bWide = a_bWide;
m_bDebug = a_bDebug;
m_enInitialGameMode = g_oState.m_enGameMode;
if ( IsNetworkGame() )
{
mg_iBackgroundNumber = g_poNetwork->GetGameParams().iBackgroundNumber;
}
m_poBackground = new CBackground();
m_poBackground->Load(mg_iBackgroundNumber++);
if ( !m_poBackground->IsOK() )
{
m_poBackground->Load(1);
mg_iBackgroundNumber = 1;
}
m_poDoodads = LoadBackground( "Doodads.png", 48, 64, true );
int i;
for ( i=0; i<g_oState.m_iNumPlayers; ++i )
{
m_aiRoundsWonByPlayer[i] = 0;
m_aiHitPointDisplayX[i] = 4 + ((i%2) * 396) + (m_bWide ? 130 : 0);
m_aiHitPointDisplayY[i] = 10 + (i/2) * 45;
m_abHitPointDisplayLeft[i] = ! (i%2);
}
m_iNumberOfRounds = 0;
SDL_EnableUNICODE( 0 );
m_iEnqueueDelay = 10;
}
CGame::~CGame()
{
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 CGame::Run()
{
while (1)
{
// 1. START A ROUND
// This will update m_iNumberOfRounds and m_aiRoundsWonByPlayer[]
m_sReplayString = "";
m_aReplayOffsets.clear();
DoOneRound();
if ( g_oState.m_bQuitFlag
|| m_enInitialGameMode != g_oState.m_enGameMode )
{
return -1;
}
// 2. CHECK FOR END-OF-MATCH CONDITIONS
int i;
for ( i=0; i<g_oState.m_iNumPlayers; ++i )
{
if ( m_aiRoundsWonByPlayer[i] >= 2 ) {
return i;
}
}
if ( m_iNumberOfRounds > g_oState.m_iNumPlayers )
{
return -1;
}
}
}
/** Returns the replay string of the last round.
*/
std::string& CGame::GetReplay()
{
return m_sReplayString;
}
/***************************************************************************
GAME DRAWING METHODS
***************************************************************************/
void CGame::DrawHitPointDisplay( int a_iPlayer )
{
int iX = m_aiHitPointDisplayX[a_iPlayer];
int iY = m_aiHitPointDisplayY[a_iPlayer];
bool bLeft = m_abHitPointDisplayLeft[a_iPlayer];
int iHp = g_oBackend.m_aoPlayers[a_iPlayer].m_iHitPoints;
SDL_Rect oSrcRect, oDstRect;
// The green part
oSrcRect.x = bLeft ? 0 : (100-iHp)*2;
oSrcRect.y = 154;
oSrcRect.h = 20;
oSrcRect.w = iHp * 2;
oDstRect.y = iY + m_iYOffset;
oDstRect.x = iX + oSrcRect.x + (bLeft ? 36 : 0 );
SDL_BlitSurface( m_poDoodads, &oSrcRect, gamescreen, &oDstRect );
// The red part
if ( bLeft )
oDstRect.x += iHp * 2;
else
oDstRect.x = iX;
oSrcRect.x = (100+iHp) * 2;
oSrcRect.w = (100-iHp) * 2;
SDL_BlitSurface( m_poDoodads, &oSrcRect, gamescreen, &oDstRect );
// The "won" icon
oSrcRect.x = 0;
oSrcRect.y = 276;
oSrcRect.w = 32;
oSrcRect.h = 32;
if ( m_aiRoundsWonByPlayer[a_iPlayer] > 0 )
{
oDstRect.x = iX + (bLeft ? 0 : 204);
oDstRect.y = iY-4 + m_iYOffset;
SDL_BlitSurface( m_poDoodads, &oSrcRect , gamescreen, &oDstRect );
}
int iTextW = g_oPlayerSelect.GetFighterNameWidth(a_iPlayer);
int iTextX = bLeft ? iX + 230 - iTextW : iX + 10 ;
if ( iTextX + iTextW + 5 > gamescreen->w ) iTextX = gamescreen->w - iTextW - 5;
if ( iTextX < iX + 5 ) iTextX = iX + 5;
sge_BF_textout( gamescreen, fastFont, g_oPlayerSelect.GetFighterName(a_iPlayer),
iTextX, iY/*+ 38*/ + m_iYOffset );
}
/** 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 CGame::DrawHitPointDisplays()
{
for ( int i=0; i<g_oState.m_iNumPlayers; ++i )
{
DrawHitPointDisplay(i);
}
}
/** Draws the background, using the m_poBackground object.
*/
void CGame::DrawBackground()
{
m_poBackground->Draw( g_oBackend.m_iBgX, g_oBackend.m_iBgY, m_iYOffset );
}
void CGame::AddBodyToBackground( int a_iPlayer )
{
CBackend::SPlayer& roPlayer = g_oBackend.m_aoPlayers[a_iPlayer];
CBackground::SBackgroundLayer oLayer;
CRlePack* poPack = g_oPlayerSelect.GetPlayerInfo(a_iPlayer).m_poPack;
oLayer.m_iXOffset = roPlayer.m_iX + g_oBackend.m_iBgX;
oLayer.m_iYOffset = roPlayer.m_iY;
oLayer.m_dDistance = 1.0;
oLayer.m_poSurface = poPack->CreateSurface( ABS(roPlayer.m_iFrame)-1, roPlayer.m_iFrame<0 );
m_poBackground->AddExtraLayer( oLayer );
}
/** In debug mode, this method is used to draw the frame of the fighters.
\param a_pcName The name of the polygon (in the perl namespace)
\param a_iColor The game color to draw the polygon with.
*/
void CGame::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 + m_iYOffset, x2, y2 + m_iYOffset, a_iColor ) ;
}
}
/** Draws every doodad that is currently defined in the backend.
*/
void CGame::DrawDoodads()
{
for ( int i=0; i<g_oBackend.m_iNumDoodads; ++i )
{
CBackend::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 > gamescreen->w ) iDoodadX = gamescreen->w - iWidth;
if ( iDoodadX < 0 ) iDoodadX = 0;
int iDoodadY = roDoodad.m_iY;
sge_BF_textout( gamescreen, fastFont, s, iDoodadX, iDoodadY + m_iYOffset );
continue;
}
if ( roDoodad.m_iGfxOwner >= 0 )
{
g_oPlayerSelect.GetPlayerInfo(roDoodad.m_iGfxOwner).m_poPack->Draw(
roDoodad.m_iFrame, roDoodad.m_iX, roDoodad.m_iY + m_iYOffset, roDoodad.m_iDir < 1 );
continue;
}
SDL_Rect rsrc, rdst;
int w, h, y0;
rdst.x = roDoodad.m_iX;
rdst.y = roDoodad.m_iY + m_iYOffset;
if ( 5 == roDoodad.m_iType )
{
y0 = 308;
w = h = 24;
}
else
{
y0 = 0;
w = h = 64;
}
rsrc.x = w * roDoodad.m_iFrame;
rsrc.y = y0;
rsrc.w = w;
rsrc.h = h;
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 g_oBackend.m_iRoundLength
\li m_iNumberOfRounds
\li oFpsCounter
*/
void CGame::Draw()
{
#define GROUNDZERO (440 + m_iYOffset)
m_iYOffset = ( gamescreen->h - 480 ) / 2;
if ( m_iYOffset )
{
SDL_Rect oRect;
oRect.x = 0;
oRect.w = gamescreen->w;
oRect.y = m_iYOffset;
oRect.h = 480;
SDL_SetClipRect( gamescreen, &oRect );
}
else
{
SDL_SetClipRect( gamescreen, NULL );
}
DrawBackground();
// DRAW THE SHADOWS
int i;
for ( i=0; i<g_oState.m_iNumPlayers; ++i )
{
CBackend::SPlayer& roPlayer = g_oBackend.m_aoPlayers[i];
int iFrame = roPlayer.m_iFrame;
if ( iFrame == 0 )
continue;
CRlePack* 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;
if ( gamescreen->format->BitsPerPixel <= 8 )
{
sge_FilledEllipse( gamescreen, g_oBackend.m_aoPlayers[i].m_iX + w/2, GROUNDZERO,
h, h2, C_BLACK );
}
else
{
sge_FilledEllipseAlpha( gamescreen, g_oBackend.m_aoPlayers[i].m_iX + w/2, GROUNDZERO,
h, h2, C_BLACK, 128 );
}
}
for ( i=0; i<g_oState.m_iNumPlayers; ++i )
{
CBackend::SPlayer& roPlayer = g_oBackend.m_aoPlayers[i];
int iFrame = roPlayer.m_iFrame;
if ( iFrame == 0 )
continue;
CRlePack* poPack = g_oPlayerSelect.GetPlayerInfo(i).m_poPack;
poPack->Draw( ABS(iFrame)-1, roPlayer.m_iX, roPlayer.m_iY + m_iYOffset, 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();
DrawHitPointDisplays();
if ( Ph_NORMAL == m_enGamePhase )
{
char s[100];
sprintf( s, "%d", m_iGameTime ); // m_iGameTime is maintained by DoGame
DrawTextMSZ( s, inkFont, 320, 10 + m_iYOffset, 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 + m_iYOffset, AlignHCenter, C_WHITE, gamescreen, false );
}
else if ( Ph_REWIND == m_enGamePhase )
{
DrawTextMSZ( "REW", inkFont, 320, 10 + m_iYOffset, AlignHCenter, C_WHITE, gamescreen );
sge_BF_textout( gamescreen, fastFont, Translate("Press F1 to skip..."), 230, 450 + m_iYOffset );
}
else if ( Ph_SLOWFORWARD == m_enGamePhase )
{
DrawTextMSZ( "REPLAY", inkFont, 320, 10 + m_iYOffset, AlignHCenter, C_WHITE, gamescreen );
sge_BF_textout( gamescreen, fastFont, Translate("Press F1 to skip..."), 230, 450 + m_iYOffset );
}
else if ( Ph_REPLAY == m_enGamePhase )
{
DrawTextMSZ( "DEMO", inkFont, 320, 10 + m_iYOffset, AlignHCenter, C_WHITE, gamescreen );
}
if ( oFpsCounter.m_iFps > 0 )
{
sge_BF_textoutf( gamescreen, fastFont, 2, 455 + m_iYOffset, "%d fps", oFpsCounter.m_iFps );
}
SDL_Flip( gamescreen );
}
/***************************************************************************
GAME PROTECTED METHODS
***************************************************************************/
bool CGame::IsTeamMode()
{
return SState::Team_ONE_VS_ONE != g_oState.m_enTeamMode;
}
bool CGame::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 CGame::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 CGame::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 <= 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;
if ( IsNetworkGame() )
{
g_poNetwork->SendGameTick( g_oBackend.m_iGameTick );
g_poNetwork->Update();
int i = 0;
while ( g_poNetwork->GetGameTick() + m_iEnqueueDelay < g_oBackend.m_iGameTick + a_iNumFrames )
{
++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;
}
}
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() );
}
}
/** A helper method of ProcessEvents; it manages keypresses and releases of
players. It is only called when keypresses are actually relevant for the
backend (not during instant replay, etc).
*/
void CGame::HandleKey( int a_iPlayer, int a_iKey, bool a_bDown )
{
int iCurrentTick = g_oBackend.m_iGameTick + m_iEnqueueDelay;
if ( IsNetworkGame() )
{
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 CGame::ProcessEvents()
{
SMortalEvent oEvent;
while (MortalPollEvent(oEvent))
{
switch (oEvent.m_enType)
{
case Me_QUIT:
g_oState.m_bQuitFlag = true;
return 1;
case Me_MENU:
if ( Ph_REWIND == m_enGamePhase
|| Ph_SLOWFORWARD == m_enGamePhase )
{
// Menu counts as 'skip' during instant replay
return 1;
}
if ( !IsNetworkGame() )
{
SState::TGameMode enMode = g_oState.m_enGameMode;
::DoMenu();
return g_oState.m_enGameMode == enMode ? 0 : 1;
}
break;
case Me_SKIP:
return 1;
case Me_PLAYERKEYDOWN:
case Me_PLAYERKEYUP:
{
if ( Ph_NORMAL != m_enGamePhase &&
Ph_REPLAY != m_enGamePhase )
break;
HandleKey( oEvent.m_iPlayer, oEvent.m_iKey, Me_PLAYERKEYDOWN == oEvent.m_enType );
break;
}
case Me_NOTHING:
break;
} // End of switch
} // End of while polling events;
return 0;
}
void CGame::HandleKO()
{
}
void CGame::HurryUp()
{
Audio->PlaySample( "GAME_HURRYUP" );
DrawGradientText( "HURRY UP!", titleFont, 200, gamescreen );
SDL_Delay( 1000 );
Audio->PlaySample( "GAME_HURRYUP_ENDS" );
}
void CGame::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 CGame::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;
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 CGame::DoOneRound()
{
m_enGamePhase = Ph_START;
m_poBackground->DeleteExtraLayers();
int iTeamSize = (SState::Team_ONE_VS_ONE==g_oState.m_enTeamMode) ?
1 : g_oPlayerSelect.GetPlayerInfo(0).m_aenTeam.size();
int aiTeamNumber[MAXPLAYERS];
for ( int i=0; i<g_oState.m_iNumPlayers; ++i )
{
aiTeamNumber[i] = 0;
}
if ( IsTeamMode() )
{
for ( int i=0; i<g_oState.m_iNumPlayers; ++i )
{
g_oPlayerSelect.SetPlayer( i, g_oPlayerSelect.GetPlayerInfo(i).m_aenTeam[aiTeamNumber[i]] );
}
}
g_oBackend.PerlEvalF( "GameStart(%d,%d,%d,%d,%d);",
IsMaster() ? g_oState.m_iHitPoints : g_poNetwork->GetGameParams().iHitPoints,
g_oState.m_iNumPlayers,
iTeamSize,
m_bWide,
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 = 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 ( Ph_START == m_enGamePhase ) // Check for the end of the START phase
{
if ( dGameTime <= 0 )
{
m_enGamePhase = Ph_NORMAL;
- dGameTime = (IsMaster() ? g_oState.m_iGameTime : g_poNetwork->GetGameParams().iGameTime) * 1000;
+ dGameTime = (IsMaster() ? g_oState.m_iRoundLength : g_poNetwork->GetGameParams().iRoundLength) * 1000;
}
}
else if ( Ph_NORMAL == m_enGamePhase ) // Check for the end of the NORMAL phase
{
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;
}
}
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;
}
oFpsCounter.Tick();
// 3. Draw the next game screen..
Draw();
// 4. Check 'end of round' condition.
for ( int i=0; i<g_oState.m_iNumPlayers; ++i )
{
if ( g_oBackend.m_aoPlayers[i].m_iRealHitPoints <= -10000 )
{
// We have a dead player here.
if ( aiTeamNumber[i] < iTeamSize-1 )
{
++aiTeamNumber[i];
AddBodyToBackground( i );
FighterEnum enFighter = g_oPlayerSelect.GetPlayerInfo(i).m_aenTeam[ aiTeamNumber[i] ];
g_oPlayerSelect.SetPlayer( i, enFighter );
g_oBackend.PerlEvalF( "NextTeamMember(%d,%d);", i, enFighter );
}
}
}
if ( g_oBackend.m_iGameOver )
{
break;
}
if ( !IsMaster() )
{
if ( g_poNetwork->IsRoundOver() )
{
break;
}
}
}
int p1h = g_oBackend.m_aoPlayers[0].m_iRealHitPoints;
int p2h = g_oBackend.m_aoPlayers[1].m_iRealHitPoints;
// 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 CGame::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;
}
}
}
// TODO move the backgroundnumber to SState
int CGame::GetBackgroundNumber() //static
{
return mg_iBackgroundNumber;
}
/** 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 )
{
bool bWide = g_oState.m_iNumPlayers > 2;
// CVideoModeChange oVideoMode( bWide );
CGame oGame( a_bIsReplay, bWide, 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;
}
}
int GetBackgroundNumber()
{
return CGame::GetBackgroundNumber();
}
diff --git a/src/MortalNetwork.h b/src/MortalNetwork.h
index 50e134c..034de27 100644
--- a/src/MortalNetwork.h
+++ b/src/MortalNetwork.h
@@ -1,136 +1,136 @@
/***************************************************************************
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_types.h"
/**
\defgroup Network Networking layer
Classes related to networking (MortalNet and internet game)
*/
/** Mortal Network messages:
\ingroup Network
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 iRoundLength;
Uint32 iGameSpeed;
Uint32 iHitPoints;
Uint32 iBackgroundNumber;
};
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, int a_iBackgroundNumber ) = 0;
+ virtual void SendGameParams( int a_iGameSpeed, int a_iRoundLength, int a_iHitPoints, int a_iBackgroundNumber ) = 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_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 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 d442734..e6a3e58 100644
--- a/src/MortalNetworkImpl.cpp
+++ b/src/MortalNetworkImpl.cpp
@@ -1,941 +1,941 @@
/***************************************************************************
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* GetRoundLengthString( 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_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_iGameTick = 0;
m_iHurryupCode = 0;
m_iIncomingBufferSize = 0;
m_aiAvailableRemoteFighters.clear();
- m_oGameParams.iGameTime = m_oGameParams.iGameSpeed = m_oGameParams.iHitPoints = m_oGameParams.iBackgroundNumber = 0;
+ m_oGameParams.iRoundLength = m_oGameParams.iGameSpeed = m_oGameParams.iHitPoints = m_oGameParams.iBackgroundNumber = 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 );
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': 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;
int iPacketLength = a_iLength + 4;
char *pcBuffer = new char[iPacketLength];
pcBuffer[0] = a_cID;
*((Uint16*)(pcBuffer+1)) = SDL_SwapBE16( a_iLength );
pcBuffer[3] = 0;
if ( a_iLength > 0 )
{
memcpy( pcBuffer+4, a_pData, a_iLength );
}
int iRetval = SDLNet_TCP_Send( m_poSocket, pcBuffer, iPacketLength );
if ( iRetval != iPacketLength ) DISCONNECTONCOMMUNICATIONERROR;
delete [] pcBuffer;
}
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, int a_iBackgroundNumber )
+void CMortalNetworkImpl::SendGameParams( int a_iGameSpeed, int a_iRoundLength, int a_iHitPoints, int a_iBackgroundNumber )
{
CHECKCONNECTION;
if ( (int)m_oGameParams.iGameSpeed == a_iGameSpeed
- && (int)m_oGameParams.iGameTime == a_iGameTime
+ && (int)m_oGameParams.iRoundLength == a_iRoundLength
&& (int)m_oGameParams.iHitPoints == a_iHitPoints
&& (int)m_oGameParams.iBackgroundNumber == a_iBackgroundNumber )
{
// Nothing to update.
return;
}
m_oGameParams.iGameSpeed = a_iGameSpeed;
- m_oGameParams.iGameTime = a_iGameTime;
+ m_oGameParams.iRoundLength = a_iRoundLength;
m_oGameParams.iHitPoints = a_iHitPoints;
m_oGameParams.iBackgroundNumber = a_iBackgroundNumber;
SGameParams oPackage;
oPackage.iGameSpeed = SDL_SwapBE32( a_iGameSpeed );
- oPackage.iGameTime = SDL_SwapBE32( a_iGameTime );
+ oPackage.iRoundLength = SDL_SwapBE32( a_iRoundLength );
oPackage.iHitPoints = SDL_SwapBE32( a_iHitPoints );
oPackage.iBackgroundNumber = SDL_SwapBE32( a_iBackgroundNumber );
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 ) )
+ if ( m_oGameParams.iRoundLength != SDL_SwapBE32( poPackage->iRoundLength ) )
{
- m_oGameParams.iGameTime = SDL_SwapBE32( poPackage->iGameTime );
- m_asMsgs.push_back( std::string("*** ") + GetGameTimeString( m_oGameParams.iGameTime) );
+ m_oGameParams.iRoundLength = SDL_SwapBE32( poPackage->iRoundLength );
+ m_asMsgs.push_back( std::string("*** ") + GetRoundLengthString( m_oGameParams.iRoundLength) );
}
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 ) );
}
m_oGameParams.iBackgroundNumber = SDL_SwapBE32( poPackage->iBackgroundNumber );
}
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_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_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();
+ a_rbOutPressed = m_abKeystrokes.front() > 0;
m_aiKeyTimes.pop_front();
m_aiKeystrokes.pop_front();
m_abKeystrokes.pop_front();
// debug( "GetKeystroke: %d, %d\n", a_riOutKey, a_rbOutPressed );
return true;
}
struct SGameTickPackage
{
Uint32 iGameTick;
};
void CMortalNetworkImpl::SendGameTick( int a_iGameTick )
{
CHECKCONNECTION;
if ( a_iGameTick < 0 ) a_iGameTick = 0;
SGameTickPackage oPackage;
oPackage.iGameTick = SDL_SwapBE32( a_iGameTick );
SendRawData( 'T', &oPackage, sizeof(SGameTickPackage) );
}
void CMortalNetworkImpl::ReceiveGameTick( void* a_pData, int a_iLength )
{
if ( a_iLength != sizeof(SGameTickPackage) ) DISCONNECTONCOMMUNICATIONERROR;
SGameTickPackage* poPackage = (SGameTickPackage*) a_pData;
m_iGameTick = SDL_SwapBE32( poPackage->iGameTick );
}
int CMortalNetworkImpl::GetGameTick()
{
return m_iGameTick;
}
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 7223b30..7b93c0f 100644
--- a/src/MortalNetworkImpl.h
+++ b/src/MortalNetworkImpl.h
@@ -1,166 +1,166 @@
/***************************************************************************
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;
/**
\ingroup Network
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, int a_iBackgroundNumber );
+ void SendGameParams( int a_iGameSpeed, int a_iRoundLength, int a_iHitPoints, int a_iBackgroundNumber );
SGameParams GetGameParams();
// Game methods
bool SynchStartRound();
void SendGameData( const char* a_pcGameData );
const char* GetLatestGameData();
void SendKeystroke( int a_iTime, int a_iKey, bool a_bPressed );
bool GetKeystroke( int& a_riOutTime, int& a_riOutKey, bool& a_rbPressed );
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 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;
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_iGameTick;
int m_iHurryupCode;
// REMOTE QUERY RESPONSES
bool m_bSynchQueryResponse;
};
#endif // MORTALNETWORKIMPL_H
diff --git a/src/PlayerSelectController.cpp b/src/PlayerSelectController.cpp
index 6f3b054..d9cef95 100644
--- a/src/PlayerSelectController.cpp
+++ b/src/PlayerSelectController.cpp
@@ -1,505 +1,505 @@
#include "PlayerSelectController.h"
#include "PlayerSelectView.h"
#include "PlayerSelect.h"
#include "Chooser.h"
#include "MortalNetwork.h"
#include "State.h"
#include "Backend.h"
#include "Event.h"
#include "Audio.h"
#include "TextArea.h"
#include "sge_tt_text.h"
#include "common.h"
int GetBackgroundNumber();
CPlayerSelectController::CPlayerSelectController( bool a_bNetworkGame )
{
m_bNetworkGame = a_bNetworkGame;
m_bTeamMode = SState::Team_CUSTOM == g_oState.m_enTeamMode;
m_poView = new CPlayerSelectView( a_bNetworkGame, m_bTeamMode );
}
CPlayerSelectController::~CPlayerSelectController()
{
delete m_poView;
m_poView = NULL;
}
void CPlayerSelectController::GetThisTick()
{
m_iLastTick = m_iThisTick;
while (1)
{
m_iThisTick = SDL_GetTicks() / m_iGameSpeed;
if ( m_iThisTick==m_iLastTick )
{
SDL_Delay(1);
}
else
{
break;
}
}
}
bool CPlayerSelectController::HandleChatKey( SDL_Event& a_roEvent )
{
if ( !m_bNetworkGame )
{
return false;
}
SDLKey enKey = a_roEvent.key.keysym.sym;
if ( enKey == SDLK_PAGEUP || enKey == SDLK_KP9 )
{
m_poView->GetTextArea()->ScrollUp();
return true;
}
if ( enKey == SDLK_PAGEDOWN || enKey == SDLK_KP3 )
{
m_poView->GetTextArea()->ScrollDown();
return true;
}
if ( !m_bChatActive && ( SDLK_TAB == enKey
|| SDLK_RETURN == enKey || SDLK_KP_ENTER == enKey ) )
{
// Activate chat.
m_acChatMsg[0] = 0;
m_poView->GetReadline()->Clear();
m_poView->GetReadline()->Restart( m_acChatMsg, strlen(m_acChatMsg), 256, C_LIGHTCYAN, C_BLACK, 255 );
m_bChatActive = true;
return true;
}
if ( !m_bChatActive )
{
return false;
}
if ( SDLK_ESCAPE == enKey || SDLK_TAB == enKey )
{
// Deactivate chat.
m_poView->GetReadline()->Clear();
m_bChatActive = false;
return true;
}
// other keys..
m_poView->GetReadline()->HandleKeyEvent( a_roEvent );
int iResult = m_poView->GetReadline()->GetResult();
if ( iResult < 0 )
{
// Escape was pressed?
m_poView->GetReadline()->Clear();
m_bChatActive = false;
}
if ( iResult > 0 )
{
if ( strlen( m_acChatMsg ) )
{
g_poNetwork->SendMsg( m_acChatMsg );
std::string sMsg = std::string("<") + g_oState.m_acNick + "> " + m_acChatMsg;
m_poView->GetTextArea()->AddString( sMsg.c_str(), C_LIGHTCYAN );
m_poView->GetTextArea()->Redraw();
m_acChatMsg[0] = 0;
m_poView->GetReadline()->Clear();
m_poView->GetReadline()->Restart( m_acChatMsg, strlen(m_acChatMsg), 256, C_LIGHTCYAN, C_BLACK, 255 );
}
else
{
m_poView->GetReadline()->Clear();
m_bChatActive = false;
}
}
return true;
}
void CPlayerSelectController::HandleEvents()
{
SDL_Event oSdlEvent;
while (SDL_PollEvent(&oSdlEvent))
{
if ( m_bNetworkGame
&& SDL_KEYDOWN == oSdlEvent.type )
{
if (HandleChatKey( oSdlEvent ))
{
continue;
}
}
if ( m_bNetworkGame
&& SDL_KEYDOWN == oSdlEvent.type
&& (oSdlEvent.key.keysym.sym == SDLK_RETURN || oSdlEvent.key.keysym.sym==SDLK_KP_ENTER) )
{
m_bChatActive = true;
continue;
}
SMortalEvent oEvent;
TranslateEvent( &oSdlEvent, &oEvent );
switch ( oEvent.m_enType )
{
case Me_QUIT:
g_oState.m_bQuitFlag = true;
break;
case Me_MENU:
DoMenu();
if ( m_bNetworkGame && g_poNetwork->IsMaster() )
{
- g_poNetwork->SendGameParams( g_oState.m_iGameSpeed, g_oState.m_iGameTime, g_oState.m_iHitPoints, GetBackgroundNumber() );
+ g_poNetwork->SendGameParams( g_oState.m_iGameSpeed, g_oState.m_iRoundLength, g_oState.m_iHitPoints, GetBackgroundNumber() );
}
break;
case Me_PLAYERKEYDOWN:
HandleKey( oEvent.m_iPlayer, oEvent.m_iKey );
break;
case Me_NOTHING:
case Me_SKIP:
case Me_PLAYERKEYUP:
break;
} // end of switch statement
}
}
void CPlayerSelectController::HandleKey( int a_iPlayer, int a_iKey )
{
if ( m_bNetworkGame )
{
a_iPlayer = g_poNetwork->IsMaster() ? 0 : 1;
}
if ( !m_abPlayerActive[a_iPlayer] )
{
return;
}
int iSetPlayer = a_iPlayer;
if ( m_bTeamMode && !g_oState.m_bTeamMultiselect )
{
iSetPlayer = 0;
}
// MOVE THE CURSOR
if ( a_iKey < 4 )
{
g_oChooser.MoveRectangle( a_iPlayer, a_iKey );
FighterEnum enNewFighter = g_oChooser.GetCurrentFighter( a_iPlayer );
if ( enNewFighter != g_oPlayerSelect.GetPlayerInfo( iSetPlayer ).m_enFighter )
{
Audio->PlaySample("PLAYER_SELECTION_CHANGES");
if ( IsFighterSelectable(enNewFighter) )
{
if ( m_bNetworkGame )
{
g_poNetwork->SendFighter( enNewFighter );
}
g_oPlayerSelect.SetPlayer( iSetPlayer, enNewFighter );
}
}
return;
}
// SELECT THE CURRENT FIGHTER
FighterEnum enFighter = g_oChooser.GetCurrentFighter( a_iPlayer );
if ( !IsFighterSelectable( enFighter ) )
{
// Current fighter cannot be selected.
return;
}
Audio->PlaySample("PLAYER_SELECTED");
g_oBackend.PerlEvalF( "PlayerSelected(%d);", iSetPlayer );
if ( m_bNetworkGame )
{
g_poNetwork->SendFighter( enFighter );
g_poNetwork->SendReady();
}
bool bKeepPlayerActive = false;
SPlayerInfo& roInfo = g_oPlayerSelect.EditPlayerInfo(a_iPlayer);
const SPlayerInfo& roOtherInfo = g_oPlayerSelect.GetPlayerInfo(1-a_iPlayer);
int iTeamSize = roInfo.m_aenTeam.size() + 1;
int iOtherTeamSize = roOtherInfo.m_aenTeam.size();
if ( m_bTeamMode )
{
m_poView->AddFighterToTeam( a_iPlayer, enFighter );
roInfo.m_aenTeam.push_back( enFighter );
if ( g_oState.m_bTeamMultiselect )
{
SetPlayerActive( a_iPlayer, iTeamSize < g_oState.m_iTeamSize );
}
else
{
--m_iNumberOfSelectableFighters;
g_oChooser.MarkFighter( enFighter, a_iPlayer ? C_LIGHTMAGENTA : C_LIGHTGREEN );
ActivateNextPlayer( a_iPlayer );
}
}
else
{
roInfo.m_aenTeam.clear();
roInfo.m_aenTeam.push_back( enFighter );
SetPlayerActive( a_iPlayer, false );
}
}
void CPlayerSelectController::HandleNetwork()
{
g_poNetwork->Update();
// 1. READ CHAT MESSAGES
bool bUpdateText = false;
while ( g_poNetwork->IsMsgAvailable() )
{
const char* pcMsg = g_poNetwork->GetMsg();
int iColor = C_YELLOW;
if ( pcMsg[0] == '*' && pcMsg[1] == '*' && pcMsg[2] == '*' ) iColor = C_WHITE;
m_poView->GetTextArea()->AddString( pcMsg, iColor );
bUpdateText = true;
}
if ( bUpdateText )
{
Audio->PlaySample("NETWORK_MESSAGE");
m_poView->GetTextArea()->Redraw();
}
// 2. READ PLAYER SELECTION ACTIONS
bool bMaster = g_poNetwork->IsMaster();
int iPlayer = bMaster ? 1 : 0;
if ( !m_abPlayerActive[iPlayer] )
{
return;
}
FighterEnum enOldFighter = g_oPlayerSelect.GetPlayerInfo(iPlayer).m_enFighter;
FighterEnum enRemoteFighter = g_poNetwork->GetRemoteFighter();
if ( enOldFighter != enRemoteFighter
&& enRemoteFighter != UNKNOWN )
{
Audio->PlaySample("PLAYER_SELECTION_CHANGES");
g_oPlayerSelect.SetPlayer( iPlayer, enRemoteFighter );
g_oChooser.SetRectangle( iPlayer, enRemoteFighter );
}
bool bDone = g_poNetwork->IsRemoteSideReady();
if ( bDone )
{
m_abPlayerActive[iPlayer] = false;
Audio->PlaySample("PLAYER_SELECTED");
g_oBackend.PerlEvalF( "PlayerSelected(%d);", iPlayer );
SPlayerInfo& roInfo = g_oPlayerSelect.EditPlayerInfo( iPlayer );
roInfo.m_aenTeam.clear();
roInfo.m_aenTeam.push_back( enRemoteFighter );
SetPlayerActive( iPlayer, false );
}
}
void CPlayerSelectController::MarkFighters()
{
int iNumberOfFighters = g_oBackend.GetNumberOfFighters();
int i;
for ( i=0; i<iNumberOfFighters; ++i )
{
FighterEnum enFighter = g_oBackend.GetFighterID( i );
if ( !g_oPlayerSelect.IsLocalFighterAvailable( enFighter ) )
{
g_oChooser.MarkFighter( enFighter, C_LIGHTRED );
}
else if ( !g_oPlayerSelect.IsFighterAvailable( enFighter ) )
{
g_oChooser.MarkFighter( enFighter, C_LIGHTBLUE );
}
}
}
void CPlayerSelectController::SetPlayerActive( int a_iPlayer, bool a_bActive )
{
m_abPlayerActive[a_iPlayer] = a_bActive;
g_oChooser.SetRectangleVisible( a_iPlayer, a_bActive );
}
/** This method finds the next player and lets him choose a fighter.
This only should be called in team mode without multiselect.
*/
void CPlayerSelectController::ActivateNextPlayer( int a_iCurrentPlayer )
{
// Go in the following order:
// 1 2 3 4 4 3 2 1 1 2 3 4 ...
int iNextPlayer = a_iCurrentPlayer;
if ( m_iNumberOfSelectableFighters <= 0 )
{
SetPlayerActive( a_iCurrentPlayer, false );
return;
}
if ( a_iCurrentPlayer == 0 )
{
iNextPlayer = 1;
}
else if ( g_oState.m_iNumPlayers-1 == a_iCurrentPlayer )
{
iNextPlayer = a_iCurrentPlayer - 1;
}
else
{
iNextPlayer = GetTeamSize(a_iCurrentPlayer-1) > GetTeamSize(a_iCurrentPlayer+1) ?
a_iCurrentPlayer+1 : a_iCurrentPlayer-1;
}
if ( GetTeamSize(a_iCurrentPlayer) <= GetTeamSize(iNextPlayer) )
{
iNextPlayer = a_iCurrentPlayer;
}
if ( iNextPlayer != a_iCurrentPlayer )
{
SetPlayerActive( a_iCurrentPlayer, false );
}
SetPlayerActive( iNextPlayer, GetTeamSize(iNextPlayer) < g_oState.m_iTeamSize );
}
int CPlayerSelectController::GetTeamSize( int a_iPlayer )
{
return g_oPlayerSelect.GetPlayerInfo( a_iPlayer ).m_aenTeam.size();
}
bool CPlayerSelectController::IsFighterSelectable( FighterEnum a_enFighter )
{
if ( !g_oPlayerSelect.IsFighterAvailable( a_enFighter ) )
{
return false;
}
if ( m_bTeamMode && !g_oState.m_bTeamMultiselect && g_oPlayerSelect.IsFighterInTeam(a_enFighter) )
{
return false;
}
return true;
}
void CPlayerSelectController::DoPlayerSelect()
{
// 1. INITIALIZE
if ( SState::IN_NETWORK == g_oState.m_enGameMode )
{
g_oState.m_enTeamMode = SState::Team_ONE_VS_ONE;
g_oState.m_iNumPlayers = 2;
}
int i;
for ( i=0; i<g_oState.m_iNumPlayers; ++i )
{
g_oPlayerSelect.SetPlayer( i, g_oChooser.GetCurrentFighter(i) );
SetPlayerActive( i, !m_bTeamMode || g_oState.m_bTeamMultiselect || 0 == i );
}
m_iNumberOfSelectableFighters = g_oBackend.GetNumberOfAvailableFighters();
MarkFighters();
if ( m_bTeamMode )
{
for ( int i=0; i<g_oState.m_iNumPlayers; ++i )
{
g_oPlayerSelect.EditPlayerInfo(i).m_aenTeam.clear();
}
}
if ( m_bNetworkGame && g_poNetwork->IsMaster() )
{
- g_poNetwork->SendGameParams( g_oState.m_iGameSpeed, g_oState.m_iGameTime, g_oState.m_iHitPoints, GetBackgroundNumber() );
+ g_poNetwork->SendGameParams( g_oState.m_iGameSpeed, g_oState.m_iRoundLength, g_oState.m_iHitPoints, GetBackgroundNumber() );
}
g_oBackend.PerlEvalF( "SelectStart(%d);", g_oState.m_iNumPlayers );
m_bChatActive = false;
m_iGameSpeed = 12 ;
m_iThisTick = SDL_GetTicks() / m_iGameSpeed;
m_iLastTick = m_iThisTick - 1;
// 2. RUN THE PLAYER SELECTION LOOP
while (1)
{
// 2.1. Wait for the next tick (on extremely fast machines..)
GetThisTick();
int iAdvance = m_iThisTick - m_iLastTick;
if ( iAdvance > 5 )
iAdvance = 5;
// 2.2. Handle events
HandleEvents();
if ( m_bNetworkGame )
{
HandleNetwork();
}
// 2.3. Update the view
m_poView->Advance( iAdvance );
m_poView->Draw();
if ( g_oState.m_bQuitFlag || SState::IN_DEMO == g_oState.m_enGameMode)
break;
//@ FIX THIS
if ( !m_abPlayerActive[0] && !m_abPlayerActive[1] && m_poView->IsOver() )
break;
}
}
diff --git a/src/State.cpp b/src/State.cpp
index 1621aa7..3e33cee 100644
--- a/src/State.cpp
+++ b/src/State.cpp
@@ -1,371 +1,371 @@
/***************************************************************************
State.cpp - description
-------------------
begin : Mon Aug 12 2003
copyright : (C) 2003 by upi
email : upi@apocalypse.rulez.org
***************************************************************************/
//[segabor] using Xcode to build OpenMortal makes config.h unneccessary
#ifndef MACOSX
#include "../config.h"
#endif
#include "gfx.h"
#include "common.h"
#include "State.h"
#include "SDL_keysym.h"
#include "SDL_mixer.h"
#include <string>
#include <fstream>
#ifdef MACOSX
//[segabor]
#include <CoreFoundation/CoreFoundation.h>
#endif
//include <unistd.h>
#include "Backend.h"
#include "MszPerl.h"
extern PerlInterpreter* my_perl;
SState g_oState;
std::string GetConfigHeader()
{
std::string sHeader( "Simple config file " );
sHeader += PACKAGE " " VERSION;
return sHeader;
}
std::string GetConfigFilename()
{
#ifdef _WINDOWS
if ( NULL != g_oState.m_pcArgv0 )
{
return std::string(g_oState.m_pcArgv0) + ".ini";
}
return "c:\\openmortal.ini";
#elif defined(MACOSX)
//[segabor] get os-ified path
return std::string(getenv("HOME")) + "/Library/Preferences/OpenMortal.cfg";
#else
return std::string(getenv("HOME")) + "/.openmortalrc";
#endif
}
SState::SState()
{
// 1. SET THE TRIVIAL DEFAULTS
m_enGameMode = IN_DEMO;
m_bQuitFlag = false;
m_pcArgv0 = NULL;
m_iNumPlayers = 2;
m_enTeamMode = Team_ONE_VS_ONE;
m_iTeamSize = 5;
m_bTeamMultiselect = false;
- m_iGameTime = 60;
+ m_iRoundLength = 60;
m_iHitPoints = 100;
m_iGameSpeed = 12;
#ifdef _WINDOWS
#ifdef _DEBUG
m_bFullscreen = false;
#else
m_bFullscreen = true;
#endif
#elif defined(MACOSX)
//[segabor]
#ifdef DEBUG
m_bFullscreen = false;
#else
m_bFullscreen = true;
#endif
#else
m_bFullscreen = false;
#endif
m_iChannels = 2;
m_iMixingRate = MIX_DEFAULT_FREQUENCY;
m_iMixingBits = 2;
m_iMusicVolume = 50;
m_iSoundVolume = 100;
static const int aiDefaultKeys[MAXPLAYERS][9] = {
{ SDLK_UP, SDLK_DOWN, SDLK_LEFT, SDLK_RIGHT, SDLK_PAGEDOWN,
SDLK_DELETE, SDLK_INSERT, SDLK_END, SDLK_HOME },
{ SDLK_w, SDLK_s, SDLK_a, SDLK_d, SDLK_x,
SDLK_f, SDLK_r, SDLK_g, SDLK_t },
{ SDLK_u, SDLK_j, SDLK_h, SDLK_k, SDLK_i,
SDLK_b, SDLK_n, SDLK_m, SDLK_COMMA },
{ SDLK_F2, SDLK_F3, SDLK_F4, SDLK_F5, SDLK_F6,
SDLK_F7, SDLK_F8, SDLK_F9, SDLK_F10 },
};
for ( int i=0; i<MAXPLAYERS; ++i )
for ( int j=0; j<9; ++j )
m_aiPlayerKeys[i][j] = aiDefaultKeys[i][j];
strcpy( m_acLatestServer, "apocalypse.rulez.org" );
m_bServer = false;
strcpy( m_acNick, "Mortal" );
strcpy( m_acLanguage, "en" );
// 2. SO FAR THESE WERE THE EASY DEFAULTS
// NOW MOVE ON TO THE TRICKIER ONES.
// 2.1. FIND THE LANGUAGE
#ifdef _WINDOWS
LANGID iLangID = GetUserDefaultLangID() & 0x007f;
const char* pcLang;
switch ( iLangID )
{
case 0x0e: pcLang = "hu";
case 0x0c: pcLang = "fr";
case 0x0a: pcLang = "es";
default: pcLang = "en";
}
strcpy( m_acNick, pcLang );
#elif defined(MACOSX)
//[segabor] OS X style locale handling
CFLocaleRef userLocaleRef = CFLocaleCopyCurrent();
char cbuff[255];
CFStringGetCString(CFLocaleGetIdentifier(userLocaleRef),
cbuff,
255,
kCFStringEncodingASCII);
m_acLanguage[0] = cbuff[0];
m_acLanguage[1] = cbuff[1];
m_acLanguage[2] = 0;
debug("Language code is %s\n", m_acLanguage);
#else
// Read the locale from the operating system
char* pcLocale = setlocale( LC_CTYPE, NULL );
debug( "The locale returned by the operating system is '%s'\n", pcLocale ? pcLocale : "NULL" );
if ( NULL == pcLocale
|| strcmp( pcLocale, "C") == 0 )
{
// Try the 'GETENV' method
pcLocale = getenv( "LANG" );
debug( "The LANG envvar is '%s'\n", pcLocale ? pcLocale : "NULL" );
}
if ( NULL != pcLocale )
{
strncpy( m_acLanguage, pcLocale, 2 );
m_acLanguage[2] = 0;
}
else
{
strcpy( m_acLanguage, "en" );
}
#endif
// 2.2. FIND THE USER NAME
#ifdef _WINDOWS
m_acNick[0] = 0;
DWORD iLen = 127;
BOOL iResult = GetUserName( m_acNick, &iLen );
if ( 0 == iResult )
{
m_acNick[0] = 0;
debug( "GetUserName failed: %d.\n", iResult );
}
#else
int iResult = getlogin_r( m_acNick, 127 );
if ( iResult )
{
debug( "getlogin_r failed: %d\n", iResult );
strcpy( m_acNick, getenv("USER") );
}
#endif
if ( !m_acNick[0] )
{
strcpy( m_acNick, "Mortal"); // Last-ditch default..
}
};
void SState::ToggleFullscreen()
{
m_bFullscreen = !m_bFullscreen;
bool bPaletted = ( gamescreen->format->BitsPerPixel <= 8 );
SDL_Color aoPalette[256];
int iNumColors = 0;
if ( bPaletted )
{
iNumColors = gamescreen->format->palette->ncolors;
if ( iNumColors > 256 ) iNumColors = 256;
for ( int i=0; i<iNumColors; ++i )
{
aoPalette[i].r = gamescreen->format->palette->colors[i].r;
aoPalette[i].g = gamescreen->format->palette->colors[i].g;
aoPalette[i].b = gamescreen->format->palette->colors[i].b;
aoPalette[i].unused = 0;
}
}
SetVideoMode( gamescreen->w > 640, m_bFullscreen );
if ( bPaletted )
{
SDL_SetPalette( gamescreen, SDL_LOGPAL | SDL_PHYSPAL, aoPalette, 0, iNumColors );
}
}
void SState::SetLanguage( const char* a_pcLanguage )
{
if ( m_acLanguage != a_pcLanguage )
{
strncpy( m_acLanguage, a_pcLanguage, 9 );
m_acLanguage[9] = 0;
}
g_oBackend.PerlEvalF( "SetLanguage('%s');", m_acLanguage );
SV* poSv = get_sv("LanguageNumber", FALSE);
if (poSv)
{
m_iLanguageCode = SvIV( poSv );
}
else
{
m_iLanguageCode = 0;
}
}
void SState::SetServer( const char* a_pcServer )
{
if ( a_pcServer )
{
strncpy( m_acLatestServer, a_pcServer, 255 );
m_acLatestServer[255] = 0;
m_bServer = false;
}
else
{
m_bServer = true;
}
}
void SState::Load()
{
std::string sFilename = GetConfigFilename();
g_oBackend.PerlEvalF( "ParseConfig('%s');", sFilename.c_str() );
SV* poSv;
// poSv = get_sv("", FALSE); if (poSv) m_ = SvIV( poSv );
poSv = get_sv("NUMPLAYERS", FALSE); if (poSv) m_iNumPlayers = SvIV( poSv );
poSv = get_sv("TEAMMODE", FALSE); if (poSv) m_enTeamMode = (TTeamModeEnum) SvIV( poSv );
poSv = get_sv("TEAMSIZE", FALSE); if (poSv) m_iTeamSize = SvIV( poSv );
poSv = get_sv("TEAMMULTISELECT", FALSE); if (poSv) m_bTeamMultiselect = SvIV( poSv );
- poSv = get_sv("GAMETIME", FALSE); if (poSv) m_iGameTime = SvIV( poSv );
+ poSv = get_sv("ROUNDLENGTH", FALSE); if (poSv) m_iRoundLength = SvIV( poSv );
poSv = get_sv("HITPOINTS", FALSE); if (poSv) m_iHitPoints = SvIV( poSv );
poSv = get_sv("GAMESPEED", FALSE); if (poSv) m_iGameSpeed = SvIV( poSv );
poSv = get_sv("FULLSCREEN", FALSE); if (poSv) m_bFullscreen = SvIV( poSv ) != 0;
poSv = get_sv("CHANNELS", FALSE); if (poSv) m_iChannels = SvIV( poSv );
poSv = get_sv("MIXINGRATE", FALSE); if (poSv) m_iMixingRate = SvIV( poSv );
poSv = get_sv("MIXINGBITS", FALSE); if (poSv) m_iMixingBits = SvIV( poSv );
poSv = get_sv("MUSICVOLUME", FALSE); if (poSv) m_iMusicVolume = SvIV( poSv );
poSv = get_sv("SOUNDVOLUME", FALSE); if (poSv) m_iSoundVolume = SvIV( poSv );
poSv = get_sv("LANGUAGE", FALSE); if (poSv) { strncpy( m_acLanguage, SvPV_nolen( poSv ), 9 ); m_acLanguage[9] = 0; }
poSv = get_sv("LATESTSERVER", FALSE); if (poSv) { strncpy( m_acLatestServer, SvPV_nolen( poSv ), 255 ); m_acLatestServer[255] = 0; }
poSv = get_sv("SERVER", FALSE); if (poSv) m_bServer = SvIV( poSv ) != 0;
poSv = get_sv("NICK", FALSE); if (poSv) { strncpy( m_acNick, SvPV_nolen( poSv ), 127 ); m_acNick[127] = 0; }
char pcBuffer[1024];
for ( int i=0; i<MAXPLAYERS; ++i )
{
for ( int j=0; j<9; ++j )
{
sprintf( pcBuffer, "PLAYER%dKEY%d", i, j );
poSv = get_sv(pcBuffer, FALSE); if (poSv) m_aiPlayerKeys[i][j] = SvIV( poSv );
}
}
}
void SState::Save()
{
std::string sFilename = GetConfigFilename();
std::ofstream oStream( sFilename.c_str(), std::ios_base::out | std::ios_base::trunc );
if ( oStream.rdstate() & std::ios::failbit )
{
debug( "Unable to open config file: %s\n", sFilename.c_str() );
return;
}
oStream << GetConfigHeader() << '\n';
// oStream << "=" << m_ << '\n';
oStream << "NUMPLAYERS=" << m_iNumPlayers << '\n';
oStream << "TEAMMODE=" << m_enTeamMode << '\n';
oStream << "TEAMSIZE=" << m_iTeamSize << '\n';
oStream << "TEAMMULTISELECT=" << m_bTeamMultiselect << '\n';
- oStream << "GAMETIME=" << m_iGameTime << '\n';
+ oStream << "ROUNDLENGTH=" << m_iRoundLength << '\n';
oStream << "HITPOINTS=" << m_iHitPoints << '\n';
oStream << "GAMESPEED=" << m_iGameSpeed << '\n';
oStream << "FULLSCREEN=" << m_bFullscreen << '\n';
oStream << "CHANNELS=" << m_iChannels << '\n';
oStream << "MIXINGRATE=" << m_iMixingRate << '\n';
oStream << "MIXINGBITS=" << m_iMixingBits << '\n';
oStream << "MUSICVOLUME=" << m_iMusicVolume << '\n';
oStream << "SOUNDVOLUME=" << m_iSoundVolume << '\n';
oStream << "LANGUAGE=" << m_acLanguage << '\n';
oStream << "LATESTSERVER=" << m_acLatestServer << '\n';
oStream << "SERVER=" << m_bServer << '\n';
oStream << "NICK=" << m_acNick << '\n';
for ( int i=0; i<MAXPLAYERS; ++i )
{
for ( int j=0; j<9; ++j )
{
oStream << "PLAYER" <<i<< "KEY" <<j<< '=' << m_aiPlayerKeys[i][j] << '\n';
}
}
oStream.flush();
oStream.close();
}
diff --git a/src/State.h b/src/State.h
index 4edf69f..74032c9 100644
--- a/src/State.h
+++ b/src/State.h
@@ -1,101 +1,102 @@
/***************************************************************************
State.h - description
-------------------
begin : Mon Aug 12 2003
copyright : (C) 2003 by upi
email : upi@apocalypse.rulez.org
***************************************************************************/
#ifndef STATE_H
#define STATE_H
#define MAXPLAYERS 4
/**
\ingroup GameLogic
SState aggregates all the state variables of OpenMortal that do not belong
to the backend. This includes transient variables such as the current
game mode (e.g. SStade::IN_DEMO) and configuration variables (such as the
keyboard layout).
SState is a singlular object, and is accessed with a global pointer,
g_oState. All other frontend modules access the state through this object.
The state is made persistent through it's methods, Load() and Save().
Load() is called on program start, Save() is called when the program exits.
The State is the way the CMenu communicate with the rest of the system.
For example, if the user chooses "Quit" from the menu, the m_bQuitFlag is
set to true, and the program will react accordingly.
The state's most important properties are:
\li m_enGameMode: The mode changes when a game is started or the game ends
(either in the GameOver screen, or via the "Surrender Game" menu option).
\li m_bQuitFlag: This is set if the program receives a quit event from the operating system (e.g. KILL signal, window close event, etc), or the user chooses "Quit" from the menu. Once the quit flag is set, the program will abort. All main loops check the value of the quit flag, and will break as soon as it is true.
\li m_bFullScreen: Quite simply, it is true in fullscreen mode, and false in windowed mode. The user can change this via the menu. The State's ToggleFullscreen() method will switch between fullscreen and windowed mode. Maybe this functionality doesn't belong to the State? ...
\li Sound properties: Mixing rate, number of channels, volume, etc.
\li m_aiPlayerKeys: A double array of each player's keys. This is used most often in processing SDL key events: if the event's keysym matches a value in m_aiPlayerKeys, that means that a meaningful key was pushed or released.
*/
struct SState
{
enum TGameMode {
IN_DEMO, ///< The game is currently in "demo" mode: displaying the intro screens, etc.
IN_SINGLE, ///< The game is in single-player mode.
IN_MULTI, ///< The game is in multi-player mode.
IN_NETWORK, ///< There is against a network opponent in progress
IN_CHAT, ///< The user is on MortalNet
} m_enGameMode;
bool m_bQuitFlag; // true if quit event came
const char* m_pcArgv0; // Set by main to argv[0]
// CONFIGURATION VARIABLES
int m_iNumPlayers; // The number of players =2
enum TTeamModeEnum {
Team_ONE_VS_ONE,
Team_GOOD_VS_EVIL,
Team_CUSTOM,
} m_enTeamMode; // Team mode
int m_iTeamSize; // The size of each team.
int m_bTeamMultiselect; // Can the same player be selected twice?
-
- int m_iGameTime; // Time of rounds in seconds.
+
+ // TODO Rename this to m_iRoundLength
+ int m_iRoundLength; // Length of rounds in seconds.
int m_iHitPoints; // The initial number of hit points.
int m_iGameSpeed; // The speed of the game (fps = 1000/GameSpeed)
bool m_bFullscreen; // True in fullscreen mode.
int m_iChannels; // 1: mono, 2: stereo
int m_iMixingRate; // The mixing rate, in kHz
int m_iMixingBits; // 1: 8bit, 2: 16bit
int m_iMusicVolume; // Volume of music; 0: off, 100: max
int m_iSoundVolume; // Volume of sound effects; 0: off, 100: max
int m_aiPlayerKeys[MAXPLAYERS][9]; // Player keysyms
char m_acLanguage[10]; // Language ID (en,hu,fr,es,..)
int m_iLanguageCode; // Non-persistend language code (set by backend based on the language)
char m_acLatestServer[256]; // Last server
bool m_bServer; // We were server in the last network game
char m_acNick[128]; // The user name on the last server.
SState();
void Load();
void Save();
void ToggleFullscreen();
void SetLanguage( const char* a_pcLanguage );
void SetServer( const char* a_pcServer );
};
extern SState g_oState;
#endif
diff --git a/src/menu.cpp b/src/menu.cpp
index bdded17..d6a0eb8 100644
--- a/src/menu.cpp
+++ b/src/menu.cpp
@@ -1,1301 +1,1301 @@
/***************************************************************************
menu.cpp - description
-------------------
begin : Sun Aug 3 2003
copyright : (C) 2003 by upi
email : upi@apocalypse.rulez.org
***************************************************************************/
#include "SDL.h"
#include "SDL_video.h"
#include "sge_primitives.h"
#include "menu.h"
#include "gfx.h"
#include "State.h"
#include "common.h"
#include "Audio.h"
#include "Backend.h"
#include "sge_tt_text.h"
#include "sge_surface.h"
#include "MortalNetwork.h"
#include "Joystick.h"
#include <stdarg.h>
-const char* g_ppcGameTime[] = { "0:30", "0:45", "1:00", "1:15", "1:30", "1:45", "2:00", "3:00", "5:00", NULL };
-const int g_piGameTime[] = { 30, 45, 60, 75, 90, 105, 120, 180, 300 };
+const char* g_ppcRoundLength[] = { "0:30", "0:45", "1:00", "1:15", "1:30", "1:45", "2:00", "3:00", "5:00", NULL };
+const int g_piRoundLength[] = { 30, 45, 60, 75, 90, 105, 120, 180, 300 };
const char* g_ppcHitPoints[] = { "BABY", "VERY LOW", "LOW", "NORMAL", "HIGH", "VERY HIGH", "NEAR IMMORTAL", NULL };
const int g_piHitPoints[] = { 1, 10, 50, 100, 150, 200, 500 };
const char* g_ppcGameSpeed[] = { "SNAIL RACE", "SLOW", "NORMAL", "TURBO", "KUNG-FU MOVIE", NULL };
const int g_piGameSpeed[] = { 16, 14, 12, 10, 8 };
const char* g_ppcChannels[] = { "MONO", "STEREO", NULL };
const int g_piChannels[] = { 1, 2 };
const char* g_ppcMixingRate[] = { "LOW", "MEDIUM", "HIGH", NULL };
const int g_piMixingRate[] = { 1, 2, 3 };
const char* g_ppcMixingBits[] = { "8 bit", "16 bit", NULL };
const int g_piMixingBits[] = { 1, 2 };
const char* g_ppcVolume[] = { "OFF", "10%", "20%", "30%", "40%", "50%", "60%", "70%", "80%", "90%", "100%", NULL };
const int g_piVolume[] = { 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 };
const char* g_ppcLanguage[] = { "English", "Spanish", "Francais", "Magyar", "Portugues", NULL };
const int g_piLanguage[] = { 0, 1, 2, 3, 4 };
const char* g_ppcLanguageCodes[] = { "en", "es", "fr", "hu", "pt" };
const char* g_ppcServer[] = { "Connect to game", "Create game", NULL };
int g_piServer[] = { 0, 1 };
const char* g_ppcTeamMode[] = { "1 VS 1", "Good VS Evil", "Custom teams", NULL };
int g_piTeamMode[] = { SState::Team_ONE_VS_ONE, SState::Team_GOOD_VS_EVIL, SState::Team_CUSTOM };
const char* g_ppcTeamSize[] = { "2", "3", "4", "5", "6", "7", "8", "9", "10", NULL };
int g_piTeamSize[] = { 2, 3, 4, 5, 6, 7, 8, 9, 10 };
const char* g_ppcNumPlayers[] = { "2", "3", "4", NULL };
int g_piNumPlayers[] = { 2, 3, 4 };
const char* g_ppcYesNo[] = { "Yes", "No", NULL };
int g_piYesNo[] = { 1, 0 };
SDL_Surface* poBackground = NULL;
void InputKeys( int a_iPlayerNumber )
{
SDL_BlitSurface( poBackground, NULL, gamescreen, NULL );
DrawGradientText( "Input keys", titleFont, 10, gamescreen );
SDL_Flip( gamescreen );
static const char* apcKeyNames[9] = { "up", "down", "left", "right", "block",
"low punch", "high punch", "low kick", "high kick" };
char acBuffer[1024];
char acSide[128];
char acFormat[128];
int iY = 75;
int iYIncrement = 35;
SDLKey enKey;
const char* pcJoyName = g_oJoystick.GetJoystickName( a_iPlayerNumber );
if ( NULL != pcJoyName )
{
DrawTextMSZ( pcJoyName, inkFont, gamescreen->w/2, iY, AlignHCenter|UseShadow, C_LIGHTCYAN, gamescreen );
iY += iYIncrement + 10;
}
DrawTextMSZ( "Press Escape to abort", inkFont, gamescreen->w/2, gamescreen->h-10-iYIncrement,
AlignHCenter|UseShadow, C_LIGHTGRAY, gamescreen );
strcpy( acSide, Translate(a_iPlayerNumber ? "Left" : "Right") );
strcpy( acFormat, Translate("%s player-'%s'?") );
for ( int i=0; i<9; ++i )
{
// 1. PRINT THE FONT AND THE CURRENT KEYSYM
sprintf( acBuffer, acFormat, acSide, Translate(apcKeyNames[i]) );
int w = DrawTextMSZ( acBuffer, inkFont, 10, iY, UseShadow, C_WHITE, gamescreen );
enKey = (SDLKey) g_oState.m_aiPlayerKeys[a_iPlayerNumber][i];
g_oBackend.PerlEvalF( "GetKeysym(%d);", enKey );
DrawTextMSZ( g_oBackend.GetPerlString("keysym"), inkFont, w+30, iY, UseShadow, C_LIGHTCYAN, gamescreen );
// 2. INPUT THE NEW KEY
enKey = GetKey( false );
if ( SDLK_ESCAPE == enKey )
{
SDL_BlitSurface( poBackground, NULL, gamescreen, NULL );
SDL_Flip( gamescreen );
return;
}
// 3. PRINT THE NEW KEY
g_oState.m_aiPlayerKeys[a_iPlayerNumber][i] = enKey;
g_oBackend.PerlEvalF( "GetKeysym(%d);", enKey );
sge_Blit( poBackground, gamescreen, w+10, iY, w+10, iY, gamescreen->w, 50 );
DrawTextMSZ( g_oBackend.GetPerlString("keysym"), inkFont, w+30, iY, UseShadow, C_WHITE, gamescreen );
sge_UpdateRect( gamescreen, w+10, iY, gamescreen->w, 50 );
iY += iYIncrement;
}
sge_Blit( poBackground, gamescreen, 0, 470-iYIncrement, 0, 470-iYIncrement, gamescreen->w, gamescreen->h );
sge_UpdateRect( gamescreen, 0, 470-iYIncrement, gamescreen->w, gamescreen->h );
DrawTextMSZ( "Thanks!", inkFont, gamescreen->w/2, iY + 20, UseShadow | AlignCenter, C_WHITE, gamescreen );
GetKey( true );
SDL_BlitSurface( poBackground, NULL, gamescreen, NULL );
SDL_Flip( gamescreen );
}
/***************************************************************************
NETWORK MENU DEFINITION
***************************************************************************/
int g_iMessageY;
static char g_acMessageBuffer[1024];
void MortalNetworkResetMessages( bool a_bClear )
{
if ( a_bClear )
{
SDL_FillRect( gamescreen, NULL, C_BLACK );
SDL_Flip( gamescreen );
g_iMessageY = 185;
}
else
{
g_iMessageY = 185;
}
}
void MortalNetworkMessage( const char* format, ... )
{
char acBuffer[1024];
va_list ap;
va_start( ap, format );
vsprintf( acBuffer, format, ap );
va_end( ap );
DrawTextMSZ( acBuffer, impactFont, 20, g_iMessageY, 0, C_LIGHTGRAY, gamescreen );
g_iMessageY += 25;
}
bool MortalNetworkCheckKey()
{
SDL_Event event;
while (SDL_PollEvent(&event))
{
switch (event.type)
{
case SDL_QUIT:
g_oState.m_bQuitFlag = true;
return true;
case SDL_KEYDOWN:
case SDL_JOYBUTTONDOWN:
return true;
} // switch statement
} // Polling events
return false;
}
bool Connect( const char* a_pcHostname )
{
MortalNetworkResetMessages( false );
bool bOK = g_poNetwork->Start( a_pcHostname );
if ( bOK )
{
// Store these settings as they are pretty good.
g_oState.SetServer( a_pcHostname );
g_oState.m_enGameMode = SState::IN_NETWORK;
}
else
{
// Print error message
const char* acError = g_poNetwork->GetLastError();
DrawTextMSZ( "Couldn't connect", inkFont, gamescreen->w/2, g_iMessageY, AlignHCenter|UseShadow, C_LIGHTRED, gamescreen );
DrawTextMSZ( acError, impactFont, gamescreen->w/2, g_iMessageY + 40, AlignHCenter|UseShadow, C_LIGHTRED, gamescreen, false );
}
// Wait for a key, unless Quit was issued.
if ( !g_oState.m_bQuitFlag )
{
if ( bOK )
{
// Wait for 1 sec, or keystroke.
for ( int i=0; i<10; ++i )
{
if ( MortalNetworkCheckKey() ) break;
SDL_Delay( 100 );
}
}
else
{
GetKey( true );
}
}
if ( g_oState.m_bQuitFlag )
{
bOK = false;
}
return bOK;
}
const char* FindString( const char* a_ppcNames[], const int a_piValues[], int a_iValue )
{
for ( int i=0; NULL != a_ppcNames[i]; ++i )
{
if ( a_iValue == a_piValues[i] )
{
return a_ppcNames[i];
}
}
return "(unknown)";
}
-const char* GetGameTimeString( int a_iValue )
+const char* GetRoundLengthString( int a_iValue )
{
strcpy( g_acMessageBuffer, Translate("GAME TIME: ") );
- strcat( g_acMessageBuffer, Translate(FindString( g_ppcGameTime, g_piGameTime, a_iValue)) );
+ strcat( g_acMessageBuffer, Translate(FindString( g_ppcRoundLength, g_piRoundLength, a_iValue)) );
return g_acMessageBuffer;
}
const char* GetGameSpeedString( int a_iValue )
{
strcpy( g_acMessageBuffer, Translate("GAME SPEED: ") );
strcat( g_acMessageBuffer, Translate(FindString( g_ppcGameSpeed, g_piGameSpeed, a_iValue)) );
return g_acMessageBuffer;
}
const char* GetHitPointsString( int a_iValue )
{
strcpy( g_acMessageBuffer, Translate("STAMINA: ") );
strcat( g_acMessageBuffer, Translate(FindString( g_ppcHitPoints, g_piHitPoints, a_iValue) ) );
return g_acMessageBuffer;
}
CNetworkMenu::CNetworkMenu(): CMenu( "Network Play Setup" )
{
m_bOK = false;
m_bServer = g_oState.m_bServer;
m_sHostname = g_oState.m_acLatestServer;
m_sNick = g_oState.m_acNick;
AddMenuItem( "Find an opponent on MortalNet", SDLK_UNKNOWN, MENU_MORTALNET );
AddMenuItem( "START NETWORK GAME!", SDLK_UNKNOWN, MENU_CONNECT );
m_poNickMenuItem = AddTextMenuItem( "Nickname: ", m_sNick.c_str(), MENU_NICK );
AddEnumMenuItem( "Network mode: ", m_bServer ? 1 : 0, g_ppcServer, g_piServer, MENU_SERVER );
m_poServerMenuItem = AddTextMenuItem( "Connect to: ", m_sHostname.c_str(), MENU_HOSTNAME );
m_poServerMenuItem->SetEnabled(!m_bServer);
CMenuItem* poItem = AddMenuItem( "Cancel", SDLK_UNKNOWN, MENU_CANCEL );
SDL_Rect oRect;
oRect.x = gamescreen->w - 150; oRect.w = 150;
oRect.y = gamescreen->h - 50; oRect.h = 30;
poItem->SetPosition( oRect );
}
CNetworkMenu::~CNetworkMenu() {}
void CNetworkMenu::Connect()
{
Clear();
SDL_Flip( gamescreen );
m_bOK = ::Connect( m_bServer ? NULL : m_sHostname.c_str() );
if ( g_oState.m_bQuitFlag || m_bOK )
{
m_bDone = true;
m_iReturnCode = 100;
}
Clear();
Draw();
}
void CMenu::EnterName( const char* a_pcTitle, std::string& a_rsTarget, CTextMenuItem* a_poMenuItem, int a_iMaxlen )
{
Clear();
Draw();
if ( a_iMaxlen > 255 ) a_iMaxlen = 255;
char acBuffer[256];
strncpy( acBuffer, a_rsTarget.c_str(), 255 );
acBuffer[255] = 0;
int y = m_oItems.size() * 40 + 100;
int x = DrawTextMSZ( a_pcTitle, impactFont, 20, y, 0, C_WHITE, gamescreen );
int iRetval;
{
CReadline oReadline( gamescreen, impactFont, acBuffer, strlen(acBuffer), a_iMaxlen,
20+x, y + sge_TTF_FontAscent(impactFont), gamescreen->w-40, C_LIGHTCYAN, C_BLACK, 255 );
iRetval = oReadline.Execute();
}
if ( iRetval == -1 )
{
m_bDone = true;
m_iReturnCode = 100;
g_oState.m_bQuitFlag = true;
}
if ( iRetval > 0 )
{
a_rsTarget = acBuffer;
a_poMenuItem->SetValue( acBuffer );
}
Clear();
Draw();
}
void CNetworkMenu::ItemActivated( int a_iItemCode, CMenuItem* a_poMenuItem )
{
switch ( a_iItemCode )
{
case MENU_SERVER:
{
CEnumMenuItem* poItem = (CEnumMenuItem*) a_poMenuItem;
if ( m_bServer )
{
poItem->Decrement();
}
else
{
poItem->Increment();
}
break;
}
case MENU_CONNECT:
strcpy( g_oState.m_acNick, m_sNick.c_str() );
Connect();
break;
case MENU_CANCEL:
m_bOK = false;
m_bDone = true;
m_iReturnCode = -1;
break;
case MENU_HOSTNAME:
EnterName( "Server name: ", m_sHostname, m_poServerMenuItem,128 );
break;
case MENU_NICK:
EnterName( "Nickname: ", m_sNick, m_poNickMenuItem, 12 );
strcpy( g_oState.m_acNick, m_sNick.c_str() );
break;
case MENU_MORTALNET:
g_oState.m_enGameMode = SState::IN_CHAT;
m_bOK = false;
m_bDone = true;
m_iReturnCode = 100;
break;
}
}
void CNetworkMenu::ItemChanged( int a_iItemCode, int a_iValue, CMenuItem* a_poMenuItem )
{
switch ( a_iItemCode )
{
case MENU_SERVER:
m_bServer = a_iValue != 0;
m_poServerMenuItem->SetEnabled(!m_bServer);
break;
}
}
/***************************************************************************
MENUITEM DEFINITION
***************************************************************************/
CMenuItem::CMenuItem( CMenu* a_poMenu, const char* a_pcUtf8Text, int a_iCode )
: m_sUtf8Text( a_pcUtf8Text )
{
m_poMenu = a_poMenu;
m_iCode = a_iCode;
m_oPosition.x = m_oPosition.y = 100;
m_oPosition.w = m_oPosition.h = 100;
m_bCenter = true;
m_iHighColor = C_WHITE;
m_iLowColor = C_LIGHTGRAY;
m_iInactiveColor = C_DARKGRAY;
m_iBackgroundColor = C_BLACK;
m_bActive = false;
m_bEnabled = true;
}
CMenuItem::~CMenuItem()
{
}
void CMenuItem::Draw()
{
if ( NULL != poBackground )
{
SDL_BlitSurface( poBackground, &m_oPosition, gamescreen, &m_oPosition );
}
else
{
SDL_FillRect( gamescreen, &m_oPosition, 0 );
}
int iX = m_oPosition.x;
int iY = m_oPosition.y;
if ( m_bCenter )
{
iX += m_oPosition.w / 2;
}
DrawTextMSZ( m_sUtf8Text.c_str(), inkFont, iX, iY,
UseTilde | UseShadow | (m_bCenter ? AlignHCenter : 0),
m_bEnabled ? (m_bActive ? m_iHighColor : m_iLowColor) : m_iInactiveColor,
gamescreen );
SDL_UpdateRect( gamescreen, m_oPosition.x, m_oPosition.y, m_oPosition.w, m_oPosition.h );
}
void CMenuItem::Clear()
{
// debug( "Clear: %d:%d %dx%d\n", m_oPosition.x, m_oPosition.y, m_oPosition.w, m_oPosition.h );
if (poBackground )
{
SDL_Rect oDest = m_oPosition;
SDL_BlitSurface( poBackground, &m_oPosition, gamescreen, &oDest );
}
else
{
SDL_FillRect( gamescreen, &m_oPosition, C_WHITE );
}
SDL_UpdateRect( gamescreen, m_oPosition.x, m_oPosition.y, m_oPosition.w, m_oPosition.h );
}
void CMenuItem::Activate()
{
if ( m_poMenu )
{
m_poMenu->ItemActivated( m_iCode, this );
}
}
void CMenuItem::SetText( const char* a_pcUtf8Text, bool a_bCenter )
{
m_sUtf8Text = a_pcUtf8Text;
m_bCenter = a_bCenter;
Draw();
}
void CMenuItem::SetPosition( const SDL_Rect& a_roPosition )
{
m_oPosition = a_roPosition;
}
void CMenuItem::SetActive( bool a_bActive )
{
if ( m_bActive == a_bActive )
{
return;
}
m_bActive = a_bActive;
Draw();
}
void CMenuItem::SetEnabled( bool a_bEnabled )
{
if ( m_bEnabled == a_bEnabled )
{
return;
}
m_bEnabled = a_bEnabled;
Draw();
}
/***************************************************************************
ENUMMENUITEM DEFINITION
***************************************************************************/
CEnumMenuItem::CEnumMenuItem( CMenu* a_poMenu, int a_iInitialValue, const char* a_pcUtf8Text, int a_iCode )
: CMenuItem( a_poMenu, a_pcUtf8Text, a_iCode )
{
m_sUtf8Title = a_pcUtf8Text;
m_iMax = -1;
m_iValue = a_iInitialValue;
}
CEnumMenuItem::~CEnumMenuItem()
{
}
int CEnumMenuItem::GetCurrentValue()
{
return m_iValue <= m_iMax ? m_piValues[m_iValue] : 0;
}
const char* CEnumMenuItem::GetCurrentText()
{
return m_iValue <= m_iMax ? m_ppcNames[m_iValue] : "";
}
void CEnumMenuItem::Draw()
{
m_sUtf8Text = Translate( m_sUtf8Title.c_str() );
if ( m_iValue <= m_iMax )
{
m_sUtf8Text += Translate(m_ppcNames[m_iValue]);
}
if ( m_iValue > 0 )
{
m_sUtf8Text = "< " + m_sUtf8Text;
}
if ( m_iValue < m_iMax )
{
m_sUtf8Text += " >";
}
CMenuItem::Draw();
}
void CEnumMenuItem::Increment()
{
if ( m_iValue < m_iMax )
{
++m_iValue;
Draw();
m_poMenu->ItemChanged( m_iCode, m_piValues[m_iValue], this );
Audio->PlaySample( "MENU_ITEM_VALUE_CHANGES" );
}
}
void CEnumMenuItem::Decrement()
{
if ( m_iValue > 0 )
{
--m_iValue;
Draw();
m_poMenu->ItemChanged( m_iCode, m_piValues[m_iValue], this );
Audio->PlaySample( "MENU_ITEM_VALUE_CHANGES" );
}
}
void CEnumMenuItem::SetEnumValues( const char ** a_ppcNames, const int * a_piValues )
{
m_ppcNames = a_ppcNames;
m_piValues = a_piValues;
int i;
bool bFoundValue = false;
for ( i=0; NULL != a_ppcNames[i]; ++i )
{
if ( !bFoundValue &&
m_iValue == a_piValues[i] )
{
bFoundValue = true;
m_iValue = i;
}
}
if ( !bFoundValue )
{
m_iValue = 0;
}
m_iMax = i-1;
}
void CEnumMenuItem::SetMaxValue( int a_iMaxValue )
{
for ( int i=0; NULL != m_ppcNames[i]; ++i )
{
if ( m_piValues[i] == a_iMaxValue )
{
m_iMax = i;
break;
}
}
debug( "CEnumMenuItem::SetMaxValue: value %d not found\n", a_iMaxValue );
}
/***************************************************************************
CTextMenuItem DEFINITION
***************************************************************************/
CTextMenuItem::CTextMenuItem( CMenu* a_poMenu, const char* a_pcInitialValue, const char* a_pcUtf8Title, int a_iCode )
: CMenuItem( a_poMenu, a_pcUtf8Title, a_iCode )
{
m_sTitle = a_pcUtf8Title;
m_sValue = a_pcInitialValue;
}
CTextMenuItem::~CTextMenuItem()
{
}
void CTextMenuItem::Draw()
{
m_sUtf8Text = Translate( m_sTitle.c_str() );
m_sUtf8Text += m_sValue;
CMenuItem::Draw();
}
void CTextMenuItem::SetValue( const char* a_pcValue )
{
m_sValue = a_pcValue;
Draw();
}
/***************************************************************************
MENU DEFINITION
***************************************************************************/
CMenu::CMenu( const char* a_pcTitle )
: m_sTitle( a_pcTitle )
{
m_iCurrentItem = 0;
m_iReturnCode = -1;
m_bDone = false;
}
CMenu::~CMenu()
{
CItemIterator it;
for ( it = m_oItems.begin(); it != m_oItems.end(); ++it )
{
delete *it;
}
}
CMenuItem* CMenu::AddMenuItem( const char* a_pcUtf8Text, SDLKey a_tShortcut, int a_iCode )
{
CMenuItem* poItem = new CMenuItem( this, a_pcUtf8Text, a_iCode );
return AddMenuItem( poItem );
}
CEnumMenuItem* CMenu::AddEnumMenuItem( const char* a_pcUtf8Text, int a_iInitialValue,
const char** a_ppcNames, const int* a_piValues, int a_iCode )
{
CEnumMenuItem* poItem = new CEnumMenuItem( this, a_iInitialValue, a_pcUtf8Text, a_iCode );
poItem->SetEnumValues( a_ppcNames, a_piValues );
AddMenuItem( poItem );
return poItem;
}
CTextMenuItem* CMenu::AddTextMenuItem( const char* a_pcTitle, const char* a_pcValue, int a_iCode )
{
CTextMenuItem* poItem = new CTextMenuItem( this, a_pcValue, a_pcTitle, a_iCode );
AddMenuItem( poItem );
return poItem;
}
CMenuItem* CMenu::AddMenuItem( CMenuItem* a_poItem )
{
m_oItems.push_back( a_poItem );
SDL_Rect oRect;
oRect.x = 0; oRect.w = gamescreen->w;
oRect.y = m_oItems.size() * 40 + 100;
oRect.h = sge_TTF_FontHeight( inkFont );
a_poItem->SetPosition( oRect );
return a_poItem;
}
void CMenu::AddOkCancel( int a_iOkCode )
{
SDL_Rect oRect;
oRect.x = 0; oRect.w = 150;
oRect.y = gamescreen->h - 50; oRect.h = sge_TTF_FontHeight( inkFont );
CMenuItem* poItem = AddMenuItem( "~OK", SDLK_o, a_iOkCode );
poItem->SetPosition( oRect );
// poItem = AddMenuItem( "Cancel", SDLK_UNKNOWN, 0 );
oRect.x = gamescreen->w - 150;
poItem->SetPosition( oRect );
}
CMenuItem* CMenu::GetMenuItem( int a_iCode ) const
{
CItemList::const_iterator it;
for ( it = m_oItems.begin(); it != m_oItems.end(); ++it )
{
if ( *it && (*it)->GetCode() == a_iCode )
{
return *it;
}
}
debug( "Couldn't find menu item %d\n", a_iCode );
return NULL;
}
void CMenu::InvokeSubmenu( CMenu* a_poMenu )
{
Audio->PlaySample( "MENU_ITEM_INVOKED" );
Clear();
m_iReturnCode = a_poMenu->Run();
if ( g_oState.m_bQuitFlag )
{
m_iReturnCode = 100;
m_bDone = true;
}
if ( m_iReturnCode < 0 )
{
Audio->PlaySample( "MENU_SUBMENU_END" );
Draw();
}
else
{
m_iReturnCode --;
m_bDone = true;
}
}
void CMenu::ItemActivated( int a_iItemCode, CMenuItem* a_poMenuItem )
{
debug( "CMenu::ItemActivated( %d )\n", a_iItemCode );
switch ( a_iItemCode )
{
case MENU_QUIT:
m_bDone = true;
m_iReturnCode = 100;
g_oState.m_bQuitFlag = true;
break;
case MENU_SURRENDER:
m_bDone = true;
m_iReturnCode = 100;
if ( SState::IN_NETWORK == g_oState.m_enGameMode )
{
g_poNetwork->Stop();
}
g_oState.m_enGameMode = SState::IN_DEMO;
break;
case MENU_NETWORK_GAME:
{
CMenu* poMenu = new CNetworkMenu();
InvokeSubmenu( poMenu );
delete poMenu;
break;
}
case MENU_MULTI_PLAYER:
{
CMenu* poMenu = new CMenu( "Multi Player" );
poMenu->AddMenuItem( "START GAME", SDLK_UNKNOWN, MENU_MULTI_PLAYER_START );
// poMenu->AddEnumMenuItem( "Number of Players: ", g_oState.m_iNumPlayers, g_ppcNumPlayers, g_piNumPlayers, MENU_NUM_PLAYERS );
poMenu->AddEnumMenuItem( "Team mode: ", g_oState.m_enTeamMode, g_ppcTeamMode, g_piTeamMode, MENU_TEAM_MODE );
poMenu->AddEnumMenuItem( "Team size: ", g_oState.m_iTeamSize, g_ppcTeamSize, g_piTeamSize, MENU_TEAM_SIZE )
->SetEnabled( SState::Team_CUSTOM == g_oState.m_enTeamMode );
poMenu->AddEnumMenuItem( "Cloning allowed: ", g_oState.m_bTeamMultiselect, g_ppcYesNo, g_piYesNo, MENU_TEAM_MULTISELECT )
->SetEnabled( SState::Team_CUSTOM == g_oState.m_enTeamMode );
InvokeSubmenu( poMenu );
delete poMenu;
break;
}
case MENU_MULTI_PLAYER_START:
m_bDone = true;
m_iReturnCode = 100;
g_oState.m_enGameMode = SState::IN_MULTI;
break;
case MENU_FULLSCREEN:
Audio->PlaySample( "MENU_ITEM_INVOKED" );
g_oState.ToggleFullscreen();
if ( NULL != poBackground )
{
SDL_BlitSurface( poBackground, NULL, gamescreen, NULL );
}
else
{
SDL_FillRect( gamescreen, NULL, 0 );
}
a_poMenuItem->SetText( g_oState.m_bFullscreen ? "~FULLSCREEN ON" : "~FULLSCREEN OFF", true );
Draw();
break;
case MENU_OPTIONS_OK:
m_bDone = true;
m_iReturnCode = -1;
break;
case MENU_OPTIONS:
{
CMenu* poMenu = new CMenu( "Options" );
if ( g_oState.m_enGameMode != SState::IN_NETWORK || g_poNetwork->IsMaster() )
{
poMenu->AddEnumMenuItem( "GAME SPEED: ", g_oState.m_iGameSpeed, g_ppcGameSpeed, g_piGameSpeed, MENU_GAME_SPEED );
- poMenu->AddEnumMenuItem( "GAME TIME: ", g_oState.m_iGameTime, g_ppcGameTime, g_piGameTime, MENU_GAME_TIME );
+ poMenu->AddEnumMenuItem( "GAME TIME: ", g_oState.m_iRoundLength, g_ppcRoundLength, g_piRoundLength, MENU_ROUND_LENGTH );
poMenu->AddEnumMenuItem( "STAMINA: ", g_oState.m_iHitPoints, g_ppcHitPoints, g_piHitPoints, MENU_TOTAL_HIT_POINTS );
}
poMenu->AddMenuItem( "~SOUND", SDLK_s, MENU_SOUND );
poMenu->AddMenuItem( g_oState.m_bFullscreen ? "~FULLSCREEN ON" : "~FULLSCREEN OFF", SDLK_f, MENU_FULLSCREEN );
poMenu->AddMenuItem( "~RIGHT PLAYER KEYS", SDLK_r, MENU_KEYS_RIGHT );
poMenu->AddMenuItem( "~LEFT PLAYER KEYS", SDLK_l, MENU_KEYS_LEFT );
poMenu->AddOkCancel( MENU_OPTIONS_OK );
InvokeSubmenu( poMenu );
delete poMenu;
break;
}
case MENU_SOUND:
{
CMenu* poMenu = new CMenu( "Sound" );
poMenu->AddEnumMenuItem( "CHANNELS: ", 1, g_ppcChannels, g_piChannels, MENU_CHANNELS )->SetEnabled(false);
poMenu->AddEnumMenuItem( "SOUND QUALITY: ", 2, g_ppcMixingRate, g_piMixingRate, MENU_MIXING_RATE )->SetEnabled(false);
poMenu->AddEnumMenuItem( "SOUND FIDELITY: ", 2, g_ppcMixingBits, g_piMixingBits, MENU_BITS )->SetEnabled(false);
poMenu->AddEnumMenuItem( "MUSIC VOLUME: ", g_oState.m_iMusicVolume, g_ppcVolume, g_piVolume, MENU_MUSIC_VOLUME );
poMenu->AddEnumMenuItem( "EFFECTS VOLUME: ", g_oState.m_iSoundVolume, g_ppcVolume, g_piVolume, MENU_SOUND_VOLUME );
poMenu->AddOkCancel( MENU_SOUND_OK );
InvokeSubmenu( poMenu );
delete poMenu;
break;
}
case MENU_SOUND_OK:
m_bDone = true;
m_iReturnCode = -1;
break;
case MENU_KEYS_LEFT:
InputKeys(1);
Draw();
break;
case MENU_KEYS_RIGHT:
InputKeys(0);
Draw();
break;
default:
break;
}
}
void CMenu::ItemChanged( int a_iItemCode, int a_iValue, CMenuItem* a_poMenuItem )
{
debug( "CMenu::ItemChanged( %d, %d )\n", a_iItemCode, a_iValue );
switch ( a_iItemCode )
{
case MENU_NUM_PLAYERS:
g_oState.m_iNumPlayers = a_iValue;
break;
case MENU_TEAM_MODE:
{
g_oState.m_enTeamMode = (SState::TTeamModeEnum) a_iValue;
CMenuItem* poItem;
poItem = GetMenuItem( MENU_TEAM_SIZE );
if ( poItem ) poItem->SetEnabled( SState::Team_CUSTOM==g_oState.m_enTeamMode );
poItem = GetMenuItem( MENU_TEAM_MULTISELECT );
if ( poItem ) poItem->SetEnabled( SState::Team_CUSTOM==g_oState.m_enTeamMode );
break;
}
case MENU_TEAM_SIZE:
g_oState.m_iTeamSize = a_iValue;
break;
case MENU_TEAM_MULTISELECT:
g_oState.m_bTeamMultiselect = a_iValue;
break;
case MENU_MUSIC_VOLUME:
g_oState.m_iMusicVolume = a_iValue;
Audio->SetMusicVolume( a_iValue );
break;
case MENU_SOUND_VOLUME:
g_oState.m_iSoundVolume = a_iValue;
break;
- case MENU_GAME_TIME:
- g_oState.m_iGameTime = a_iValue;
+ case MENU_ROUND_LENGTH:
+ g_oState.m_iRoundLength = a_iValue;
break;
case MENU_GAME_SPEED:
g_oState.m_iGameSpeed = a_iValue;
break;
case MENU_TOTAL_HIT_POINTS:
g_oState.m_iHitPoints = a_iValue;
break;
case MENU_LANGUAGE:
g_oState.SetLanguage( g_ppcLanguageCodes[ a_iValue ] );
Clear();
Draw();
break;
} // end of switch a_iItemCode
}
/** Run executes the menus, maybe invoking submenus as well. The
menus modify the global game state.
Returns 0, or the number of parent menus that should be cleared. */
int CMenu::Run()
{
if ( m_oItems[m_iCurrentItem]->GetEnabled() )
{
m_oItems[m_iCurrentItem]->SetActive(true);
}
else
{
FocusNext();
}
Draw();
while ( !m_bDone )
{
if ( g_oState.m_bQuitFlag )
{
m_bDone = true;
m_iReturnCode = -1;
break;
}
SDLKey enKey = GetKey( true );
if ( g_oState.m_bQuitFlag ||
SDLK_ESCAPE == enKey )
{
m_bDone = true;
m_iReturnCode = -1;
break;
}
switch ( enKey )
{
case SDLK_UP:
{
FocusPrev();
break;
} // end of SDLK_UP
case SDLK_DOWN:
{
FocusNext();
break;
} // end of SDLK_DOWN
case SDLK_LEFT:
{
CMenuItem* poItem = m_oItems[m_iCurrentItem];
poItem->Decrement();
break;
}
case SDLK_RIGHT:
{
CMenuItem* poItem = m_oItems[m_iCurrentItem];
poItem->Increment();
break;
}
case SDLK_RETURN:
{
CMenuItem* poItem = m_oItems[m_iCurrentItem];
if ( poItem->GetEnabled() )
{
poItem->Activate();
}
}
default:
break;
} // end of switch
}
Clear();
return m_iReturnCode;
}
void CMenu::Draw()
{
DrawGradientText( m_sTitle.c_str(), titleFont, 20, gamescreen );
for ( CItemIterator it=m_oItems.begin(); it!=m_oItems.end(); ++it )
{
(*it)->Draw();
}
SDL_Flip( gamescreen );
}
void CMenu::FocusNext()
{
CMenuItem* poItem = NULL;
int iNextItem;
for ( iNextItem = m_iCurrentItem+1; iNextItem < (int) m_oItems.size(); ++iNextItem )
{
poItem = m_oItems[iNextItem];
if ( poItem->GetEnabled() )
{
break;
}
poItem = NULL;
}
if ( NULL != poItem )
{
Audio->PlaySample("MENU_ITEM_SELECTION");
m_oItems[m_iCurrentItem]->SetActive(false);
m_oItems[iNextItem]->SetActive(true);
m_iCurrentItem = iNextItem;
}
}
void CMenu::FocusPrev()
{
CMenuItem* poItem = NULL;
int iPrevItem;
for ( iPrevItem = m_iCurrentItem-1; iPrevItem >= 0; --iPrevItem )
{
poItem = m_oItems[iPrevItem];
if ( poItem->GetEnabled() )
{
break;
}
poItem = NULL;
}
if ( NULL != poItem )
{
Audio->PlaySample("MENU_ITEM_SELECTION");
m_oItems[m_iCurrentItem]->SetActive(false);
m_oItems[iPrevItem]->SetActive(true);
m_iCurrentItem = iPrevItem;
}
}
void CMenu::Clear()
{
if (poBackground)
{
SDL_BlitSurface( poBackground, 0, gamescreen, 0 );
}
else
{
SDL_FillRect( gamescreen, NULL, 0 );
}
}
void MakeMenuBackground()
{
if ( poBackground != NULL )
{
return;
}
poBackground = SDL_ConvertSurface( gamescreen, gamescreen->format, SDL_SWSURFACE );
if ( NULL == poBackground )
{
debug( "DoMenu: Couldn't allocate background.\n" );
return;
}
if ( gamescreen->format->BitsPerPixel <= 8 )
{
int i;
SDL_Rect oRect;
oRect.x = 0; oRect.w = poBackground->w; oRect.h = 1;
for ( i=0; i<poBackground->h; i += 2 )
{
oRect.y = i;
SDL_FillRect( poBackground, &oRect, C_BLACK );
}
oRect.w = 1; oRect.y = 0; oRect.h = poBackground->h;
for ( i=0; i<poBackground->w; i+=2 )
{
oRect.x = i;
SDL_FillRect(poBackground, &oRect, C_BLACK );
}
}
else
{
sge_FilledRectAlpha( poBackground, 0, 0, poBackground->w, poBackground->h, C_BLACK, 128 );
sge_FilledRectAlpha( poBackground, 0, 0, poBackground->w, poBackground->h, C_BLUE, 64 );
}
SDL_BlitSurface( poBackground, 0, gamescreen, 0 );
SDL_Flip( gamescreen );
}
void DoMenu( CMenu& a_roMenu )
{
Audio->PlaySample( "MENU_START" );
MakeMenuBackground();
a_roMenu.Clear();
a_roMenu.Draw();
a_roMenu.Run();
if ( !g_oState.m_bQuitFlag )
{
Audio->PlaySample("MENU_END");
}
if ( NULL != poBackground )
{
SDL_FreeSurface( poBackground );
poBackground = NULL;
}
}
void DoMenu()
{
MakeMenuBackground();
CMenu oMenu( "Main Menu" );
if ( SState::IN_DEMO == g_oState.m_enGameMode )
{
oMenu.AddMenuItem( "~SINGLE PLAYER GAME", SDLK_s, MENU_SINGLE_PLAYER )->SetEnabled(false);
oMenu.AddMenuItem( "~NETWORK GAME", SDLK_n, MENU_NETWORK_GAME );
oMenu.AddMenuItem( "~MULTI PLAYER GAME", SDLK_m, MENU_MULTI_PLAYER );
}
else
{
oMenu.AddMenuItem( "~SURRENDER GAME", SDLK_s, MENU_SURRENDER );
}
oMenu.AddEnumMenuItem( "~LANGUAGE: ", g_oState.m_iLanguageCode, g_ppcLanguage, g_piLanguage, MENU_LANGUAGE );
oMenu.AddMenuItem( "~OPTIONS", SDLK_o, MENU_OPTIONS );
oMenu.AddMenuItem( "~INFO", SDLK_i, MENU_INFO )->SetEnabled(false);
oMenu.AddMenuItem( "QUIT", SDLK_UNKNOWN, MENU_QUIT );
DoMenu( oMenu );
}
diff --git a/src/menu.h b/src/menu.h
index 1f7dda1..7150ddf 100644
--- a/src/menu.h
+++ b/src/menu.h
@@ -1,259 +1,259 @@
/***************************************************************************
menu.h - description
-------------------
begin : Sun Aug 3 2003
copyright : (C) 2003 by upi
email : upi@apocalypse.rulez.org
***************************************************************************/
#ifndef MENU_H
#define MENU_H
#include <string>
#include <vector>
class CMenuItem;
class CEnumMenuItem;
class CTextMenuItem;
enum
{
/* Master menu structure:
MAIN MENU
*/
MENU_UNKNOWN,
MENU_SURRENDER,
MENU_SINGLE_PLAYER,
MENU_EASY,
MENU_MEDIUM,
MENU_HARD,
MENU_MULTI_PLAYER,
MENU_MULTI_PLAYER_START,
MENU_NUM_PLAYERS, // 2-4
MENU_TEAM_MODE, // 1 vs 1, good vs evil, evil vs good, custom
MENU_TEAM_SIZE, // 2-10
MENU_TEAM_MULTISELECT, // yes / no
MENU_NETWORK_GAME,
MENU_SERVER,
MENU_HOSTNAME,
MENU_NICK,
MENU_CONNECT,
MENU_MORTALNET,
MENU_CANCEL,
MENU_OPTIONS,
MENU_GAME_SPEED,
- MENU_GAME_TIME, // ( :30 - 5:00 )
+ MENU_ROUND_LENGTH, // ( :30 - 5:00 )
MENU_TOTAL_HIT_POINTS, // ( 25 - 1000 )
MENU_SOUND,
MENU_CHANNELS, // MONO / STEREO
MENU_MIXING_RATE, // 11kHz / 22kHz / 44.1 kHz
MENU_BITS, // 8 bit / 16 bit
MENU_MUSIC_VOLUME, // (0% - 100%)
MENU_SOUND_VOLUME, // (0% - 100%)
MENU_SOUND_OK,
MENU_FULLSCREEN,
MENU_KEYS_RIGHT,
MENU_KEYS_LEFT,
MENU_OPTIONS_OK,
MENU_LANGUAGE,
MENU_INFO,
MENU_QUIT, // (confirm)
};
/**
\brief Base class for menu systems in OpenMortal.
\ingroup GameLogic
Menus are displayed over the current screen and allow for the editing
of the game's state, SState. If you think in terms of model, view and
controller, then SState is the model and CMenu is both the view and
the controller.
Menus have menu items. The items in turn can edit various state
variables or invoke submenus. The menu ends then the user exits the
toplevel menu or invokes a command which unrolls the entire menu
stack.
*/
class CMenu
{
public:
CMenu( const char* a_pcTitle );
virtual ~CMenu();
virtual CMenuItem* AddMenuItem( const char* a_pcUtf8Text, SDLKey a_tShortcut = SDLK_UNKNOWN, int a_iCode = 0 );
virtual CEnumMenuItem* AddEnumMenuItem( const char* a_pcUtf8Text, int a_iInitialValue,
const char** a_ppcNames, const int* a_piValues, int a_iCode = 0 );
virtual CTextMenuItem* AddTextMenuItem( const char* a_pcTitle, const char* a_pcValue, int a_iCode = 0 );
virtual CMenuItem* AddMenuItem( CMenuItem* a_poItem );
virtual void AddOkCancel( int a_iOkCode = 0 );
virtual CMenuItem* GetMenuItem( int a_iCode ) const;
virtual void ItemActivated( int a_iItemCode, CMenuItem* a_poMenuItem );
virtual void ItemChanged( int a_iItemCode, int a_iValue, CMenuItem* a_poMenuItem );
virtual int Run();
virtual void Draw();
virtual void Clear();
virtual void EnterName( const char* a_pcTitle, std::string& a_rsTarget, CTextMenuItem* a_poMenuItem, int a_iMaxlen );
protected:
virtual void FocusNext();
virtual void FocusPrev();
virtual void InvokeSubmenu( CMenu* a_poSubmenu );
typedef std::vector<CMenuItem*> CItemList;
typedef CItemList::iterator CItemIterator;
std::string m_sTitle;
CItemList m_oItems;
int m_iCurrentItem;
int m_iReturnCode;
bool m_bDone;
};
/**
\ingroup GameLogic
Basic menu item. Menu items have a code which they pass to their parent
menu when they are activated. Menu items can be enabled or disabled.
*/
class CMenuItem
{
public:
CMenuItem( CMenu* a_poMenu, const char* a_pcUtf8Text, int a_iCode = -1 );
virtual ~CMenuItem();
virtual void Draw();
virtual void Clear();
virtual void Activate();
virtual void Increment() {};
virtual void Decrement() {};
virtual void SetText( const char* a_pcUtf8Text, bool a_bCenter );
virtual void SetPosition( const SDL_Rect& a_roPosition );
virtual void SetActive( bool a_bActive );
virtual void SetEnabled( bool a_bEnabled );
virtual bool GetEnabled() const { return m_bEnabled; }
virtual int GetCode() const { return m_iCode; }
protected:
CMenu* m_poMenu;
// appearance
std::string m_sUtf8Text;
SDL_Rect m_oPosition;
bool m_bCenter;
Uint32 m_iHighColor;
Uint32 m_iLowColor;
Uint32 m_iInactiveColor;
Uint32 m_iBackgroundColor;
// data content
int m_iCode;
bool m_bActive;
bool m_bEnabled;
};
/**
\ingroup GameLogic
Enumerated menu items have an integer value, and a set of values and texts
which they can display for the user. The user can switch between these
values by incrementing and decrementing the value with the left and right
arrow keys.
*/
class CEnumMenuItem: public CMenuItem
{
public:
CEnumMenuItem( CMenu* a_poMenu, int a_iInitialValue, const char* a_pcUtf8Text, int a_iCode = -1 );
virtual ~CEnumMenuItem();
int GetCurrentValue();
const char* GetCurrentText();
virtual void Draw();
virtual void Increment();
virtual void Decrement();
virtual void SetEnumValues( const char ** a_ppcNames, const int * a_piValues );
virtual void SetMaxValue( int a_iMaxValue );
protected:
int m_iValue;
int m_iMax;
std::string m_sUtf8Title;
const char** m_ppcNames;
const int* m_piValues;
};
/**
\ingroup GameLogic
Text menu items are like regular menu items, but they have a text value
which can be set. This value is displayed next to the regular name of
the menu item.
*/
class CTextMenuItem: public CMenuItem
{
public:
CTextMenuItem( CMenu* a_poMenu, const char* a_pcInitialValue, const char* a_pcUtf8Title, int a_iCode );
virtual ~CTextMenuItem();
virtual void Draw();
virtual void SetValue( const char* a_pcValue );
protected:
std::string m_sTitle;
std::string m_sValue;
};
/**
\ingroup GameLogic
The Network displays and modifies the network connection parameters.
*/
class CNetworkMenu: public CMenu
{
public:
CNetworkMenu();
virtual ~CNetworkMenu();
void Connect();
void ItemActivated( int a_iItemCode, CMenuItem* a_poMenuItem );
void ItemChanged( int a_iItemCode, int a_iValue, CMenuItem* a_poMenuItem );
protected:
bool m_bOK;
bool m_bServer;
std::string m_sHostname;
std::string m_sNick;
CTextMenuItem* m_poServerMenuItem;
CTextMenuItem* m_poNickMenuItem;
};
void DoMenu();
void DoMenu( CMenu& a_roMenu );
#endif

File Metadata

Mime Type
text/x-diff
Expires
Tue, Jun 16, 1:09 AM (2 w, 21 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
72717
Default Alt Text
(125 KB)

Event Timeline