/* * cfg_level.c - managing level data * * $Id: cfg_level.c,v 1.4 2004/05/14 10:00:33 alfie Exp $ * * Program XBLAST * (C) by Oliver Vogel (e-mail: m.vogel@ndh.net) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2; or (at your option) * any later version * * This program is distributed in the hope that it will be entertaining, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILTY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc. * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "cfg_level.h" #include "atom.h" #include "str_util.h" #include "util.h" #include "random.h" /* * level info structure */ /* * local marcos */ #define MAX_SHUFFLE (2<<24) #define IOB_INFO 0u #define IOB_PLAYER 1u #define IOB_SHRINK 2u #define IOB_SCRAMBLE_DRAW 3u #define IOB_SCRAMBLE_DEL 4u #define IOB_FUNC 5u #define IOB_BOMBS 6u #define IOB_GRAPHICS 7u #define IOB_MAP 8u #define NUM_IOBS 9u #define IOB_NAME 254u #define IOB_INVALID 255u /* * local types */ typedef struct { union { const char *name; /* name to sort by */ int value; /* random number to sort by */ time_t lastplayed; /* last play date to sort by */ } key; XBAtom level; /* level */ } LevelTable; /* lookup table for shuffled levels */ typedef struct { XBAtom level; /* level */ } LevelShuffled; /* * local variables */ static DBRoot *dbLevel = NULL; static LevelTable *sortedTable = NULL; static LevelTable *shuffledTable = NULL; static LevelTable *timeTable = NULL; // Better level randomness static const char *gameModeString = "R23456STDL"; static unsigned gameMode = 0; static XBBool randomOrder = XBFalse; static int levelOrder = 0; static XBBool allLevels = XBTrue; static int indexSorted = 0; static int indexShuffled = 0; static int indexTime = 0; static DBRoot *levelRemote; static DBRoot *levelLocal; /* * additional info for new levels files */ static void InsertLevel (DBSection *section) { /* new levels are automatically selected */ DB_CreateEntryBool (section, atomSelect, XBTrue); /* set new value for level shuffling */ (void) DB_CreateEntryTime(section, atomLastPlayed, 0); } /* InsertLevel */ /* * */ void LoadLevelConfig (void) { #ifdef DEBUG Dbg_StartClock (); #endif /* create and load level database */ dbLevel = DB_Create (DT_Config, atomLevel); assert (dbLevel != NULL); if (DB_LoadDir (dbLevel, GAME_DATADIR"/level", "xal", DT_Level, atomTime, atomInfo, InsertLevel)) { DB_Store (dbLevel); } #ifdef DEBUG Dbg_Out ("init levels: %lu msec\n", Dbg_FinishClock ()); #endif } /* LoadLevelConfig */ /* * */ void SaveLevelConfig (void) { assert (dbLevel != NULL); if (DB_Changed (dbLevel) ) { DB_Store (dbLevel); } } /* SaveLevelConfig */ /* * */ void FinishLevelConfig (void) { DB_Delete (dbLevel); if (NULL != sortedTable) { free (sortedTable); } if (NULL != shuffledTable) { free (shuffledTable); } if (NULL != levelRemote) { DB_Delete (levelRemote); } if (NULL != levelLocal) { DB_Delete (levelLocal); } } /* FinishLevelConfig */ /* * number of levels in database */ int GetNumLevels (void) { return DB_NumSections (dbLevel); } /* GetNumLevels */ /* * get atom for level */ XBAtom GetLevelAtom (int index) { return DB_IndexSection (dbLevel, index); } /* GetLevelAtom */ /* * get string of level */ static const char * GetLevelStringByAtom (XBAtom level, XBAtom atom) { const char *s; const DBSection *section; assert (dbLevel != NULL); section = DB_GetSection (dbLevel, level); if (NULL == section) { return NULL; } if (! DB_GetEntryString (section, atom, &s) ) { return NULL; } return s; } /* GetLevelString */ /* * get name of level */ const char * GetLevelNameByAtom (XBAtom level) { return GetLevelStringByAtom (level, atomName); } /* GetLevelName */ /* * is level selected */ XBBool GetLevelSelected (XBAtom level) { XBBool selected; const DBSection *section; assert (dbLevel != NULL); section = DB_GetSection (dbLevel, level); if (NULL == section) { return XBFalse; } if (! DB_GetEntryBool (section, atomSelect, &selected) ) { return XBTrue; } return selected; } /* GetLevelSelected */ /* * Update Level in Database */ void StoreLevelSelected (XBAtom atom, XBBool select) { DBSection *section; assert (NULL != dbLevel); section = DB_CreateSection (dbLevel, atom); assert (section != NULL); /* we only change data not stored in file */ Dbg_Out("Level select %s = %d\n",GetLevelNameByAtom(atom),select); (void) DB_CreateEntryBool (section, atomSelect, select); } /* StoreLevelSelection */ /* * get shuffle index for level */ static XBBool GetLevelShuffle (XBAtom level, int *pShuffle) { const DBSection *section; /* sanity checks */ assert (pShuffle != NULL); assert (dbLevel != NULL); /* get entry for level */ section = DB_GetSection (dbLevel, level); if (NULL == section) { return XBFalse; } if (! DB_GetEntryInt (section, atomShuffle, pShuffle) ) { return XBFalse; } return XBTrue; } /* GetLevelShuffle */ /* * Update Level in Database */ static void StoreLevelShuffle (XBAtom atom, int shuffle) { DBSection *section; assert (NULL != dbLevel); section = DB_CreateSection (dbLevel, atom); assert (section != NULL); /* we only change data not stored in file */ (void) DB_CreateEntryInt (section, atomShuffle, shuffle); } /* StoreLevelShuffle */ /* * get shuffle index for level */ static XBBool GetLevelTime (XBAtom level, time_t *lpTime) { const DBSection *section; /* sanity checks */ assert (lpTime != NULL); assert (dbLevel != NULL); /* get entry for level */ section = DB_GetSection (dbLevel, level); if (NULL == section) { return XBFalse; } if (! DB_GetEntryTime (section, atomLastPlayed, lpTime) ) { return XBFalse; } return XBTrue; } /* GetLevelTime */ /* * Update Level in Database */ static void StoreLevelTime (XBAtom atom, int lpTime) { DBSection *section; assert (NULL != dbLevel); section = DB_CreateSection (dbLevel, atom); assert (section != NULL); /* we only change data not stored in file */ (void) DB_CreateEntryTime (section, atomLastPlayed, lpTime); } /* StoreLevelTime */ /* * */ unsigned GetLevelGameMode (XBAtom atom) { const DBSection *section; const char *s; int i; unsigned gameMode = 0; if (NULL == (section = DB_GetSection (dbLevel, atom) ) ) { return 0; } if (! DB_GetEntryString (section, atomGameMode, &s) ) { return 0; } for (i = 0; s[i] != 0 && gameModeString[i] != 0; i ++) { if (s[i] == gameModeString[i]) { gameMode |= (1 << i); } } return gameMode; } /* GetLevelGameMode */ /* * set last level for specific level order */ static void SetLevelWasLast (XBAtom level, XBAtom entry, XBBool flag) { DBSection *section; section = DB_CreateSection (dbLevel, level); assert (section != NULL); (void) DB_CreateEntryBool (section, entry, flag); } /* SetWasLastLevel */ /* * get last level for specific level order */ static XBBool GetLevelWasLast (XBAtom level, XBAtom entry) { const DBSection *section; XBBool result; section = DB_GetSection (dbLevel, level); assert (section != NULL); if (! DB_GetEntryBool (section, entry, &result) ) { return XBFalse; } return result; } /* GetWasLastLevel */ /* * load a level from file */ const DBRoot * LoadLevelFile (XBAtom atom) { #ifdef DEBUG Dbg_StartClock (); #endif if (NULL != levelLocal) { DB_Delete (levelLocal); } levelLocal = DB_Create (DT_Level, atom); assert (levelLocal != NULL); if (! DB_Load (levelLocal) ) { DB_Delete (levelLocal); return NULL; } #ifdef DEBUG Dbg_Out ("load level: %lu msec\n", Dbg_FinishClock ()); #endif return levelLocal; } /* LoadLevelFile */ /* * comprare shuffles by key */ static int CompareSorted (const void *a, const void *b) { return strcmp ( ( (LevelTable *) a)->key.name, ( (LevelTable *) b)->key.name); } /* CompareSorted */ /* * comprare shuffles by key */ static int CompareShuffle (const void *a, const void *b) { return ( (LevelTable *) a)->key.value - ( (LevelTable *) b)->key.value; } /* CompareShuffle */ /* * comprare shuffles by time */ static int CompareTime (const void *a, const void *b) { return ( (LevelTable *) a)->key.lastplayed - ( (LevelTable *) b)->key.lastplayed; } /* CompareShuffle */ /* * */ static int GetLastIndexSorted (void) { int i,numLevels; XBAtom atom; numLevels = GetNumLevels (); /* build shuffle table */ if (NULL == sortedTable) { sortedTable = calloc (numLevels, sizeof (LevelTable) ); assert (sortedTable != NULL); } for (i = 0; i < numLevels; i ++) { atom = GetLevelAtom (i); sortedTable[i].level = atom; sortedTable[i].key.name = GetLevelNameByAtom (atom); } qsort (sortedTable, numLevels, sizeof (LevelTable), CompareSorted); /* get last level from database */ for (i = 0; i < numLevels; i ++) { if (GetLevelWasLast (sortedTable[i].level, atomLevelSorted) ) { Dbg_Out ("last level (sorted) = %s\n", GetLevelNameByAtom (sortedTable[i].level) ); return i; } } return numLevels; } /* GetLastLevelIndex */ /* * */ static int GetLastIndexShuffled (void) { int i,j,k, numLevels; Dbg_Out("Shuffling levels for the first time\n"); numLevels = GetNumLevels (); /* build shuffle table */ if (NULL == shuffledTable) { shuffledTable = calloc (numLevels, sizeof (LevelTable) ); assert (shuffledTable != NULL); } j=0; k=0; for (i = 0; i < numLevels; i ++) { shuffledTable[i].level = GetLevelAtom (i); assert (ATOM_INVALID != shuffledTable[i].level); if (!GetLevelShuffle (shuffledTable[i].level, &shuffledTable[i].key.value) ) { shuffledTable[i].key.value = -1; j++; } if(shuffledTable[i].key.value > k) k = shuffledTable[i].key.value++; } for (i = 0; i < numLevels; i ++) { if(shuffledTable[i].key.value < 0) { shuffledTable[i].key.value = -k; k++; } } Dbg_Out("%i new keys for levels\n",j); for(i=0;isetup.randomLevels; levelOrder = cfgGame->setup.levelOrder; allLevels = cfgGame->setup.allLevels; /* game mode selection */ gameMode = 0; /* number of players */ switch (cfgGame->players.num) { #ifdef DEBUG case 1: gameMode |= GM_2_Player; break; #endif case 2: gameMode |= GM_2_Player; break; case 3: gameMode |= GM_3_Player; break; case 4: gameMode |= GM_4_Player; break; case 5: gameMode |= GM_5_Player; break; case 6: gameMode |= GM_6_Player; break; } /* team mode */ gameMode |= GM_Single; /* two players on any one host */ for (i = 0; i+1 < cfgGame->players.num; i ++) { if (cfgGame->players.host[i] == cfgGame->players.host[i+1]) { gameMode |= GM_LR_Players; } } /* now count levels to play */ numSelected = 0; numLevels = GetNumLevels (); for (i = 0; i < numLevels; i ++) { /* get level data */ level = GetLevelAtom (i); assert (ATOM_INVALID != level); /* check game mode */ if (gameMode != (gameMode & GetLevelGameMode (level) ) ) { continue; } /* check selection */ if (! allLevels && ! GetLevelSelected (level)) { continue; } numSelected ++; } Dbg_Out ("%d levels selected\n", numSelected); /* last levels played ... */ indexSorted = GetLastIndexSorted (); indexShuffled = GetLastIndexShuffled (); indexTime = GetLastIndexTime (); ReshuffleLevels (); return (numSelected != 0); } /* InitLevels */ /* * reset sorted level */ static void ResortLevels (void) { indexSorted = 0; } /* ResortLevels */ /* * reset sorted level */ static void ReTimeLevels (void) { indexTime = 0; } /* ReTimeLevels */ /* * recreate shuffle table */ static void ReshuffleLevels (void) { int i,j,k, numLevels; Dbg_Out ("shuffling levels\n"); numLevels = GetNumLevels (); for (i=0; i< numLevels; i++) { shuffledTable[i].key.value=i; } for (i = numLevels-1; i > 0 ; i--) { j=OtherRandomNumber(i+1); k=shuffledTable[i].key.value; shuffledTable[i].key.value=shuffledTable[j].key.value; shuffledTable[j].key.value=k; } for (i=0; i< numLevels; i++) { StoreLevelShuffle(shuffledTable[i].level, shuffledTable[i].key.value); } SaveLevelConfig(); qsort (shuffledTable, numLevels, sizeof (LevelTable), CompareShuffle); indexShuffled = 0; } /* ShuffleLevels */ /* * get next level from table */ static XBAtom GetNextLevelFromTable (const LevelTable *table, int *pIndex, XBAtom entry, void (*pFunc) (void)) { int numLevels; XBAtom atom; assert (pIndex != NULL); assert (pFunc != NULL); numLevels = GetNumLevels (); /* unmark last level */ if (*pIndex < numLevels) { SetLevelWasLast (table[*pIndex].level, entry, XBFalse); } /* find next level to play */ do { *pIndex = *pIndex + 1; if (*pIndex >= numLevels) { (*pFunc) (); *pIndex=0; } assert (table != NULL); /* get level */ atom = table[*pIndex].level; assert (ATOM_INVALID != atom); /* check game mode */ } while ( gameMode != (gameMode & GetLevelGameMode (atom) ) || (! allLevels && ! GetLevelSelected (atom) ) ); /* mark as last level */ SetLevelWasLast (atom, entry, XBTrue); StoreLevelTime(timeTable[*pIndex].level, time(NULL)); /* that's all */ return atom; } /* GetNextLevelFromTable */ static XBAtom GetNextLevelFromTable2 (LevelTable *table, int *pIndex, XBAtom entry, void (*pFunc) (void)) { int numLevels; int i,ll; time_t t0,t1; XBAtom atom; assert (pIndex != NULL); assert (pFunc != NULL); numLevels = GetNumLevels (); /* find next level to play */ t0=0; t1=0; ll=0; *pIndex=0; do { atom = table[*pIndex].level; assert (ATOM_INVALID != atom); // Dbg_Out("Time of level %s = %s]\n",GetLevelNameByAtom(timeTable[*pIndex].level),DateString(table[*pIndex].key.lastplayed)); if( gameMode == (gameMode & GetLevelGameMode (atom) ) && ( allLevels || GetLevelSelected (atom) ) ) { if(ll==0) { t0=table[*pIndex].key.lastplayed; t1=t0; ll++; } else { ll++; if(table[*pIndex].key.lastplayed < t0) t0 = table[*pIndex].key.lastplayed; if(table[*pIndex].key.lastplayed > t1) t1 = table[*pIndex].key.lastplayed; } } *pIndex = *pIndex + 1; } while(*pIndex < numLevels); Dbg_Out("There are %i selected levels between [%s",ll,DateString(t0)); Dbg_Out(", %s]\n",DateString(t1)); assert (ll > 0); t1=t0+(t1-t0)/10; Dbg_Out("Finding those between [%s",DateString(t0)); Dbg_Out(", %s]\n",DateString(t1)); ll=0; *pIndex=0; do { atom = table[*pIndex].level; assert (ATOM_INVALID != atom); if( gameMode == (gameMode & GetLevelGameMode (atom) ) && ( allLevels || GetLevelSelected (atom) ) ) { if(table[*pIndex].key.lastplayed <= t1) ll++; } *pIndex = *pIndex + 1; } while(*pIndex < numLevels); Dbg_Out("There were %i levels found between [%s",ll,DateString(t0)); Dbg_Out(", %s]\n",DateString(t1)); assert (ll > 0); i=GameRandomNumber(ll)+1; Dbg_Out("The %ith Level was chosen\n",i); *pIndex=0; ll=1; do { atom = table[*pIndex].level; assert (ATOM_INVALID != atom); if( gameMode == (gameMode & GetLevelGameMode (atom) ) && ( allLevels || GetLevelSelected (atom) ) ) { if(table[*pIndex].key.lastplayed <= t1) {; if(i==ll) break; ll++; } } *pIndex = *pIndex + 1; } while(*pIndex < numLevels); t0=table[*pIndex].key.lastplayed; Dbg_Out("Lastplayed time of level %s to %s\n",GetLevelNameByAtom(timeTable[*pIndex].level),DateString(t0)); t0=time(NULL); table[*pIndex].key.lastplayed=t0; Dbg_Out("Set lastplayed time of level %s to %s\n",GetLevelNameByAtom(timeTable[*pIndex].level),DateString(t0)); StoreLevelTime(timeTable[*pIndex].level, timeTable[*pIndex].key.lastplayed); DB_Store(dbLevel); /* that's all */ return atom; } /* GetNextLevelFromTable */ /* * get next level to plays */ XBAtom GetNextLevel (void) { /* Old way Dbg_Out ("get next level %s\n", randomOrder ? "shuffled" : "sorted"); if (randomOrder) { return GetNextLevelFromTable (shuffledTable, &indexShuffled, atomLevelShuffled, ReshuffleLevels); } else { return GetNextLevelFromTable (sortedTable, &indexSorted, atomLevelSorted, ResortLevels); }*/ // New way if (levelOrder==2) { // LRF Dbg_Out ("get next level: shuffled\n"); return GetNextLevelFromTable (shuffledTable, &indexShuffled, atomLevelShuffled, ReshuffleLevels); } else if (levelOrder==1) { Dbg_Out ("get next level: alfabet\n"); return GetNextLevelFromTable (sortedTable, &indexSorted, atomLevelSorted, ResortLevels); } else { Dbg_Out ("get next level: time based\n"); return GetNextLevelFromTable2 (timeTable, &indexTime, atomLastPlayed, ReTimeLevels); } } /* GetNextLevel */ #if 0 /* * convert level section to iob */ static XBTeleIOB SectionToIOB (XBAtom atom) { if (GUI_CompareAtoms (atom, atomInfo) ) { return IOB_INFO; } else if (GUI_CompareAtoms (atom, atomPlayer) ) { return IOB_PLAYER; } else if (GUI_CompareAtoms (atom, atomShrink) ) { return IOB_SHRINK; } else if (GUI_CompareAtoms (atom, atomScrambleDraw) ) { return IOB_SCRAMBLE_DRAW; } else if (GUI_CompareAtoms (atom, atomScrambleDel) ) { return IOB_SCRAMBLE_DEL; } else if (GUI_CompareAtoms (atom, atomFunc) ) { return IOB_FUNC; } else if (GUI_CompareAtoms (atom, atomBombs) ) { return IOB_BOMBS; } else if (GUI_CompareAtoms (atom, atomGraphics) ) { return IOB_GRAPHICS; } else if (GUI_CompareAtoms (atom, atomMap) ) { return IOB_MAP; } return IOB_INVALID; } /* SectionToIOB */ #endif /* * convert iob to level section */ static XBAtom IOBToSection (XBTeleIOB iob) { switch (iob) { case IOB_INFO: return atomInfo; case IOB_PLAYER: return atomPlayer; case IOB_SHRINK: return atomShrink; case IOB_SCRAMBLE_DRAW: return atomScrambleDraw; case IOB_SCRAMBLE_DEL: return atomScrambleDel; case IOB_FUNC: return atomFunc; case IOB_BOMBS: return atomBombs; case IOB_GRAPHICS: return atomGraphics; case IOB_MAP: return atomMap; default: return ATOM_INVALID; } } /* IOBToSection */ /* * send level data to host */ void SendLevelConfig (XBSndQueue *queue, XBTeleCOT cot, const DBRoot *level) { XBTeleIOB iob; int j; const DBSection *section; XBTelegram *tele; size_t len; char buffer[256]; /* sanity check */ assert (queue != NULL); assert (level != NULL); /* first send level atom (aka filename) */ len = sprintf (buffer, "%s", GUI_AtomToString (DB_Atom (level) ) ); tele = Net_CreateTelegram (cot, XBT_ID_LevelConfig, IOB_NAME, buffer, len + 1); assert (tele != NULL); Net_SendTelegram (queue, tele); /* iterate sections */ for (iob = 0; iob < NUM_IOBS; iob ++) { section = DB_GetSection (level, IOBToSection (iob)); if (NULL != section) { /* iterate entries */ j = 0; while (0 < (len = DB_PrintEntry (buffer, section, j) ) ) { tele = Net_CreateTelegram (cot, XBT_ID_LevelConfig, iob, buffer, len + 1); assert (tele != NULL); Net_SendTelegram (queue, tele); j ++; } } } /* no data means end of section */ tele = Net_CreateTelegram (cot, XBT_ID_LevelConfig, iob, NULL, 0); assert (tele != NULL); Net_SendTelegram (queue, tele); } /* SendLevelConfig */ /* * clear remote level config */ void ClearRemoteLevelConfig (void) { XBTeleIOB iob; if (NULL != levelRemote) { DB_Delete (levelRemote); } levelRemote = DB_Create (DT_Level, atomRemote); for (iob = 0; iob < NUM_IOBS; iob ++) { (void) DB_CreateSection (levelRemote, IOBToSection (iob)); } } /* ClearRemoteLevelConfig */ /* * add new line to remote level config */ void AddToRemoteLevelConfig (unsigned iob, const char *data) { XBAtom atom; DBSection *section; assert (levelRemote != NULL); if (IOB_NAME == iob) { /* level names received */ DB_SetAtom (levelRemote, GUI_StringToAtom (data)); Dbg_Out ("set remote level file to \"%s\"\n", data); } else { /* possible part of level section */ atom = IOBToSection (iob); if (ATOM_INVALID != atom) { /* create new section */ section = DB_CreateSection (levelRemote, atom); assert (NULL != section); /* add line */ (void) DB_ParseEntry (section, data); } } } /* AddToRemoteLevelConfig */ /* * retrieve remote level config */ const DBRoot * GetRemoteLevelConfig (void) { return levelRemote; } /* GetRemoteLevelConfig */ /* * end of file cfg_level.c */