INT PAL_MKFDecompressChunk( LPBYTE lpBuffer, UINT uiBufferSize, UINT uiChunkNum, FILE *fp ) /*++ Purpose: Decompress a compressed chunk from an MKF archive into lpBuffer. Parameters: [OUT] lpBuffer - pointer to the destination buffer. [IN] uiBufferSize - size of the destination buffer. [IN] uiChunkNum - the number of the chunk in the MKF archive to read. [IN] fp - pointer to the fopen'ed MKF file. Return value: Integer value which indicates the size of the chunk. -1 if there are error in parameters, or buffer size is not enough. -3 if cannot allocate memory for decompression. --*/ { LPBYTE buf; int len; len = PAL_MKFGetChunkSize(uiChunkNum, fp); if (len <= 0) { return len; } buf = (LPBYTE)malloc(len); if (buf == NULL) { return -3; } PAL_MKFReadChunk(buf, len, uiChunkNum, fp); len = Decompress(buf, lpBuffer, uiBufferSize); free(buf); return len; }
INT PAL_InitUI( VOID ) /*++ Purpose: Initialze the UI subsystem. Parameters: None. Return value: 0 = success, -1 = fail. --*/ { int iSize; // // Load the UI sprite. // iSize = PAL_MKFGetChunkSize(CHUNKNUM_SPRITEUI, gpGlobals->f.fpDATA); if (iSize < 0) { return -1; } gpSpriteUI = (LPSPRITE)calloc(1, iSize); if (gpSpriteUI == NULL) { return -1; } PAL_MKFReadChunk(gpSpriteUI, iSize, CHUNKNUM_SPRITEUI, gpGlobals->f.fpDATA); return 0; }
INT PAL_InitText( VOID ) /*++ Purpose: Initialize the in-game texts. Parameters: None. Return value: 0 = success. -1 = memory allocation error. --*/ { FILE *fpMsg, *fpWord; int i; // // Open the message and word data files. // fpMsg = UTIL_OpenRequiredFile("m.msg"); fpWord = UTIL_OpenRequiredFile("word.dat"); // // See how many words we have // fseek(fpWord, 0, SEEK_END); i = ftell(fpWord); // // Each word has 10 bytes // g_TextLib.nWords = (i + (WORD_LENGTH - 1)) / WORD_LENGTH; // // Read the words // g_TextLib.lpWordBuf = (LPBYTE)malloc(i); if (g_TextLib.lpWordBuf == NULL) { fclose(fpWord); fclose(fpMsg); return -1; } fseek(fpWord, 0, SEEK_SET); fread(g_TextLib.lpWordBuf, i, 1, fpWord); // // Close the words file // fclose(fpWord); // // Read the message offsets. The message offsets are in SSS.MKF #3 // i = PAL_MKFGetChunkSize(3, gpGlobals->f.fpSSS) / sizeof(DWORD); g_TextLib.nMsgs = i - 1; g_TextLib.lpMsgOffset = (LPDWORD)malloc(i * sizeof(DWORD)); if (g_TextLib.lpMsgOffset == NULL) { free(g_TextLib.lpWordBuf); fclose(fpMsg); return -1; } PAL_MKFReadChunk((LPBYTE)(g_TextLib.lpMsgOffset), i * sizeof(DWORD), 3, gpGlobals->f.fpSSS); // // Read the messages. // fseek(fpMsg, 0, SEEK_END); i = ftell(fpMsg); g_TextLib.lpMsgBuf = (LPBYTE)malloc(i); if (g_TextLib.lpMsgBuf == NULL) { free(g_TextLib.lpMsgOffset); free(g_TextLib.lpWordBuf); fclose(fpMsg); return -1; } fseek(fpMsg, 0, SEEK_SET); fread(g_TextLib.lpMsgBuf, 1, i, fpMsg); fclose(fpMsg); g_TextLib.bCurrentFontColor = FONT_COLOR_DEFAULT; g_TextLib.bIcon = 0; g_TextLib.posIcon = 0; g_TextLib.nCurrentDialogLine = 0; g_TextLib.iDelayTime = 3; g_TextLib.posDialogTitle = PAL_XY(12, 8); g_TextLib.posDialogText = PAL_XY(44, 26); g_TextLib.bDialogPosition = kDialogUpper; g_TextLib.fUserSkip = FALSE; PAL_MKFReadChunk(g_TextLib.bufDialogIcons, 282, 12, gpGlobals->f.fpDATA); return 0; }
VOID MIDI_Play( INT iNumRIX, BOOL fLoop ) /*++ Purpose: Start playing the specified music in MIDI format. Parameters: [IN] iNumRIX - number of the music. 0 to stop playing current music. [IN] fLoop - Whether the music should be looped or not. Return value: None. --*/ { FILE *fp; unsigned char *buf; int size; SDL_RWops *rw; #ifdef PAL_WIN95 char filename[1024]; #endif if (g_pMid != NULL && iNumRIX == iMidCurrent && native_midi_active()) { return; } SOUND_PlayCDA(-1); native_midi_freesong(g_pMid); g_pMid = NULL; iMidCurrent = -1; if (g_fNoMusic || iNumRIX <= 0) { return; } #ifdef PAL_WIN95 sprintf(filename, "%s/musics/%.3d.mid", PAL_PREFIX, iNumRIX); g_pMid = native_midi_loadsong(filename); if (g_pMid != NULL) { native_midi_start(g_pMid); iMidCurrent = iNumRIX; fMidLoop = fLoop; } #else fp = UTIL_OpenFile("midi.mkf"); if (fp == NULL) { return; } if (iNumRIX > PAL_MKFGetChunkCount(fp)) { fclose(fp); return; } size = PAL_MKFGetChunkSize(iNumRIX, fp); if (size <= 0) { fclose(fp); return; } buf = (unsigned char *)UTIL_malloc(size); PAL_MKFReadChunk((LPBYTE)buf, size, iNumRIX, fp); fclose(fp); rw = SDL_RWFromConstMem((const void *)buf, size); g_pMid = native_midi_loadsong_RW(rw); if (g_pMid != NULL) { native_midi_start(g_pMid); iMidCurrent = iNumRIX; fMidLoop = fLoop; } SDL_RWclose(rw); free(buf); #endif }
/*++ Start a battle. Parameters: [IN] wEnemyTeam - the number of the enemy team. [IN] fIsBoss - TRUE for boss fight (not allowed to flee). Return value: The result of the battle. --*/ BATTLERESULT PAL_StartBattle(WORD wEnemyTeam, BOOL fIsBoss) { int i; WORD w, wPrevWaveLevel; SHORT sPrevWaveProgression; // // Set the screen waving effects // wPrevWaveLevel = gpGlobals->wScreenWave; sPrevWaveProgression = gpGlobals->sWaveProgression; gpGlobals->sWaveProgression = 0; gpGlobals->wScreenWave = gpGlobals->g.lprgBattleField[gpGlobals->wNumBattleField].wScreenWave; // // Make sure everyone in the party is alive, also clear all hidden // EXP count records // for (i = 0; i <= gpGlobals->wMaxPartyMemberIndex; i++) { w = gpGlobals->rgParty[i].wPlayerRole; if (gpGlobals->g.PlayerRoles.rgwHP[w] == 0) { gpGlobals->g.PlayerRoles.rgwHP[w] = 1; gpGlobals->rgPlayerStatus[w][kStatusPuppet] = 0; } gpGlobals->Exp.rgHealthExp[w].wCount = 0; gpGlobals->Exp.rgMagicExp[w].wCount = 0; gpGlobals->Exp.rgAttackExp[w].wCount = 0; gpGlobals->Exp.rgMagicPowerExp[w].wCount = 0; gpGlobals->Exp.rgDefenseExp[w].wCount = 0; gpGlobals->Exp.rgDexterityExp[w].wCount = 0; gpGlobals->Exp.rgFleeExp[w].wCount = 0; } // // Clear all item-using records // for (i = 0; i < MAX_INVENTORY; i++) { gpGlobals->rgInventory[i].nAmountInUse = 0; } // // Store all enemies // for (i = 0; i < MAX_ENEMIES_IN_TEAM; i++) { memset(&(g_Battle.rgEnemy[i]), 0, sizeof(BATTLEENEMY)); w = gpGlobals->g.lprgEnemyTeam[wEnemyTeam].rgwEnemy[i]; if (w == 0xFFFF) { break; } if (w != 0) { g_Battle.rgEnemy[i].e = gpGlobals->g.lprgEnemy[gpGlobals->g.rgObject[w].enemy.wEnemyID]; g_Battle.rgEnemy[i].wObjectID = w; g_Battle.rgEnemy[i].state = kFighterWait; g_Battle.rgEnemy[i].wScriptOnTurnStart = gpGlobals->g.rgObject[w].enemy.wScriptOnTurnStart; g_Battle.rgEnemy[i].wScriptOnBattleEnd = gpGlobals->g.rgObject[w].enemy.wScriptOnBattleEnd; g_Battle.rgEnemy[i].wScriptOnReady = gpGlobals->g.rgObject[w].enemy.wScriptOnReady; g_Battle.rgEnemy[i].iColorShift = 0; g_Battle.rgEnemy[i].dwMaxHealth = g_Battle.rgEnemy[i].e.wHealth; #ifndef PAL_CLASSIC g_Battle.rgEnemy[i].flTimeMeter = 50; // // HACK: Otherwise the black thief lady will be too hard to beat // if (g_Battle.rgEnemy[i].e.wDexterity == 164) { g_Battle.rgEnemy[i].e.wDexterity /= ((gpGlobals->wMaxPartyMemberIndex == 0) ? 6 : 3); } // // HACK: Heal up automatically for final boss // if (g_Battle.rgEnemy[i].e.wHealth == 32760) { for (w = 0; w < MAX_PLAYER_ROLES; w++) { gpGlobals->g.PlayerRoles.rgwHP[w] = gpGlobals->g.PlayerRoles.rgwMaxHP[w]; gpGlobals->g.PlayerRoles.rgwMP[w] = gpGlobals->g.PlayerRoles.rgwMaxMP[w]; } } // // Yet another HACKs // if ((SHORT)g_Battle.rgEnemy[i].e.wDexterity == -32) { g_Battle.rgEnemy[i].e.wDexterity = 0; // for Grandma Knife } else if (g_Battle.rgEnemy[i].e.wDexterity == 20) { // // for Fox Demon // if (gpGlobals->g.PlayerRoles.rgwLevel[0] < 15) { g_Battle.rgEnemy[i].e.wDexterity = 8; } else if (gpGlobals->g.PlayerRoles.rgwLevel[4] > 28 || gpGlobals->Exp.rgPrimaryExp[4].wExp > 0) { g_Battle.rgEnemy[i].e.wDexterity = 60; } } else if (g_Battle.rgEnemy[i].e.wExp == 250 && g_Battle.rgEnemy[i].e.wCash == 1100) { g_Battle.rgEnemy[i].e.wDexterity += 12; // for Snake Demon } else if ((SHORT)g_Battle.rgEnemy[i].e.wDexterity == -60) { g_Battle.rgEnemy[i].e.wDexterity = 15; // for Spider } else if ((SHORT)g_Battle.rgEnemy[i].e.wDexterity == -30) { g_Battle.rgEnemy[i].e.wDexterity = (WORD)-10; // for Stone Head } else if ((SHORT)g_Battle.rgEnemy[i].e.wDexterity == -16) { g_Battle.rgEnemy[i].e.wDexterity = 0; // for Zombie } else if ((SHORT)g_Battle.rgEnemy[i].e.wDexterity == -20) { g_Battle.rgEnemy[i].e.wDexterity = -8; // for Flower Demon } else if (g_Battle.rgEnemy[i].e.wLevel < 20 && gpGlobals->wNumScene >= 0xD8 && gpGlobals->wNumScene <= 0xE2) { // // for low-level monsters in the Cave of Trial // g_Battle.rgEnemy[i].e.wLevel += 15; g_Battle.rgEnemy[i].e.wDexterity += 25; } else if (gpGlobals->wNumScene == 0x90) { g_Battle.rgEnemy[i].e.wDexterity += 25; // for Tower Dragons } else if (g_Battle.rgEnemy[i].e.wLevel == 2 && g_Battle.rgEnemy[i].e.wCash == 48) { g_Battle.rgEnemy[i].e.wDexterity += 8; // for Miao Fists } else if (g_Battle.rgEnemy[i].e.wLevel == 4 && g_Battle.rgEnemy[i].e.wCash == 240) { g_Battle.rgEnemy[i].e.wDexterity += 18; // for Fat Miao } else if (g_Battle.rgEnemy[i].e.wLevel == 16 && g_Battle.rgEnemy[i].e.wMagicRate == 4 && g_Battle.rgEnemy[i].e.wAttackEquivItemRate == 4) { g_Battle.rgEnemy[i].e.wDexterity += 50; // for Black Spider } #endif } } g_Battle.wMaxEnemyIndex = i - 1; // // Store all players // for (i = 0; i <= gpGlobals->wMaxPartyMemberIndex; i++) { g_Battle.rgPlayer[i].flTimeMeter = 15.0f; #ifndef PAL_CLASSIC g_Battle.rgPlayer[i].flTimeSpeedModifier = 2.0f; g_Battle.rgPlayer[i].sTurnOrder = -1; #endif g_Battle.rgPlayer[i].wHidingTime = 0; g_Battle.rgPlayer[i].state = kFighterWait; g_Battle.rgPlayer[i].action.sTarget = -1; g_Battle.rgPlayer[i].fDefending = FALSE; g_Battle.rgPlayer[i].wCurrentFrame = 0; g_Battle.rgPlayer[i].iColorShift = FALSE; } // // Load sprites and background // PAL_LoadBattleSprites(); PAL_LoadBattleBackground(); // // Create the surface for scene buffer // g_Battle.lpSceneBuf = SDL_CreateRGBSurface(gpScreen->flags & ~SDL_HWSURFACE, 320, 200, 8, gpScreen->format->Rmask, gpScreen->format->Gmask, gpScreen->format->Bmask, gpScreen->format->Amask); if (g_Battle.lpSceneBuf == NULL) { TerminateOnError("PAL_StartBattle(): creating surface for scene buffer failed!"); } #if SDL_VERSION_ATLEAST(2, 0, 0) SDL_SetSurfacePalette(g_Battle.lpSceneBuf, gpScreen->format->palette); #else SDL_SetPalette(g_Battle.lpSceneBuf, SDL_PHYSPAL | SDL_LOGPAL, VIDEO_GetPalette(), 0, 256); #endif PAL_UpdateEquipments(); g_Battle.iExpGained = 0; g_Battle.iCashGained = 0; g_Battle.fIsBoss = fIsBoss; g_Battle.fEnemyCleared = FALSE; g_Battle.fEnemyMoving = FALSE; g_Battle.iHidingTime = 0; g_Battle.wMovingPlayerIndex = 0; g_Battle.UI.szMsg[0] = '\0'; g_Battle.UI.szNextMsg[0] = '\0'; g_Battle.UI.dwMsgShowTime = 0; g_Battle.UI.state = kBattleUIWait; g_Battle.UI.fAutoAttack = FALSE; g_Battle.UI.wSelectedIndex = 0; g_Battle.UI.wPrevEnemyTarget = 0; memset(g_Battle.UI.rgShowNum, 0, sizeof(g_Battle.UI.rgShowNum)); g_Battle.lpSummonSprite = NULL; g_Battle.sBackgroundColorShift = 0; gpGlobals->fInBattle = TRUE; g_Battle.BattleResult = kBattleResultPreBattle; PAL_BattleUpdateFighters(); // // Load the battle effect sprite. // i = PAL_MKFGetChunkSize(10, gpGlobals->f.fpDATA); g_Battle.lpEffectSprite = UTIL_malloc(i); PAL_MKFReadChunk(g_Battle.lpEffectSprite, i, 10, gpGlobals->f.fpDATA); #ifdef PAL_CLASSIC g_Battle.Phase = kBattlePhaseSelectAction; g_Battle.fRepeat = FALSE; g_Battle.fForce = FALSE; g_Battle.fFlee = FALSE; #endif #ifdef PAL_ALLOW_KEYREPEAT SDL_EnableKeyRepeat(120, 75); #endif // // Run the main battle routine. // i = PAL_BattleMain(); #ifdef PAL_ALLOW_KEYREPEAT SDL_EnableKeyRepeat(0, 0); PAL_ClearKeyState(); g_InputState.prevdir = kDirUnknown; #endif if (i == kBattleResultWon) { // // Player won the battle. Add the Experience points. // PAL_BattleWon(); } // // Clear all item-using records // for (w = 0; w < MAX_INVENTORY; w++) { gpGlobals->rgInventory[w].nAmountInUse = 0; } // // Clear all player status, poisons and temporary effects // PAL_ClearAllPlayerStatus(); for (w = 0; w < MAX_PLAYER_ROLES; w++) { PAL_CurePoisonByLevel(w, 3); PAL_RemoveEquipmentEffect(w, kBodyPartExtra); } // // Free all the battle sprites // PAL_FreeBattleSprites(); free(g_Battle.lpEffectSprite); // // Free the surfaces for the background picture and scene buffer // SDL_FreeSurface(g_Battle.lpBackground); SDL_FreeSurface(g_Battle.lpSceneBuf); g_Battle.lpBackground = NULL; g_Battle.lpSceneBuf = NULL; gpGlobals->fInBattle = FALSE; PAL_PlayMUS(gpGlobals->wNumMusic, TRUE, 1); // // Restore the screen waving effects // gpGlobals->sWaveProgression = sPrevWaveProgression; gpGlobals->wScreenWave = wPrevWaveLevel; return i; }
VOID PAL_SaveGame( LPCSTR szFileName, WORD wSavedTimes ) /*++ Purpose: Save the current game state to file. Parameters: [IN] szFileName - file name of saved game. Return value: None. --*/ { FILE *fp; static SAVEDGAME s; UINT32 i; // // Put all the data to the saved game struct. // s.wViewportX = PAL_X(gpGlobals->viewport); s.wViewportY = PAL_Y(gpGlobals->viewport); s.nPartyMember = gpGlobals->wMaxPartyMemberIndex; s.wNumScene = gpGlobals->wNumScene; s.wPaletteOffset = (gpGlobals->fNightPalette ? 0x180 : 0); s.wPartyDirection = gpGlobals->wPartyDirection; s.wNumMusic = gpGlobals->wNumMusic; s.wNumBattleMusic = gpGlobals->wNumBattleMusic; s.wNumBattleField = gpGlobals->wNumBattleField; s.wScreenWave = gpGlobals->wScreenWave; s.wCollectValue = gpGlobals->wCollectValue; s.wLayer = gpGlobals->wLayer; s.wChaseRange = gpGlobals->wChaseRange; s.wChasespeedChangeCycles = gpGlobals->wChasespeedChangeCycles; s.nFollower = gpGlobals->nFollower; s.dwCash = gpGlobals->dwCash; #ifndef PAL_CLASSIC s.wBattleSpeed = gpGlobals->bBattleSpeed; #else s.wBattleSpeed = 2; #endif memcpy(s.rgParty, gpGlobals->rgParty, sizeof(gpGlobals->rgParty)); memcpy(s.rgTrail, gpGlobals->rgTrail, sizeof(gpGlobals->rgTrail)); s.Exp = gpGlobals->Exp; s.PlayerRoles = gpGlobals->g.PlayerRoles; memcpy(s.rgPoisonStatus, gpGlobals->rgPoisonStatus, sizeof(gpGlobals->rgPoisonStatus)); memcpy(s.rgInventory, gpGlobals->rgInventory, sizeof(gpGlobals->rgInventory)); memcpy(s.rgScene, gpGlobals->g.rgScene, sizeof(gpGlobals->g.rgScene)); memcpy(s.rgObject, gpGlobals->g.rgObject, sizeof(gpGlobals->g.rgObject)); memcpy(s.rgEventObject, gpGlobals->g.lprgEventObject, sizeof(EVENTOBJECT) * gpGlobals->g.nEventObject); s.wSavedTimes = wSavedTimes; // // Adjust endianness // DO_BYTESWAP(&s, sizeof(SAVEDGAME)); // // Cash amount is in DWORD, so do a wordswap in Big-Endian. // #if SDL_BYTEORDER == SDL_BIG_ENDIAN s.dwCash = ((s.dwCash >> 16) | (s.dwCash << 16)); #endif // // Try writing to file // fp = fopen(szFileName, "wb"); if (fp == NULL) { return; } i = PAL_MKFGetChunkSize(0, gpGlobals->f.fpSSS); i += sizeof(SAVEDGAME) - sizeof(EVENTOBJECT) * MAX_EVENT_OBJECTS; fwrite(&s, i, 1, fp); fclose(fp); }
VOID MIDI_Play( INT iNumRIX, BOOL fLoop ) /*++ Purpose: Start playing the specified music in MIDI format. Parameters: [IN] iNumRIX - number of the music. 0 to stop playing current music. [IN] fLoop - Whether the music should be looped or not. Return value: None. --*/ { FILE *fp; unsigned char *buf; int size; SDL_RWops *rw; #ifdef TIMIDITY if (g_pMid != NULL && iNumRIX == iMidCurrent && Timidity_Active()) #else if (g_pMid != NULL && iNumRIX == iMidCurrent && native_midi_active()) #endif { return; } SOUND_PlayCDA(-1); #ifdef TIMIDITY Timidity_FreeSong(g_pMid); #else native_midi_freesong(g_pMid); #endif g_pMid = NULL; iMidCurrent = -1; if (g_fNoMusic || iNumRIX <= 0) { return; } fp = fopen(PAL_PREFIX "midi.mkf", "rb"); if (fp == NULL) { return; } if (iNumRIX > PAL_MKFGetChunkCount(fp)) { fclose(fp); return; } size = PAL_MKFGetChunkSize(iNumRIX, fp); if (size <= 0) { fclose(fp); return; } buf = (unsigned char *)UTIL_malloc(size); PAL_MKFReadChunk((LPBYTE)buf, size, iNumRIX, fp); fclose(fp); rw = SDL_RWFromConstMem((const void *)buf, size); #ifdef TIMIDITY g_pMid = Timidity_LoadSong_RW(rw); #else g_pMid = native_midi_loadsong_RW(rw); #endif if (g_pMid != NULL) { #ifdef TIMIDITY Timidity_Start(g_pMid); #else native_midi_start(g_pMid); #endif iMidCurrent = iNumRIX; fMidLoop = fLoop; } SDL_RWclose(rw); free(buf); }