/* -*- mode: c; tab-width: 4; c-basic-offset: 3; c-file-style: "linux" -*- */ // // Copyright (c) 2008, Wei Mingzhi . // All rights reserved. // // Portions based on PALx Project by palxex. // Copyright (c) 2006-2008, Pal Lockheart . // // This file is part of SDLPAL. // // SDLPAL 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 3 of the License, or // (at your option) any later version. // // This program 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 this program. If not, see . // // Modified by Lou Yihua with Unicode support, 2015 // #include "main.h" #define FONT_COLOR_DEFAULT 0x4F #define FONT_COLOR_YELLOW 0x2D #define FONT_COLOR_RED 0x1A #define FONT_COLOR_CYAN 0x8D #define FONT_COLOR_CYAN_ALT 0x8C BOOL g_fUpdatedInBattle = FALSE; #define MESSAGE_MAX_BUFFER_SIZE 512 #define INCLUDE_CODEPAGE_H #include "codepage.h" #ifndef PAL_CLASSIC static const WCHAR* gc_rgszAdditionalWords[CP_MAX][6] = { { L"\x6230\x9B25\x901F\x5EA6", L"\x4E00", L"\x4E8C", L"\x4E09", L"\x56DB", L"\x4E94" }, { L"\x6218\x6597\x901F\x5EA6", L"\x4E00", L"\x4E8C", L"\x4E09", L"\x56DB", L"\x4E94" }, { L"\x6226\x95D8\x901F\x5EA6", L"\x4E00", L"\x4E8C", L"\x4E09", L"\x56DB", L"\x4E94" }, }; static const WCHAR** g_rgszAdditionalWords; #endif typedef struct tagTEXTLIB { LPWSTR *lpWordBuf; LPWSTR *lpMsgBuf; int **lpIndexBuf; int nWords; int nMsgs; int nIndices; int nCurrentDialogLine; BYTE bCurrentFontColor; PAL_POS posIcon; PAL_POS posDialogTitle; PAL_POS posDialogText; BYTE bDialogPosition; BYTE bIcon; int iDelayTime; BOOL fUserSkip; BOOL fPlayingRNG; BYTE bufDialogIcons[282]; } TEXTLIB, *LPTEXTLIB; static TEXTLIB g_TextLib; PAL_FORCE_INLINE int PAL_ParseLine( char *line, char **value, int *length ) { // // Remove the leading spaces // while (*line && iswspace(*line)) line++; // // Skip comments starting with '#' // if (*line && *line != '#') { // // Split the index and value // LPSTR val = strchr(line, '='); if (val) { // // Remove the trailing spaces // LPSTR end = line + strlen(line); int index; if (end > line && end[-1] == '\n') *(--end) = 0; while (end > line && iswspace(end[-1])) *(--end) = 0; // // Parse the index and pass out value // if (sscanf(line, "%d", &index) == 1) { *value = val + 1; *length = end - *value; return index; } } } return 0; } PAL_FORCE_INLINE char * PAL_ReadOneLine( char *temp, int limit, FILE *fp ) { if (fgets(temp, limit, fp)) { int n = strlen(temp); if (n == limit - 1 && temp[n - 1] != '\n' && !feof(fp)) { // Line too long, try to read it as a whole int nn = 2; char *tmp = strdup(temp); while (!feof(fp)) { if (!(tmp = (char *)realloc(tmp, nn * limit))) { TerminateOnError("PAL_ReadOneLine(): failed to allocate memory for long line!"); } if (fgets(tmp + n, limit + 1, fp)) { n += strlen(tmp + n); if (n < limit - 1 || temp[n - 1] == '\n') break; else nn++; } } if (tmp[n - 1] == '\n') tmp[n - 1] = 0; return tmp; } else { if (n > 0 && temp[n - 1] == '\n') temp[n - 1] = 0; return temp; } } else return NULL; } static int PAL_ReadMessageFile( FILE *fp ) { char temp[MESSAGE_MAX_BUFFER_SIZE]; struct _msg_entry { struct _msg_entry *next; wchar_t *value; } *cur_val; struct _msg_list_entry { struct _msg_list_entry *next; struct _msg_entry *value; int index; int count; } *head = NULL, *item; struct _word_list_entry { struct _word_list_entry *next; wchar_t *value; } whead = { NULL, NULL }, *witem = NULL; enum _message_state { ST_OUTSIDE, ST_DIALOG, ST_WORD } state = ST_OUTSIDE; int idx_cnt = 0, msg_cnt = 0, word_cnt = 0, sid, eid = -1; while (!feof(fp)) { char *buffer; if (buffer = PAL_ReadOneLine(temp, MESSAGE_MAX_BUFFER_SIZE, fp)) { switch(state) { case ST_OUTSIDE: // // Skip comments starting with '#' // if (*buffer && *buffer != '#') { if (strncmp(buffer, "[BEGIN MESSAGE]", 15) == 0 && sscanf(buffer + 15, "%d", &sid) == 1 && sid > eid) { state = ST_DIALOG; // // First save values (converted wide string) into a linked list // if (head) { item->next = (struct _msg_list_entry *)malloc(sizeof(struct _msg_list_entry)); item = item->next; } else { head = (struct _msg_list_entry *)malloc(sizeof(struct _msg_list_entry)); item = head; } item->value = NULL; item->index = sid; item->count = 0; cur_val = NULL; if (idx_cnt < item->index) idx_cnt = item->index; } else if (strncmp(buffer, "[BEGIN WORDS]", 13) == 0 && !witem) { state = ST_WORD; // // First save values (converted wide string) into a linked list // witem = &whead; } else { // Just ignore invalid lines #ifdef ENABLE_LOG UTIL_WriteLog(LOG_ERR, "PAL_ReadMessageFile(): encounter invalid line '%s'!\n", line); #endif } } break; case ST_DIALOG: // // Check if to end one dialog // if (strncmp(buffer, "[END MESSAGE]", 13) == 0 && sscanf(buffer + 13, "%d", &eid) == 1 && eid >= sid) { // End dialog state = ST_OUTSIDE; } else { if (cur_val) { cur_val->next = (struct _msg_entry *)malloc(sizeof(struct _msg_entry)); cur_val = cur_val->next; } else cur_val = (struct _msg_entry *)malloc(sizeof(struct _msg_entry)); if (strncmp(buffer, "[CLEAR MESSAGE]", 15) == 0) { cur_val->value = NULL; } else { int len = PAL_MultiByteToWideCharCP(CP_UTF_8, buffer, -1, NULL, 0); cur_val->value = (wchar_t *)malloc(len * sizeof(wchar_t)); PAL_MultiByteToWideCharCP(CP_UTF_8, buffer, -1, cur_val->value, len); msg_cnt++; } if (!item->value) item->value = cur_val; cur_val->next = NULL; item->count++; } break; case ST_WORD: // // Check if to end word list // if (strncmp(buffer, "[END WORDS]", 11) == 0) { // End word list state = ST_OUTSIDE; } else { char *v; int l, i = PAL_ParseLine(buffer, &v, &l); if (i > 0) { int len = PAL_MultiByteToWideCharCP(CP_UTF_8, v, -1, NULL, 0); struct _word_list_entry *val = (struct _word_list_entry *)malloc(sizeof(struct _word_list_entry)); val->value = (wchar_t *)malloc(len * sizeof(wchar_t)); PAL_MultiByteToWideCharCP(CP_UTF_8, v, -1, val->value, len); val->next = NULL; witem->next = val; witem = witem->next; if (word_cnt < i) word_cnt = i; } } break; default: TerminateOnError("PAL_ReadMessageFile(): Reached an unknown state. Something really wrong may have happened!"); break; } if (buffer != temp) free(buffer); } } if (msg_cnt > 0) { // // Move values from linked list to array // int idx_msg = 1; g_TextLib.nIndices = (idx_cnt += 1); g_TextLib.nMsgs = (msg_cnt += 1); g_TextLib.lpIndexBuf = (int **)calloc(idx_cnt, sizeof(int *)); g_TextLib.lpMsgBuf = (LPWSTR *)calloc(msg_cnt, sizeof(LPWSTR)); for (item = head; item; ) { struct _msg_list_entry *temp = item->next; struct _msg_entry *msg = item->value; int index = 0; g_TextLib.lpIndexBuf[item->index] = (int *)calloc(item->count + 1, sizeof(int)); while (msg) { struct _msg_entry *tmp = msg->next; if (msg->value) { g_TextLib.lpIndexBuf[item->index][index++] = idx_msg; g_TextLib.lpMsgBuf[idx_msg++] = msg->value; } else g_TextLib.lpIndexBuf[item->index][index++] = 0; free(msg); msg = tmp; } g_TextLib.lpIndexBuf[item->index][item->count] = -1; free(item); item = temp; } } if (word_cnt > 0) { // // Move values from linked list to array // int index = 1; g_TextLib.nWords = (word_cnt += 1); g_TextLib.lpWordBuf = (LPWSTR *)calloc(word_cnt, sizeof(LPWSTR)); for (witem = whead.next; witem; ) { struct _word_list_entry *temp = witem->next; g_TextLib.lpWordBuf[index++] = witem->value; free(witem); witem = temp; } } fclose(fp); return (msg_cnt > 0 && word_cnt > 0) ? 1 : 0; } INT PAL_InitText( VOID ) /*++ Purpose: Initialize the in-game texts. Parameters: None. Return value: 0 = success. -1 = memory allocation error. --*/ { if (gpGlobals->pszMsgName) { // // Open the message, index and word data files. // FILE *fp = UTIL_OpenRequiredFileForMode(gpGlobals->pszMsgName, "r"); // // Read the contents of the message, index and word data files. // if (!PAL_ReadMessageFile(fp)) { return -1; } else { DWORD dwWordLength = 0; int i; for (i = 1; i < g_TextLib.nWords; i++) { LPWSTR ptr = g_TextLib.lpWordBuf[i]; DWORD n = 0; while (*ptr) n += PAL_CharWidth(*ptr++) >> 3; if (dwWordLength < n) dwWordLength = n; } gpGlobals->dwWordLength = dwWordLength; } } else { FILE *fpMsg, *fpWord; DWORD *offsets; LPWSTR tmp; LPBYTE temp; int wpos, wlen, 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 or 16 bytes // g_TextLib.nWords = (i + (gpGlobals->dwWordLength - 1)) / gpGlobals->dwWordLength; // // Read the words // temp = (LPBYTE)malloc(i); if (temp == NULL) { fclose(fpWord); fclose(fpMsg); return -1; } fseek(fpWord, 0, SEEK_SET); fread(temp, i, 1, fpWord); // // Close the words file // fclose(fpWord); // Split the words and do code page conversion for (i = 0, wlen = 0; i < g_TextLib.nWords; i++) { int base = i * gpGlobals->dwWordLength; int pos = base + gpGlobals->dwWordLength - 1; while (pos >= base && temp[pos] == ' ') temp[pos--] = 0; wlen += PAL_MultiByteToWideChar((LPCSTR)temp + base, gpGlobals->dwWordLength, NULL, 0) + 1; } g_TextLib.lpWordBuf = (LPWSTR*)malloc(g_TextLib.nWords * sizeof(LPWSTR)); tmp = (LPWSTR)malloc(wlen * sizeof(WCHAR)); for (i = 0, wpos = 0; i < g_TextLib.nWords; i++) { int l; g_TextLib.lpWordBuf[i] = tmp + wpos; l = PAL_MultiByteToWideChar((LPCSTR)temp + i * gpGlobals->dwWordLength, gpGlobals->dwWordLength, g_TextLib.lpWordBuf[i], wlen - wpos); if (l > 0 && g_TextLib.lpWordBuf[i][l - 1] == '1') g_TextLib.lpWordBuf[i][l - 1] = 0; g_TextLib.lpWordBuf[i][l] = 0; wpos += l + 1; } free(temp); // // 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; offsets = (LPDWORD)malloc(i * sizeof(DWORD)); if (offsets == NULL) { free(g_TextLib.lpWordBuf); fclose(fpMsg); return -1; } PAL_MKFReadChunk((LPBYTE)(offsets), i * sizeof(DWORD), 3, gpGlobals->f.fpSSS); // // Read the messages. // fseek(fpMsg, 0, SEEK_END); i = ftell(fpMsg); temp = (LPBYTE)malloc(i); if (temp == NULL) { free(offsets); free(g_TextLib.lpWordBuf[0]); free(g_TextLib.lpWordBuf); fclose(fpMsg); return -1; } fseek(fpMsg, 0, SEEK_SET); fread(temp, 1, i, fpMsg); fclose(fpMsg); // Split messages and do code page conversion here for (i = 0, wlen = 0; i < g_TextLib.nMsgs; i++) { wlen += PAL_MultiByteToWideChar((LPCSTR)temp + SDL_SwapLE32(offsets[i]), SDL_SwapLE32(offsets[i + 1]) - SDL_SwapLE32(offsets[i]), NULL, 0) + 1; } g_TextLib.lpMsgBuf = (LPWSTR*)malloc(g_TextLib.nMsgs * sizeof(LPWSTR)); tmp = (LPWSTR)malloc(wlen * sizeof(WCHAR)); for (i = 0, wpos = 0; i < g_TextLib.nMsgs; i++) { int l; g_TextLib.lpMsgBuf[i] = tmp + wpos; l = PAL_MultiByteToWideChar((LPCSTR)temp + SDL_SwapLE32(offsets[i]), SDL_SwapLE32(offsets[i + 1]) - SDL_SwapLE32(offsets[i]), g_TextLib.lpMsgBuf[i], wlen - wpos); g_TextLib.lpMsgBuf[i][l] = 0; wpos += l + 1; } free(temp); free(offsets); g_TextLib.lpIndexBuf = NULL; } #ifndef PAL_CLASSIC g_rgszAdditionalWords = gc_rgszAdditionalWords[gpGlobals->iCodePage]; #endif 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 PAL_FreeText( VOID ) /*++ Purpose: Free the memory used by the texts. Parameters: None. Return value: None. --*/ { int i; if (g_TextLib.lpMsgBuf != NULL) { if (gpGlobals->pszMsgName) for(i = 0; i < g_TextLib.nMsgs; i++) free(g_TextLib.lpMsgBuf[i]); else free(g_TextLib.lpMsgBuf[0]); free(g_TextLib.lpMsgBuf); g_TextLib.lpMsgBuf = NULL; } if (g_TextLib.lpWordBuf != NULL) { if (gpGlobals->pszMsgName) for(i = 0; i < g_TextLib.nWords; i++) free(g_TextLib.lpWordBuf[i]); else free(g_TextLib.lpWordBuf[0]); free(g_TextLib.lpWordBuf); g_TextLib.lpWordBuf = NULL; } if (g_TextLib.lpIndexBuf != NULL) { if (gpGlobals->pszMsgName) for(i = 0; i < g_TextLib.nIndices; i++) free(g_TextLib.lpIndexBuf[i]); else free(g_TextLib.lpIndexBuf[0]); free(g_TextLib.lpIndexBuf); g_TextLib.lpIndexBuf = NULL; } } LPCWSTR PAL_GetWord( int iNumWord ) /*++ Purpose: Get the specified word. Parameters: [IN] wNumWord - the number of the requested word. Return value: Pointer to the requested word. NULL if not found. --*/ { #ifndef PAL_CLASSIC if (wNumWord >= PAL_ADDITIONAL_WORD_FIRST) { return g_rgszAdditionalWords[wNumWord - PAL_ADDITIONAL_WORD_FIRST]; } #endif return (iNumWord >= g_TextLib.nWords || !g_TextLib.lpWordBuf[iNumWord]) ? L"" : g_TextLib.lpWordBuf[iNumWord]; } LPCWSTR PAL_GetMsg( int iNumMsg ) /*++ Purpose: Get the specified message. Parameters: [IN] wNumMsg - the number of the requested message. Return value: Pointer to the requested message. NULL if not found. --*/ { return (iNumMsg >= g_TextLib.nMsgs || !g_TextLib.lpMsgBuf[iNumMsg]) ? L"" : g_TextLib.lpMsgBuf[iNumMsg]; } int PAL_GetMsgNum( int iIndex, int iOrder ) /*++ Purpose: Get the number of specified message from index & order. Parameters: [IN] iMsgIndex - index. [IN] iOrder - order inside the index. Return value: The number of message. Zero means pausing for key, and -1 means end. --*/ { return (iIndex >= g_TextLib.nMsgs || !g_TextLib.lpIndexBuf[iIndex]) ? -1 : g_TextLib.lpIndexBuf[iIndex][iOrder]; } VOID PAL_DrawText( LPCWSTR lpszText, PAL_POS pos, BYTE bColor, BOOL fShadow, BOOL fUpdate ) /*++ Purpose: Draw text on the screen. Parameters: [IN] lpszText - the text to be drawn. [IN] pos - Position of the text. [IN] bColor - Color of the text. [IN] fShadow - TRUE if the text is shadowed or not. [IN] fUpdate - TRUE if update the screen area. Return value: None. --*/ { SDL_Rect rect, urect; rect.x = PAL_X(pos); rect.y = PAL_Y(pos); urect.x = rect.x; urect.y = rect.y; urect.h = gpGlobals->fUseEmbeddedFonts ? 16 : 17; urect.w = 0; while (*lpszText) { // // Draw the character // int char_width = PAL_CharWidth(*lpszText); if (fShadow) { PAL_DrawCharOnSurface(*lpszText, gpScreen, PAL_XY(rect.x + 1, rect.y + 1), 0); PAL_DrawCharOnSurface(*lpszText, gpScreen, PAL_XY(rect.x + 1, rect.y), 0); } PAL_DrawCharOnSurface(*lpszText, gpScreen, PAL_XY(rect.x, rect.y), bColor); lpszText++; rect.x += char_width; urect.w += char_width; } // // Update the screen area // if (fUpdate && urect.w > 0) { if (gpGlobals->fIsWIN95) { urect.w++; if (urect.x + urect.w > 320) { urect.w = 320 - urect.x; } } VIDEO_UpdateScreen(&urect); } } VOID PAL_DialogSetDelayTime( INT iDelayTime ) /*++ Purpose: Set the delay time for dialog. Parameters: [IN] iDelayTime - the delay time to be set. Return value: None. --*/ { g_TextLib.iDelayTime = iDelayTime; } VOID PAL_StartDialog( BYTE bDialogLocation, BYTE bFontColor, INT iNumCharFace, BOOL fPlayingRNG ) /*++ Purpose: Start a new dialog. Parameters: [IN] bDialogLocation - the location of the text on the screen. [IN] bFontColor - the font color of the text. [IN] iNumCharFace - number of the character face in RGM.MKF. [IN] fPlayingRNG - whether we are playing a RNG video or not. Return value: None. --*/ { PAL_LARGE BYTE buf[16384]; SDL_Rect rect; if (gpGlobals->fInBattle && !g_fUpdatedInBattle) { // // Update the screen in battle, or the graphics may seem messed up // VIDEO_UpdateScreen(NULL); g_fUpdatedInBattle = TRUE; } g_TextLib.bIcon = 0; g_TextLib.posIcon = 0; g_TextLib.nCurrentDialogLine = 0; g_TextLib.posDialogTitle = PAL_XY(12, 8); g_TextLib.fUserSkip = FALSE; if (bFontColor != 0) { g_TextLib.bCurrentFontColor = bFontColor; } if (fPlayingRNG && iNumCharFace) { VIDEO_BackupScreen(); g_TextLib.fPlayingRNG = TRUE; } switch (bDialogLocation) { case kDialogUpper: if (iNumCharFace > 0) { // // Display the character face at the upper part of the screen // if (PAL_MKFReadChunk(buf, 16384, iNumCharFace, gpGlobals->f.fpRGM) > 0) { rect.w = PAL_RLEGetWidth((LPCBITMAPRLE)buf); rect.h = PAL_RLEGetHeight((LPCBITMAPRLE)buf); rect.x = 48 - rect.w / 2; rect.y = 55 - rect.h / 2; if (rect.x < 0) { rect.x = 0; } if (rect.y < 0) { rect.y = 0; } PAL_RLEBlitToSurface((LPCBITMAPRLE)buf, gpScreen, PAL_XY(rect.x, rect.y)); if (rect.x < 0) { rect.x = 0; } if (rect.y < 0) { rect.y = 0; } VIDEO_UpdateScreen(&rect); } } g_TextLib.posDialogTitle = PAL_XY(iNumCharFace > 0 ? 80 : 12, 8); g_TextLib.posDialogText = PAL_XY(iNumCharFace > 0 ? 96 : 44, 26); break; case kDialogCenter: g_TextLib.posDialogText = PAL_XY(80, 40); break; case kDialogLower: if (iNumCharFace > 0) { // // Display the character face at the lower part of the screen // if (PAL_MKFReadChunk(buf, 16384, iNumCharFace, gpGlobals->f.fpRGM) > 0) { rect.x = 270 - PAL_RLEGetWidth((LPCBITMAPRLE)buf) / 2; rect.y = 144 - PAL_RLEGetHeight((LPCBITMAPRLE)buf) / 2; PAL_RLEBlitToSurface((LPCBITMAPRLE)buf, gpScreen, PAL_XY(rect.x, rect.y)); VIDEO_UpdateScreen(NULL); } } g_TextLib.posDialogTitle = PAL_XY(iNumCharFace > 0 ? 4 : 12, 108); g_TextLib.posDialogText = PAL_XY(iNumCharFace > 0 ? 20 : 44, 126); break; case kDialogCenterWindow: g_TextLib.posDialogText = PAL_XY(160, 40); break; } g_TextLib.bDialogPosition = bDialogLocation; } static VOID PAL_DialogWaitForKey( VOID ) /*++ Purpose: Wait for player to press a key after showing a dialog. Parameters: None. Return value: None. --*/ { PAL_LARGE SDL_Color palette[256]; SDL_Color *pCurrentPalette, t; int i; // // get the current palette // pCurrentPalette = PAL_GetPalette(gpGlobals->wNumPalette, gpGlobals->fNightPalette); memcpy(palette, pCurrentPalette, sizeof(palette)); if (g_TextLib.bDialogPosition != kDialogCenterWindow && g_TextLib.bDialogPosition != kDialogCenter) { // // show the icon // LPCBITMAPRLE p = PAL_SpriteGetFrame(g_TextLib.bufDialogIcons, g_TextLib.bIcon); if (p != NULL) { SDL_Rect rect; rect.x = PAL_X(g_TextLib.posIcon); rect.y = PAL_Y(g_TextLib.posIcon); rect.w = 16; rect.h = 16; PAL_RLEBlitToSurface(p, gpScreen, g_TextLib.posIcon); VIDEO_UpdateScreen(&rect); } } PAL_ClearKeyState(); while (TRUE) { UTIL_Delay(100); if (g_TextLib.bDialogPosition != kDialogCenterWindow && g_TextLib.bDialogPosition != kDialogCenter) { // // palette shift // t = palette[0xF9]; for (i = 0xF9; i < 0xFE; i++) { palette[i] = palette[i + 1]; } palette[0xFE] = t; VIDEO_SetPalette(palette); } if (g_InputState.dwKeyPress != 0) { break; } } if (g_TextLib.bDialogPosition != kDialogCenterWindow && g_TextLib.bDialogPosition != kDialogCenter) { PAL_SetPalette(gpGlobals->wNumPalette, gpGlobals->fNightPalette); } PAL_ClearKeyState(); g_TextLib.fUserSkip = FALSE; } VOID PAL_ShowDialogText( LPCWSTR lpszText ) /*++ Purpose: Show one line of the dialog text. Parameters: [IN] lpszText - the text to be shown. Return value: None. --*/ { SDL_Rect rect; int x, y; PAL_ClearKeyState(); g_TextLib.bIcon = 0; if (gpGlobals->fInBattle && !g_fUpdatedInBattle) { // // Update the screen in battle, or the graphics may seem messed up // VIDEO_UpdateScreen(NULL); g_fUpdatedInBattle = TRUE; } if (g_TextLib.nCurrentDialogLine > 3) { // // The rest dialogs should be shown in the next page. // PAL_DialogWaitForKey(); g_TextLib.nCurrentDialogLine = 0; VIDEO_RestoreScreen(); VIDEO_UpdateScreen(NULL); } x = PAL_X(g_TextLib.posDialogText); y = PAL_Y(g_TextLib.posDialogText) + g_TextLib.nCurrentDialogLine * 18; if (g_TextLib.bDialogPosition == kDialogCenterWindow) { // // The text should be shown in a small window at the center of the screen // #ifndef PAL_CLASSIC if (gpGlobals->fInBattle && g_Battle.BattleResult == kBattleResultOnGoing) { PAL_BattleUIShowText(lpszText, 1400); } else #endif { PAL_POS pos; LPBOX lpBox; int i, w = wcslen(lpszText), len = 0; for (i = 0; i < w; i++) len += PAL_CharWidth(lpszText[i]) >> 3; // // Create the window box // pos = PAL_XY(PAL_X(g_TextLib.posDialogText) - len * 4, PAL_Y(g_TextLib.posDialogText)); lpBox = PAL_CreateSingleLineBox(pos, (len + 1) / 2, TRUE); rect.x = PAL_X(pos); rect.y = PAL_Y(pos); rect.w = 320 - rect.x * 2 + 32; rect.h = 64; // // Show the text on the screen // pos = PAL_XY(PAL_X(pos) + 8 + ((len & 1) << 2), PAL_Y(pos) + 10); PAL_DrawText(lpszText, pos, 0, FALSE, FALSE); VIDEO_UpdateScreen(&rect); PAL_DialogWaitForKey(); // // Delete the box // PAL_DeleteBox(lpBox); VIDEO_UpdateScreen(&rect); PAL_EndDialog(); } } else { int len = wcslen(lpszText); if (g_TextLib.nCurrentDialogLine == 0 && g_TextLib.bDialogPosition != kDialogCenter && (lpszText[len - 1] == 0xff1a || lpszText[len - 1] == 0x2236 || // Special case for Pal WIN95 Simplified Chinese version lpszText[len - 1] == ':') ) { // // name of character // PAL_DrawText(lpszText, g_TextLib.posDialogTitle, FONT_COLOR_CYAN_ALT, TRUE, TRUE); } else { // // normal texts // WCHAR text[2]; if (!g_TextLib.fPlayingRNG && g_TextLib.nCurrentDialogLine == 0) { // // Save the screen before we show the first line of dialog // VIDEO_BackupScreen(); } while (lpszText != NULL && *lpszText != '\0') { switch (*lpszText) { case '-': // // Set the font color to Cyan // if (g_TextLib.bCurrentFontColor == FONT_COLOR_CYAN) { g_TextLib.bCurrentFontColor = FONT_COLOR_DEFAULT; } else { g_TextLib.bCurrentFontColor = FONT_COLOR_CYAN; } lpszText++; break; #if 0 /* Not used */ case '\'': // // Set the font color to Red // if (g_TextLib.bCurrentFontColor == FONT_COLOR_RED) { g_TextLib.bCurrentFontColor = FONT_COLOR_DEFAULT; } else { g_TextLib.bCurrentFontColor = FONT_COLOR_RED; } lpszText++; break; #endif case '\"': // // Set the font color to Yellow // if (g_TextLib.bCurrentFontColor == FONT_COLOR_YELLOW) { g_TextLib.bCurrentFontColor = FONT_COLOR_DEFAULT; } else { g_TextLib.bCurrentFontColor = FONT_COLOR_YELLOW; } lpszText++; break; case '$': // // Set the delay time of text-displaying // g_TextLib.iDelayTime = wcstol(lpszText + 1, NULL, 10) * 10 / 7; lpszText += 3; break; case '~': // // Delay for a period and quit // UTIL_Delay(wcstol(lpszText + 1, NULL, 10) * 80 / 7); g_TextLib.nCurrentDialogLine = 0; g_TextLib.fUserSkip = FALSE; return; // don't go further case ')': // // Set the waiting icon // g_TextLib.bIcon = 1; lpszText++; break; case '(': // // Set the waiting icon // g_TextLib.bIcon = 2; lpszText++; break; case '\\': lpszText++; default: text[0] = *lpszText++; text[1] = 0; PAL_DrawText(text, PAL_XY(x, y), g_TextLib.bCurrentFontColor, TRUE, TRUE); x += PAL_CharWidth(text[0]); if (!g_TextLib.fUserSkip) { PAL_ClearKeyState(); UTIL_Delay(g_TextLib.iDelayTime * 8); if (g_InputState.dwKeyPress & (kKeySearch | kKeyMenu)) { // // User pressed a key to skip the dialog // g_TextLib.fUserSkip = TRUE; } } } } g_TextLib.posIcon = PAL_XY(x, y); g_TextLib.nCurrentDialogLine++; } } } VOID PAL_ClearDialog( BOOL fWaitForKey ) /*++ Purpose: Clear the state of the dialog. Parameters: [IN] fWaitForKey - whether wait for any key or not. Return value: None. --*/ { if (g_TextLib.nCurrentDialogLine > 0 && fWaitForKey) { PAL_DialogWaitForKey(); } g_TextLib.nCurrentDialogLine = 0; if (g_TextLib.bDialogPosition == kDialogCenter) { g_TextLib.posDialogTitle = PAL_XY(12, 8); g_TextLib.posDialogText = PAL_XY(44, 26); g_TextLib.bCurrentFontColor = FONT_COLOR_DEFAULT; g_TextLib.bDialogPosition = kDialogUpper; } } VOID PAL_EndDialog( VOID ) /*++ Purpose: Ends a dialog. Parameters: None. Return value: None. --*/ { PAL_ClearDialog(TRUE); // // Set some default parameters, as there are some parts of script // which doesn't have a "start dialog" instruction before showing the dialog. // g_TextLib.posDialogTitle = PAL_XY(12, 8); g_TextLib.posDialogText = PAL_XY(44, 26); g_TextLib.bCurrentFontColor = FONT_COLOR_DEFAULT; g_TextLib.bDialogPosition = kDialogUpper; g_TextLib.fUserSkip = FALSE; g_TextLib.fPlayingRNG = FALSE; } BOOL PAL_IsInDialog( VOID ) /*++ Purpose: Check if there are dialog texts on the screen. Parameters: None. Return value: TRUE if there are dialog texts on the screen, FALSE if not. --*/ { return (g_TextLib.nCurrentDialogLine != 0); } BOOL PAL_DialogIsPlayingRNG( VOID ) /*++ Purpose: Check if the script used the RNG playing parameter when displaying texts. Parameters: None. Return value: TRUE if the script used the RNG playing parameter, FALSE if not. --*/ { return g_TextLib.fPlayingRNG; } INT PAL_MultiByteToWideCharCP( CODEPAGE cp, LPCSTR mbs, int mbslength, LPWSTR wcs, int wcslength ) /*++ Purpose: Convert multi-byte string into the corresponding unicode string. Parameters: [IN] cp - Code page for conversion. [IN] mbs - Pointer to the multi-byte string. [IN] mbslength - Length of the multi-byte string, or -1 for auto-detect. [IN] wcs - Pointer to the wide string buffer. [IN] wcslength - Length of the wide string buffer. Return value: The length of converted wide string. If mbslength is set to -1, the returned value includes the terminal null-char; otherwise, the null-char is not included. If wcslength is set to 0, wcs can be set to NULL and the return value is the required length of the wide string buffer. --*/ { int i = 0, state = 0, wlen = 0, null = 0; if (mbslength == -1) { mbslength = strlen(mbs); null = 1; } if (!wcs) { switch (cp) { case CP_SHIFTJIS: for (i = 0; i < mbslength && mbs[i]; i++) { if (state == 0) { if ((BYTE)mbs[i] <= 0x80 || (BYTE)mbs[i] >= 0xfd || ((BYTE)mbs[i] >= 0xa0 && (BYTE)mbs[i] <= 0xdf)) wlen++; else state = 1; } else { wlen++; state = 0; } } break; case CP_GBK: case CP_BIG5: for (i = 0; i < mbslength && mbs[i]; i++) { if (state == 0) { if ((BYTE)mbs[i] <= 0x80 || (BYTE)mbs[i] == 0xff) wlen++; else state = 1; } else { wlen++; state = 0; } } break; case CP_UTF_8: for (i = 0; i < mbslength && mbs[i]; i++) { if (state == 0) { if ((BYTE)mbs[i] >= 0x80) { BYTE s = (BYTE)mbs[i] << 1; while (s >= 0x80) { state++; s <<= 1; } if (state < 1 || state > 3) { state = 0; wlen++; } } else wlen++; } else { if ((BYTE)mbs[i] >= 0x80 && (BYTE)mbs[i] < 0xc0) { if (--state == 0) wlen++; } else { state = 0; wlen++; } } } break; default: return -1; } if (i < mbslength && !mbs[i]) null = 1; return wlen + null + (state != 0); } else { WCHAR invalid_char; switch (cp) { case CP_SHIFTJIS: invalid_char = 0x30fb; for (i = 0; i < mbslength && wlen < wcslength && mbs[i]; i++) { if (state == 0) { if ((BYTE)mbs[i] <= 0x80) wcs[wlen++] = mbs[i]; else if ((BYTE)mbs[i] >= 0xa0 && (BYTE)mbs[i] <= 0xdf) wcs[wlen++] = cptbl_jis_half[(BYTE)mbs[i] - 0xa0]; else if ((BYTE)mbs[i] == 0xfd) wcs[wlen++] = 0xf8f1; else if ((BYTE)mbs[i] == 0xfe) wcs[wlen++] = 0xf8f2; else if ((BYTE)mbs[i] == 0xff) wcs[wlen++] = 0xf8f3; else state = 1; } else { if ((BYTE)mbs[i] < 0x40) wcs[wlen++] = 0x30fb; else if ((BYTE)mbs[i - 1] < 0xa0) wcs[wlen++] = cptbl_jis[(BYTE)mbs[i - 1] - 0x81][(BYTE)mbs[i] - 0x40]; else wcs[wlen++] = cptbl_jis[(BYTE)mbs[i - 1] - 0xc1][(BYTE)mbs[i] - 0x40]; state = 0; } } break; case CP_GBK: invalid_char = 0x3f; for (i = 0; i < mbslength && wlen < wcslength && mbs[i]; i++) { if (state == 0) { if ((BYTE)mbs[i] < 0x80) wcs[wlen++] = mbs[i]; else if ((BYTE)mbs[i] == 0x80) wcs[wlen++] = 0x20ac; else if ((BYTE)mbs[i] == 0xff) wcs[wlen++] = 0xf8f5; else state = 1; } else { if ((BYTE)mbs[i] < 0x40) wcs[wlen++] = invalid_char; else wcs[wlen++] = cptbl_gbk[(BYTE)mbs[i - 1] - 0x81][(BYTE)mbs[i] - 0x40]; state = 0; } } break; case CP_BIG5: invalid_char = 0x3f; for (i = 0; i < mbslength && wlen < wcslength && mbs[i]; i++) { if (state == 0) { if ((BYTE)mbs[i] <= 0x80) wcs[wlen++] = mbs[i]; else if ((BYTE)mbs[i] == 0xff) wcs[wlen++] = 0xf8f8; else state = 1; } else { if ((BYTE)mbs[i] < 0x40 || ((BYTE)mbs[i] >= 0x7f && (BYTE)mbs[i] <= 0xa0)) wcs[wlen++] = invalid_char; else if ((BYTE)mbs[i] <= 0x7e) wcs[wlen++] = cptbl_big5[(BYTE)mbs[i - 1] - 0x81][(BYTE)mbs[i] - 0x40]; else wcs[wlen++] = cptbl_big5[(BYTE)mbs[i - 1] - 0x81][(BYTE)mbs[i] - 0x60]; state = 0; } } break; case CP_UTF_8: invalid_char = 0x3f; for (i = 0; i < mbslength && wlen < wcslength && mbs[i]; i++) { if (state == 0) { if ((BYTE)mbs[i] >= 0x80) { BYTE s = (BYTE)mbs[i] << 1; while (s >= 0x80) { state++; s <<= 1; } if (state < 1 || state > 3) { state = 0; wcs[wlen++] = invalid_char; } else { wcs[wlen] = s >> (state + 1); } } else wcs[wlen++] = mbs[i]; } else { if ((BYTE)mbs[i] >= 0x80 && (BYTE)mbs[i] < 0xc0) { wcs[wlen] <<= 6; wcs[wlen] |= (BYTE)mbs[i] & 0x3f; if (--state == 0) wlen++; } else { state = 0; wcs[wlen++] = invalid_char; } } } break; default: return -1; } if (state != 0 && wlen < wcslength) { wcs[wlen++] = invalid_char; } if (null || (i < mbslength && !mbs[i])) { if (wlen < wcslength) wcs[wlen++] = 0; else wcs[wlen - 1] = 0; } return wlen; } } INT PAL_MultiByteToWideChar( LPCSTR mbs, int mbslength, LPWSTR wcs, int wcslength ) /*++ Purpose: Convert multi-byte string into the corresponding unicode string. Parameters: [IN] mbs - Pointer to the multi-byte string. [IN] mbslength - Length of the multi-byte string, or -1 for auto-detect. [IN] wcs - Pointer to the wide string buffer. [IN] wcslength - Length of the wide string buffer. Return value: The length of converted wide string. If mbslength is set to -1, the returned value includes the terminal null-char; otherwise, the null-char is not included. If wcslength is set to 0, wcs can be set to NULL and the return value is the required length of the wide string buffer. --*/ { return PAL_MultiByteToWideCharCP(gpGlobals->iCodePage, mbs, mbslength, wcs, wcslength); } WCHAR PAL_GetInvalidChar( CODEPAGE iCodePage ) { switch(iCodePage) { case CP_BIG5: return 0x3f; case CP_GBK: return 0x3f; case CP_SHIFTJIS: return 0x30fb; case CP_UTF_8: return 0x3f; default: return 0; } }