/* ScummVM - Scumm Interpreter * Copyright (C) 2005-2006 The ScummVM project * * 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 * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $URL$ * $Id$ * */ #include "lure/surface.h" #include "lure/decode.h" #include "lure/system.h" #include "lure/events.h" #include "lure/screen.h" #include "lure/room.h" #include "lure/strings.h" #include "common/endian.h" namespace Lure { // These variables hold resources commonly used by the Surfaces, and must be initialised and freed // by the static Surface methods initialise and deinitailse static MemoryBlock *int_font = NULL; static MemoryBlock *int_dialog_frame = NULL; static uint8 fontSize[NUM_CHARS_IN_FONT]; void Surface::initialise() { int_font = Disk::getReference().getEntry(FONT_RESOURCE_ID); int_dialog_frame = Disk::getReference().getEntry(DIALOG_RESOURCE_ID); // Calculate the size of each font character for (int ctr = 0; ctr < NUM_CHARS_IN_FONT; ++ctr) { byte *pChar = int_font->data() + (ctr * 8); fontSize[ctr] = 0; for (int yp = 0; yp < FONT_HEIGHT; ++yp) { byte v = *pChar++; for (int xp = 0; xp < FONT_WIDTH; ++xp) { if ((v & 0x80) && (xp > fontSize[ctr])) fontSize[ctr] = xp; v = (v << 1) & 0xff; } } // If character is empty, like for a space, give a default size if (fontSize[ctr] == 0) fontSize[ctr] = 2; } } void Surface::deinitialise() { delete int_font; delete int_dialog_frame; } /*--------------------------------------------------------------------------*/ Surface::Surface(MemoryBlock *src, uint16 wdth, uint16 hght): _data(src), _width(wdth), _height(hght) { if ((uint32) (wdth * hght) != src->size()) error("Surface dimensions do not match size of passed data"); } Surface::Surface(uint16 wdth, uint16 hght): _data(Memory::allocate(wdth*hght)), _width(wdth), _height(hght) { } Surface::~Surface() { delete _data; } void Surface::loadScreen(uint16 resourceId) { MemoryBlock *rawData = Disk::getReference().getEntry(resourceId); PictureDecoder decoder; MemoryBlock *tmpScreen = decoder.decode(rawData, FULL_SCREEN_HEIGHT * FULL_SCREEN_WIDTH); delete rawData; empty(); copyFrom(tmpScreen, MENUBAR_Y_SIZE * FULL_SCREEN_WIDTH); delete tmpScreen; } void Surface::writeChar(uint16 x, uint16 y, uint8 ascii, bool transparent, uint8 colour) { byte *const addr = _data->data() + (y * _width) + x; if ((ascii < 32) || (ascii >= 32 + NUM_CHARS_IN_FONT)) error("Invalid ascii character passed for display '%d'", ascii); uint8 v; byte *pFont = int_font->data() + ((ascii - 32) * 8); byte *pDest; uint8 charWidth = 0; for (int y1 = 0; y1 < 8; ++y1) { v = *pFont++; pDest = addr + (y1 * _width); for (int x1 = 0; x1 < 8; ++x1, ++pDest) { if (v & 0x80) { *pDest = colour; if (x1+1 > charWidth) charWidth = x1 + 1; } else if (!transparent) *pDest = 0; v = (v << 1) & 0xff; } } } void Surface::writeString(uint16 x, uint16 y, Common::String line, bool transparent, uint8 colour, bool varLength) { const char *sPtr = line.c_str(); while (*sPtr) { writeChar(x, y, (uint8) *sPtr, transparent, colour); // Move to after the character in preparation for the next character if (!varLength) x += FONT_WIDTH; else x += fontSize[*sPtr - ' '] + 2; ++sPtr; // Move to next character } } void Surface::transparentCopyTo(Surface *dest) { if (dest->width() != _width) error("Incompatible surface sizes for transparent copy"); byte *pSrc = _data->data(); byte *pDest = dest->data().data(); uint16 numBytes = MIN(_height,dest->height()) * FULL_SCREEN_WIDTH; while (numBytes-- > 0) { if (*pSrc) *pDest = *pSrc; ++pSrc; ++pDest; } } void Surface::copyTo(Surface *dest) { copyTo(dest, 0, 0); } void Surface::copyTo(Surface *dest, uint16 x, uint16 y) { if ((x == 0) && (dest->width() == _width)) { // Use fast data transfer uint32 dataSize = dest->data().size() - (y * _width); if (dataSize > _data->size()) dataSize = _data->size(); dest->data().copyFrom(_data, 0, y * _width, dataSize); } else { // Use slower transfer Rect rect; rect.left = 0; rect.top = 0; rect.right = _width-1; rect.bottom = _height-1; copyTo(dest, rect, x, y); } } void Surface::copyTo(Surface *dest, const Rect &srcBounds, uint16 destX, uint16 destY, int transparentColour) { for (uint16 y=0; y<=(srcBounds.bottom-srcBounds.top); ++y) { const uint32 srcPos = (srcBounds.top + y) * _width + srcBounds.left; const uint32 destPos = (destY+y) * dest->width() + destX; uint16 numBytes = srcBounds.right-srcBounds.left+1; if (transparentColour == -1) { // No trnnsparent colour, so copy all the bytes of the line dest->data().copyFrom(_data, srcPos, destPos, numBytes); } else { byte *pSrc = _data->data() + srcPos; byte *pDest = dest->data().data() + destPos; while (numBytes-- > 0) { if (*pSrc != (uint8) transparentColour) *pDest = *pSrc; ++pSrc; ++pDest; } } } } void Surface::copyFrom(MemoryBlock *src, uint32 destOffset) { uint32 size = _data->size() - destOffset; if (src->size() > size) size = src->size(); _data->copyFrom(src, 0, destOffset, size); } // fillRect // Fills a rectangular area with a colour void Surface::fillRect(const Rect &r, uint8 colour) { for (int yp = r.top; yp <= r.bottom; ++yp) { byte *const addr = _data->data() + (yp * _width) + r.left; memset(addr, colour, r.width()); } } // createDialog // Forms a dialog encompassing the entire surface void copyLine(byte *pSrc, byte *pDest, uint16 leftSide, uint16 center, uint16 rightSide) { // Left area memcpy(pDest, pSrc, leftSide); pSrc += leftSide; pDest += leftSide; // Center area memset(pDest, *pSrc, center); ++pSrc; pDest += center; // Right side memcpy(pDest, pSrc, rightSide); pSrc += rightSide; pDest += rightSide; } void Surface::createDialog(bool blackFlag) { if ((_width < 20) || (_height < 20)) return; byte *pSrc = int_dialog_frame->data(); byte *pDest = _data->data(); uint16 xCenter = _width - DIALOG_EDGE_SIZE * 2; uint16 yCenter = _height - DIALOG_EDGE_SIZE * 2; int y; // Dialog top for (y = 0; y < 9; ++y) { copyLine(pSrc, pDest, DIALOG_EDGE_SIZE - 2, xCenter + 2, DIALOG_EDGE_SIZE); pSrc += (DIALOG_EDGE_SIZE - 2) + 1 + DIALOG_EDGE_SIZE; pDest += _width; } // Dialog sides - note that the same source data gets used for all side lines for (y = 0; y < yCenter; ++y) { copyLine(pSrc, pDest, DIALOG_EDGE_SIZE, xCenter, DIALOG_EDGE_SIZE); pDest += _width; } pSrc += DIALOG_EDGE_SIZE * 2 + 1; // Dialog bottom for (y = 0; y < 9; ++y) { copyLine(pSrc, pDest, DIALOG_EDGE_SIZE, xCenter + 1, DIALOG_EDGE_SIZE - 1); pSrc += DIALOG_EDGE_SIZE + 1 + (DIALOG_EDGE_SIZE - 1); pDest += _width; } // Final processing - if black flag set, clear dialog inside area if (blackFlag) { Rect r = Rect(DIALOG_EDGE_SIZE, DIALOG_EDGE_SIZE, _width - DIALOG_EDGE_SIZE, _height-DIALOG_EDGE_SIZE); fillRect(r, 0); } } void Surface::copyToScreen(uint16 x, uint16 y) { OSystem &system = System::getReference(); system.copyRectToScreen(_data->data(), _width, x, y, _width, _height); system.updateScreen(); } void Surface::centerOnScreen() { OSystem &system = System::getReference(); system.copyRectToScreen(_data->data(), _width, (FULL_SCREEN_WIDTH - _width) / 2, (FULL_SCREEN_HEIGHT - _height) / 2, _width, _height); system.updateScreen(); } uint16 Surface::textWidth(const char *s, int numChars) { uint16 result = 0; if (numChars == 0) numChars = strlen(s); while (numChars-- > 0) result += fontSize[*s++ - ' '] + 2; return result; } void Surface::wordWrap(char *text, uint16 width, char **&lines, uint8 &numLines) { numLines = 1; uint16 lineWidth = 0; char *s; bool newLine; s = text; // Scan through the text and insert NULLs to break the line into allowable widths while (*s != '\0') { char *wordStart = s; while (*wordStart == ' ') ++wordStart; char *wordEnd = strchr(wordStart, ' '); char *wordEnd2 = strchr(wordStart, '\n'); if ((!wordEnd) || ((wordEnd2) && (wordEnd2 < wordEnd))) { wordEnd = wordEnd2; newLine = (wordEnd2 != NULL); } else { newLine = false; } if (wordEnd) { if (!newLine) --wordEnd; } else { wordEnd = strchr(s, '\0') - 1; } uint16 wordSize = textWidth(s, (int) (wordEnd - s + 1)); if (lineWidth + wordSize > width) { // Break word onto next line *(wordStart - 1) = '\0'; ++numLines; if (newLine) lineWidth = 0; else lineWidth = textWidth(wordStart, (int) (wordEnd - wordStart + 1)); } else if (newLine) { // Break on newline ++numLines; *wordEnd = '\0'; lineWidth = 0; } else { // Add word's length to total for line lineWidth += wordSize; } s = wordEnd+1; } // Set up a list for the start of each line lines = (char **) Memory::alloc(sizeof(char *) * numLines); lines[0] = text; for (int ctr = 1; ctr < numLines; ++ctr) lines[ctr] = strchr(lines[ctr-1], 0) + 1; } Surface *Surface::newDialog(uint16 width, uint8 numLines, char **lines, bool varLength, uint8 colour) { Surface *s = new Surface(width, (DIALOG_EDGE_SIZE + 3) * 2 + numLines * (FONT_HEIGHT - 1)); s->createDialog(); for (uint8 ctr = 0; ctr < numLines; ++ctr) s->writeString(DIALOG_EDGE_SIZE + 3, DIALOG_EDGE_SIZE + 3 + (ctr * (FONT_HEIGHT - 1)), lines[ctr], true, colour, varLength); return s; } Surface *Surface::newDialog(uint16 width, const char *line, uint8 colour) { char **lines; char *lineCopy = strdup(line); uint8 numLines; wordWrap(lineCopy, width - (DIALOG_EDGE_SIZE + 3) * 2, lines, numLines); // Create the dialog Surface *result = newDialog(width, numLines, lines, true, colour); // Deallocate used resources free(lines); free(lineCopy); return result; } Surface *Surface::getScreen(uint16 resourceId) { MemoryBlock *block = Disk::getReference().getEntry(resourceId); PictureDecoder d; MemoryBlock *decodedData = d.decode(block); delete block; return new Surface(decodedData, FULL_SCREEN_WIDTH, decodedData->size() / FULL_SCREEN_WIDTH); } /*--------------------------------------------------------------------------*/ void Dialog::show(const char *text) { Screen &screen = Screen::getReference(); Mouse &mouse = Mouse::getReference(); Room &room = Room::getReference(); mouse.cursorOff(); room.update(); Surface *s = Surface::newDialog(INFO_DIALOG_WIDTH, text); s->copyToScreen(INFO_DIALOG_X, INFO_DIALOG_Y); // Wait for a keypress or mouse button Events::getReference().waitForPress(); screen.update(); mouse.cursorOn(); } void Dialog::show(uint16 stringId) { char buffer[MAX_DESC_SIZE]; Resources &res = Resources::getReference(); Room &r = Room::getReference(); StringData &sl = StringData::getReference(); const char *actionName = res.getCurrentActionStr(); char hotspotName[MAX_HOTSPOT_NAME_SIZE]; if (r.hotspotId() == 0) strcpy(hotspotName, ""); else sl.getString(r.hotspot().nameId, hotspotName, NULL, NULL); sl.getString(stringId, buffer, hotspotName, actionName); show(buffer); } void Dialog::showMessage(uint16 messageId, uint16 characterId) { MemoryBlock *data = Resources::getReference().messagesData(); uint16 *v = (uint16 *) data->data(); uint16 v2, idVal; messageId &= 0x7fff; // Skip through header to find table for given character while (READ_LE_UINT16(v) != characterId) v += 2; // Scan through secondary list ++v; v = (uint16 *) (data->data() + READ_LE_UINT16(v)); v2 = 0; while ((idVal = READ_LE_UINT16(v)) != 0xffff) { ++v; if (READ_LE_UINT16(v) == messageId) break; ++v; } // default response if a specific response not found if (idVal == 0xffff) idVal = 0x8c4; if (idVal == 0x76) { /* call sub_154 ; (64E7) mov ax,word ptr ds:[5813h] ; (273F:5813=1BA3h) mov [bx+ANIM_SEGMENT],ax mov ax,word ptr ds:[5817h] ; (273F:5817=0ED8Eh) mov [bx+ANIM_FRAME],ax retn */ } else if (idVal == 0x120) { /* call sub_154 ; (64E7) mov ax,word ptr ds:[5813h] ; (273F:5813=1BA3h) mov [bx+ANIM_SEGMENT],ax mov ax,word ptr ds:[5817h] ; (273F:5817=0ED8Eh) shl ax,1 mov [bx+ANIM_FRAME],ax */ } else if (idVal >= 0x8000) { // Handle string display idVal &= 0x7fff; Dialog::show(idVal); } else if (idVal != 0) { // Handle message as a talking dialog // TODO: show talk dialog warning("Dialog style for message #%d not yet implemented", idVal); } } /*--------------------------------------------------------------------------*/ TalkDialog::TalkDialog(uint16 characterId, uint16 descId) { StringData &strings = StringData::getReference(); Resources &res = Resources::getReference(); HotspotData *character = res.getHotspot(characterId); char charName[MAX_DESC_SIZE]; strings.getString(character->nameId, charName, NULL, NULL); strings.getString(descId, _desc, NULL, NULL); // Apply word wrapping to figure out the needed size of the dialog Surface::wordWrap(_desc, TALK_DIALOG_WIDTH - TALK_DIALOG_EDGE_SIZE * 2 - 2, _lines, _numLines); _surface = new Surface(TALK_DIALOG_WIDTH, (_numLines + 1) * FONT_HEIGHT + TALK_DIALOG_EDGE_SIZE * 4); // Draw the dialog byte *pSrc = res.getTalkDialogData().data(); byte *pDest = _surface->data().data(); int xPos, yPos; // Handle the dialog top for (yPos = 0; yPos < TALK_DIALOG_EDGE_SIZE; ++yPos) { *pDest++ = *pSrc++; *pDest++ = *pSrc++; for (xPos = 0; xPos < TALK_DIALOG_WIDTH - TALK_DIALOG_EDGE_SIZE - 2; ++xPos) *pDest++ = *pSrc; ++pSrc; for (xPos = 0; xPos < TALK_DIALOG_EDGE_SIZE; ++xPos) *pDest++ = *pSrc++; } // Handle the middle section for (yPos = 0; yPos < _surface->height() - TALK_DIALOG_EDGE_SIZE * 2; ++yPos) { byte *pSrcTemp = pSrc; // Left edge for (xPos = 0; xPos < TALK_DIALOG_EDGE_SIZE; ++xPos) *pDest++ = *pSrcTemp++; // Middle section for (xPos = 0; xPos < _surface->width() - TALK_DIALOG_EDGE_SIZE * 2; ++xPos) *pDest++ = *pSrcTemp; ++pSrcTemp; // Right edge for (xPos = 0; xPos < TALK_DIALOG_EDGE_SIZE; ++xPos) *pDest++ = *pSrcTemp++; } // Bottom section pSrc += TALK_DIALOG_EDGE_SIZE * 2 + 1; for (yPos = 0; yPos < TALK_DIALOG_EDGE_SIZE; ++yPos) { for (xPos = 0; xPos < TALK_DIALOG_EDGE_SIZE; ++xPos) *pDest++ = *pSrc++; for (xPos = 0; xPos < TALK_DIALOG_WIDTH - TALK_DIALOG_EDGE_SIZE - 2; ++xPos) *pDest++ = *pSrc; ++pSrc; *pDest++ = *pSrc++; *pDest++ = *pSrc++; } // Write out the character name uint16 charWidth = Surface::textWidth(charName); _surface->writeString((TALK_DIALOG_WIDTH-charWidth)/2, TALK_DIALOG_EDGE_SIZE + 2, charName, true, DIALOG_WHITE_COLOUR); // TEMPORARY CODE - write out description. More properly, the text is meant to // be displayed slowly, word by word for (int lineCtr = 0; lineCtr < _numLines; ++lineCtr) _surface->writeString(TALK_DIALOG_EDGE_SIZE + 2, TALK_DIALOG_EDGE_SIZE + 4 + (lineCtr + 1) * FONT_HEIGHT, _lines[lineCtr], true); } TalkDialog::~TalkDialog() { delete _lines; delete _surface; } } // end of namespace Lure