mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-24 18:56:33 +00:00
dba0c5ca2c
svn-id: r22407
560 lines
15 KiB
C++
560 lines
15 KiB
C++
/* 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
|