warzone2100/src/loadsave.c

693 lines
20 KiB
C
Raw Normal View History

/*
This file is part of Warzone 2100.
Copyright (C) 1999-2004 Eidos Interactive
Copyright (C) 2005-2007 Warzone Resurrection Project
Warzone 2100 is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
Warzone 2100 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Warzone 2100; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/*
* loadsave.c
* load and save Popup screens.
*
* these don't actually do any loading or saving, but just
* return a filename to use for the ops.
*/
#include <ctype.h>
#include <physfs.h>
#include "lib/framework/frame.h"
#include "lib/framework/strres.h"
#include "lib/widget/widget.h"
#include "lib/ivis_common/piepalette.h" // for predefined colours.
#include "lib/ivis_common/rendmode.h" // for boxfill
#include "hci.h"
#include "loadsave.h"
#include "multiplay.h"
#include "game.h"
#include "lib/sound/audio_id.h"
#include "lib/sound/audio.h"
#include "frontend.h"
#include "winmain.h"
#include "display3d.h"
#include "display.h"
#ifndef WIN32
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#endif
#include "lib/netplay/netplay.h"
#include "loop.h"
#include "intdisplay.h"
#include "text.h"
#include "mission.h"
#include "lib/gamelib/gtime.h"
//======================================================================================
//--------------------------------
#define totalslots 20 //saves slots.. was 10 , now 20 *Away with hard coding values!* -Q
#define totalslotspace 64 //guessing 64 max chars for filename.
//--------------------------------
// ////////////////////////////////////////////////////////////////////////////
#define LOADSAVE_X 130 + D_W
#define LOADSAVE_Y 30 + D_H //was 170 -Q
#define LOADSAVE_W 380
#define LOADSAVE_H 240 //was 200 -Q
#define MAX_SAVE_NAME 60
#define LOADSAVE_HGAP 9 //from 5 to 9 -Q
#define LOADSAVE_VGAP 9 //from 5 to 9 -Q
#define LOADSAVE_BANNER_DEPTH 40 //was 25 top banner which displays either load or save
#define LOADENTRY_W (LOADSAVE_W -(3 * LOADSAVE_HGAP)) /2
#define LOADENTRY_H (LOADSAVE_H -(6 * LOADSAVE_VGAP )- (LOADSAVE_BANNER_DEPTH+LOADSAVE_VGAP) ) /5
#define ID_LOADSAVE 21000
#define LOADSAVE_FORM ID_LOADSAVE+1 // back form.
#define LOADSAVE_CANCEL ID_LOADSAVE+2 // cancel but.
#define LOADSAVE_LABEL ID_LOADSAVE+3 // load/save
#define LOADSAVE_BANNER ID_LOADSAVE+4 // banner.
#define LOADENTRY_START ID_LOADSAVE+10 // each of the buttons.
#define LOADENTRY_END ID_LOADSAVE+10 +totalslots // must have unique ID hmm -Q
#define SAVEENTRY_EDIT ID_LOADSAVE+50 // save edit box. must be highest value possible I guess. -Q
// ////////////////////////////////////////////////////////////////////////////
void drawBlueBox (UDWORD x,UDWORD y, UDWORD w, UDWORD h);
BOOL closeLoadSave (void);
BOOL runLoadSave (BOOL bResetMissionWidgets);
BOOL displayLoadSave (void);
static BOOL _addLoadSave (BOOL bLoad, const char *sSearchPath, const char *sExtension, const char *title);
static BOOL _runLoadSave (BOOL bResetMissionWidgets);
static void displayLoadBanner (WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset, UDWORD *pColours);
static void displayLoadSlot (WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset, UDWORD *pColours);
static void displayLoadSaveEdit (WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset, UDWORD *pColours);
void removeWildcards (char *pStr);
static W_SCREEN *psRequestScreen; // Widget screen for requester
static BOOL mode;
static UDWORD chosenSlotId;
BOOL bLoadSaveUp = FALSE; // true when interface is up and should be run.
char saveGameName[256]; //the name of the save game to load from the front end
char sRequestResult[255]; // filename returned;
char sDelete[MAX_STR_LENGTH];
BOOL bRequestLoad = FALSE;
LOADSAVE_MODE bLoadSaveMode;
static char sPath[255];
static char sExt[4];
// ////////////////////////////////////////////////////////////////////////////
// return whether the save screen was displayed in the mission results screen
BOOL saveInMissionRes(void)
{
return bLoadSaveMode == SAVE_MISSIONEND;
}
// ////////////////////////////////////////////////////////////////////////////
// return whether the save screen was displayed in the middle of a mission
BOOL saveMidMission(void)
{
return bLoadSaveMode == SAVE_INGAME;
}
// ////////////////////////////////////////////////////////////////////////////
BOOL addLoadSave(LOADSAVE_MODE mode, const char *sSearchPath, const char *sExtension, const char *title)
{
BOOL bLoad;
bLoadSaveMode = mode;
switch(mode)
{
case LOAD_FRONTEND:
case LOAD_MISSIONEND:
case LOAD_INGAME:
case LOAD_FORCE:
bLoad = TRUE;
break;
case SAVE_MISSIONEND:
case SAVE_INGAME:
case SAVE_FORCE:
default:
bLoad = FALSE;
break;
}
return _addLoadSave(bLoad,sSearchPath,sExtension,title);
}
//****************************************************************************************
// Load menu/save menu?
//*****************************************************************************************
static BOOL _addLoadSave(BOOL bLoad, const char *sSearchPath, const char *sExtension, const char *title)
{
W_FORMINIT sFormInit;
W_BUTINIT sButInit;
W_LABINIT sLabInit;
UDWORD slotCount;
// removed hardcoded values! change with the defines above! -Q
static char sSlots[totalslots][totalslotspace];
char **i, **files;
mode = bLoad;
debug(LOG_WZ, "_addLoadSave(%d, %s, %s, %s)", bLoad, sSearchPath, sExtension, title);
if ((bLoadSaveMode == LOAD_INGAME) || (bLoadSaveMode == SAVE_INGAME))
{
if (!bMultiPlayer || (NetPlay.bComms ==0))
{
gameTimeStop();
if(GetGameMode() == GS_NORMAL)
{
BOOL radOnScreen = radarOnScreen; // Only do this in main game.
bRender3DOnly = TRUE;
radarOnScreen = FALSE;
displayWorld(); // Just display the 3d, no interface
pie_UploadDisplayBuffer(); // Upload the current display back buffer into system memory.
radarOnScreen = radOnScreen;
bRender3DOnly = FALSE;
}
setGamePauseStatus( TRUE );
setGameUpdatePause(TRUE);
setScriptPause(TRUE);
setScrollPause(TRUE);
setConsolePause(TRUE);
}
forceHidePowerBar();
intRemoveReticule();
}
(void) PHYSFS_mkdir(sSearchPath); // just in case
widgCreateScreen(&psRequestScreen); // init the screen.
widgSetTipFont(psRequestScreen,WFont);
/* add a form to place the tabbed form on */
memset(&sFormInit, 0, sizeof(W_FORMINIT));
sFormInit.formID = 0; //this adds the blue background, and the "box" behind the buttons -Q
sFormInit.id = LOADSAVE_FORM;
sFormInit.style = WFORM_PLAIN;
sFormInit.x = (SWORD)(LOADSAVE_X);
sFormInit.y = (SWORD)(LOADSAVE_Y);
sFormInit.width = LOADSAVE_W;
sFormInit.height = (LOADSAVE_H*2)-46; // hmm..the bottom of the box.... -Q
sFormInit.disableChildren = TRUE;
sFormInit.pDisplay = intOpenPlainForm;
widgAddForm(psRequestScreen, &sFormInit);
// Add Banner
sFormInit.formID = LOADSAVE_FORM;
sFormInit.id = LOADSAVE_BANNER;
sFormInit.x = LOADSAVE_HGAP;
sFormInit.y = LOADSAVE_VGAP;
sFormInit.width = LOADSAVE_W-(2*LOADSAVE_HGAP);
sFormInit.height = LOADSAVE_BANNER_DEPTH;
sFormInit.disableChildren = FALSE;
sFormInit.pDisplay = displayLoadBanner;
sFormInit.pUserData = (void *)bLoad;
widgAddForm(psRequestScreen, &sFormInit);
// Add Banner Label
memset(&sLabInit, 0, sizeof(W_LABINIT));
sLabInit.formID = LOADSAVE_BANNER;
sLabInit.id = LOADSAVE_LABEL;
sLabInit.style = WLAB_ALIGNCENTRE;
sLabInit.x = 0;
sLabInit.y = 3;
sLabInit.width = LOADSAVE_W-(2*LOADSAVE_HGAP); //LOADSAVE_W;
sLabInit.height = LOADSAVE_BANNER_DEPTH; //This looks right -Q
sLabInit.pText = title;
sLabInit.FontID = WFont;
widgAddLabel(psRequestScreen, &sLabInit);
// add cancel.
memset(&sButInit, 0, sizeof(W_BUTINIT));
sButInit.formID = LOADSAVE_BANNER;
sButInit.x = 8;
sButInit.y = 8;
sButInit.width = iV_GetImageWidth(IntImages,IMAGE_NRUTER);
sButInit.height = iV_GetImageHeight(IntImages,IMAGE_NRUTER);
sButInit.pUserData = (void*)PACKDWORD_TRI(0,IMAGE_NRUTER , IMAGE_NRUTER);
sButInit.id = LOADSAVE_CANCEL;
sButInit.style = WBUT_PLAIN;
sButInit.pTip = _("Close");
sButInit.FontID = WFont;
sButInit.pDisplay = intDisplayImageHilight;
widgAddButton(psRequestScreen, &sButInit);
// add slots
memset(&sButInit, 0, sizeof(W_BUTINIT));
sButInit.formID = LOADSAVE_FORM;
sButInit.style = WBUT_PLAIN;
sButInit.width = LOADENTRY_W;
sButInit.height = LOADENTRY_H;
sButInit.pDisplay = displayLoadSlot;
sButInit.FontID = WFont;
for(slotCount = 0; slotCount< totalslots; slotCount++)
{
sButInit.id = slotCount+LOADENTRY_START;
if(slotCount<(totalslots/2))
{
sButInit.x = LOADSAVE_HGAP;
sButInit.y = (SWORD)((LOADSAVE_BANNER_DEPTH +(2*LOADSAVE_VGAP)) + (
slotCount*(LOADSAVE_VGAP+LOADENTRY_H)));
}
else
{
sButInit.x = (2*LOADSAVE_HGAP)+LOADENTRY_W;
sButInit.y = (SWORD)((LOADSAVE_BANNER_DEPTH +(2* LOADSAVE_VGAP)) + (
(slotCount-(totalslots/2)) *(LOADSAVE_VGAP+LOADENTRY_H)));
}
widgAddButton(psRequestScreen, &sButInit);
}
// fill slots.
slotCount = 0;
strcpy(sPath,sSearchPath); // setup locals.
strcpy(sExt,sExtension);
debug(LOG_WZ, "_addLoadSave: Searching \"%s\" for savegames", sSearchPath);
// add savegame filenames minus extensions to buttons (up to max 10)
files = PHYSFS_enumerateFiles(sSearchPath);
for (i = files; *i != NULL; i++) {
W_BUTTON *button;
if (!strstr(*i, sExtension)) {
continue;
}
button = (W_BUTTON*)widgGetFromID(psRequestScreen, LOADENTRY_START + slotCount);
debug(LOG_WZ, "_addLoadSave: We found [%s]", *i);
/* Set the tip and add the button */
(*i)[strlen(*i) - 4] = '\0'; // remove .gam extension
strcpy(sSlots[slotCount], *i); //store it!
button->pTip = sSlots[slotCount];
button->pText = sSlots[slotCount];
slotCount++; // goto next but...
if (slotCount == totalslots) {
break; // only show up to 10 entries
}
}
PHYSFS_freeList(files);
bLoadSaveUp = TRUE;
return TRUE;
}
// ////////////////////////////////////////////////////////////////////////////
BOOL closeLoadSave(void)
{
widgDelete(psRequestScreen,LOADSAVE_FORM);
bLoadSaveUp = FALSE;
if ((bLoadSaveMode == LOAD_INGAME) || (bLoadSaveMode == SAVE_INGAME))
{
if (!bMultiPlayer || (NetPlay.bComms == 0))
{
gameTimeStart();
setGamePauseStatus( FALSE );
setGameUpdatePause(FALSE);
setScriptPause(FALSE);
setScrollPause(FALSE);
setConsolePause(FALSE);
}
intAddReticule();
intShowPowerBar();
}
widgReleaseScreen(psRequestScreen);
return TRUE;
}
// ////////////////////////////////////////////////////////////////////////////
BOOL runLoadSave(BOOL bResetMissionWidgets)
{
return _runLoadSave(bResetMissionWidgets);
}
/***************************************************************************
Delete a savegame. saveGameName should be a .gam extension save game
filename reference. We delete this file, any .es file with the same
name, and any files in the directory with the same name.
***************************************************************************/
void deleteSaveGame(char* saveGameName)
{
char **files, **i;
ASSERT( strlen(saveGameName) < MAX_STR_LENGTH,"deleteSaveGame; save game name too long" );
PHYSFS_delete(saveGameName);
saveGameName[strlen(saveGameName)-4] = '\0';// strip extension
strcat(saveGameName,".es"); // remove script data if it exists.
PHYSFS_delete(saveGameName);
saveGameName[strlen(saveGameName)-3] = '\0';// strip extension
// check for a directory and remove that too.
files = PHYSFS_enumerateFiles(saveGameName);
for (i = files; *i != NULL; i++) {
debug(LOG_WZ, "Deleting [%s].", *i);
PHYSFS_delete(*i);
}
PHYSFS_freeList(files);
PHYSFS_delete(saveGameName); // now empty directory
return;
}
// ////////////////////////////////////////////////////////////////////////////
// Returns TRUE if cancel pressed or a valid game slot was selected.
// if when returning TRUE strlen(sRequestResult) != 0 then a valid game
// slot was selected otherwise cancel was selected..
static BOOL _runLoadSave(BOOL bResetMissionWidgets)
{
UDWORD id=0;
W_EDBINIT sEdInit;
char sTemp[MAX_STR_LENGTH];
UDWORD i;
W_CONTEXT context;
id = widgRunScreen(psRequestScreen);
strcpy(sRequestResult,""); // set returned filename to null;
// cancel this operation...
if(id == LOADSAVE_CANCEL || CancelPressed() )
{
goto failure;
}
// clicked a load entry
if( id >= LOADENTRY_START && id <= LOADENTRY_END )
{
if(mode) // Loading, return that entry.
{
if( ((W_BUTTON *)widgGetFromID(psRequestScreen,id))->pText )
{
sprintf(sRequestResult,"%s%s.%s",sPath, ((W_BUTTON *)widgGetFromID(psRequestScreen,id))->pText ,sExt);
}
else
{
goto failure; // clicked on an empty box
}
if( bLoadSaveMode == LOAD_FORCE || bLoadSaveMode ==SAVE_FORCE )
{
goto successforce; // it's a force, dont check the cd.
}
goto success;
}
else // SAVING!add edit box at that position.
{
if( ! widgGetFromID(psRequestScreen,SAVEENTRY_EDIT))
{
// add blank box.
memset(&sEdInit, 0, sizeof(W_EDBINIT));
sEdInit.formID= LOADSAVE_FORM;
sEdInit.id = SAVEENTRY_EDIT;
sEdInit.style = WEDB_PLAIN;
sEdInit.x = widgGetFromID(psRequestScreen,id)->x;
sEdInit.y = widgGetFromID(psRequestScreen,id)->y;
sEdInit.width = widgGetFromID(psRequestScreen,id)->width;
sEdInit.height= widgGetFromID(psRequestScreen,id)->height;
sEdInit.pText = ((W_BUTTON *)widgGetFromID(psRequestScreen,id))->pText;
sEdInit.FontID= WFont;
sEdInit.pBoxDisplay = displayLoadSaveEdit;
widgAddEditBox(psRequestScreen, &sEdInit);
sprintf(sTemp,"%s%s.%s",
sPath,
((W_BUTTON *)widgGetFromID(psRequestScreen,id))->pText ,
sExt);
widgHide(psRequestScreen,id); // hide the old button
chosenSlotId = id;
strcpy(sDelete,sTemp); // prepare the savegame name.
sTemp[strlen(sTemp)-4] = '\0'; // strip extension
// auto click in the edit box we just made.
context.psScreen = psRequestScreen;
context.psForm = (W_FORM *)psRequestScreen->psForm;
context.xOffset = 0;
context.yOffset = 0;
context.mx = mouseX();
context.my = mouseY();
editBoxClicked((W_EDITBOX*)widgGetFromID(psRequestScreen,SAVEENTRY_EDIT), &context);
}
else
{
// clicked in a different box. shouldnt be possible!(since we autoclicked in editbox)
}
}
}
// finished entering a name.
if( id == SAVEENTRY_EDIT)
{
if(!keyPressed(KEY_RETURN)) // enter was not pushed, so not a vaild entry.
{
widgDelete(psRequestScreen,SAVEENTRY_EDIT); //unselect this box, and go back ..
widgReveal(psRequestScreen,chosenSlotId);
return TRUE;
}
// scan to see if that game exists in another slot, if
// so then fail.
strcpy(sTemp,((W_EDITBOX *)widgGetFromID(psRequestScreen,id))->aText);
for(i=LOADENTRY_START;i<LOADENTRY_END;i++)
{
if( i != chosenSlotId)
{
if( ((W_BUTTON *)widgGetFromID(psRequestScreen,i))->pText
&& strcmp( sTemp, ((W_BUTTON *)widgGetFromID(psRequestScreen,i))->pText ) ==0)
{
widgDelete(psRequestScreen,SAVEENTRY_EDIT); //unselect this box, and go back ..
widgReveal(psRequestScreen,chosenSlotId);
// move mouse to same box..
// SetMousePos(widgGetFromID(psRequestScreen,i)->x ,widgGetFromID(psRequestScreen,i)->y);
audio_PlayTrack(ID_SOUND_BUILD_FAIL);
return TRUE;
}
}
}
// return with this name, as we've edited it.
if (strlen(((W_EDITBOX *)widgGetFromID(psRequestScreen,id))->aText))
{
strcpy(sTemp,((W_EDITBOX *)widgGetFromID(psRequestScreen,id))->aText);
removeWildcards(sTemp);
sprintf(sRequestResult,"%s%s.%s",
sPath,
sTemp,
sExt);
deleteSaveGame(sDelete); //only delete game if a new game fills the slot
}
else
{
goto failure; // we entered a blank name..
}
// we're done. saving.
closeLoadSave();
bRequestLoad = FALSE;
if (bResetMissionWidgets && widgGetFromID(psWScreen,IDMISSIONRES_FORM) == NULL)
{
resetMissionWidgets(); //reset the mission widgets here if necessary
}
return TRUE;
}
return FALSE;
// failed and/or cancelled..
failure:
closeLoadSave();
bRequestLoad = FALSE;
if (bResetMissionWidgets && widgGetFromID(psWScreen,IDMISSIONRES_FORM) == NULL)
{
resetMissionWidgets();
}
return TRUE;
// success on load.
success:
setCampaignNumber( getCampaign(sRequestResult) );
successforce:
closeLoadSave();
bRequestLoad = TRUE;
return TRUE;
}
// ////////////////////////////////////////////////////////////////////////////
// should be done when drawing the other widgets.
BOOL displayLoadSave(void)
{
widgDisplayScreen(psRequestScreen); // display widgets.
return TRUE;
}
// ////////////////////////////////////////////////////////////////////////////
// char HANDLER, replaces dos wildcards in a string with harmless chars.
void removeWildcards(char *pStr)
{
UDWORD i;
for(i=0;i<strlen(pStr);i++)
{
/* if( pStr[i] == '?'
|| pStr[i] == '*'
|| pStr[i] == '"'
|| pStr[i] == '.'
|| pStr[i] == '/'
|| pStr[i] == '\\'
|| pStr[i] == '|' )
{
pStr[i] = '_';
}
*/
if( !isalnum(pStr[i])
&& pStr[i] != ' '
&& pStr[i] != '-'
&& pStr[i] != '+'
&& pStr[i] != '!'
)
{
pStr[i] = '_';
}
}
if (strlen(pStr) >= MAX_SAVE_NAME)
{
pStr[MAX_SAVE_NAME - 1] = 0;
}
return;
}
// ////////////////////////////////////////////////////////////////////////////
// ////////////////////////////////////////////////////////////////////////////
// ////////////////////////////////////////////////////////////////////////////
// DISPLAY FUNCTIONS
static void displayLoadBanner(WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset, UDWORD *pColours)
{
//UDWORD col;
UBYTE col;
UDWORD x = xOffset+psWidget->x;
UDWORD y = yOffset+psWidget->y;
if(psWidget->pUserData)
{
col = COL_GREEN;
}
else
{
col = COL_RED;
}
iV_BoxFill(x,y,x+psWidget->width,y+psWidget->height,col);
iV_BoxFill(x+2,y+2,x+psWidget->width-2,y+psWidget->height-2,COL_BLUE);
}
// ////////////////////////////////////////////////////////////////////////////
static void displayLoadSlot(WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset, UDWORD *pColours)
{
UDWORD x = xOffset+psWidget->x;
UDWORD y = yOffset+psWidget->y;
// UWORD im = (UWORD)UNPACKDWORD_TRI_B((UDWORD)psWidget->pUserData);
// UWORD im2= (UWORD)(UNPACKDWORD_TRI_C((UDWORD)psWidget->pUserData));
char butString[64];
drawBlueBox(x,y,psWidget->width,psWidget->height); //draw box
if(((W_BUTTON *)psWidget)->pTip )
{
strcpy(butString,((W_BUTTON *)psWidget)->pTip);
iV_SetFont(WFont); // font
iV_SetTextColour(-1); //colour
while(iV_GetTextWidth(butString) > psWidget->width)
{
butString[strlen(butString)-1]='\0';
}
//draw text
iV_DrawText( butString, x+4, y+17);
}
}
// ////////////////////////////////////////////////////////////////////////////
static void displayLoadSaveEdit(WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset, UDWORD *pColours)
{
UDWORD x = xOffset+psWidget->x;
UDWORD y = yOffset+psWidget->y;
UDWORD w = psWidget->width;
UDWORD h = psWidget->height;
iV_BoxFill(x,y,x+w,y+h,COL_RED);
iV_BoxFill(x+1,y+1,x+w-1,y+h-1,COL_BLUE);
}
// ////////////////////////////////////////////////////////////////////////////
void drawBlueBox(UDWORD x,UDWORD y, UDWORD w, UDWORD h)
{
UBYTE dark = COL_BLUE;
UBYTE light = COL_LIGHTBLUE;
// box
pie_BoxFillIndex(x-1,y-1,x+w+1,y+h+1,light);
pie_BoxFillIndex(x,y,x+w,y+h,dark);
}