/* MAIN.C
This is CloneKeen's main source file.
The CloneKeen source may be freely distributed and
modified as per the GPL but please give credit to
the original author, Caitlin Shaw and to the new author
Gerstrong.
If you make any changes or improvements to the code that
you feel merit inclusion in the source tree email them
to me at gerstrong@gmail.com or get my latest email
from the SourceForge site.
Thanks to ID Software for the "Commander Keen: Invasion of
the Vorticons" games. "Commander Keen" and it's associated
graphics, level, and sound files are the property of ID
Software. Commander Genius requires the original version of a
Commander Keen game in order to be able to emulate that
episode.
Enjoy the Code
- The Commander Genius Team
CloneKeen 2003-2005 Caitlin Shaw
CloneKeenPlus 2008-2009 Gerstrong
Commander Genius 2009 Tulip, Pickle, DaVince and Albert
*/
#include "keen.h"
#include "sdl/joydrv.h"
#include "sdl/CInput.h"
#include "sdl/CTimer.h"
#include "sdl/sound/CSound.h"
#include "include/misc.h"
#include "include/menu.h"
#include "sdl/CVideoDriver.h"
#include "include/game.h"
#include "include/eseq_ep2.h"
#include "include/fileio.h"
#include "include/fileio/story.h"
#include "include/main.h"
#include "fileio/CParser.h"
#include "vorticon/CPlayer.h"
#include "vorticon/CHighScores.h"
#include "CLogFile.h"
#include "CGame.h"
#include "CGraphics.h"
#include "sdl/CSettings.h"
int IntroCanceled;
int NessieObjectHandle;
int DemoObjectHandle;
int BlankSprite;
int DemoSprite;
int framebyframe;
int fps=0, curfps=0;
stOption *options;
unsigned int demo_RLERunLen;
unsigned char demo_data[DEMO_MAX_SIZE+1];
unsigned int demo_data_index;
char QuitState = NO_QUIT;
stString strings[MAX_STRINGS+1];
int numStrings;
int demomode;
FILE *demofile;
char ScreenIsScrolling;
int gunfiretimer, gunfirefreq;
extern unsigned long scroll_x;
extern unsigned int scrollx_buf;
extern unsigned char scrollpix;
extern unsigned int mapx;
extern unsigned int mapxstripepos;
extern unsigned int scroll_y;
extern unsigned int scrolly_buf;
extern unsigned char scrollpixy;
extern unsigned int mapy;
extern unsigned int mapystripepos;
extern CPlayer *Player;
char loadinggame, loadslot;
stFade fade;
stMap map;
unsigned int AnimTileInUse[ATILEINUSE_SIZEX][ATILEINUSE_SIZEY];
stTile tiles[MAX_TILES+1];
int numtiles;
int **TileProperty; // This version will replace the old stTile Structure and save memory
unsigned char tiledata[MAX_TILES+1][16][16];
stSprite *sprites;
stBitmap bitmaps[MAX_BITMAPS+1];
stObject objects[MAX_OBJECTS+1];
char font[MAX_FONT+1][8][8];
stAnimTile animtiles[MAX_ANIMTILES+1];
stPlayer player[MAX_PLAYERS];
stPlayer net_lastplayer[MAX_PLAYERS];
unsigned int scroll_y = 0;
unsigned int objdefsprites[NUM_OBJ_TYPES+1];
char frameskiptimer=0;
int thisplayer;
unsigned int primaryplayer;
unsigned int numplayers;
int crashflag,crashflag2,crashflag3;
const char *why_term_ptr = "No reason given.";
int main(int argc, char *argv[])
{
stCloneKeenPlus CKP; // This is the future main structure of CloneKeen. It will be one variable which controls all
// the program instead of having global variables around somewhere.
g_pLogFile->CreateLogfile("CGLog.html");
banner(); // Intro on the text-console.
CGame* Game;
Game = new CGame();
Game->preallocateCKP(&CKP);
CSettings *Settings;
Settings = new CSettings;
if(Settings->loadDrvCfg() != 0) // Always return 0 if no ERROR
{
g_pLogFile->textOut(RED,"First time message: CKP didn't find the driver config file. However, it is going to generate one basing on default configurations.
");
Settings->saveDrvCfg();
}
if(readCommandLine(argc, argv, &CKP) != 0)
{
g_pLogFile->textOut(RED,"Sorry, but CKP needs correct command line parameters.
");
printf("Sorry, but CKP needs correct command line parameters.\n");
}
if(loadCKPDrivers(&CKP) != 0)
{
g_pLogFile->textOut(RED,"The game cannot start, because you do not meet the hardware requirements.
");
return 1;
}
Settings->loadDefaultGameCfg(CKP.Option);
if(Settings->loadGameCfg(CKP.Option) != 0)
{
g_pLogFile->textOut(PURPLE,"There are no settings! CKP is going to use the default options. You can change them later in the game.
");
Settings->saveGameCfg(CKP.Option);
}
delete Settings; Settings = NULL;
if(loadResourcesforStartMenu(&CKP, Game) != 0)
{
g_pLogFile->textOut(RED,"Error! Resources for start menu cannot be loaded! Maybe you need to copy the data files!
");
return 1;
}
if(!CKP.Control.skipstarting)
{
if(loadStartMenu(&CKP) == 1)
{
cleanupResources(&CKP);
return 0;
}
}
if(!g_pInput->getExitEvent())
{
CKP.Control.levelcontrol.episode = CKP.GameData[CKP.Resources.GameSelected-1].Episode; // Assign the correct Episode
options = CKP.Option;
if(Game->loadResources(CKP.Control.levelcontrol.episode, CKP.GameData[CKP.Resources.GameSelected-1].DataDirectory))
CKP.shutdown = SHUTDOWN_BOOTUP; // Prepare game for starting
else
CKP.shutdown = SHUTDOWN_NEW_GAME;
while( CKP.shutdown == SHUTDOWN_RESTART || CKP.shutdown == SHUTDOWN_BOOTUP || CKP.shutdown == SHUTDOWN_NEW_GAME )
{
if(CKP.shutdown != SHUTDOWN_NEW_GAME) {
CKP.shutdown = SHUTDOWN_NONE; // Game is runnning
Game->runCycle(&CKP);
}
if(CKP.shutdown == SHUTDOWN_NEW_GAME)
{
if(loadStartMenu(&CKP) == 1)
{
CKP.shutdown = SHUTDOWN_EXIT;
break;
}
else
{
//loadResourcesforGame(pCKP);
if(Game->loadResources(CKP.Control.levelcontrol.episode, CKP.GameData[CKP.Resources.GameSelected-1].DataDirectory))
CKP.shutdown = SHUTDOWN_RESTART;
else
CKP.shutdown = SHUTDOWN_NEW_GAME;
}
}
}
}
if(Game){ delete Game; Game = NULL; }
cleanupResources(&CKP);
printf("Thank you very much for playing this wonderful game!");
return 0;
}
void cleanupResources(stCloneKeenPlus *pCKP)
{
cleanup(pCKP);
return;
}
short loadCKPDrivers(stCloneKeenPlus *pCKP)
{
g_pGraphics->allocScrollBufmem();
// initialize/activate all drivers
g_pLogFile->ftextOut("Starting graphics driver...
");
if (!g_pVideoDriver->start())
return abortCKP(pCKP);
g_pLogFile->ftextOut("Starting sound driver...
");
g_pSound->init();
g_pLogFile->ftextOut("Starting input driver...
");
JoyDrv_Start(&(pCKP->Device.Joystick));
g_pInput->loadControlconfig();
return 0;
}
short abortCKP(stCloneKeenPlus *pCKP)
{
g_pLogFile->ftextOut("Fatal error, cleaning up.
");
cleanup(pCKP);
g_pLogFile->ftextOut("A fatal error has occurred; game is shutting down.
");
return 1;
}
short closeCKP(stCloneKeenPlus *pCKP)
{
int count;
int i;
CSettings Settings;
banner();
Settings.saveGameCfg(pCKP->Option);
g_pLogFile->ftextOut("
Thanks for playing!
");
cleanup(pCKP);
if (crashflag)
{
if (crashflag != QUIT_NONFATAL) g_pLogFile->ftextOut("\a");
g_pLogFile->ftextOut("abnormal program termination, error code %d/%d/%d
explanation: %s
", crashflag,crashflag2,crashflag3, why_term_ptr);
g_pLogFile->ftextOut("numplayers: %d
", numplayers);
for(count=0,i=0;iftextOut("# of player instances with isPlaying set: %d
", count);
}
return 0;
}
void playgame_levelmanager(stCloneKeenPlus *pCKP)
{
int i, o, wm, firsttime = 1;
char levelname[80];
char SaveGameFileName[40];
int newlevel;
stLevelControl *p_levelcontrol;
stOption *p_option;
p_option = pCKP->Option;
p_levelcontrol = &(pCKP->Control.levelcontrol);
if( p_levelcontrol->command != LVLC_START_LEVEL )
{
p_levelcontrol->command = LVLC_CHANGE_LEVEL;
p_levelcontrol->chglevelto = WM_MAP_NUM;
}
p_levelcontrol->tobonuslevel = 0;
p_levelcontrol->success = 0;
map.firsttime = 1;
do
{
initgame(pCKP);
newlevel = p_levelcontrol->chglevelto;
if (p_levelcontrol->episode==1 && p_levelcontrol->hardmode)
{
// in high-difficulity mode switch levels 5 & 9 so
// you can't get the pogo stick until you make it
// to the dark side of mars.
if (newlevel==5)
newlevel = 9;
else if (newlevel==9)
newlevel = 5;
}
sprintf(levelname, "level%02d.ck%d", newlevel, p_levelcontrol->episode);
if (p_levelcontrol->chglevelto==WORLD_MAP)
{
wm = 1;
}
else
{
wm = 0;
}
p_levelcontrol->canexit = 1; // assume can exit before loading map
if (loadmap(levelname, pCKP->GameData[pCKP->Resources.GameSelected-1].DataDirectory, p_levelcontrol->chglevelto, wm, pCKP))
{
crashflag = 1;
crashflag2 = p_levelcontrol->chglevelto;
why_term_ptr = "Unable to load the map (# shown in crashflag2).";
}
p_levelcontrol->curlevel = p_levelcontrol->chglevelto;
if (p_levelcontrol->curlevel == FINAL_MAP)
{
p_levelcontrol->isfinallevel = 1;
p_levelcontrol->canexit = 0;
}
else
{
p_levelcontrol->isfinallevel = 0;
}
if (firsttime)
{
int op;
for(i=0;icommand = LVLC_NOCOMMAND;
p_levelcontrol->dark = 0;
if (loadinggame)
{
sprintf(SaveGameFileName, "ep%csave%c.dat", p_levelcontrol->episode+'0', loadslot+'0');
wm = savegameiswm(SaveGameFileName);
if (game_load(SaveGameFileName, pCKP))
{
crashflag = 1;
crashflag2 = loadslot;
why_term_ptr = "Error loading game (slot # in flag2)! The save file may be corrupt or created by a different version of CloneKeen.";
return;
}
}
g_pGraphics->initPalette(p_levelcontrol->dark);
// Now load HQ Stuff, because the game could have been loaded too.
g_pGraphics->loadHQGraphics(p_levelcontrol->episode,p_levelcontrol->chglevelto,pCKP->GameData[pCKP->Resources.GameSelected-1].DataDirectory);
g_pInput->flushAll();
if (wm)
{ // entering map from normal level, or first time around
if (!p_levelcontrol->tobonuslevel)
{
if (!loadinggame)
{
for(i=0;i>CSF>>4)<>CSF>>4)+1)<playStereofromCoord(SOUND_TELEPORT, PLAY_NOW, objects[player[i].useObject].scrx);
}
}
}
if (!p_levelcontrol->success || firsttime)
{
if (!p_levelcontrol->tobonuslevel) p_levelcontrol->dokeensleft = 1;
// when you die you lose all keycards
for(i=0;idokeensleft = 0;
gameloop(pCKP);
for(i=0;idokeensleft = 0;
gameloop(pCKP);
// after completion of a normal level check if the game is won
if (gameiswon(pCKP))
{
p_levelcontrol->command = LVLC_END_SEQUENCE;
}
}
if(QuitState==QUIT_PROGRAM)
{
pCKP->shutdown = SHUTDOWN_EXIT;
break;
}
g_pGraphics->unloadHQGraphics();
for(unsigned int i=0;icommand==LVLC_CHANGE_LEVEL && !crashflag);
g_pGraphics->unloadHQGraphics();
if (p_levelcontrol->command==LVLC_END_SEQUENCE)
{
endsequence(pCKP);
g_pLogFile->ftextOut("eseq complete
");
}
else if (p_levelcontrol->command==LVLC_TANTALUS_RAY)
{
eseq2_vibrate(pCKP);
eseq2_TantalusRay(pCKP);
IntroCanceled = 1; // popup main menu immediately
}
}
// plays the demo file specified in fname
// returns:
// DEMO_RESULT_FILE_BAD demo does not exist or file corrupt
// DEMO_RESULT_COMPLETED demo played all the way through
// DEMO_RESULT_CANCELED user canceled the demo
char play_demo(int demonum, stCloneKeenPlus *pCKP, int s)
{
int i;
int byt;
int lvl;
char filename[40];
char SaveOptions[NUM_OPTIONS];
stLevelControl *p_levelcontrol;
stOption *p_option;
p_levelcontrol = &(pCKP->Control.levelcontrol);
p_option = pCKP->Option;
// open the demo file
sprintf(filename, "ep%ddemo%d.dat", p_levelcontrol->episode, demonum);
demofile = fopen(filename, "rb");
if (!demofile)
{
return DEMO_RESULT_FILE_BAD;
}
// read in the header
if (fgetc(demofile) != 'D') goto demoHeaderCorrupt;
if (fgetc(demofile) != 'M') goto demoHeaderCorrupt;
if (fgetc(demofile) != 'O') goto demoHeaderCorrupt;
if (fgetc(demofile) != p_levelcontrol->episode) goto demoHeaderCorrupt;
lvl = fgetc(demofile);
// load the compressed demo into the demo_data[] array
for(i=0;icurlevel = lvl;
p_levelcontrol->command = LVLC_NOCOMMAND;
initgamefirsttime(pCKP, s);
initgame(pCKP);
// now load the map and play the level
sprintf(filename, "level%02d.ck%d", p_levelcontrol->curlevel, p_levelcontrol->episode);
if (loadmap(filename, pCKP->GameData[pCKP->Resources.GameSelected-1].DataDirectory,
p_levelcontrol->curlevel, 0, pCKP)) return DEMO_RESULT_FILE_BAD;
for(i=0;isuccess)
{
return DEMO_RESULT_COMPLETED;
}
else
{
return DEMO_RESULT_CANCELED;
}
// this label is jumped to when there's an error reading the header.
// it closes the demo file and aborts.
demoHeaderCorrupt: ;
fclose(demofile);
demofile = NULL;
return DEMO_RESULT_FILE_BAD;
}
short readCommandLine(int argc, char *argv[], stCloneKeenPlus *pCKP)
{
int i;
static const int MAX_STRING_LENGTH = 256;
char tempbuf[MAX_STRING_LENGTH];
unsigned short *p_gamesel;
// process command line options
g_pLogFile->ftextOut("Processing command-line options.
");
numplayers = 1;
p_gamesel = &(pCKP->Resources.GameSelected);
if (argc>1)
{
for(i=1;iOption[OPT_ALLOWPKING].value = 0;
}
else if (strncmp(tempbuf, "-game",strlen("-game"))==0) // select the game
{
int g;
sscanf(tempbuf+strlen("-game"),"%d",&g);
*p_gamesel = g;
}
else if (strcmp(tempbuf, "-dtm")==0) // direct to map
{
pCKP->Control.dtm = 1;
}
else if (strcmp(tempbuf, "-mean")==0) // increase difficulty
{
pCKP->Control.levelcontrol.hardmode = true;
}
else if (strcmp(tempbuf, "-cheat")==0) // enable cheat codes
{
pCKP->Option[OPT_CHEATS].value = 1;
}
else if (strcmp(tempbuf, "-rec")==0) // record a demo
{
demomode = DEMO_RECORD;
}
else if (strcmp(tempbuf, "-eseq")==0) // play end sequence
{
pCKP->Control.eseq = 1;
}
else if (strcmp(tempbuf, "-story")==0) // play end sequence
{
pCKP->Control.storyboard = 1;
pCKP->Control.skipstarting = 1;
}
else if (strcmp(tempbuf, "-fs")==0) // full-screen
{
g_pVideoDriver->isFullscreen(true);
}
else if (strcmp(tempbuf, "-dbl")==0) // 2x
{
g_pVideoDriver->setZoom(2);
}
else if (strcmp(tempbuf, "-stereo")==0) // Enable Stereo Sound
{
g_pSound->setSoundmode(0,true);
}
else if (strcmp(tempbuf, "-showfps")==0) // show fps
{
g_pVideoDriver->showFPS(true);
}
else if (strncmp(tempbuf, "-level",strlen("-level"))==0) // select the game
{
int g;
sscanf(tempbuf+strlen("-level"),"%d",&g);
pCKP->Control.levelcontrol.command = LVLC_START_LEVEL;
pCKP->Control.levelcontrol.chglevelto = g;
pCKP->Control.skipstarting = 1;
}
else if (i!=1 || atoi(argv[i])==0)
{
g_pLogFile->ftextOut("Wait a minute...what the hell does '%s' mean? I'm going to ignore this!
",tempbuf);
usage();
return 1;
}
}
}
return 0;
}