Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

2
engines/sky/POTFILES Normal file
View File

@@ -0,0 +1,2 @@
engines/sky/compact.cpp
engines/sky/metaengine.cpp

280
engines/sky/autoroute.cpp Normal file
View File

@@ -0,0 +1,280 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#include "common/endian.h"
#include "common/textconsole.h"
#include "common/util.h"
#include "sky/autoroute.h"
#include "sky/compact.h"
#include "sky/grid.h"
#include "sky/skydefs.h"
#include "sky/struc.h"
namespace Sky {
#define ROUTE_GRID_WIDTH ((GAME_SCREEN_WIDTH/8)+2)
#define ROUTE_GRID_HEIGHT ((GAME_SCREEN_HEIGHT/8)+2)
#define ROUTE_GRID_SIZE (ROUTE_GRID_WIDTH*ROUTE_GRID_HEIGHT*2)
#define WALK_JUMP 8 // walk in blocks of 8
const int16 AutoRoute::_routeDirections[4] = { -1, 1, -ROUTE_GRID_WIDTH, ROUTE_GRID_WIDTH };
const uint16 AutoRoute::_logicCommands[4] = { RIGHTY, LEFTY, DOWNY, UPY };
AutoRoute::AutoRoute(Grid *pGrid, SkyCompact *compact) {
_grid = pGrid;
_skyCompact = compact;
_routeGrid = (uint16 *)malloc(ROUTE_GRID_SIZE);
_routeBuf = (uint16 *)malloc(ROUTE_SPACE);
}
AutoRoute::~AutoRoute() {
free(_routeGrid);
free(_routeBuf);
}
uint16 AutoRoute::checkBlock(uint16 *blockPos) {
uint16 retVal = 0xFFFF;
for (uint8 cnt = 0; cnt < 4; cnt++) {
uint16 fieldVal = *(blockPos + _routeDirections[cnt]);
if (fieldVal && (fieldVal < retVal))
retVal = fieldVal;
}
return retVal;
}
void AutoRoute::clipCoordX(uint16 x, uint8 &blkX, int16 &initX) {
if (x < TOP_LEFT_X) {
blkX = 0;
initX = x - TOP_LEFT_X;
} else if (x >= TOP_LEFT_X + GAME_SCREEN_WIDTH) {
blkX = (GAME_SCREEN_WIDTH - 1) >> 3;
initX = x - (TOP_LEFT_X + GAME_SCREEN_WIDTH - 1);
} else {
blkX = (x - TOP_LEFT_X) >> 3;
initX = 0;
}
}
void AutoRoute::clipCoordY(uint16 y, uint8 &blkY, int16 &initY) {
if (y < TOP_LEFT_Y) {
blkY = 0;
initY = y - TOP_LEFT_Y;
} else if (y >= TOP_LEFT_Y + GAME_SCREEN_HEIGHT) {
blkY = (GAME_SCREEN_HEIGHT - 1) >> 3;
initY = y - (TOP_LEFT_Y + GAME_SCREEN_HEIGHT);
} else {
blkY = (y - TOP_LEFT_Y) >> 3;
initY = 0;
}
}
void AutoRoute::initWalkGrid(uint8 screen, uint8 width) {
uint16 *wGridPos;
uint8 stretch = 0;
uint8 *screenGrid = _grid->giveGrid(screen);
screenGrid += GRID_SIZE;
wGridPos = _routeGrid + (ROUTE_GRID_SIZE >> 1) - ROUTE_GRID_WIDTH - 2;
memset(_routeGrid, 0, ROUTE_GRID_SIZE);
uint8 bitsLeft = 0; uint32 gridData = 0;
for (uint8 gridCntY = 0; gridCntY < ROUTE_GRID_HEIGHT - 2; gridCntY++) {
for (uint8 gridCntX = 0; gridCntX < ROUTE_GRID_WIDTH - 2; gridCntX++) {
if (!bitsLeft) {
screenGrid -= 4;
gridData = READ_LE_UINT32(screenGrid);
bitsLeft = 32;
}
if (gridData & 1) {
*wGridPos = 0xFFFF; // block is not accessible
stretch = width;
} else if (stretch) {
*wGridPos = 0xFFFF;
stretch--;
}
wGridPos--;
bitsLeft--;
gridData >>= 1;
}
wGridPos -= 2;
stretch = 0;
}
}
bool AutoRoute::calcWalkGrid(uint8 startX, uint8 startY, uint8 destX, uint8 destY) {
int16 directionX, directionY;
uint8 roiX, roiY; // Rectangle Of Interest in the walk grid
if (startY > destY) {
directionY = -ROUTE_GRID_WIDTH;
roiY = startY;
} else {
directionY = ROUTE_GRID_WIDTH;
roiY = (ROUTE_GRID_HEIGHT-1) - startY;
}
if (startX > destX) {
directionX = -1;
roiX = startX + 2;
} else {
directionX = 1;
roiX = (ROUTE_GRID_WIDTH - 1) - startX;
}
uint16 *walkDest = _routeGrid + (destY + 1) * ROUTE_GRID_WIDTH + destX + 1;
uint16 *walkStart = _routeGrid + (startY + 1) * ROUTE_GRID_WIDTH + startX + 1;
*walkStart = 1;
// if we are on the edge, move diagonally from start
if (roiY < ROUTE_GRID_HEIGHT-3)
walkStart -= directionY;
if (roiX < ROUTE_GRID_WIDTH-2)
walkStart -= directionX;
bool gridChanged = true;
bool foundRoute = false;
while ((!foundRoute) && gridChanged) {
gridChanged = false;
uint16 *yWalkCalc = walkStart;
for (uint8 cnty = 0; cnty < roiY; cnty++) {
uint16 *xWalkCalc = yWalkCalc;
for (uint8 cntx = 0; cntx < roiX; cntx++) {
if (!*xWalkCalc) { // block wasn't done, yet
uint16 blockRet = checkBlock(xWalkCalc);
if (blockRet < 0xFFFF) {
*xWalkCalc = blockRet + 1;
gridChanged = true;
}
}
xWalkCalc += directionX;
}
yWalkCalc += directionY;
}
if (*walkDest) { // okay, finished
foundRoute = true;
} else { // we couldn't find the route, let's extend the ROI
if (roiY < ROUTE_GRID_HEIGHT - 4) {
walkStart -= directionY;
roiY++;
}
if (roiX < ROUTE_GRID_WIDTH - 4) {
walkStart -= directionX;
roiX++;
}
}
}
return foundRoute;
}
uint16 *AutoRoute::makeRouteData(uint8 startX, uint8 startY, uint8 destX, uint8 destY) {
memset(_routeBuf, 0, ROUTE_SPACE);
uint16 *routePos = _routeGrid + (destY + 1) * ROUTE_GRID_WIDTH + destX + 1;
uint16 *dataTrg = _routeBuf + (ROUTE_SPACE >> 1) - 2;
uint16 lastVal = (*routePos) - 1;
while (lastVal) { // lastVal == 0 means route is done.
dataTrg -= 2;
int16 walkDirection = 0;
for (uint8 cnt = 0; cnt < 4; cnt++)
if (lastVal == *(routePos + _routeDirections[cnt])) {
*(dataTrg + 1) = _logicCommands[cnt];
walkDirection = _routeDirections[cnt];
break;
}
if (!walkDirection)
error("makeRouteData:: can't find way through walkGrid (pos %d)", lastVal);
while (lastVal && (lastVal == *(routePos + walkDirection))) {
*dataTrg += WALK_JUMP;
lastVal--;
routePos += walkDirection;
}
}
return dataTrg;
}
uint16 *AutoRoute::checkInitMove(uint16 *data, int16 initStaX) {
if (initStaX < 0) {
data -= 2;
*(data + 1) = RIGHTY;
*data = ((-initStaX) + 7) & 0xFFF8;
} else if (initStaX > 0) {
data -= 2;
*(data + 1) = LEFTY;
*data = (initStaX + 7) & 0xFFF8;
}
return data;
}
uint16 AutoRoute::autoRoute(Compact *cpt) {
uint8 cptScreen = (uint8)cpt->screen;
uint8 cptWidth = (uint8)SkyCompact::getMegaSet(cpt)->gridWidth;
initWalkGrid(cptScreen, cptWidth);
uint8 startX, startY, destX, destY;
int16 initStaX, initStaY, initDestX, initDestY;
clipCoordX(cpt->xcood, startX, initStaX);
clipCoordY(cpt->ycood, startY, initStaY);
clipCoordX(cpt->arTargetX, destX, initDestX);
clipCoordY(cpt->arTargetY, destY, initDestY);
uint16 *routeDest = (uint16 *)_skyCompact->fetchCpt(cpt->animScratchId);
memset(routeDest, 0, 64);
if ((startX == destX) && (startY == destY))
return 2;
if (_routeGrid[(destY + 1) * ROUTE_GRID_WIDTH + destX + 1]) {
//if ((cpt == &Sky::SkyCompact::foster) && (cptScreen == 12) && (destX == 2) && (destY == 14)) {
if (_skyCompact->cptIsId(cpt, CPT_FOSTER) && (cptScreen == 12) && (destX == 2) && (destY == 14)) {
/* workaround for Script bug #1804
In screen 12 (the pipe factory) Joey can block Foster's target
coordinates (2/14). This is normally not too tragic, but in the
scene when foster gets thrown out by Lamb (first time you enter
the pipe factory), the game would enter an infinite loop. */
_routeGrid[(destY + 1) * ROUTE_GRID_WIDTH + destX + 1] = 0;
// hide this part joey from the grid
} else
return 1; // AR destination is an unaccessible block
}
if (!calcWalkGrid(startX, startY, destX, destY))
return 1; // can't find route to block
uint16 *routeData = makeRouteData(startX, startY, destX, destY);
// the route is done.
// if there was an initial x movement (due to clipping) tag it onto the start
routeData = checkInitMove(routeData, initStaX);
uint8 cnt = 0;
do {
routeDest[cnt] = routeData[cnt];
routeDest[cnt + 1] = routeData[cnt + 1];
cnt += 2;
} while (routeData[cnt - 2]);
return 0;
}
} // End of namespace Sky

57
engines/sky/autoroute.h Normal file
View File

@@ -0,0 +1,57 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef SKY_AUTOROUTE_H
#define SKY_AUTOROUTE_H
#include "common/scummsys.h"
namespace Sky {
struct Compact;
class Grid;
class SkyCompact;
class AutoRoute {
public:
AutoRoute(Grid *pGrid, SkyCompact *compact);
~AutoRoute();
uint16 autoRoute(Compact *cpt);
private:
uint16 checkBlock(uint16 *blockPos);
void clipCoordX(uint16 x, uint8 &blkX, int16 &initX);
void clipCoordY(uint16 y, uint8 &blkY, int16 &initY);
void initWalkGrid(uint8 screen, uint8 width);
bool calcWalkGrid(uint8 startX, uint8 startY, uint8 destX, uint8 destY);
uint16 *makeRouteData(uint8 startX, uint8 startY, uint8 destX, uint8 destY);
uint16 *checkInitMove(uint16 *data, int16 initStaX);
Grid *_grid;
SkyCompact *_skyCompact;
uint16 *_routeGrid;
uint16 *_routeBuf;
static const int16 _routeDirections[4];
static const uint16 _logicCommands[4];
};
} // End of namespace Sky
#endif // AUTOROUTE_H

532
engines/sky/compact.cpp Normal file
View File

@@ -0,0 +1,532 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#include "common/debug.h"
#include "common/endian.h"
#include "common/file.h"
#include "common/textconsole.h"
#include "common/translation.h"
#include "sky/compact.h"
#include "gui/message.h"
namespace Sky {
#define SKY_CPT_SIZE 419427
#define OFFS(type,item) ((uint32)(((ptrdiff_t)(&((type *)42)->item))-42))
#define MK32(type,item) OFFS(type, item),0,0,0
#define MK16(type,item) OFFS(type, item),0
#define MK32_A5(type, item) MK32(type, item[0]), MK32(type, item[1]), \
MK32(type, item[2]), MK32(type, item[3]), MK32(type, item[4])
static const uint32 compactOffsets[] = {
MK16(Compact, logic),
MK16(Compact, status),
MK16(Compact, sync),
MK16(Compact, screen),
MK16(Compact, place),
MK32(Compact, getToTableId),
MK16(Compact, xcood),
MK16(Compact, ycood),
MK16(Compact, frame),
MK16(Compact, cursorText),
MK16(Compact, mouseOn),
MK16(Compact, mouseOff),
MK16(Compact, mouseClick),
MK16(Compact, mouseRelX),
MK16(Compact, mouseRelY),
MK16(Compact, mouseSizeX),
MK16(Compact, mouseSizeY),
MK16(Compact, actionScript),
MK16(Compact, upFlag),
MK16(Compact, downFlag),
MK16(Compact, getToFlag),
MK16(Compact, flag),
MK16(Compact, mood),
MK32(Compact, grafixProgId),
MK16(Compact, offset),
MK16(Compact, mode),
MK16(Compact, baseSub),
MK16(Compact, baseSub_off),
MK16(Compact, actionSub),
MK16(Compact, actionSub_off),
MK16(Compact, getToSub),
MK16(Compact, getToSub_off),
MK16(Compact, extraSub),
MK16(Compact, extraSub_off),
MK16(Compact, dir),
MK16(Compact, stopScript),
MK16(Compact, miniBump),
MK16(Compact, leaving),
MK16(Compact, atWatch),
MK16(Compact, atWas),
MK16(Compact, alt),
MK16(Compact, request),
MK16(Compact, spWidth_xx),
MK16(Compact, spColor),
MK16(Compact, spTextId),
MK16(Compact, spTime),
MK16(Compact, arAnimIndex),
MK32(Compact, turnProgId),
MK16(Compact, waitingFor),
MK16(Compact, arTargetX),
MK16(Compact, arTargetY),
MK32(Compact, animScratchId),
MK16(Compact, megaSet),
};
static const uint32 megaSetOffsets[] = {
MK16(MegaSet, gridWidth),
MK16(MegaSet, colOffset),
MK16(MegaSet, colWidth),
MK16(MegaSet, lastChr),
MK32(MegaSet, animUpId),
MK32(MegaSet, animDownId),
MK32(MegaSet, animLeftId),
MK32(MegaSet, animRightId),
MK32(MegaSet, standUpId),
MK32(MegaSet, standDownId),
MK32(MegaSet, standLeftId),
MK32(MegaSet, standRightId),
MK32(MegaSet, standTalkId),
};
static const uint32 turnTableOffsets[] = {
MK32_A5(TurnTable, turnTableUp),
MK32_A5(TurnTable, turnTableDown),
MK32_A5(TurnTable, turnTableLeft),
MK32_A5(TurnTable, turnTableRight),
MK32_A5(TurnTable, turnTableTalk),
};
#define COMPACT_SIZE (sizeof(compactOffsets)/sizeof(uint32))
#define MEGASET_SIZE (sizeof(megaSetOffsets)/sizeof(uint32))
#define TURNTABLE_SIZE (sizeof(turnTableOffsets)/sizeof(uint32))
SkyCompact::SkyCompact() {
_cptFile = new Common::File();
Common::String filename = "sky.cpt";
if (!_cptFile->open(filename.c_str())) {
const char *msg = _s("Unable to locate the '%s' engine data file.");
Common::U32String errorMessage = Common::U32String::format(_(msg), filename.c_str());
GUIErrorMessage(errorMessage);
error(msg, filename.c_str());
}
uint16 fileVersion = _cptFile->readUint16LE();
if (fileVersion != 0)
error("unknown \"sky.cpt\" version");
if (SKY_CPT_SIZE != _cptFile->size()) {
GUI::MessageDialog dialog(_("The \"sky.cpt\" engine data file has an incorrect size."));
dialog.runModal();
error("Incorrect sky.cpt size (%d, expected: %d)", (int)_cptFile->size(), SKY_CPT_SIZE);
}
// set the necessary data structs up...
_numDataLists = _cptFile->readUint16LE();
_cptNames = (char***)malloc(_numDataLists * sizeof(char**));
_dataListLen = (uint16 *)malloc(_numDataLists * sizeof(uint16));
_cptSizes = (uint16 **)malloc(_numDataLists * sizeof(uint16 *));
_cptTypes = (uint16 **)malloc(_numDataLists * sizeof(uint16 *));
_compacts = (Compact***)malloc(_numDataLists * sizeof(Compact**));
for (int i = 0; i < _numDataLists; i++) {
_dataListLen[i] = _cptFile->readUint16LE();
_cptNames[i] = (char**)malloc(_dataListLen[i] * sizeof(char *));
_cptSizes[i] = (uint16 *)malloc(_dataListLen[i] * sizeof(uint16));
_cptTypes[i] = (uint16 *)malloc(_dataListLen[i] * sizeof(uint16));
_compacts[i] = (Compact**)malloc(_dataListLen[i] * sizeof(Compact *));
}
uint32 rawSize = _cptFile->readUint32LE() * sizeof(uint16);
uint16 *rawPos = _rawBuf = (uint16 *)malloc(rawSize);
uint32 srcSize = _cptFile->readUint32LE() * sizeof(uint16);
uint16 *srcBuf = (uint16 *)malloc(srcSize);
uint16 *srcPos = srcBuf;
_cptFile->read(srcBuf, srcSize);
uint32 asciiSize = _cptFile->readUint32LE();
char *asciiPos = _asciiBuf = (char *)malloc(asciiSize);
_cptFile->read(_asciiBuf, asciiSize);
// and fill them with the compact data
for (uint32 lcnt = 0; lcnt < _numDataLists; lcnt++) {
for (uint32 ecnt = 0; ecnt < _dataListLen[lcnt]; ecnt++) {
_cptSizes[lcnt][ecnt] = READ_LE_UINT16(srcPos++);
if (_cptSizes[lcnt][ecnt]) {
_cptTypes[lcnt][ecnt] = READ_LE_UINT16(srcPos++);
_compacts[lcnt][ecnt] = (Compact *)rawPos;
_cptNames[lcnt][ecnt] = asciiPos;
asciiPos += strlen(asciiPos) + 1;
for (uint16 elemCnt = 0; elemCnt < _cptSizes[lcnt][ecnt]; elemCnt++)
*rawPos++ = READ_LE_UINT16(srcPos++);
} else {
_cptTypes[lcnt][ecnt] = 0;
_compacts[lcnt][ecnt] = NULL;
_cptNames[lcnt][ecnt] = NULL;
}
}
}
free(srcBuf);
uint16 numDlincs = _cptFile->readUint16LE();
uint16 *dlincBuf = (uint16 *)malloc(numDlincs * 2 * sizeof(uint16));
uint16 *dlincPos = dlincBuf;
_cptFile->read(dlincBuf, numDlincs * 2 * sizeof(uint16));
// these compacts don't actually exist but only point to other ones...
uint16 cnt;
for (cnt = 0; cnt < numDlincs; cnt++) {
uint16 dlincId = READ_LE_UINT16(dlincPos++);
uint16 destId = READ_LE_UINT16(dlincPos++);
assert(((dlincId >> 12) < _numDataLists) && ((dlincId & 0xFFF) < _dataListLen[dlincId >> 12]) && (_compacts[dlincId >> 12][dlincId & 0xFFF] == NULL));
_compacts[dlincId >> 12][dlincId & 0xFFF] = _compacts[destId >> 12][destId & 0xFFF];
assert(_cptNames[dlincId >> 12][dlincId & 0xFFF] == NULL);
_cptNames[dlincId >> 12][dlincId & 0xFFF] = asciiPos;
asciiPos += strlen(asciiPos) + 1;
}
free(dlincBuf);
// if this is v0.0288, parse this diff data
uint16 numDiffs = _cptFile->readUint16LE();
uint16 diffSize = _cptFile->readUint16LE();
uint16 *diffBuf = (uint16 *)malloc(diffSize * sizeof(uint16));
_cptFile->read(diffBuf, diffSize * sizeof(uint16));
if (SkyEngine::_systemVars->gameVersion == 288) {
uint16 *diffPos = diffBuf;
for (cnt = 0; cnt < numDiffs; cnt++) {
uint16 cptId = READ_LE_UINT16(diffPos++);
uint16 *rawCpt = (uint16 *)fetchCpt(cptId);
rawCpt += READ_LE_UINT16(diffPos++);
uint16 len = READ_LE_UINT16(diffPos++);
for (uint16 elemCnt = 0; elemCnt < len; elemCnt++)
rawCpt[elemCnt] = READ_LE_UINT16(diffPos++);
}
assert(diffPos == (diffBuf + diffSize));
}
free(diffBuf);
// these are the IDs that have to be saved into savegame files.
_numSaveIds = _cptFile->readUint16LE();
_saveIds = (uint16 *)malloc(_numSaveIds * sizeof(uint16));
_cptFile->read(_saveIds, _numSaveIds * sizeof(uint16));
for (cnt = 0; cnt < _numSaveIds; cnt++)
_saveIds[cnt] = FROM_LE_16(_saveIds[cnt]);
_resetDataPos = _cptFile->pos();
checkAndFixOfficerBluntError();
}
SkyCompact::~SkyCompact() {
free(_rawBuf);
free(_asciiBuf);
free(_saveIds);
for (int i = 0; i < _numDataLists; i++) {
free(_cptNames[i]);
free(_cptSizes[i]);
free(_cptTypes[i]);
free(_compacts[i]);
}
free(_cptNames);
free(_dataListLen);
free(_cptSizes);
free(_cptTypes);
free(_compacts);
_cptFile->close();
delete _cptFile;
}
/* WORKAROUND for bug #2687:
The first release of scummvm with externalized, binary compact data has one broken 16 bit reference.
When talking to Officer Blunt on ground level while in a crouched position, the game enters an
unfinishable state because Blunt jumps into the lake and can no longer be interacted with.
This fixes the problem when playing with a broken sky.cpt */
#define SCUMMVM_BROKEN_TALK_INDEX 158
void SkyCompact::checkAndFixOfficerBluntError() {
// Retrieve the table with the animation ids to use for talking
uint16 *talkTable = (uint16*)fetchCpt(CPT_TALK_TABLE_LIST);
if (talkTable[SCUMMVM_BROKEN_TALK_INDEX] == ID_SC31_GUARD_TALK) {
debug(1, "SKY.CPT with Officer Blunt bug encountered, fixing talk gfx.");
talkTable[SCUMMVM_BROKEN_TALK_INDEX] = ID_SC31_GUARD_TALK2;
}
}
// needed for some workaround where the engine has to check if it's currently processing joey, for example
bool SkyCompact::cptIsId(Compact *cpt, uint16 id) {
return (cpt == fetchCpt(id));
}
Compact *SkyCompact::fetchCpt(uint16 cptId) {
if (cptId == 0xFFFF) // is this really still necessary?
return NULL;
assert(((cptId >> 12) < _numDataLists) && ((cptId & 0xFFF) < _dataListLen[cptId >> 12]));
debug(8, "Loading Compact %s [%s] (%04X=%d,%d)", _cptNames[cptId >> 12][cptId & 0xFFF], nameForType(_cptTypes[cptId >> 12][cptId & 0xFFF]), cptId, cptId >> 12, cptId & 0xFFF);
return _compacts[cptId >> 12][cptId & 0xFFF];
}
Compact *SkyCompact::fetchCptInfo(uint16 cptId, uint16 *elems, uint16 *type, char *name, size_t nameSize) {
assert(((cptId >> 12) < _numDataLists) && ((cptId & 0xFFF) < _dataListLen[cptId >> 12]));
if (elems)
*elems = _cptSizes[cptId >> 12][cptId & 0xFFF];
if (type)
*type = _cptTypes[cptId >> 12][cptId & 0xFFF];
if (name) {
if (_cptNames[cptId >> 12][cptId & 0xFFF] != NULL)
Common::strcpy_s(name, nameSize, _cptNames[cptId >> 12][cptId & 0xFFF]);
else
Common::strcpy_s(name, nameSize, "(null)");
}
return fetchCpt(cptId);
}
const char *SkyCompact::nameForType(uint16 type) {
if (type >= NUM_CPT_TYPES)
return "unknown";
else
return _typeNames[type];
}
uint16 SkyCompact::getSub(Compact *cpt, uint16 mode) {
switch (mode) {
case 0:
return cpt->baseSub;
case 2:
return cpt->baseSub_off;
case 4:
return cpt->actionSub;
case 6:
return cpt->actionSub_off;
case 8:
return cpt->getToSub;
case 10:
return cpt->getToSub_off;
case 12:
return cpt->extraSub;
case 14:
return cpt->extraSub_off;
default:
error("Invalid Mode (%d)", mode);
}
}
void SkyCompact::setSub(Compact *cpt, uint16 mode, uint16 value) {
switch (mode) {
case 0:
cpt->baseSub = value;
return;
case 2:
cpt->baseSub_off = value;
return;
case 4:
cpt->actionSub = value;
return;
case 6:
cpt->actionSub_off = value;
return;
case 8:
cpt->getToSub = value;
return;
case 10:
cpt->getToSub_off = value;
return;
case 12:
cpt->extraSub = value;
return;
case 14:
cpt->extraSub_off = value;
return;
default:
error("Invalid Mode (%d)", mode);
}
}
uint16 *SkyCompact::getGrafixPtr(Compact *cpt) {
uint16 *gfxBase = (uint16 *)fetchCpt(cpt->grafixProgId);
if (gfxBase == NULL)
return NULL;
return gfxBase + cpt->grafixProgPos;
}
/**
* Returns the n'th mega set specified by \a megaSet from Compact \a cpt.
*/
MegaSet *SkyCompact::getMegaSet(Compact *cpt) {
switch (cpt->megaSet) {
case 0:
return &cpt->megaSet0;
case NEXT_MEGA_SET:
return &cpt->megaSet1;
case NEXT_MEGA_SET*2:
return &cpt->megaSet2;
case NEXT_MEGA_SET*3:
return &cpt->megaSet3;
default:
error("Invalid MegaSet (%d)", cpt->megaSet);
}
}
/**
\brief Returns the turn table for direction \a dir
from Compact \a cpt in \a megaSet.
Functionally equivalent to:
\verbatim
clear eax
mov al,20
mul (cpt[esi]).c_dir
add ax,(cpt[esi]).c_mega_set
lea eax,(cpt[esi+eax]).c_turn_table_up
\endverbatim
*/
uint16 *SkyCompact::getTurnTable(Compact *cpt, uint16 dir) {
MegaSet *m = getMegaSet(cpt);
TurnTable *turnTable = (TurnTable *)fetchCpt(m->turnTableId);
switch (dir) {
case 0:
return turnTable->turnTableUp;
case 1:
return turnTable->turnTableDown;
case 2:
return turnTable->turnTableLeft;
case 3:
return turnTable->turnTableRight;
case 4:
return turnTable->turnTableTalk;
default:
error("No TurnTable (%d) in MegaSet (%d)", dir, cpt->megaSet);
}
}
void *SkyCompact::getCompactElem(Compact *cpt, uint16 off) {
if (off < COMPACT_SIZE)
return((uint8 *)cpt + compactOffsets[off]);
off -= COMPACT_SIZE;
if (off < MEGASET_SIZE)
return((uint8 *)&(cpt->megaSet0) + megaSetOffsets[off]);
off -= MEGASET_SIZE;
if (off < TURNTABLE_SIZE)
return ((uint8 *)fetchCpt(cpt->megaSet0.turnTableId) + turnTableOffsets[off]);
off -= TURNTABLE_SIZE;
if (off < MEGASET_SIZE)
return((uint8 *)&(cpt->megaSet1) + megaSetOffsets[off]);
off -= MEGASET_SIZE;
if (off < TURNTABLE_SIZE)
return ((uint8 *)fetchCpt(cpt->megaSet1.turnTableId) + turnTableOffsets[off]);
off -= TURNTABLE_SIZE;
if (off < MEGASET_SIZE)
return((uint8 *)&(cpt->megaSet2) + megaSetOffsets[off]);
off -= MEGASET_SIZE;
if (off < TURNTABLE_SIZE)
return ((uint8 *)fetchCpt(cpt->megaSet2.turnTableId) + turnTableOffsets[off]);
off -= TURNTABLE_SIZE;
if (off < MEGASET_SIZE)
return((uint8 *)&(cpt->megaSet3) + megaSetOffsets[off]);
off -= MEGASET_SIZE;
if (off < TURNTABLE_SIZE)
return ((uint8 *)fetchCpt(cpt->megaSet3.turnTableId) + turnTableOffsets[off]);
off -= TURNTABLE_SIZE;
error("Offset %X out of bounds of compact", (int)(off + COMPACT_SIZE + 4 * MEGASET_SIZE + 4 * TURNTABLE_SIZE));
}
uint8 *SkyCompact::createResetData(uint16 gameVersion) {
_cptFile->seek(_resetDataPos);
uint32 dataSize = _cptFile->readUint16LE() * sizeof(uint16);
uint16 *resetBuf = (uint16 *)malloc(dataSize);
_cptFile->read(resetBuf, dataSize);
uint16 numDiffs = _cptFile->readUint16LE();
for (uint16 cnt = 0; cnt < numDiffs; cnt++) {
uint16 version = _cptFile->readUint16LE();
uint16 diffFields = _cptFile->readUint16LE();
if (version == gameVersion) {
for (uint16 diffCnt = 0; diffCnt < diffFields; diffCnt++) {
uint16 pos = _cptFile->readUint16LE();
resetBuf[pos] = TO_LE_16(_cptFile->readUint16LE());
}
return (uint8 *)resetBuf;
} else
_cptFile->seek(diffFields * 2 * sizeof(uint16), SEEK_CUR);
}
free(resetBuf);
error("Unable to find reset data for Beneath a Steel Sky Version 0.0%03d", gameVersion);
}
// - debugging functions
uint16 SkyCompact::findCptId(void *cpt) {
for (uint16 listCnt = 0; listCnt < _numDataLists; listCnt++)
for (uint16 elemCnt = 0; elemCnt < _dataListLen[listCnt]; elemCnt++)
if (_compacts[listCnt][elemCnt] == cpt)
return (listCnt << 12) | elemCnt;
// not found
debug(1, "Id for Compact %p wasn't found", cpt);
return 0;
}
uint16 SkyCompact::findCptId(const char *cptName) {
for (uint16 listCnt = 0; listCnt < _numDataLists; listCnt++)
for (uint16 elemCnt = 0; elemCnt < _dataListLen[listCnt]; elemCnt++)
if (_cptNames[listCnt][elemCnt] != 0)
if (scumm_stricmp(cptName, _cptNames[listCnt][elemCnt]) == 0)
return (listCnt << 12) | elemCnt;
// not found
debug(1, "Id for Compact %s wasn't found", cptName);
return 0;
}
uint16 SkyCompact::giveNumDataLists() {
return _numDataLists;
}
uint16 SkyCompact::giveDataListLen(uint16 listNum) {
if (listNum >= _numDataLists) // list doesn't exist
return 0;
else
return _dataListLen[listNum];
}
const char *const SkyCompact::_typeNames[NUM_CPT_TYPES] = {
"null",
"COMPACT",
"TURNTABLE",
"ANIM SEQ",
"UNKNOWN",
"GETTOTABLE",
"AR BUFFER",
"MAIN LIST"
};
} // End of namespace Sky

98
engines/sky/compact.h Normal file
View File

@@ -0,0 +1,98 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef SKY_COMPACT_H
#define SKY_COMPACT_H
#include "sky/sky.h"
#include "sky/struc.h"
#include "sky/skydefs.h"
namespace Common {
class File;
}
enum CptIds {
CPT_JOEY = 1,
CPT_FOSTER = 3,
CPT_TEXT_1 = 0x17,
CPT_TEXT_11 = 0x21,
CPT_MENU_BAR = 0x2E,
CPT_REICH_DOOR_20 = 0x30AB,
CPT_MOVE_LIST = 0xBD,
CPT_TALK_TABLE_LIST = 0xBC
};
enum CptTypeIds {
CPT_NULL = 0,
COMPACT,
TURNTAB,
ANIMSEQ,
MISCBIN,
GETTOTAB,
ROUTEBUF,
MAINLIST,
NUM_CPT_TYPES
};
namespace Sky {
class SkyCompact {
public:
SkyCompact();
~SkyCompact();
Compact *fetchCpt(uint16 cptId);
Compact *fetchCptInfo(uint16 cptId, uint16 *elems = NULL, uint16 *type = NULL, char *name = NULL, size_t nameSize = 0);
static uint16 getSub(Compact *cpt, uint16 mode);
static void setSub(Compact *cpt, uint16 mode, uint16 value);
static MegaSet *getMegaSet(Compact *cpt);
uint16 *getGrafixPtr(Compact *cpt);
uint16 *getTurnTable(Compact *cpt, uint16 dir);
void *getCompactElem(Compact *cpt, uint16 off);
bool cptIsId(Compact *cpt, uint16 id);
uint8 *createResetData(uint16 gameVersion);
uint16 _numSaveIds;
uint16 *_saveIds;
// - debugging functions
uint16 findCptId(void *cpt);
uint16 findCptId(const char *cptName);
uint16 giveNumDataLists();
uint16 giveDataListLen(uint16 listNum);
const char *nameForType(uint16 type);
private:
void checkAndFixOfficerBluntError();
uint16 _numDataLists;
uint16 *_dataListLen;
uint16 *_rawBuf;
char *_asciiBuf;
Compact ***_compacts;
char ***_cptNames;
uint16 **_cptSizes;
uint16 **_cptTypes;
Common::File *_cptFile;
uint32 _resetDataPos;
static const char *const _typeNames[NUM_CPT_TYPES];
};
} // End of namespace Sky
#endif

View File

@@ -0,0 +1,3 @@
# This file is included from the main "configure" script
# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] [components]
add_engine sky "Beneath a Steel Sky" yes "" "" "" "midi"

1726
engines/sky/control.cpp Normal file

File diff suppressed because it is too large Load Diff

305
engines/sky/control.h Normal file
View File

@@ -0,0 +1,305 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef SKY_CONTROL_H
#define SKY_CONTROL_H
#include "common/events.h"
#include "common/scummsys.h"
#include "common/str-array.h"
class OSystem;
namespace Common {
class SaveFileManager;
}
namespace Sky {
class Disk;
class Screen;
class Logic;
class Mouse;
class Text;
class MusicBase;
class Sound;
class SkyCompact;
class SkyEngine;
struct Compact;
struct DataFileHeader;
struct MegaSet;
#define MAX_SAVE_GAMES 999
#define MAX_TEXT_LEN 80
#define PAN_LINE_WIDTH 184
#define PAN_CHAR_HEIGHT 12
#define STATUS_WIDTH 146
#define MPNL_X 60 // Main Panel
#define MPNL_Y 10
#define SPNL_X 20 // Save Panel
#define SPNL_Y 20
#define SP_HEIGHT 149
#define SP_TOP_GAP 12
#define SP_BOT_GAP 27
#define CROSS_SZ_X 27
#define CROSS_SZ_Y 22
#define TEXT_FLAG_MASK (SF_ALLOW_SPEECH | SF_ALLOW_TEXT)
#define GAME_NAME_X (SPNL_X + 18) // x coordinate of game names
#define GAME_NAME_Y (SPNL_Y + SP_TOP_GAP) // start y coord of game names
#define MAX_ON_SCREEN ((SP_HEIGHT - SP_TOP_GAP - SP_BOT_GAP) / PAN_CHAR_HEIGHT) // no of save games on screen
#define CP_PANEL 60400 // main panel sprite
#define MAINPANEL 0
#define SAVEPANEL 1
#define NO_MASK false
#define WITH_MASK true
// resource's onClick routines
#define DO_NOTHING 0
#define REST_GAME_PANEL 1
#define SAVE_GAME_PANEL 2
#define SAVE_A_GAME 3
#define RESTORE_A_GAME 4
#define SP_CANCEL 5
#define SHIFT_DOWN_FAST 6
#define SHIFT_DOWN_SLOW 7
#define SHIFT_UP_FAST 8
#define SHIFT_UP_SLOW 9
#define SPEED_SLIDE 10
#define MUSIC_SLIDE 11
#define TOGGLE_FX 12
#define TOGGLE_MS 13
#define TOGGLE_TEXT 14
#define EXIT 15
#define RESTART 16
#define QUIT_TO_DOS 17
#define RESTORE_AUTO 18
// onClick return codes
#define CANCEL_PRESSED 100
#define NAME_TOO_SHORT 101
#define GAME_SAVED 102
#define SHIFTED 103
#define TOGGLED 104
#define RESTARTED 105
#define GAME_RESTORED 106
#define RESTORE_FAILED 107
#define NO_DISK_SPACE 108
#define SPEED_CHANGED 109
#define QUIT_PANEL 110
#define SLOW 0
#define FAST 1
#define SPEED_MULTIPLY 12
//-
#define SAVE_EXT 1
#define SAVE_MEGA0 2
#define SAVE_MEGA1 4
#define SAVE_MEGA2 8
#define SAVE_MEGA3 16
#define SAVE_GRAFX 32
#define SAVE_TURNP 64
#define SAVE_FILE_REVISION 6
#define OLD_SAVEGAME_TYPE 5
struct AllocedMem {
uint16 *mem;
AllocedMem *next;
};
class ConResource {
public:
ConResource(void *pSpData, uint32 pNSprites, uint32 pCurSprite, uint16 pX, uint16 pY, uint32 pText, uint8 pOnClick, OSystem *system, uint8 *screen);
virtual ~ConResource() {}
void setSprite(void *pSpData) { _spriteData = (DataFileHeader *)pSpData; }
void setText(uint32 pText) { if (pText) _text = pText + 0x7000; else _text = 0; }
void setXY(uint16 x, uint16 y) { _x = x; _y = y; }
bool isMouseOver(uint32 mouseX, uint32 mouseY);
virtual void drawToScreen(bool doMask);
DataFileHeader *_spriteData;
uint32 _numSprites, _curSprite;
uint16 _x, _y;
uint32 _text;
uint8 _onClick;
OSystem *_system;
uint8 *_screen;
private:
};
class TextResource : public ConResource {
public:
TextResource(void *pSpData, uint32 pNSprites, uint32 pCurSprite, uint16 pX, uint16 pY, uint32 pText, uint8 pOnClick, OSystem *system, uint8 *screen);
~TextResource() override;
void drawToScreen(bool doMask) override;
void flushForRedraw();
private:
uint16 _oldX, _oldY;
uint8 *_oldScreen;
};
class ControlStatus {
public:
ControlStatus(Text *skyText, OSystem *system, uint8 *scrBuf);
~ControlStatus();
void setToText(const char *newText);
void setToText(uint16 textNum);
void drawToScreen();
private:
TextResource *_statusText;
DataFileHeader *_textData;
Text *_skyText;
OSystem *_system;
uint8 *_screenBuf;
};
class Control {
public:
Control(SkyEngine *vm, Common::SaveFileManager *saveFileMan, Screen *screen, Disk *disk, Mouse *mouse, Text *text, MusicBase *music, Logic *logic, Sound *sound, SkyCompact *skyCompact, OSystem *system, Common::Keymap *shortcutsKeymap);
void doControlPanel();
void doLoadSavePanel();
void restartGame();
void showGameQuitMsg();
uint16 quickXRestore(uint16 slot);
bool loadSaveAllowed();
bool isControlPanelOpen();
SkyEngine *_vm;
uint16 _selectedGame;
uint16 saveGameToFile(bool fromControlPanel, const char *filename = 0, bool isAutosave = false);
void loadDescriptions(Common::StringArray &list);
void saveDescriptions(const Common::StringArray &list);
private:
int displayMessage(MSVC_PRINTF const char *message, ...) GCC_PRINTF(2, 3);
void initPanel();
void removePanel();
void drawMainPanel();
/**
* Waits for a specified amount while still processing events.
*
* @param amount The duration in milliseconds to wait
*/
void delay(unsigned int amount);
void animClick(ConResource *pButton);
bool getYesNo(char *text, uint bufSize);
void buttonControl(ConResource *pButton);
uint16 handleClick(ConResource *pButton);
uint16 doMusicSlide();
uint16 doSpeedSlide();
void toggleFx(ConResource *pButton);
uint16 toggleText();
void toggleMusic(ConResource *pButton);
uint16 shiftDown(uint8 speed);
uint16 shiftUp(uint8 speed);
void drawTextCross(uint32 flags);
void drawCross(uint16 x, uint16 y);
uint16 saveRestorePanel(bool allowSave);
void setUpGameSprites(const Common::StringArray &saveGameNames, DataFileHeader **nameSprites, uint16 firstNum, uint16 selectedGame, const Common::String &dirtyString);
void showSprites(DataFileHeader **nameSprites, bool allowSave);
void handleKeyPress(Common::KeyState kbd, Common::String &textBuf);
uint32 prepareSaveData(uint8 *destBuf);
bool autoSaveExists();
uint16 restoreGameFromFile(bool autoSave);
void importOldMegaSet(uint8 **srcPos, MegaSet *mega);
void importOldCompact(Compact* destCpt, uint8 **srcPos, uint16 numElems, uint16 type, char *name);
uint16 parseSaveData(uint8 *srcBuf);
Common::SaveFileManager *_saveFileMan;
SkyCompact *_skyCompact;
Screen *_skyScreen;
Disk *_skyDisk;
Mouse *_skyMouse;
Text *_skyText;
MusicBase *_skyMusic;
Logic *_skyLogic;
Sound *_skySound;
OSystem *_system;
bool _mouseClicked;
Common::KeyState _keyPressed;
Common::CustomEventType _action;
int _mouseWheel;
Common::Keymap *_shortcutsKeymap;
struct {
uint8 *controlPanel;
uint8 *button;
uint8 *buttonDown;
uint8 *savePanel;
uint8 *yesNo;
uint8 *slide;
uint8 *slode;
uint8 *slode2;
uint8 *slide2;
uint8 *musicBodge;
} _sprites;
uint8 *_screenBuf;
int _lastButton;
uint32 _curButtonText;
uint16 _firstText;
uint16 _savedMouse;
uint32 _savedCharSet;
uint16 _enteredTextWidth;
ConResource *createResource(void *pSpData, uint32 pNSprites, uint32 pCurSprite, int16 pX, int16 pY, uint32 pText, uint8 pOnClick, uint8 panelType);
DataFileHeader *_textSprite;
TextResource *_text;
ConResource *_controlPanel, *_exitButton, *_slide, *_slide2, *_slode;
ConResource *_restorePanButton, *_savePanButton, *_dosPanButton, *_restartPanButton, *_fxPanButton, *_musicPanButton;
ConResource *_bodge, *_yesNo;
ConResource *_controlPanLookList[9];
//- Save/restore panel
ConResource *_savePanel;
ConResource *_saveButton, *_downFastButton, *_downSlowButton;
ConResource *_upFastButton, *_upSlowButton, *_quitButton, *_restoreButton;
ConResource *_autoSaveButton;
ConResource *_savePanLookList[6], *_restorePanLookList[7];
ControlStatus *_statusBar;
static char _quitTexts[20][45];
static uint8 _crossImg[594];
};
} // End of namespace Sky
#endif // CONTROL_H

5
engines/sky/credits.pl Normal file
View File

@@ -0,0 +1,5 @@
begin_section("Sky");
add_person("Robert G&ouml;ffringmann", "lavosspawn", "(retired)");
add_person("Oliver Kiehl", "olki", "(retired)");
add_person("Joost Peters", "joostp", "");
end_section();

1353
engines/sky/debug.cpp Normal file

File diff suppressed because it is too large Load Diff

75
engines/sky/debug.h Normal file
View File

@@ -0,0 +1,75 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef SKY_DEBUG_H
#define SKY_DEBUG_H
#include "common/scummsys.h"
#include "gui/debugger.h"
namespace Sky {
class Logic;
class Mouse;
class Screen;
class SkyCompact;
class Debugger : public GUI::Debugger {
public:
Debugger(Logic *logic, Mouse *mouse, Screen *screen, SkyCompact *skyCompact);
bool showGrid() { return _showGrid; }
private:
void preEnter() override;
void postEnter() override;
private:
bool Cmd_ShowGrid(int argc, const char **argv);
bool Cmd_ReloadGrid(int argc, const char **argv);
bool Cmd_ShowCompact(int argc, const char **argv);
bool Cmd_LogicCommand(int argc, const char **argv);
bool Cmd_Info(int argc, const char **argv);
bool Cmd_ScriptVar(int argc, const char **argv);
bool Cmd_Section(int argc, const char **argv);
bool Cmd_LogicList(int argc, const char **argv);
void dumpCompact(uint16 cptId);
Logic *_logic;
Mouse *_mouse;
Screen *_screen;
SkyCompact *_skyCompact;
bool _showGrid;
};
class Debug {
public:
static void logic(uint32 logic);
static void script(uint32 command, uint16 *scriptData);
static void mcode(uint32 mcode, uint32 a, uint32 b, uint32 c);
};
} // End of namespace Sky
#endif

217
engines/sky/detection.cpp Normal file
View File

@@ -0,0 +1,217 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#include "base/plugins.h"
#include "common/config-manager.h"
#include "common/md5.h"
#include "engines/advancedDetector.h"
#include "engines/metaengine.h"
#include "common/system.h"
#include "common/file.h"
#include "common/textconsole.h"
static const PlainGameDescriptor skySetting =
{"sky", "Beneath a Steel Sky" };
struct SkyVersion {
int dinnerTableEntries;
int dataDiskSize;
const char *extraDesc;
int version;
const char *guioptions;
};
// TODO: Would be nice if Disk::determineGameVersion() used this table, too.
static const SkyVersion skyVersions[] = {
{ 232, 734425, "Floppy Demo", 272, GUIO1(GUIO_NOSPEECH) }, // German
{ 243, 1328979, "PC Gamer Demo", 109, GUIO1(GUIO_NOSPEECH) },
{ 247, 814147, "Floppy Demo", 267, GUIO1(GUIO_NOSPEECH) }, // English
{ 1404, 8252443, "Floppy", 288, GUIO1(GUIO_NOSPEECH) },
{ 1413, 8387069, "Floppy", 303, GUIO1(GUIO_NOSPEECH) },
{ 1445, 8830435, "Floppy", 348, GUIO1(GUIO_NOSPEECH) },
{ 1445, -1, "Floppy", 331, GUIO1(GUIO_NOSPEECH) },
{ 1711, 26623798, "CD Demo", 365, GUIO0() },
{ 5099, 72429382, "CD", 368, GUIO0() },
{ 5097, 72395713, "CD", 372, GUIO0() },
{ 5097, 73123264, "CD", 372, GUIO0() },
{ 0, 0, 0, 0, 0 }
};
class SkyMetaEngineDetection : public MetaEngineDetection {
public:
const char *getEngineName() const override;
const char *getOriginalCopyright() const override;
const char *getName() const override {
return "sky";
}
PlainGameList getSupportedGames() const override;
PlainGameDescriptor findGame(const char *gameid) const override;
Common::Error identifyGame(DetectedGame &game, const void **descriptor) override;
DetectedGames detectGames(const Common::FSList &fslist, uint32 /*skipADFlags*/, bool /*skipIncomplete*/) override;
uint getMD5Bytes() const override {
return 0;
}
void dumpDetectionEntries() const override final {}
int getGameVariantCount() const override {
int entries = 0;
for (const SkyVersion *sv = skyVersions; sv->dinnerTableEntries; ++sv)
++entries;
return entries;
}
};
const char *SkyMetaEngineDetection::getEngineName() const {
return "Beneath a Steel Sky";
}
const char *SkyMetaEngineDetection::getOriginalCopyright() const {
return "Beneath a Steel Sky (C) Revolution";
}
PlainGameList SkyMetaEngineDetection::getSupportedGames() const {
PlainGameList games;
games.push_back(skySetting);
return games;
}
PlainGameDescriptor SkyMetaEngineDetection::findGame(const char *gameid) const {
if (0 == scumm_stricmp(gameid, skySetting.gameId))
return skySetting;
return PlainGameDescriptor::empty();
}
Common::Error SkyMetaEngineDetection::identifyGame(DetectedGame &game, const void **descriptor) {
*descriptor = nullptr;
game = DetectedGame(getName(), findGame(ConfMan.get("gameid").c_str()));
return game.gameId.empty() ? Common::kUnknownError : Common::kNoError;
}
DetectedGames SkyMetaEngineDetection::detectGames(const Common::FSList &fslist, uint32 /*skipADFlags*/, bool /*skipIncomplete*/) {
DetectedGames detectedGames;
bool hasSkyDsk = false;
bool hasSkyDnr = false;
int dinnerTableEntries = -1;
int dataDiskSize = -1;
Common::String dataDiskHeadMD5 = "";
int exeSize = -1;
const Common::Language langs[] = {
Common::EN_GRB,
Common::DE_DEU,
Common::FR_FRA,
Common::EN_USA,
Common::SV_SWE,
Common::IT_ITA,
Common::PT_BRA,
Common::ES_ESP,
};
bool langTable[ARRAYSIZE(langs)];
memset(langTable, 0, sizeof(langTable));
// Iterate over all files in the given directory
for (Common::FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) {
if (!file->isDirectory()) {
if (0 == scumm_stricmp("sky.dsk", file->getName().c_str())) {
Common::File dataDisk;
if (dataDisk.open(*file)) {
hasSkyDsk = true;
dataDiskSize = dataDisk.size();
if (dataDiskSize == 73123264 || dataDiskSize == 75893200)
dataDiskHeadMD5 = Common::computeStreamMD5AsString(dataDisk, 5000);
}
}
if (0 == scumm_stricmp("sky.dnr", file->getName().c_str())) {
Common::File dinner;
if (dinner.open(*file)) {
hasSkyDnr = true;
dinnerTableEntries = dinner.readUint32LE();
for (int i = 0; i < dinnerTableEntries; i++) {
uint16 entry = dinner.readUint16LE();
dinner.readUint16LE();
if (entry >= 60600 && entry < 60600 + 8 * ARRAYSIZE(langs) && (entry % 8) == 0)
langTable[(entry - 60600) / 8] = true;
}
}
}
if (0 == scumm_stricmp("sky.exe", file->getName().c_str())) {
Common::File skyExe;
if (skyExe.open(*file)) {
exeSize = skyExe.size();
}
}
}
}
if (hasSkyDsk && hasSkyDnr) {
// Match found, add to list of candidates, then abort inner loop.
// The game detector uses US English by default. We want British
// English to match the recorded voices better.
const SkyVersion *sv = skyVersions;
while (sv->dinnerTableEntries) {
if (dinnerTableEntries == sv->dinnerTableEntries &&
(sv->dataDiskSize == dataDiskSize || sv->dataDiskSize == -1)) {
break;
}
++sv;
}
DetectedGame game;
Common::Language lang = Common::Language::UNK_LANG;
if (dataDiskSize == 73123264 && dataDiskHeadMD5 == "886d6faecd97488be09b73f4f87b92d9")
lang = Common::Language::RU_RUS;
if (dataDiskSize == 75893200 && dataDiskHeadMD5 == "886d6faecd97488be09b73f4f87b92d9")
lang = Common::Language::HE_ISR;
if (sv->dinnerTableEntries) {
Common::String extra = Common::String::format("v0.0%d %s", sv->version, sv->extraDesc);
game = DetectedGame(getName(), skySetting.gameId, skySetting.description, lang, Common::kPlatformDOS, extra);
game.setGUIOptions(sv->guioptions);
} else {
game = DetectedGame(getName(), skySetting.gameId, skySetting.description, lang);
}
if (lang == Common::Language::UNK_LANG) {
for (uint i = 0; i < ARRAYSIZE(langs); i++) {
if (langTable[i])
game.appendGUIOptions(Common::getGameGUIOptionsDescriptionLanguage(langs[i]));
}
if (exeSize == 575538)
game.appendGUIOptions(Common::getGameGUIOptionsDescriptionLanguage(Common::ZH_TWN));
} else
game.appendGUIOptions(Common::getGameGUIOptionsDescriptionLanguage(lang));
detectedGames.push_back(game);
}
return detectedGames;
}
REGISTER_PLUGIN_STATIC(SKY_DETECTION, PLUGIN_TYPE_ENGINE_DETECTION, SkyMetaEngineDetection);

371
engines/sky/disk.cpp Normal file
View File

@@ -0,0 +1,371 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#include "common/debug.h"
#include "common/textconsole.h"
#include "common/endian.h"
#include "common/file.h"
#include "sky/disk.h"
#include "sky/sky.h"
#include "sky/struc.h"
namespace Sky {
static const char *const dataFilename = "sky.dsk";
static const char *const dinnerFilename = "sky.dnr";
Disk::Disk() {
_dataDiskHandle = new Common::File();
Common::File *dnrHandle = new Common::File();
dnrHandle->open(dinnerFilename);
if (!dnrHandle->isOpen())
error("Could not open %s", dinnerFilename);
if (!(_dinnerTableEntries = dnrHandle->readUint32LE()))
error("Error reading from sky.dnr"); //even though it was opened correctly?!
_dinnerTableArea = (uint8 *)malloc(_dinnerTableEntries * 8);
uint32 entriesRead = dnrHandle->read(_dinnerTableArea, 8 * _dinnerTableEntries) / 8;
if (entriesRead != _dinnerTableEntries)
error("entriesRead != dinnerTableEntries. [%d/%d]", entriesRead, _dinnerTableEntries);
_dataDiskHandle->open(dataFilename);
if (!_dataDiskHandle->isOpen())
error("Error opening %s", dataFilename);
debug("Found BASS version v0.0%d (%d dnr entries)", determineGameVersion(), _dinnerTableEntries);
memset(_buildList, 0, 60 * 2);
memset(_loadedFilesList, 0, 60 * 4);
dnrHandle->close();
delete dnrHandle;
}
Disk::~Disk() {
if (_dataDiskHandle->isOpen())
_dataDiskHandle->close();
fnFlushBuffers();
free(_dinnerTableArea);
delete _dataDiskHandle;
}
bool Disk::fileExists(uint16 fileNr) {
return (getFileInfo(fileNr) != NULL);
}
// allocate memory, load the file and return a pointer
uint8 *Disk::loadFile(uint16 fileNr) {
uint8 cflag;
debug(3, "load file %d,%d (%d)", (fileNr >> 11), (fileNr & 2047), fileNr);
uint8 *fileInfoPtr = getFileInfo(fileNr);
if (fileInfoPtr == NULL) {
debug(1, "File %d not found", fileNr);
return NULL;
}
uint32 fileFlags = READ_LE_UINT24(fileInfoPtr + 5);
uint32 fileSize = fileFlags & 0x03fffff;
uint32 fileOffset = READ_LE_UINT32(fileInfoPtr + 2) & 0x0ffffff;
_lastLoadedFileSize = fileSize;
cflag = (uint8)((fileOffset >> 23) & 0x1);
fileOffset &= 0x7FFFFF;
if (cflag) {
if (SkyEngine::_systemVars->gameVersion == 331)
fileOffset <<= 3;
else
fileOffset <<= 4;
}
uint8 *fileDest = (uint8 *)malloc(fileSize + 4); // allocate memory for file
_dataDiskHandle->seek(fileOffset, SEEK_SET);
//now read in the data
int32 bytesRead = _dataDiskHandle->read(fileDest, fileSize);
if (bytesRead != (int32)fileSize)
warning("Unable to read %d bytes from datadisk (%d bytes read)", fileSize, bytesRead);
cflag = (uint8)((fileFlags >> 23) & 0x1);
//if cflag == 0 then file is compressed, 1 == uncompressed
DataFileHeader *fileHeader = (DataFileHeader *)fileDest;
if ((!cflag) && ((FROM_LE_16(fileHeader->flag) >> 7) & 1)) {
debug(4, "File is RNC compressed.");
uint32 decompSize = (FROM_LE_16(fileHeader->flag) & ~0xFF) << 8;
decompSize |= FROM_LE_16(fileHeader->s_tot_size);
uint8 *uncompDest = (uint8 *)malloc(decompSize);
int32 unpackLen;
void *output, *input = fileDest + sizeof(DataFileHeader);
if ((fileFlags >> 22) & 0x1) { //do we include the header?
// don't return the file's header
output = uncompDest;
unpackLen = _rncDecoder.unpackM1(input, fileSize - sizeof(DataFileHeader), output);
} else {
#ifdef SCUMM_BIG_ENDIAN
// Convert DataFileHeader to BE (it only consists of 16 bit words)
uint16 *headPtr = (uint16 *)fileDest;
for (uint i = 0; i < sizeof(DataFileHeader) / 2; i++)
*(headPtr + i) = READ_LE_UINT16(headPtr + i);
#endif
memcpy(uncompDest, fileDest, sizeof(DataFileHeader));
output = uncompDest + sizeof(DataFileHeader);
unpackLen = _rncDecoder.unpackM1(input, fileSize - sizeof(DataFileHeader), output);
if (unpackLen)
unpackLen += sizeof(DataFileHeader);
}
debug(5, "UnpackM1 returned: %d", unpackLen);
if (unpackLen == 0) { //Unpack returned 0: file was probably not packed.
free(uncompDest);
return fileDest;
} else {
if (unpackLen != (int32)decompSize)
debug(1, "ERROR: File %d: invalid decomp size! (was: %d, should be: %d)", fileNr, unpackLen, decompSize);
_lastLoadedFileSize = decompSize;
free(fileDest);
return uncompDest;
}
} else {
#ifdef SCUMM_BIG_ENDIAN
if (!cflag) {
uint16 *headPtr = (uint16 *)fileDest;
for (uint i = 0; i < sizeof(DataFileHeader) / 2; i++)
*(headPtr + i) = READ_LE_UINT16(headPtr + i);
}
#endif
return fileDest;
}
}
uint16 *Disk::loadScriptFile(uint16 fileNr) {
uint16 *buf = (uint16 *)loadFile(fileNr);
#ifdef SCUMM_BIG_ENDIAN
for (uint i = 0; i < _lastLoadedFileSize / 2; i++)
buf[i] = FROM_LE_16(buf[i]);
#endif
return buf;
}
uint8 *Disk::getFileInfo(uint16 fileNr) {
uint16 i;
uint16 *dnrTbl16Ptr = (uint16 *)_dinnerTableArea;
for (i = 0; i < _dinnerTableEntries; i++) {
if (READ_LE_UINT16(dnrTbl16Ptr) == fileNr) {
debug(4, "file %d found", fileNr);
return (uint8 *)dnrTbl16Ptr;
}
dnrTbl16Ptr += 4;
}
return 0; //not found
}
void Disk::fnCacheChip(uint16 *fList) {
// fnCacheChip is called after fnCacheFast
uint16 cnt = 0;
while (_buildList[cnt])
cnt++;
uint16 fCnt = 0;
do {
_buildList[cnt + fCnt] = fList[fCnt] & 0x7FFFU;
fCnt++;
} while (fList[fCnt-1]);
fnCacheFiles();
}
void Disk::fnCacheFast(uint16 *fList) {
if (fList != NULL) {
uint8 cnt = 0;
do {
_buildList[cnt] = fList[cnt] & 0x7FFFU;
cnt++;
} while (fList[cnt-1]);
}
}
void Disk::fnCacheFiles() {
uint16 lCnt, bCnt, targCnt;
targCnt = lCnt = 0;
bool found;
while (_loadedFilesList[lCnt]) {
bCnt = 0;
found = false;
while (_buildList[bCnt] && (!found)) {
if ((_buildList[bCnt] & 0x7FFFU) == _loadedFilesList[lCnt])
found = true;
else
bCnt++;
}
if (found) {
_loadedFilesList[targCnt] = _loadedFilesList[lCnt];
targCnt++;
} else {
free(SkyEngine::_itemList[_loadedFilesList[lCnt] & 2047]);
SkyEngine::_itemList[_loadedFilesList[lCnt] & 2047] = NULL;
}
lCnt++;
}
_loadedFilesList[targCnt] = 0; // mark end of list
bCnt = 0;
while (_buildList[bCnt]) {
if ((_buildList[bCnt] & 0x7FF) == 0x7FF) {
// amiga dummy files
bCnt++;
continue;
}
lCnt = 0;
found = false;
while (_loadedFilesList[lCnt] && (!found)) {
if (_loadedFilesList[lCnt] == (_buildList[bCnt] & 0x7FFFU))
found = true;
lCnt++;
}
if (found) {
bCnt++;
continue;
}
// ok, we really have to load the file.
_loadedFilesList[targCnt] = _buildList[bCnt] & 0x7FFFU;
targCnt++;
_loadedFilesList[targCnt] = 0;
SkyEngine::_itemList[_buildList[bCnt] & 2047] = (void**)loadFile(_buildList[bCnt] & 0x7FFF);
if (!SkyEngine::_itemList[_buildList[bCnt] & 2047])
warning("fnCacheFiles: Disk::loadFile() returned NULL for file %d",_buildList[bCnt] & 0x7FFF);
bCnt++;
}
_buildList[0] = 0;
}
void Disk::refreshFilesList(uint32 *list) {
uint8 cnt = 0;
while (_loadedFilesList[cnt]) {
if (SkyEngine::_itemList[_loadedFilesList[cnt] & 2047])
free(SkyEngine::_itemList[_loadedFilesList[cnt] & 2047]);
SkyEngine::_itemList[_loadedFilesList[cnt] & 2047] = NULL;
cnt++;
}
cnt = 0;
while (list[cnt]) {
_loadedFilesList[cnt] = list[cnt];
SkyEngine::_itemList[_loadedFilesList[cnt] & 2047] = (void**)loadFile((uint16)(_loadedFilesList[cnt] & 0x7FFF));
cnt++;
}
_loadedFilesList[cnt] = 0;
}
void Disk::fnMiniLoad(uint16 fileNum) {
uint16 cnt = 0;
while (_loadedFilesList[cnt]) {
if (_loadedFilesList[cnt] == fileNum)
return;
cnt++;
}
_loadedFilesList[cnt] = fileNum & 0x7FFFU;
_loadedFilesList[cnt + 1] = 0;
SkyEngine::_itemList[fileNum & 2047] = (void**)loadFile(fileNum);
}
void Disk::fnFlushBuffers() {
// dump all loaded sprites
uint8 lCnt = 0;
while (_loadedFilesList[lCnt]) {
free(SkyEngine::_itemList[_loadedFilesList[lCnt] & 2047]);
SkyEngine::_itemList[_loadedFilesList[lCnt] & 2047] = NULL;
lCnt++;
}
_loadedFilesList[0] = 0;
}
void Disk::dumpFile(uint16 fileNr) {
char buf[128];
Common::DumpFile out;
byte* filePtr;
filePtr = loadFile(fileNr);
Common::sprintf_s(buf, "dumps/file-%d.dmp", fileNr);
if (!Common::File::exists(buf)) {
if (out.open(buf))
out.write(filePtr, _lastLoadedFileSize);
}
free(filePtr);
}
uint32 Disk::determineGameVersion() {
//determine game version based on number of entries in dinner table
switch (_dinnerTableEntries) {
case 232:
// German floppy demo (v0.0272)
return 272;
case 243:
// pc gamer demo (v0.0109)
return 109;
case 247:
// English floppy demo (v0.0267)
return 267;
case 1404:
//floppy (v0.0288)
return 288;
case 1413:
//floppy (v0.0303)
return 303;
case 1445:
//floppy (v0.0331 or v0.0348)
if (_dataDiskHandle->size() == 8830435)
return 348;
else
return 331;
case 1711:
//cd demo (v0.0365)
return 365;
case 5099:
//cd (v0.0368)
return 368;
case 5097:
//cd (v0.0372)
return 372;
default:
//unknown version
error("Unknown game version! %d dinner table entries", _dinnerTableEntries);
break;
}
}
} // End of namespace Sky

73
engines/sky/disk.h Normal file
View File

@@ -0,0 +1,73 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef SKY_DISK_H
#define SKY_DISK_H
#include "common/scummsys.h"
#include "common/compression/rnc_deco.h"
#define MAX_FILES_IN_LIST 60
namespace Common {
class File;
}
namespace Sky {
class Disk {
public:
Disk();
~Disk();
uint8 *loadFile(uint16 fileNr);
uint16 *loadScriptFile(uint16 fileNr);
bool fileExists(uint16 fileNr);
uint32 determineGameVersion();
uint32 _lastLoadedFileSize;
void fnMiniLoad(uint16 fileNum);
void fnCacheFast(uint16 *fList);
void fnCacheChip(uint16 *fList);
void fnCacheFiles();
void fnFlushBuffers();
uint32 *giveLoadedFilesList() { return _loadedFilesList; }
void refreshFilesList(uint32 *list);
protected:
uint8 *getFileInfo(uint16 fileNr);
void dumpFile(uint16 fileNr);
uint32 _dinnerTableEntries;
uint8 *_dinnerTableArea;
Common::File *_dataDiskHandle;
Common::RncDecoder _rncDecoder;
uint16 _buildList[MAX_FILES_IN_LIST];
uint32 _loadedFilesList[MAX_FILES_IN_LIST];
};
} // End of namespace Sky
#endif

260
engines/sky/grid.cpp Normal file
View File

@@ -0,0 +1,260 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#include "sky/compact.h"
#include "sky/disk.h"
#include "sky/grid.h"
#include "sky/logic.h"
#include "sky/compact.h"
namespace Sky {
#define GRID_FILE_START 60000
int8 Grid::_gridConvertTable[] = {
0, //0
1, //1
2, //2
3, //3
4, //4
5, //5
6, //6
7, //7
8, //8
9, //9
10, //10
11, //11
12, //12
13, //13
14, //14
15, //15
16, //16
17, //17
18, //18
19, //19
20, //20
21, //21
22, //22
23, //23
24, //24
25, //25
26, //26
27, //27
28, //28
29, //29
30, //30
31, //31
32, //32
33, //33
34, //34
-1, //35
35, //36
36, //37
37, //38
38, //39
39, //40
40, //41
41, //42
-1, //43
42, //44
43, //45
44, //46
45, //47
46, //48
-1, //49
-1, //50
-1, //51
-1, //52
-1, //53
-1, //54
-1, //55
-1, //56
-1, //57
-1, //58
-1, //59
-1, //60
-1, //61
-1, //62
-1, //63
-1, //64
47, //65
TOT_NO_GRIDS, //66
48, //67
49, //68
50, //69
51, //70
52, //71
53, //72
54, //73
55, //74
56, //75
57, //76
58, //77
59, //78
60, //79
-1, //80
61, //81
62, //82
-1, //83
-1, //84
-1, //85
-1, //86
-1, //87
-1, //88
TOT_NO_GRIDS, //89
63, //90
64, //91
65, //92
66, //93
67, //94
68, //95
69, //96
};
Grid::Grid(Disk *pDisk, SkyCompact *skyCompact) {
for (int cnt = 0; cnt < TOT_NO_GRIDS; cnt++)
_gameGrids[cnt] = NULL;
_skyDisk = pDisk;
_skyCompact = skyCompact;
}
Grid::~Grid() {
for (uint8 cnt = 0; cnt < TOT_NO_GRIDS; cnt++)
if (_gameGrids[cnt])
free(_gameGrids[cnt]);
}
void Grid::loadGrids() {
// no endian conversion necessary as I'm using uint8* instead of uint32*
for (uint8 cnt = 0; cnt < TOT_NO_GRIDS; cnt++) {
if (_gameGrids[cnt])
free(_gameGrids[cnt]);
_gameGrids[cnt] = _skyDisk->loadFile(GRID_FILE_START + cnt);
}
if (!SkyEngine::isDemo()) { // single disk demos never get that far
// Reloading the grids can sometimes cause problems eg when reichs door is
// open the door grid bit gets replaced so you can't get back in (or out)
if (Logic::_scriptVariables[REICH_DOOR_FLAG])
removeGrid(256, 280, 1, _skyCompact->fetchCpt(CPT_REICH_DOOR_20));
//removeGrid(256, 280, 1, &SkyCompact::reich_door_20);
}
}
bool Grid::getGridValues(Compact *cpt, uint8 *resGrid, uint32 *resBitNum, uint32 *resWidth) {
uint16 width = SkyCompact::getMegaSet(cpt)->gridWidth;
return getGridValues(cpt->xcood, cpt->ycood, width, cpt, resGrid, resBitNum, resWidth);
}
bool Grid::getGridValues(uint32 x, uint32 y, uint32 width, Compact *cpt, uint8 *resGrid, uint32 *resBitNum, uint32 *resWidth) {
uint32 bitPos;
if (y < TOP_LEFT_Y)
return false; // off screen
y -= TOP_LEFT_Y;
y >>= 3; // convert to blocks
if (y >= GAME_SCREEN_HEIGHT >> 3)
return false; // off screen
bitPos = y * 40;
width++;
x >>= 3; // convert to blocks
if (x < (TOP_LEFT_X >> 3)) { // at least partially off screen
if (x + width < (TOP_LEFT_X >> 3))
return false; // completely off screen
else {
width -= (TOP_LEFT_X >> 3) - x;
x = 0;
}
} else
x -= TOP_LEFT_X >> 3;
if ((GAME_SCREEN_WIDTH >> 3) <= x)
return false; // off screen
if ((GAME_SCREEN_WIDTH >> 3) < x + width) // partially off screen
width = (GAME_SCREEN_WIDTH >> 3) - x;
bitPos += x;
assert((_gridConvertTable[cpt->screen] >= 0) && (_gridConvertTable[cpt->screen] < TOT_NO_GRIDS));
*resGrid = (uint8)_gridConvertTable[cpt->screen];
uint32 tmpBits = 0x1F - (bitPos&0x1F);
bitPos &= ~0x1F; // divide into dword address and bit number
bitPos += tmpBits;
*resBitNum = bitPos;
*resWidth = width;
return true;
}
void Grid::removeObjectFromWalk(Compact *cpt) {
uint32 bitNum, width;
uint8 gridIdx;
if (getGridValues(cpt, &gridIdx, &bitNum, &width))
removeObjectFromWalk(gridIdx, bitNum, width);
}
void Grid::removeObjectFromWalk(uint8 gridIdx, uint32 bitNum, uint32 width) {
for (uint32 cnt = 0; cnt < width; cnt++) {
_gameGrids[gridIdx][bitNum >> 3] &= ~(1 << (bitNum & 0x7));
if ((bitNum & 0x1F) == 0)
bitNum += 0x3F;
else
bitNum--;
}
}
void Grid::objectToWalk(Compact *cpt) {
uint32 bitNum, width;
uint8 gridIdx;
if (getGridValues(cpt, &gridIdx, &bitNum, &width))
objectToWalk(gridIdx, bitNum, width);
}
void Grid::objectToWalk(uint8 gridIdx, uint32 bitNum, uint32 width) {
for (uint32 cnt = 0; cnt < width; cnt++) {
_gameGrids[gridIdx][bitNum >> 3] |= (1 << (bitNum & 0x7));
if ((bitNum & 0x1F) == 0)
bitNum += 0x3F;
else
bitNum--;
}
}
void Grid::plotGrid(uint32 x, uint32 y, uint32 width, Compact *cpt) {
uint32 resBitPos, resWidth;
uint8 resGridIdx;
if (getGridValues(x, y, width-1, cpt, &resGridIdx, &resBitPos, &resWidth))
objectToWalk(resGridIdx, resBitPos, resWidth);
}
void Grid::removeGrid(uint32 x, uint32 y, uint32 width, Compact *cpt) {
uint32 resBitPos, resWidth;
uint8 resGridIdx;
if (getGridValues(x, y, width, cpt, &resGridIdx, &resBitPos, &resWidth))
removeObjectFromWalk(resGridIdx, resBitPos, resWidth);
}
uint8 *Grid::giveGrid(uint32 pScreen) {
if ((_gridConvertTable[pScreen] >= 0) && (_gridConvertTable[pScreen] < TOT_NO_GRIDS)) {
return _gameGrids[_gridConvertTable[pScreen]];
}
return 0;
}
} // End of namespace Sky

67
engines/sky/grid.h Normal file
View File

@@ -0,0 +1,67 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef SKY_GRID_H
#define SKY_GRID_H
#include "common/scummsys.h"
#include "skydefs.h"
namespace Sky {
struct Compact;
class Disk;
class SkyCompact;
class Grid {
public:
Grid(Disk *pDisk, SkyCompact *skyCompact);
~Grid();
// grid.asm routines
void loadGrids();
void removeObjectFromWalk(Compact *cpt);
void objectToWalk(Compact *cpt);
// function.asm
// note that this routine does the same as objectToWalk, it just doesn't get
// its x, y, width parameters from cpt.
void plotGrid(uint32 x, uint32 y, uint32 width, Compact *cpt);
// same here, it's basically the same as removeObjectFromWalk
void removeGrid(uint32 x, uint32 y, uint32 width, Compact *cpt);
uint8 *giveGrid(uint32 pScreen);
private:
void objectToWalk(uint8 gridIdx, uint32 bitNum, uint32 width);
void removeObjectFromWalk(uint8 gridIdx, uint32 bitNum, uint32 width);
bool getGridValues(Compact *cpt, uint8 *resGrid, uint32 *resBitNum, uint32 *resWidth);
bool getGridValues(uint32 x, uint32 y, uint32 width, Compact *cpt, uint8 *resGrid, uint32 *resBitNum, uint32 *resWidth);
static int8 _gridConvertTable[];
uint8 *_gameGrids[TOT_NO_GRIDS];
Disk *_skyDisk;
SkyCompact *_skyCompact;
};
} // End of namespace Sky
#endif //SKYGRID_H

2004
engines/sky/hufftext.cpp Normal file

File diff suppressed because it is too large Load Diff

951
engines/sky/intro.cpp Normal file
View File

@@ -0,0 +1,951 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#include "common/endian.h"
#include "common/util.h"
#include "common/events.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "sky/disk.h"
#include "sky/intro.h"
#include "sky/music/musicbase.h"
#include "sky/screen.h"
#include "sky/sky.h"
#include "sky/sound.h"
#include "sky/struc.h"
#include "sky/text.h"
#include "audio/audiostream.h"
#include "audio/decoders/raw.h"
namespace Sky {
#define SHOWSCREEN 0
#define COMMANDEND 0 // end of COMMANDFLIRT block
#define FADEUP 1 // fade up palette
#define FADEDOWN 2
#define DELAY 3
#define DOFLIRT 4 // start flirt sequence (and wait for it to finish)
#define SCROLLFLIRT 5 // start special floppy intro flirt sequence (and wait for it)
#define COMMANDFLIRT 6 // start flirt sequence and wait for it, while processing command block
#define BGFLIRT 7 // start flirt sequence without waiting for it
#define WAITFLIRT 8 // wait for sequence started by BGFLIRT
#define STOPFLIRT 9
#define STARTMUSIC 10
#define WAITMUSIC 11
#define PLAYVOICE 12
#define WAITVOICE 13
#define LOADBG 14 // load new background sound
#define PLAYBG 15 // play background sound
#define LOOPBG 16 // loop background sound
#define STOPBG 17 // stop background sound
#define CLEARBOTTOM 18 // clear the screen
#define SEQEND 65535 // end of intro sequence
// Modifier flag for SHOWSCREEN when we want the image to cover the entire
// screen.
#define FULLSCREEN 0x8000
#define IC_PREPARE_TEXT 20 // commands used in COMMANDFLIRT block
#define IC_SHOW_TEXT 21
#define IC_REMOVE_TEXT 22
#define IC_MAKE_SOUND 23
#define IC_FX_VOLUME 24
#define FRAME_SIZE (GAME_SCREEN_WIDTH * GAME_SCREEN_HEIGHT)
#define INTRO_TEXT_WIDTH 128
//CD intro file defines
#define CDV_00 59500
#define CD_PAL 59501
#define CD_1_LOG 59502
#define CD_1 59503
#define CDV_01 59504
#define CDV_02 59505
#define CD_2 59506
#define CDV_03 59507
#define CDV_04 59508
#define CD_3 59509
#define CDV_05 59510
#define CDV_06 59511
#define CD_5 59512
#define CDV_07 59513
#define CDV_08 59514
#define CDV_09 59515
#define CD_7 59516
#define CDV_10 59518
#define CD_11 59519
#define CDV_11 59520
#define CD_11_PAL 59521
#define CD_11_LOG 59522
#define CDV_12 59523
#define CD_13 59524
#define CDV_13 59525
#define CDV_14 59527
#define CDV_15 59528
#define CD_15_PAL 59529
#define CD_15_LOG 59530
#define CDV_16 59531
#define CD_17_LOG 59532
#define CD_17 59533
#define CDV_17 59534
#define CDV_18 59535
#define CDV_19 59536
#define CD_19_PAL 59537
#define CD_19_LOG 59538
#define CDV_20 59539
#define CD_20_LOG 59540
#define CDV_21 59541
#define CD_21_LOG 59542
#define CDV_22 59545
#define CDV_23 59546
#define CD_23_PAL 59547
#define CD_24_LOG 59550
#define CDV_24 59551
#define CDV_25 59554
#define CDV_26 59556
#define CD_27 59557
#define CDV_27 59558
#define CD_27_PAL 59559
#define CD_27_LOG 59560
#define CDV_28 59561
#define CDV_29 59562
#define CDV_30 59563
#define CDV_31 59565
#define CDV_32 59566
#define CDV_33 59567
#define CDV_34 59568
#define CD_35 59569
#define CDV_35 59570
#define CD_35_PAL 59571
#define CD_35_LOG 59572
#define CDV_36 59574
#define CD_37 59575
#define CDV_37 59576
#define CD_37_PAL 59577
#define CD_37_LOG 59578
#define CDV_38 59579
#define CDV_39 59581
#define CDV_40 59583
#define CD_40_PAL 59584
#define CD_40_LOG 59585
#define CDV_41 59587
#define CDV_42 59588
#define CD_43 59589
#define CDV_43 59590
#define CD_43_PAL 59591
#define CD_43_LOG 59592
#define CDV_44 59594
#define CD_45 59595
#define CDV_45 59596
#define CD_45_PAL 59597
#define CD_45_LOG 59598
#define CDV_46 59600
#define CDV_47 59602
#define CD_47_PAL 59603
#define CD_47_LOG 59604
#define CD_48 59605
#define CDV_48 59606
#define CD_48_PAL 59607
#define CD_48_LOG 59608
#define CD_49 59609
#define CDV_49 59610
#define CD_50 59611
#define CDV_50 59612
#define CDV_51 59613
#define CDV_52 59614
#define CDV_53 59615
#define CDV_54 59616
#define CDV_55 59618
#define CD_55_PAL 59619
#define CD_55_LOG 59620
#define CDV_56 59621
#define CDV_57 59622
#define CD_58 59623
#define CDV_58 59624
#define CD_58_PAL 59625
#define CD_58_LOG 59626
#define CDV_59 59627
#define CDV_60 59628
#define CDV_61 59629
#define CDV_62 59630
#define CDV_63 59631
#define CDV_64 59632
#define CDV_65 59633
#define CDV_66 59635
#define CD_66_PAL 59636
#define CD_66_LOG 59637
#define CDV_67 59639
#define CD_67_PAL 59640
#define CD_67_LOG 59641
#define CDV_68 59642
#define CD_69 59643
#define CDV_69 59644
#define CD_69_PAL 59645
#define CD_69_LOG 59646
#define CDV_70 59647
#define CDV_71 59648
#define CDV_72 59649
#define CD_72_PAL 59650
#define CD_72_LOG 59651
#define CD_73_PAL 59652
#define CD_73_LOG 59653
#define CDV_73 59654
#define CDV_74 59655
#define CDV_75 59656
#define CD_76_PAL 59657
#define CD_76_LOG 59658
#define CDV_76 59659
#define CDV_77 59660
#define CD_78_PAL 59661
#define CD_78_LOG 59662
#define CDV_78 59663
#define CDV_79 59664
#define CDV_80 59665
#define CDV_81 59666
#define CDV_82 59667
#define CDV_83 59668
#define CDV_84 59669
#define CDV_85 59670
#define CDV_86 59671
#define CDV_87 59672
#define CD_100 60087
#define CD_101_LOG 60088
#define CD_101 60099
#define CD_102_LOG 60090
#define CD_102 60091
#define CD_103_PAL 60092
#define CD_103_LOG 60093
#define CD_103 60094
#define CD_104_PAL 60095
#define CD_104_LOG 60096
#define CD_104 60097
#define CD_105 60098
uint16 Intro::_mainIntroSeq[] = {
DELAY, 3000, // keep virgin screen up
FADEDOWN,
SHOWSCREEN, 60112, // revo screen + palette
FADEUP, 60113,
DELAY, 8000,
FADEDOWN,
SHOWSCREEN, 60114, // gibbo screen + palette
FADEUP, 60115,
DELAY, 2000,
FADEDOWN,
SEQEND
};
uint16 Intro::_cdIntroSeq[] = {
/* black screen */
PLAYVOICE, CDV_00, // Foster: "The old man was trying to tell the future. Looking for pictures in the campfire..."
LOADBG, 59499, // Fire crackle
LOOPBG,
WAITVOICE,
PLAYVOICE, CDV_01, // Shaman: "ohhh, I see evil..."
/* Fade up shaman image while he says his line... */
SHOWSCREEN, CD_1_LOG,
FADEUP, CD_PAL,
/* And then play the animation showing the shadows of the fire on his face */
BGFLIRT, CD_1,
WAITVOICE,
PLAYVOICE, CDV_02, // Shaman: "Evil born deep beneath the city... far from the light of day."
WAITVOICE,
STOPFLIRT,
BGFLIRT, CD_2,
PLAYVOICE, CDV_03, // Shaman: "I see it growing, safe beneath a sky of steel..."
WAITVOICE,
PLAYVOICE, CDV_04, // Shaman: "Scheming in the dark... gathering strength."
WAITFLIRT,
WAITVOICE,
PLAYVOICE, CDV_05, // Shaman: "And now... ohhh.... now the evil spreads..."
DELAY, 2000,
BGFLIRT, CD_3,
WAITVOICE,
PLAYVOICE, CDV_06, // Shaman: "It sends deadly feelers over the land above..."
WAITFLIRT,
WAITVOICE,
PLAYVOICE, CDV_07, // Shaman: "Across the gap... reaching towards this very place!"
BGFLIRT, CD_5,
WAITVOICE,
PLAYVOICE, CDV_08, // Foster: "I'd seen him do this a hundred times, but I humoured him."
WAITVOICE,
PLAYVOICE, CDV_09, // Foster: "After all, he'd been like a father to me."
WAITFLIRT,
WAITVOICE,
PLAYVOICE, CDV_10, // Foster: "And what does this evil want here?"
BGFLIRT, CD_7,
WAITVOICE,
PLAYVOICE, CDV_11, // Shaman: "Oh, my son. I fear..."
WAITFLIRT,
FADEDOWN,
SHOWSCREEN, CD_11_LOG,
FADEUP, CD_11_PAL,
WAITVOICE,
PLAYVOICE, CDV_12, // Shaman: "I fear the evil wants you!"
DELAY, 1600,
BGFLIRT, CD_11,
WAITVOICE,
PLAYVOICE, CDV_13, // Foster: "That was when Joey piped up..."
WAITVOICE,
WAITFLIRT,
WAITVOICE,
PLAYVOICE, CDV_14, // Joey: "Foster! Sensors detect incoming audio source!"
LOADBG, 59498, // fire crackle to heli start
PLAYBG,
DOFLIRT, CD_13,
WAITVOICE,
PLAYVOICE, CDV_15, // Shaman: "The evil! The evil is nearly here...!"
FADEDOWN,
SHOWSCREEN, CD_15_LOG,
FADEUP, CD_15_PAL,
WAITVOICE,
LOADBG, 59496, // quiet heli
LOOPBG,
PLAYVOICE, CDV_16, // Foster: "It sounded more like a 'copter than a demon."
WAITVOICE,
PLAYVOICE, CDV_17, // Foster: "But next thing, all hell let loose anyway..."
DELAY, 2000,
SHOWSCREEN, CD_17_LOG,
WAITVOICE,
BGFLIRT, CD_17,
PLAYVOICE, CDV_18, // Shaman: "Run, Foster! Run! Hide from the evil!"
LOADBG, 59497, // loud heli
LOOPBG,
WAITFLIRT,
WAITVOICE,
FADEDOWN,
SHOWSCREEN | FULLSCREEN, CD_19_LOG,
FADEUP, CD_19_PAL,
PLAYVOICE, CDV_19, // Joey: "Foster! (zzzt) H-Help!"
WAITVOICE,
PLAYVOICE, CDV_20, // Joey: "Better make my next body move faster, Foster..."
FADEDOWN,
SHOWSCREEN | FULLSCREEN, CD_20_LOG,
FADEUP, CD_19_PAL,
WAITVOICE,
LOADBG, 59496, // quiet heli
LOOPBG,
PLAYVOICE, CDV_21, // Foster: "He was only a robot, but, well, I loved the little guy."
FADEDOWN,
SHOWSCREEN | FULLSCREEN, CD_21_LOG,
FADEUP, CD_19_PAL,
WAITVOICE,
PLAYVOICE, CDV_22, // Foster: "Then, as suddenly as it started, the shooting stopped."
LOADBG, 59494, // heli whine
PLAYBG,
WAITVOICE,
PLAYVOICE, CDV_23, // Foster: "There was a moment's silence as the copter cut its rotors, then..."
/* fade down while Foster's saying his line */
FADEDOWN,
WAITVOICE,
SHOWSCREEN | FULLSCREEN, CD_24_LOG,
FADEUP, CD_23_PAL,
PLAYVOICE, CDV_24, // Reich: "Whoever is in charge here, come forward..."
WAITVOICE,
PLAYVOICE, CDV_25, // Reich: "Now!!"
WAITVOICE,
PLAYVOICE, CDV_26, // Foster: "Only a fool would have argued with that firepower."
WAITVOICE,
FADEDOWN,
SHOWSCREEN | FULLSCREEN, CD_27_LOG,
FADEUP, CD_27_PAL,
PLAYVOICE, CDV_27, // Shaman: "... I am the leader of these people... We are peaceful..."
WAITVOICE,
PLAYVOICE, CDV_29, // Reich: "Bring him here."
WAITVOICE,
PLAYVOICE, CDV_30, // Guard: "At once, Commander Reich."
WAITVOICE,
BGFLIRT, CD_27,
PLAYVOICE, CDV_31, // Reich: "We're looking for someone..."
WAITFLIRT,
CLEARBOTTOM,
WAITVOICE,
PLAYVOICE, CDV_32, // Reich: "Someone who doesn't belong here..."
WAITVOICE,
PLAYVOICE, CDV_33, // Reich: "Who wasn't born in this garbage dump..."
WAITVOICE,
PLAYVOICE, CDV_34, // Reich: "Who came from the city as a child..."
WAITVOICE,
PLAYVOICE, CDV_35, // Reich: "We want to take him home again."
WAITVOICE,
PLAYVOICE, CDV_36, // Foster: "My mind racing, I remembered where I'd seen that symbol before..."
FADEDOWN,
SHOWSCREEN | FULLSCREEN, CD_35_LOG,
FADEUP, CD_35_PAL,
WAITVOICE,
PLAYVOICE, CDV_37, // Foster: "It was the day the tribe found me..."
DOFLIRT, CD_35,
CLEARBOTTOM,
WAITVOICE,
PLAYVOICE, CDV_38, // Foster: "The day of the crash..."
DOFLIRT, CD_37,
WAITVOICE,
PLAYVOICE, CDV_39, // Foster: "The day my mother died."
WAITVOICE,
FADEDOWN,
SHOWSCREEN | FULLSCREEN, CD_40_LOG,
FADEUP, CD_40_PAL,
PLAYVOICE, CDV_40, // Shaman: "You alright, city boy?"
WAITVOICE,
PLAYVOICE, CDV_41, // Shaman: "Got a name, son?"
WAITVOICE,
PLAYVOICE, CDV_42, // Foster: "R-Robert."
WAITVOICE,
FADEDOWN,
SHOWSCREEN | FULLSCREEN, CD_43_LOG,
FADEUP, CD_43_PAL,
PLAYVOICE, CDV_43, // Shaman: "Hah! Welcome to the Gap, Robert!"
WAITVOICE,
DOFLIRT, CD_43,
PLAYVOICE, CDV_45, // Foster: "As he patched me up, the old man had gently explained that there was no way back into the City..."
FADEDOWN,
SHOWSCREEN, CD_45_LOG,
FADEUP, CD_45_PAL,
WAITVOICE,
PLAYVOICE, CDV_46, // Foster: "And I already knew there was nothing he could do for mother."
DOFLIRT, CD_45,
WAITVOICE,
FADEDOWN,
SHOWSCREEN | FULLSCREEN, CD_47_LOG,
FADEUP, CD_47_PAL,
PLAYVOICE, CDV_47, // Foster: "His tribe was poor, but they treated me like one of their own..."
WAITVOICE,
PLAYVOICE, CDV_48, // Foster: "I learned how to survive in the wasteland they called the Gap..."
FADEDOWN,
SHOWSCREEN | FULLSCREEN, CD_48_LOG,
FADEUP, CD_48_PAL,
WAITVOICE,
BGFLIRT, CD_48,
PLAYVOICE, CDV_49, // Foster: "And scavenging from the City dumps."
WAITVOICE,
PLAYVOICE, CDV_50, // Foster: "As the years passed, I forgot my life in the City."
WAITFLIRT,
WAITVOICE,
PLAYVOICE, CDV_51, // Foster: "Discovered new talents..."
BGFLIRT, CD_49,
WAITVOICE,
PLAYVOICE, CDV_52, // Foster: "Hah!"
WAITVOICE,
PLAYVOICE, CDV_53, // Joey: "I'm your (zzzt) friend... call me (zzzt) Joey."
WAITVOICE,
WAITFLIRT,
PLAYVOICE, CDV_54, // Foster: "And got a second name."
DOFLIRT, CD_50,
WAITVOICE,
PLAYVOICE, CDV_55, // Shaman: "This is what we'll call you now you've come of age, son."
WAITVOICE,
PLAYVOICE, CDV_56, // Shaman: "We found you... we fostered you..."
FADEDOWN,
SHOWSCREEN, CD_55_LOG,
FADEUP, CD_55_PAL,
WAITVOICE,
PLAYVOICE, CDV_57, // Shaman: "So that makes you Robert Foster."
WAITVOICE,
FADEDOWN,
SHOWSCREEN, CD_58_LOG,
FADEUP, CD_58_PAL,
PLAYVOICE, CDV_58, // Reich: "...Wasted enough time!"
WAITVOICE,
PLAYVOICE, CDV_59, // Reich: "Give us the runaway or we'll shoot everyone..."
WAITVOICE,
PLAYVOICE, CDV_60, // Reich: "Starting with you, grandad!"
WAITVOICE,
PLAYVOICE, CDV_61, // Foster: "The old man had been right, for once..."
WAITVOICE,
PLAYVOICE, CDV_62, // Foster: "It was me they wanted."
BGFLIRT, CD_58,
WAITVOICE,
PLAYVOICE, CDV_63, // Shaman: "No, my son! Don't let the evil take you! Run!"
WAITVOICE,
PLAYVOICE, CDV_64, // Guard: "DNA scan confirms it's him, sir."
WAITFLIRT,
WAITVOICE,
PLAYVOICE, CDV_65, // Foster: "Evil had come to the Gap, just as he said."
FADEDOWN,
WAITVOICE,
SHOWSCREEN, CD_66_LOG,
FADEUP, CD_66_PAL,
PLAYVOICE, CDV_66, // Reich: "Take him."
WAITVOICE,
PLAYVOICE, CDV_67, // Foster: "But had the old man seen why it wanted me?"
FADEDOWN,
SHOWSCREEN, CD_67_LOG,
FADEUP, CD_67_PAL,
WAITVOICE,
PLAYVOICE, CDV_68, // Foster: "Or what it would do next?"
WAITVOICE,
PLAYVOICE, CDV_69, // Foster: "It was too late to ask him now."
FADEDOWN,
SHOWSCREEN, CD_69_LOG,
FADEUP, CD_69_PAL,
WAITVOICE,
PLAYVOICE, CDV_70, // Guard: "Leaving destruction zone, Commander Reich."
DOFLIRT, CD_69,
WAITVOICE,
FADEDOWN,
PLAYVOICE, CDV_71, // Reich: "Good. Detonate."
WAITVOICE,
SHOWSCREEN | FULLSCREEN, CD_72_LOG,
FADEUP, CD_72_PAL,
PLAYVOICE, CDV_72, // Foster: "Much too late."
WAITVOICE,
FADEDOWN,
SHOWSCREEN | FULLSCREEN, CD_73_LOG,
FADEUP, CD_73_PAL,
PLAYVOICE, CDV_73, // Foster: "Why, you murdering..."
WAITVOICE,
PLAYVOICE, CDV_74, // Reich: "Keep him quiet."
WAITVOICE,
PLAYVOICE, CDV_75, // Foster: "All I could do was wait."
FADEDOWN,
SHOWSCREEN | FULLSCREEN, CD_76_LOG,
FADEUP, CD_76_PAL,
WAITVOICE,
PLAYVOICE, CDV_76, // Foster: "Just like on a hunt. Just like the old man taught me."
WAITVOICE,
PLAYVOICE, CDV_77, // Foster: "Wait... and be ready."
WAITVOICE,
FADEDOWN,
CLEARBOTTOM,
SHOWSCREEN, CD_78_LOG,
FADEUP, CD_78_PAL,
PLAYVOICE, CDV_78, // Foster: "It was dawn when we reached the City."
WAITVOICE,
PLAYVOICE, CDV_79, // Reich: "Land in the central Security compound."
WAITVOICE,
PLAYVOICE, CDV_80, // Foster: "A dawn my tribe would never see."
BGFLIRT, CD_100,
WAITVOICE,
PLAYVOICE, CDV_81, // Foster: "They were no more than a note in Reich's book now."
WAITVOICE,
PLAYVOICE, CDV_82, // Guard: "Yes, sir. Locking on automatic landing beacon."
WAITVOICE,
WAITFLIRT,
SHOWSCREEN, CD_101_LOG,
BGFLIRT, CD_101,
PLAYVOICE, CDV_83, // Foster: "But what was I? Why did..."
WAITVOICE,
PLAYVOICE, CDV_84, // Guard: "Sir! The guidance system! It's gone crazy!"
WAITVOICE,
PLAYVOICE, CDV_85, // Guard: "We're going to HIT!"
WAITVOICE,
WAITFLIRT,
CLEARBOTTOM,
SHOWSCREEN, CD_102_LOG,
PLAYVOICE, CDV_86, // Foster: "Maybe I'd get some answers now."
DOFLIRT, CD_102,
FADEDOWN,
// This one could be fullscreen, but that causes animation glitches.
SHOWSCREEN, CD_103_LOG,
FADEUP, CD_103_PAL,
BGFLIRT, CD_103,
WAITVOICE,
PLAYVOICE, CDV_87, // Foster: "If I survived another 'copter crash."
WAITFLIRT,
WAITVOICE,
STARTMUSIC, 2,
FADEDOWN,
SHOWSCREEN | FULLSCREEN, CD_104_LOG,
FADEUP, CD_104_PAL,
DOFLIRT, CD_104,
DOFLIRT, CD_105,
SEQEND
};
uint16 Intro::_floppyIntroSeq[] = {
// This one could be fullscreen, but that causes animation glitches.
SHOWSCREEN, 60081,
FADEUP, 60080,
DOFLIRT, 60082,
DOFLIRT, 60083,
DOFLIRT, 60084, // Beneath a Steel Sky
DOFLIRT, 60085,
DOFLIRT, 60086,
SCROLLFLIRT,
COMMANDFLIRT, 60087, // => command list 4a
136, IC_MAKE_SOUND, 1, 70,
90, IC_FX_VOLUME, 80,
50, IC_FX_VOLUME, 90,
5, IC_FX_VOLUME, 100,
COMMANDEND,
SHOWSCREEN, 60088,
COMMANDFLIRT, 60089, // => command list 4b (cockpit)
1000, IC_PREPARE_TEXT, 77,
220, IC_SHOW_TEXT, 20, 160, // radar detects jamming signal
105, IC_REMOVE_TEXT,
105, IC_PREPARE_TEXT, 81,
105, IC_SHOW_TEXT, 170, 86, // well switch to override you fool
35, IC_REMOVE_TEXT,
35, IC_PREPARE_TEXT, 477,
35, IC_SHOW_TEXT, 30, 160,
3, IC_REMOVE_TEXT,
COMMANDEND,
CLEARBOTTOM,
SHOWSCREEN, 60090,
COMMANDFLIRT, 60091, // => command list 4c
1000, IC_FX_VOLUME, 100,
25, IC_FX_VOLUME, 110,
15, IC_FX_VOLUME, 120,
4, IC_FX_VOLUME, 127,
COMMANDEND,
FADEDOWN,
// This one could be fullscreen, but that causes animation glitches.
SHOWSCREEN, 60093,
FADEUP, 60092,
COMMANDFLIRT, 60094, // => command list 5
31, IC_MAKE_SOUND, 2, 127,
COMMANDEND,
WAITMUSIC,
FADEDOWN,
SHOWSCREEN | FULLSCREEN, 60096,
STARTMUSIC, 2,
FADEUP, 60095,
COMMANDFLIRT, 60097, // => command list 6a
1000, IC_PREPARE_TEXT, 478,
13, IC_SHOW_TEXT, 175, 155,
COMMANDEND,
COMMANDFLIRT, 60098, // => command list 6b
131, IC_REMOVE_TEXT,
131, IC_PREPARE_TEXT, 479,
74, IC_SHOW_TEXT, 175, 155,
45, IC_REMOVE_TEXT,
45, IC_PREPARE_TEXT, 162,
44, IC_SHOW_TEXT, 175, 155,
4, IC_REMOVE_TEXT,
COMMANDEND,
SEQEND
};
Intro::Intro(Disk *disk, Screen *screen, MusicBase *music, Sound *sound, Text *text, Audio::Mixer *mixer, OSystem *system) {
_skyDisk = disk;
_skyScreen = screen;
_skyMusic = music;
_skySound = sound;
_skyText = text;
_mixer = mixer;
_system = system;
_textBuf = (uint8 *)malloc(10000);
_saveBuf = (uint8 *)malloc(10000);
_bgBuf = NULL;
_relDelay = 0;
}
Intro::~Intro() {
if (_skyScreen->sequenceRunning())
_skyScreen->stopSequence();
free(_textBuf);
free(_saveBuf);
_mixer->stopID(SOUND_BG);
free(_bgBuf);
}
bool Intro::doIntro(bool floppyIntro) {
if (!SkyEngine::isCDVersion())
floppyIntro = true;
_skyMusic->loadSection(0);
_skySound->loadSection(0);
if (!escDelay(3000))
return false;
if (floppyIntro)
_skyMusic->startMusic(1);
uint16 *seqData = _mainIntroSeq;
while (*seqData != SEQEND) {
if (!nextPart(seqData))
return false;
}
if (floppyIntro)
seqData = _floppyIntroSeq;
else
seqData = _cdIntroSeq;
while (*seqData != SEQEND) {
if (!nextPart(seqData))
return false;
}
return true;
}
bool Intro::nextPart(uint16 *&data) {
uint8 *vData = NULL;
Audio::RewindableAudioStream *stream = 0;
// return false means cancel intro
uint16 command = *data++;
switch (command & 0x7fff) {
case SHOWSCREEN:
_skyScreen->showScreen(*data++, (command & FULLSCREEN) ? true : false);
return true;
case FADEUP:
_skyScreen->paletteFadeUp(*data++);
_relDelay += 32 * 20; // hack: the screen uses a separate delay function for the
// blocking fadeups. So add 32*20 msecs to out delay counter.
return true;
case FADEDOWN:
_skyScreen->fnFadeDown(0);
_relDelay += 32 * 20; // hack: see above.
return true;
case DELAY:
if (!escDelay(*data++))
return false;
return true;
case DOFLIRT:
_skyScreen->startSequence(*data++);
while (_skyScreen->sequenceRunning())
if (!escDelay(50))
return false;
return true;
case SCROLLFLIRT:
return floppyScrollFlirt();
case COMMANDFLIRT:
return commandFlirt(data);
case STOPFLIRT:
_skyScreen->stopSequence();
return true;
case STARTMUSIC:
_skyMusic->startMusic(*data++);
return true;
case WAITMUSIC:
while (_skyMusic->musicIsPlaying())
if (!escDelay(50))
return false;
return true;
case BGFLIRT:
_skyScreen->startSequence(*data++);
return true;
case WAITFLIRT:
while (_skyScreen->sequenceRunning())
if (!escDelay(50))
return false;
return true;
case PLAYVOICE:
if (!escDelay(200))
return false;
vData = _skyDisk->loadFile(*data++);
// HACK: Fill the header with silence. We should
// probably use _skySound instead of calling playStream()
// directly, but this will have to do for now.
memset(vData, 127, sizeof(DataFileHeader));
stream = Audio::makeRawStream(vData, _skyDisk->_lastLoadedFileSize, 11025, Audio::FLAG_UNSIGNED);
_mixer->playStream(Audio::Mixer::kSpeechSoundType, &_voice, stream, SOUND_VOICE);
return true;
case WAITVOICE:
while (_mixer->isSoundHandleActive(_voice))
if (!escDelay(50))
return false;
return true;
case LOADBG:
_mixer->stopID(SOUND_BG);
free(_bgBuf);
_bgBuf = _skyDisk->loadFile(*data++);
_bgSize = _skyDisk->_lastLoadedFileSize;
return true;
case LOOPBG:
_mixer->stopID(SOUND_BG);
stream = Audio::makeRawStream(_bgBuf + 256, _bgSize - 768, 11025, Audio::FLAG_UNSIGNED, DisposeAfterUse::NO);
_mixer->playStream(Audio::Mixer::kSFXSoundType, &_bgSfx, Audio::makeLoopingAudioStream(stream, 0), SOUND_BG);
return true;
case PLAYBG:
_mixer->stopID(SOUND_BG);
stream = Audio::makeRawStream(_bgBuf + 256, _bgSize - 768, 11025, Audio::FLAG_UNSIGNED, DisposeAfterUse::NO);
_mixer->playStream(Audio::Mixer::kSFXSoundType, &_bgSfx, stream, SOUND_BG);
return true;
case STOPBG:
_mixer->stopID(SOUND_BG);
return true;
case CLEARBOTTOM:
{
byte *screenBuf = _skyScreen->giveCurrent() + GAME_SCREEN_HEIGHT * GAME_SCREEN_WIDTH;
memset(screenBuf, 0, GAME_SCREEN_WIDTH * (FULL_SCREEN_HEIGHT - GAME_SCREEN_HEIGHT));
_system->copyRectToScreen(screenBuf, GAME_SCREEN_WIDTH, 0, GAME_SCREEN_HEIGHT, GAME_SCREEN_WIDTH, FULL_SCREEN_HEIGHT - GAME_SCREEN_HEIGHT);
_system->updateScreen();
}
return true;
default:
error("Unknown intro command %X", command);
}
return true;
}
bool Intro::floppyScrollFlirt() {
uint8 *scrollScreen = (uint8 *)malloc(FRAME_SIZE * 2);
memset(scrollScreen, 0, FRAME_SIZE);
memcpy(scrollScreen + FRAME_SIZE, _skyScreen->giveCurrent(), FRAME_SIZE);
uint8 *scrollPos = scrollScreen + FRAME_SIZE;
uint8 *vgaData = _skyDisk->loadFile(60100);
uint8 *diffData = _skyDisk->loadFile(60101);
uint16 frameNum = READ_LE_UINT16(diffData);
uint8 *diffPtr = diffData + 2;
uint8 *vgaPtr = vgaData;
bool doContinue = true;
for (uint16 frameCnt = 1; (frameCnt < frameNum) && doContinue; frameCnt++) {
uint8 scrollVal = *diffPtr++;
if (scrollVal)
scrollPos -= scrollVal * GAME_SCREEN_WIDTH;
uint16 scrPos = 0;
while (scrPos < FRAME_SIZE) {
uint8 nrToDo, nrToSkip;
do {
nrToSkip = *diffPtr++;
scrPos += nrToSkip;
} while (nrToSkip == 255);
do {
nrToDo = *diffPtr++;
memcpy(scrollPos + scrPos, vgaPtr, nrToDo);
scrPos += nrToDo;
vgaPtr += nrToDo;
} while (nrToDo == 255);
}
_system->copyRectToScreen(scrollPos, GAME_SCREEN_WIDTH, 0, 0, GAME_SCREEN_WIDTH, GAME_SCREEN_HEIGHT);
_system->updateScreen();
if (!escDelay(60))
doContinue = false;
}
memcpy(_skyScreen->giveCurrent(), scrollPos, FRAME_SIZE);
free(diffData);
free(vgaData);
free(scrollScreen);
return doContinue;
}
bool Intro::commandFlirt(uint16 *&data) {
_skyScreen->startSequence(*data++);
while ((*data != COMMANDEND) || _skyScreen->sequenceRunning()) {
while ((_skyScreen->seqFramesLeft() < *data)) {
data++;
uint16 command = *data++;
switch (command) {
case IC_PREPARE_TEXT:
_skyText->displayText(*data++, _textBuf, Graphics::kTextAlignCenter, INTRO_TEXT_WIDTH, 255);
break;
case IC_SHOW_TEXT:
((DataFileHeader *)_textBuf)->s_x = *data++;
((DataFileHeader *)_textBuf)->s_y = *data++;
showTextBuf();
break;
case IC_REMOVE_TEXT:
restoreScreen();
break;
case IC_MAKE_SOUND:
_skySound->playSound(data[0], data[1], 0);
data += 2;
break;
case IC_FX_VOLUME:
_skySound->playSound(1, *data++, 0);
break;
default:
error("Unknown FLIRT command %X", command);
}
}
if (!escDelay(50)) {
_skyScreen->stopSequence();
return false;
}
}
data++; // move pointer over "COMMANDEND"
return true;
}
void Intro::showTextBuf() {
uint16 x = ((DataFileHeader *)_textBuf)->s_x;
uint16 y = ((DataFileHeader *)_textBuf)->s_y;
uint16 width = ((DataFileHeader *)_textBuf)->s_width;
uint16 height = ((DataFileHeader *)_textBuf)->s_height;
uint8 *screenBuf = _skyScreen->giveCurrent() + y * GAME_SCREEN_WIDTH + x;
memcpy(_saveBuf, _textBuf, sizeof(DataFileHeader));
uint8 *saveBuf = _saveBuf + sizeof(DataFileHeader);
uint8 *textBuf = _textBuf + sizeof(DataFileHeader);
for (uint16 cnty = 0; cnty < height; cnty++) {
memcpy(saveBuf, screenBuf, width);
for (uint16 cntx = 0; cntx < width; cntx++)
if (textBuf[cntx])
screenBuf[cntx] = textBuf[cntx];
screenBuf += GAME_SCREEN_WIDTH;
textBuf += width;
saveBuf += width;
}
screenBuf = _skyScreen->giveCurrent() + y * GAME_SCREEN_WIDTH + x;
_system->copyRectToScreen(screenBuf, GAME_SCREEN_WIDTH, x, y, width, height);
}
void Intro::restoreScreen() {
uint16 x = ((DataFileHeader *)_saveBuf)->s_x;
uint16 y = ((DataFileHeader *)_saveBuf)->s_y;
uint16 width = ((DataFileHeader *)_saveBuf)->s_width;
uint16 height = ((DataFileHeader *)_saveBuf)->s_height;
uint8 *screenBuf = _skyScreen->giveCurrent() + y * GAME_SCREEN_WIDTH + x;
uint8 *saveBuf = _saveBuf + sizeof(DataFileHeader);
for (uint16 cnt = 0; cnt < height; cnt++) {
memcpy(screenBuf, saveBuf, width);
screenBuf += GAME_SCREEN_WIDTH;
saveBuf += width;
}
_system->copyRectToScreen(_saveBuf + sizeof(DataFileHeader), width, x, y, width, height);
}
bool Intro::escDelay(uint32 msecs) {
Common::EventManager *eventMan = _system->getEventManager();
Common::Event event;
if (_relDelay == 0) // first call, init with system time
_relDelay = (int32)_system->getMillis();
_relDelay += msecs; // now wait until _system->getMillis() >= _relDelay
int32 nDelay = 0;
do {
while (eventMan->pollEvent(event)) {
if (event.type == Common::EVENT_CUSTOM_ENGINE_ACTION_START) {
if (event.customType == kSkyActionSkip)
return false;
} else if (event.type == Common::EVENT_QUIT || event.type == Common::EVENT_RETURN_TO_LAUNCHER) {
return false;
}
}
nDelay = _relDelay - _system->getMillis();
if (nDelay < 0)
nDelay = 0;
else if (nDelay > 20)
nDelay = 20;
_system->delayMillis(nDelay);
_skyScreen->processSequence();
_system->updateScreen();
} while (nDelay == 20);
return true;
}
} // End of namespace Sky

72
engines/sky/intro.h Normal file
View File

@@ -0,0 +1,72 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef SKY_INTRO_H
#define SKY_INTRO_H
#include "common/scummsys.h"
#include "audio/mixer.h"
namespace Sky {
class Disk;
class Screen;
class MusicBase;
class Sound;
class Text;
class Intro {
public:
Intro(Disk *disk, Screen *screen, MusicBase *music, Sound *sound, Text *text, Audio::Mixer *mixer, OSystem *system);
~Intro();
bool doIntro(bool floppyIntro);
private:
static uint16 _mainIntroSeq[];
static uint16 _floppyIntroSeq[];
static uint16 _cdIntroSeq[];
Disk *_skyDisk;
Screen *_skyScreen;
MusicBase *_skyMusic;
Sound *_skySound;
Text *_skyText;
OSystem *_system;
Audio::Mixer *_mixer;
uint8 *_textBuf, *_saveBuf;
uint8 *_bgBuf;
uint32 _bgSize;
Audio::SoundHandle _voice, _bgSfx;
int32 _relDelay;
bool escDelay(uint32 msecs);
bool nextPart(uint16 *&data);
bool floppyScrollFlirt();
bool commandFlirt(uint16 *&data);
void showTextBuf();
void restoreScreen();
};
} // End of namespace Sky
#endif // INTRO_H

2585
engines/sky/logic.cpp Normal file

File diff suppressed because it is too large Load Diff

337
engines/sky/logic.h Normal file
View File

@@ -0,0 +1,337 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef SKY_LOGIC_H
#define SKY_LOGIC_H
#include "common/util.h"
#include "common/random.h"
namespace Sky {
struct Compact;
enum scriptVariableOffsets {
RESULT = 0,
SCREEN = 1,
LOGIC_LIST_NO = 2,
MOUSE_LIST_NO = 6,
DRAW_LIST_NO = 8,
CUR_ID = 12,
MOUSE_STATUS = 13,
MOUSE_STOP = 14,
BUTTON = 15,
SPECIAL_ITEM = 17,
GET_OFF = 18,
CURSOR_ID = 22,
SAFEX = 25,
SAFEY = 26,
PLAYER_X = 27,
PLAYER_Y = 28,
PLAYER_MOOD = 29,
PLAYER_SCREEN = 30,
HIT_ID = 37,
LAYER_0_ID = 41,
LAYER_1_ID = 42,
LAYER_2_ID = 43,
LAYER_3_ID = 44,
GRID_1_ID = 45,
GRID_2_ID = 46,
GRID_3_ID = 47,
THE_CHOSEN_ONE = 51,
TEXT1 = 53,
MENU_LENGTH = 100,
SCROLL_OFFSET = 101,
MENU = 102,
OBJECT_HELD = 103,
LAMB_GREET = 109,
RND = 115,
CUR_SECTION = 143,
JOEY_SECTION = 145,
LAMB_SECTION = 146,
KNOWS_PORT = 190,
GOT_SPONSOR = 240,
GOT_JAMMER = 258,
CONSOLE_TYPE = 345,
S15_FLOOR = 450,
FOREMAN_FRIEND = 451,
REICH_DOOR_FLAG = 470,
CARD_STATUS = 479,
CARD_FIX = 480,
GUARDIAN_THERE = 640,
FS_COMMAND = 643,
ENTER_DIGITS = 644,
LINC_DIGIT_0 = 646,
LINC_DIGIT_1 = 647,
LINC_DIGIT_2 = 648,
LINC_DIGIT_3 = 649,
LINC_DIGIT_4 = 650,
LINC_DIGIT_5 = 651,
LINC_DIGIT_6 = 651,
LINC_DIGIT_7 = 653,
LINC_DIGIT_8 = 654,
LINC_DIGIT_9 = 655,
DOOR_67_68_FLAG = 678,
SC70_IRIS_FLAG = 693,
DOOR_73_75_FLAG = 704,
SC76_CABINET1_FLAG = 709,
SC76_CABINET2_FLAG = 710,
SC76_CABINET3_FLAG = 711,
DOOR_77_78_FLAG = 719,
SC80_EXIT_FLAG = 720,
SC31_LIFT_FLAG = 793,
SC32_LIFT_FLAG = 797,
SC33_SHED_DOOR_FLAG = 798,
BAND_PLAYING = 804,
COLSTON_AT_TABLE = 805,
SC36_NEXT_DEALER = 806,
SC36_DOOR_FLAG = 807,
SC37_DOOR_FLAG = 808,
SC40_LOCKER_1_FLAG = 817,
SC40_LOCKER_2_FLAG = 818,
SC40_LOCKER_3_FLAG = 819,
SC40_LOCKER_4_FLAG = 820,
SC40_LOCKER_5_FLAG = 821
};
#define NUM_SKY_SCRIPTVARS 838
class AutoRoute;
class Control;
class Disk;
class Grid;
class Mouse;
class MusicBase;
class Screen;
class Sound;
class Text;
class SkyCompact;
class Logic;
typedef void (Logic::*LogicTable) ();
typedef bool (Logic::*McodeTable) (uint32, uint32, uint32);
class Logic {
public:
Logic(
SkyCompact *skyCompact,
Screen *skyScreen,
Disk *skyDisk,
Text *skyText,
MusicBase *skyMusic,
Mouse *skyMouse,
Sound *skySound);
~Logic();
void engine();
void useControlInstance(Control *control) { _skyControl = control; }
uint16 mouseScript(uint32 scrNum, Compact *scriptComp);
static uint32 _scriptVariables[NUM_SKY_SCRIPTVARS];
Grid *_skyGrid;
uint16 script(uint16 scriptNo, uint16 offset);
void initScreen0();
void parseSaveData(uint32 *data);
private:
void setupLogicTable();
void setupMcodeTable();
const LogicTable *_logicTable;
const McodeTable *_mcodeTable;
protected:
void push(uint32);
uint32 pop();
void checkModuleLoaded(uint16 moduleNo);
bool isCollision(Compact *cpt);
void initScriptVariables();
void mainAnim();
void runGetOff();
void stopAndWait();
bool checkProtection();
void nop();
void logicScript();
void autoRoute();
void arAnim();
void arTurn();
void alt();
void anim();
void turn();
void cursor();
void talk();
void listen();
void stopped();
void choose();
void frames();
void pause();
void waitSync();
void simpleAnim();
bool fnCacheChip(uint32 a, uint32 b, uint32 c);
bool fnCacheFast(uint32 a, uint32 b, uint32 c);
bool fnDrawScreen(uint32 a, uint32 b, uint32 c);
bool fnAr(uint32 a, uint32 b, uint32 c);
bool fnArAnimate(uint32 a, uint32 b, uint32 c);
bool fnIdle(uint32 a, uint32 b, uint32 c);
bool fnInteract(uint32 a, uint32 b, uint32 c);
bool fnStartSub(uint32 a, uint32 b, uint32 c);
bool fnTheyStartSub(uint32 a, uint32 b, uint32 c);
bool fnAssignBase(uint32 a, uint32 b, uint32 c);
bool fnDiskMouse(uint32 a, uint32 b, uint32 c);
bool fnNormalMouse(uint32 a, uint32 b, uint32 c);
bool fnBlankMouse(uint32 a, uint32 b, uint32 c);
bool fnCrossMouse(uint32 a, uint32 b, uint32 c);
bool fnCursorRight(uint32 a, uint32 b, uint32 c);
bool fnCursorLeft(uint32 a, uint32 b, uint32 c);
bool fnCursorDown(uint32 a, uint32 b, uint32 c);
bool fnOpenHand(uint32 a, uint32 b, uint32 c);
bool fnCloseHand(uint32 a, uint32 b, uint32 c);
bool fnGetTo(uint32 a, uint32 b, uint32 c);
bool fnSetToStand(uint32 a, uint32 b, uint32 c);
bool fnTurnTo(uint32 a, uint32 b, uint32 c);
bool fnArrived(uint32 a, uint32 b, uint32 c);
bool fnLeaving(uint32 a, uint32 b, uint32 c);
bool fnSetAlternate(uint32 a, uint32 b, uint32 c);
bool fnAltSetAlternate(uint32 a, uint32 b, uint32 c);
bool fnKillId(uint32 a, uint32 b, uint32 c);
bool fnNoHuman(uint32 a, uint32 b, uint32 c);
bool fnAddHuman(uint32 a, uint32 b, uint32 c);
bool fnAddButtons(uint32 a, uint32 b, uint32 c);
bool fnNoButtons(uint32 a, uint32 b, uint32 c);
bool fnSetStop(uint32 a, uint32 b, uint32 c);
bool fnClearStop(uint32 a, uint32 b, uint32 c);
bool fnPointerText(uint32 a, uint32 b, uint32 c);
bool fnQuit(uint32 a, uint32 b, uint32 c);
bool fnSpeakMe(uint32 targetId, uint32 mesgNum, uint32 animNum);
bool fnSpeakMeDir(uint32 targetId, uint32 mesgNum, uint32 animNum);
bool fnSpeakWait(uint32 a, uint32 b, uint32 c);
bool fnSpeakWaitDir(uint32 a, uint32 b, uint32 c);
bool fnChooser(uint32 a, uint32 b, uint32 c);
bool fnHighlight(uint32 a, uint32 b, uint32 c);
bool fnTextKill(uint32 a, uint32 b, uint32 c);
bool fnStopMode(uint32 a, uint32 b, uint32 c);
bool fnWeWait(uint32 a, uint32 b, uint32 c);
bool fnSendSync(uint32 a, uint32 b, uint32 c);
bool fnSendFastSync(uint32 a, uint32 b, uint32 c);
bool fnSendRequest(uint32 a, uint32 b, uint32 c);
bool fnClearRequest(uint32 a, uint32 b, uint32 c);
bool fnCheckRequest(uint32 a, uint32 b, uint32 c);
bool fnStartMenu(uint32 a, uint32 b, uint32 c);
bool fnUnhighlight(uint32 a, uint32 b, uint32 c);
bool fnFaceId(uint32 a, uint32 b, uint32 c);
bool fnForeground(uint32 a, uint32 b, uint32 c);
bool fnBackground(uint32 a, uint32 b, uint32 c);
bool fnNewBackground(uint32 a, uint32 b, uint32 c);
bool fnSort(uint32 a, uint32 b, uint32 c);
bool fnNoSpriteEngine(uint32 a, uint32 b, uint32 c);
bool fnNoSpritesA6(uint32 a, uint32 b, uint32 c);
bool fnResetId(uint32 a, uint32 b, uint32 c);
bool fnToggleGrid(uint32 a, uint32 b, uint32 c);
bool fnPause(uint32 a, uint32 b, uint32 c);
bool fnRunAnimMod(uint32 a, uint32 b, uint32 c);
bool fnSimpleMod(uint32 a, uint32 b, uint32 c);
bool fnRunFrames(uint32 a, uint32 b, uint32 c);
bool fnAwaitSync(uint32 a, uint32 b, uint32 c);
bool fnIncMegaSet(uint32 a, uint32 b, uint32 c);
bool fnDecMegaSet(uint32 a, uint32 b, uint32 c);
bool fnSetMegaSet(uint32 a, uint32 b, uint32 c);
bool fnMoveItems(uint32 a, uint32 b, uint32 c);
bool fnNewList(uint32 a, uint32 b, uint32 c);
bool fnAskThis(uint32 a, uint32 b, uint32 c);
bool fnRandom(uint32 a, uint32 b, uint32 c);
bool fnPersonHere(uint32 a, uint32 b, uint32 c);
bool fnToggleMouse(uint32 a, uint32 b, uint32 c);
bool fnMouseOn(uint32 a, uint32 b, uint32 c);
bool fnMouseOff(uint32 a, uint32 b, uint32 c);
bool fnFetchX(uint32 a, uint32 b, uint32 c);
bool fnFetchY(uint32 a, uint32 b, uint32 c);
bool fnTestList(uint32 a, uint32 b, uint32 c);
bool fnFetchPlace(uint32 a, uint32 b, uint32 c);
bool fnCustomJoey(uint32 a, uint32 b, uint32 c);
bool fnSetPalette(uint32 a, uint32 b, uint32 c);
bool fnTextModule(uint32 a, uint32 b, uint32 c);
bool fnChangeName(uint32 a, uint32 b, uint32 c);
bool fnMiniLoad(uint32 a, uint32 b, uint32 c);
bool fnFlushBuffers(uint32 a, uint32 b, uint32 c);
bool fnFlushChip(uint32 a, uint32 b, uint32 c);
bool fnSaveCoods(uint32 a, uint32 b, uint32 c);
bool fnPlotGrid(uint32 a, uint32 b, uint32 c);
bool fnRemoveGrid(uint32 a, uint32 b, uint32 c);
bool fnEyeball(uint32 a, uint32 b, uint32 c);
bool fnCursorUp(uint32 a, uint32 b, uint32 c);
bool fnLeaveSection(uint32 a, uint32 b, uint32 c);
bool fnEnterSection(uint32 sectionNo, uint32 b, uint32 c);
bool fnRestoreGame(uint32 a, uint32 b, uint32 c);
bool fnRestartGame(uint32 a, uint32 b, uint32 c);
bool fnNewSwingSeq(uint32 a, uint32 b, uint32 c);
bool fnWaitSwingEnd(uint32 a, uint32 b, uint32 c);
bool fnSkipIntroCode(uint32 a, uint32 b, uint32 c);
bool fnBlankScreen(uint32 a, uint32 b, uint32 c);
bool fnPrintCredit(uint32 a, uint32 b, uint32 c);
bool fnLookAt(uint32 a, uint32 b, uint32 c);
bool fnLincTextModule(uint32 a, uint32 b, uint32 c);
bool fnTextKill2(uint32 a, uint32 b, uint32 c);
bool fnSetFont(uint32 a, uint32 b, uint32 c);
bool fnStartFx(uint32 a, uint32 b, uint32 c);
bool fnStopFx(uint32 a, uint32 b, uint32 c);
bool fnStartMusic(uint32 a, uint32 b, uint32 c);
bool fnStopMusic(uint32 a, uint32 b, uint32 c);
bool fnFadeDown(uint32 a, uint32 b, uint32 c);
bool fnFadeUp(uint32 a, uint32 b, uint32 c);
bool fnQuitToDos(uint32 a, uint32 b, uint32 c);
bool fnPauseFx(uint32 a, uint32 b, uint32 c);
bool fnUnPauseFx(uint32 a, uint32 b, uint32 c);
bool fnPrintf(uint32 a, uint32 b, uint32 c);
void stdSpeak(Compact *target, uint32 textNum, uint32 animNum, uint32 base);
void fnExec(uint16 num, uint32 a, uint32 b, uint32 c);
uint16 *_moduleList[16];
uint32 _stack[20];
byte _stackPtr;
Compact *_compact;
uint32 _objectList[30];
uint32 _currentSection;
Common::RandomSource _rnd;
SkyCompact *_skyCompact;
Screen *_skyScreen;
Disk *_skyDisk;
Text *_skyText;
MusicBase *_skyMusic;
Sound *_skySound;
AutoRoute *_skyAutoRoute;
Mouse *_skyMouse;
Control *_skyControl;
friend class Debugger;
};
} // End of namespace Sky
#endif

404
engines/sky/metaengine.cpp Normal file
View File

@@ -0,0 +1,404 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#include "engines/metaengine.h"
#include "backends/keymapper/action.h"
#include "backends/keymapper/keymap.h"
#include "backends/keymapper/standard-actions.h"
#include "common/gui_options.h"
#include "common/system.h"
#include "common/savefile.h"
#include "common/translation.h"
#include "common/file.h"
#include "common/fs.h"
#include "gui/message.h"
#include "sky/control.h"
#include "sky/sky.h"
class SkyMetaEngine : public MetaEngine {
const char *getName() const override {
return "sky";
}
bool hasFeature(MetaEngineFeature f) const override;
Common::Error createInstance(OSystem *syst, Engine **engine,
const DetectedGame &gameDescriptor, const void *metaEngineDescriptor) override;
const ExtraGuiOptions getExtraGuiOptions(const Common::String &target) const override;
SaveStateList listSaves(const char *target) const override;
int getMaximumSaveSlot() const override;
bool removeSaveState(const char *target, int slot) const override;
SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const override;
Common::KeymapArray initKeymaps(const char *target) const override;
Common::String getSavegameFile(int saveGameIdx, const char *target) const override {
if (saveGameIdx == kSavegameFilePattern)
return Common::String::format("SKY-VM.###");
else
return Common::String::format("SKY-VM.%03d", saveGameIdx);
}
};
bool SkyMetaEngine::hasFeature(MetaEngineFeature f) const {
return
(f == kSupportsListSaves) ||
(f == kSupportsLoadingDuringStartup) ||
(f == kSupportsDeleteSave) ||
(f == kSavesSupportMetaInfo);
}
bool Sky::SkyEngine::hasFeature(EngineFeature f) const {
return
(f == kSupportsReturnToLauncher) ||
(f == kSupportsLoadingDuringRuntime) ||
(f == kSupportsSavingDuringRuntime);
}
Common::KeymapArray SkyMetaEngine::initKeymaps(const char *target) const {
using namespace Common;
using namespace Sky;
Keymap *mainKeymap = new Keymap(Keymap::kKeymapTypeGame, "sky-main", "Beneath a Steel Sky");
Action *act;
act = new Action(kStandardActionLeftClick, _("Walk / Look / Talk"));
act->setLeftClickEvent();
act->addDefaultInputMapping("MOUSE_LEFT");
act->addDefaultInputMapping("JOY_A");
mainKeymap->addAction(act);
act = new Action(kStandardActionRightClick, _("Use"));
act->setRightClickEvent();
act->addDefaultInputMapping("MOUSE_RIGHT");
act->addDefaultInputMapping("JOY_B");
mainKeymap->addAction(act);
act = new Action("CONFIRM", _("Confirm"));
act->setCustomEngineActionEvent(kSkyActionConfirm);
act->addDefaultInputMapping("RETURN");
act->addDefaultInputMapping("KP_ENTER");
mainKeymap->addAction(act);
act = new Action(kStandardActionSkip, _("Skip / Close"));
act->setCustomEngineActionEvent(kSkyActionSkip);
act->addDefaultInputMapping("ESCAPE");
act->addDefaultInputMapping("JOY_Y");
mainKeymap->addAction(act);
Keymap *shortcutsKeymap = new Keymap(Keymap::kKeymapTypeGame, SkyEngine::shortcutsKeymapId, "Beneath a Steel Sky - Shortcuts");
act = new Action(kStandardActionOpenMainMenu, _("Open control panel"));
act->setCustomEngineActionEvent(kSkyActionOpenControlPanel);
act->addDefaultInputMapping("F5");
act->addDefaultInputMapping("JOY_X");
shortcutsKeymap->addAction(act);
act = new Action("SKPL", _("Skip line"));
act->setCustomEngineActionEvent(kSkyActionSkipLine);
act->addDefaultInputMapping("PERIOD");
shortcutsKeymap->addAction(act);
act = new Action(kStandardActionPause, _("Pause"));
act->setCustomEngineActionEvent(kSkyActionPause);
act->addDefaultInputMapping("p");
shortcutsKeymap->addAction(act);
act = new Action("FAST", _("Toggle fast mode"));
act->setCustomEngineActionEvent(kSkyActionToggleFastMode);
act->addDefaultInputMapping("C+f");
shortcutsKeymap->addAction(act);
act = new Action("RFAST", _("Toggle really fast mode"));
act->setCustomEngineActionEvent(kSkyActionToggleReallyFastMode);
act->addDefaultInputMapping("C+g");
shortcutsKeymap->addAction(act);
KeymapArray keymaps(2);
keymaps[0] = mainKeymap;
keymaps[1] = shortcutsKeymap;
return keymaps;
}
Common::Error SkyMetaEngine::createInstance(OSystem *syst, Engine **engine,
const DetectedGame &gameDescriptor, const void *metaEngineDescriptor) {
assert(engine);
*engine = new Sky::SkyEngine(syst);
return Common::kNoError;
}
static const ExtraGuiOption skyExtraGuiOption = {
_s("Floppy intro"),
_s("Use the floppy version's intro (CD version only)"),
"alt_intro",
false,
0,
0
};
const ExtraGuiOptions SkyMetaEngine::getExtraGuiOptions(const Common::String &target) const {
Common::String guiOptions;
ExtraGuiOptions options;
if (target.empty()) {
options.push_back(skyExtraGuiOption);
return options;
}
if (ConfMan.hasKey("guioptions", target)) {
guiOptions = ConfMan.get("guioptions", target);
guiOptions = parseGameGUIOptions(guiOptions);
}
if (!guiOptions.contains(GUIO_NOSPEECH))
options.push_back(skyExtraGuiOption);
return options;
}
SaveStateList SkyMetaEngine::listSaves(const char *target) const {
Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
SaveStateList saveList;
// Load the descriptions
Common::StringArray savenames;
savenames.resize(MAX_SAVE_GAMES+1);
Common::InSaveFile *inf;
inf = saveFileMan->openForLoading("SKY-VM.SAV");
if (inf != NULL) {
char *tmpBuf = new char[MAX_SAVE_GAMES * MAX_TEXT_LEN];
char *tmpPtr = tmpBuf;
inf->read(tmpBuf, MAX_SAVE_GAMES * MAX_TEXT_LEN);
for (int i = 0; i < MAX_SAVE_GAMES; ++i) {
savenames[i] = tmpPtr;
tmpPtr += savenames[i].size() + 1;
}
delete inf;
delete[] tmpBuf;
}
// Find all saves
Common::StringArray filenames;
filenames = saveFileMan->listSavefiles("SKY-VM.###");
// Prepare the list of savestates by looping over all matching savefiles
for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) {
// Extract the extension
Common::String ext = file->c_str() + file->size() - 3;
ext.toUppercase();
int slotNum = atoi(ext.c_str());
Common::InSaveFile *in = saveFileMan->openForLoading(*file);
if (in) {
saveList.push_back(SaveStateDescriptor(this, slotNum,
(slotNum == 0) ? _("Autosave") : Common::U32String(savenames[slotNum - 1])));
delete in;
}
}
// Sort saves based on slot number.
Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator());
return saveList;
}
int SkyMetaEngine::getMaximumSaveSlot() const { return MAX_SAVE_GAMES; }
bool SkyMetaEngine::removeSaveState(const char *target, int slot) const {
if (slot == 0) {
// Do not delete the auto save
// Note: Setting the autosave slot as write protected (with setWriteProtectedFlag())
// does not disable the delete action on the slot.
const Common::U32String message = _("WARNING: Deleting the autosave slot is not supported by this engine");
GUI::MessageDialog warn(message);
warn.runModal();
return false;
}
Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
char fName[20];
Common::sprintf_s(fName,"SKY-VM.%03d", slot);
saveFileMan->removeSavefile(fName);
// Load current save game descriptions
Common::StringArray savenames;
savenames.resize(MAX_SAVE_GAMES+1);
Common::InSaveFile *inf;
inf = saveFileMan->openForLoading("SKY-VM.SAV");
if (inf != NULL) {
char *tmpBuf = new char[MAX_SAVE_GAMES * MAX_TEXT_LEN];
char *tmpPtr = tmpBuf;
inf->read(tmpBuf, MAX_SAVE_GAMES * MAX_TEXT_LEN);
for (int i = 0; i < MAX_SAVE_GAMES; ++i) {
savenames[i] = tmpPtr;
tmpPtr += savenames[i].size() + 1;
}
delete inf;
delete[] tmpBuf;
}
// Update the save game description at the given slot
savenames[slot - 1] = "";
// Save the updated descriptions
Common::OutSaveFile *outf;
outf = saveFileMan->openForSaving("SKY-VM.SAV");
bool ioFailed = true;
if (outf) {
for (uint16 cnt = 0; cnt < MAX_SAVE_GAMES; cnt++) {
outf->write(savenames[cnt].c_str(), savenames[cnt].size() + 1);
}
outf->finalize();
if (!outf->err())
ioFailed = false;
delete outf;
}
if (ioFailed)
warning("Unable to store Savegame names to file SKY-VM.SAV. (%s)", saveFileMan->popErrorDesc().c_str());
return !ioFailed;
}
SaveStateDescriptor SkyMetaEngine::querySaveMetaInfos(const char *target, int slot) const {
Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
if (slot > 0) {
// Search current save game descriptions
// for the description of the specified slot, if any
Common::String tmpSavename;
Common::InSaveFile *inf;
inf = saveFileMan->openForLoading("SKY-VM.SAV");
if (inf != NULL) {
char *tmpBuf = new char[MAX_SAVE_GAMES * MAX_TEXT_LEN];
char *tmpPtr = tmpBuf;
inf->read(tmpBuf, MAX_SAVE_GAMES * MAX_TEXT_LEN);
for (int i = 0; i < MAX_SAVE_GAMES; ++i) {
tmpSavename = tmpPtr;
tmpPtr += tmpSavename.size() + 1;
if (i == slot - 1) {
break;
}
}
delete inf;
delete[] tmpBuf;
}
// Make sure the file exists
// Note: there can be valid saved file names with empty savename
char fName[20];
Common::sprintf_s(fName,"SKY-VM.%03d", slot);
Common::InSaveFile *in = saveFileMan->openForLoading(fName);
if (in) {
delete in;
SaveStateDescriptor descriptor(this, slot, tmpSavename);
return descriptor;
}
}
// Reaching here, means we selected an empty save slot, that does not correspond to a save file
SaveStateDescriptor emptySave;
// Do not allow save slot 0 (used for auto-saving) to be overwritten.
if (slot == 0) {
emptySave.setAutosave(true);
emptySave.setWriteProtectedFlag(true);
} else {
emptySave.setWriteProtectedFlag(false);
}
return emptySave;
}
#if PLUGIN_ENABLED_DYNAMIC(SKY)
REGISTER_PLUGIN_DYNAMIC(SKY, PLUGIN_TYPE_ENGINE, SkyMetaEngine);
#else
REGISTER_PLUGIN_STATIC(SKY, PLUGIN_TYPE_ENGINE, SkyMetaEngine);
#endif
namespace Sky {
Common::Error SkyEngine::loadGameState(int slot) {
// We don't need to offset "slot" here. Both loadGameState and quickXRestore
// are called with the ScummVM Save File Manager's "slot" as argument
uint16 result = _skyControl->quickXRestore(slot);
return (result == GAME_RESTORED) ? Common::kNoError : Common::kUnknownError;
}
/**
* Manually saving a game should save it into ScummVM Save File Managers slots 1 or greater.
* ScummVM Save file manager's slot 0 is reserved for the autosave.
* However, natively, the index 0 (_selectedGame) is the first manually saved game.
* @param slot is the save slot on the ScummVM file manager's list
*
*/
Common::Error SkyEngine::saveGameState(int slot, const Common::String &desc, bool isAutosave) {
// prevent writing to autosave slot when user selects it manually
// ie. from the ScummVM in-game menu Save feature
// This also secures _selectedGame which is unsigned integer (uint16)
// from overflowing in the subtraction below
if (slot < 0 || (!isAutosave && slot == 0)) {
return Common::kWritePermissionDenied;
}
// Set the save slot and save the game
// _selectedGame value is one unit lower than the ScummVM's Save File Manager's slot value
// Note that *_selectedGame* value 0 corresponds to a manually saved game (the first in order)
// whereas *slot* value 0 corresponds to the autosave
if (slot > 0) {
// We don't care for updating the _selectedGame when slot == 0
// (in the case of autosave) but we do include the check for slot > 0
// to guard from overflow, which would be bad practice to allow.
_skyControl->_selectedGame = slot - 1;
}
if (_skyControl->saveGameToFile(false, nullptr, isAutosave) != GAME_SAVED)
return Common::kWritePermissionDenied;
// Load current save game descriptions
Common::StringArray saveGameTexts;
saveGameTexts.resize(MAX_SAVE_GAMES+1);
_skyControl->loadDescriptions(saveGameTexts);
// Update the save game description at the given slot
if (!isAutosave) {
saveGameTexts[_skyControl->_selectedGame] = desc;
}
// Save the updated descriptions
_skyControl->saveDescriptions(saveGameTexts);
return Common::kNoError;
}
bool SkyEngine::canLoadGameStateCurrently(Common::U32String *msg) {
return _systemVars->pastIntro
&& _skyControl->loadSaveAllowed()
&& !_skyControl->isControlPanelOpen();
}
bool SkyEngine::canSaveGameStateCurrently(Common::U32String *msg) {
return _systemVars->pastIntro
&& _skyControl->loadSaveAllowed()
&& !_skyControl->isControlPanelOpen();
}
} // End of namespace Sky

35
engines/sky/module.mk Normal file
View File

@@ -0,0 +1,35 @@
MODULE := engines/sky
MODULE_OBJS := \
autoroute.o \
compact.o \
control.o \
debug.o \
disk.o \
grid.o \
hufftext.o \
intro.o \
logic.o \
metaengine.o \
mouse.o \
screen.o \
sky.o \
sound.o \
text.o \
music/adlibchannel.o \
music/adlibmusic.o \
music/gmchannel.o \
music/gmmusic.o \
music/mt32music.o \
music/musicbase.o
# This module can be built as a plugin
ifeq ($(ENABLE_SKY), DYNAMIC_PLUGIN)
PLUGIN := 1
endif
# Include common rules
include $(srcdir)/rules.mk
# Detection objects
DETECT_OBJS += $(MODULE)/detection.o

339
engines/sky/mouse.cpp Normal file
View File

@@ -0,0 +1,339 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#include "common/events.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "graphics/cursorman.h"
#include "sky/disk.h"
#include "sky/logic.h"
#include "sky/mouse.h"
#include "sky/sky.h"
#include "sky/skydefs.h"
#include "sky/struc.h"
#include "sky/compact.h"
namespace Sky {
#define MICE_FILE 60300
#define NO_MAIN_OBJECTS 24
#define NO_LINC_OBJECTS 21
uint32 Mouse::_mouseMainObjects[24] = {
65,
9,
66,
64,
8,
63,
10,
11,
71,
76,
37,
36,
42,
75,
79,
6,
74,
39,
49,
43,
34,
35,
77,
38
};
uint32 Mouse::_mouseLincObjects[21] = {
24625,
24649,
24827,
24651,
24583,
24581,
24582,
24628,
24650,
24629,
24732,
24631,
24584,
24630,
24626,
24627,
24632,
24643,
24828,
24830,
24829
};
Mouse::Mouse(OSystem *system, Disk *skyDisk, SkyCompact *skyCompact) {
_skyDisk = skyDisk;
_skyCompact = skyCompact;
_system = system;
_mouseB = 0;
_currentCursor = 6;
_mouseX = GAME_SCREEN_WIDTH / 2;
_mouseY = GAME_SCREEN_HEIGHT / 2;
_miceData = _skyDisk->loadFile(MICE_FILE);
//load in the object mouse file
_objectMouseData = _skyDisk->loadFile(MICE_FILE + 1);
}
Mouse::~Mouse( ){
free (_miceData);
free (_objectMouseData);
}
void Mouse::replaceMouseCursors(uint16 fileNo) {
free(_objectMouseData);
_objectMouseData = _skyDisk->loadFile(fileNo);
}
bool Mouse::fnAddHuman() {
//reintroduce the mouse so that the human can control the player
//could still be switched out at high-level
if (!Logic::_scriptVariables[MOUSE_STOP]) {
Logic::_scriptVariables[MOUSE_STATUS] |= 6; //cursor & mouse
if (_mouseY < 2) //stop mouse activating top line
_mouseY = 2;
_system->warpMouse(_mouseX, _mouseY);
//force the pointer engine into running a get-off
//even if it's over nothing
//KWIK-FIX
//get off may contain script to remove mouse pointer text
//surely this script should be run just in case
//I am going to try it anyway
if (Logic::_scriptVariables[GET_OFF])
_skyLogic->script((uint16)Logic::_scriptVariables[GET_OFF],(uint16)(Logic::_scriptVariables[GET_OFF] >> 16));
Logic::_scriptVariables[SPECIAL_ITEM] = 0xFFFFFFFF;
Logic::_scriptVariables[GET_OFF] = RESET_MOUSE;
}
return true;
}
void Mouse::fnSaveCoods() {
Logic::_scriptVariables[SAFEX] = _mouseX + TOP_LEFT_X;
Logic::_scriptVariables[SAFEY] = _mouseY + TOP_LEFT_Y;
}
void Mouse::lockMouse() {
SkyEngine::_systemVars->systemFlags |= SF_MOUSE_LOCKED;
}
void Mouse::unlockMouse() {
SkyEngine::_systemVars->systemFlags &= ~SF_MOUSE_LOCKED;
}
void Mouse::restoreMouseData(uint16 frameNum) {
warning("Stub: Mouse::restoreMouseData");
}
void Mouse::drawNewMouse() {
warning("Stub: Mouse::drawNewMouse");
//calculateMouseValues();
//saveMouseData();
//drawMouse();
}
void Mouse::waitMouseNotPressed(int minDelay) {
bool mousePressed = true;
uint32 now = _system->getMillis();
Common::Event event;
Common::EventManager *eventMan = _system->getEventManager();
while (mousePressed || _system->getMillis() < now + minDelay) {
if (eventMan->shouldQuit()) {
minDelay = 0;
mousePressed = false;
}
if (!eventMan->getButtonState())
mousePressed = false;
while (eventMan->pollEvent(event)) {
switch (event.type) {
case Common::EVENT_CUSTOM_ENGINE_ACTION_START:
if (event.customType == kSkyActionSkip) {
minDelay = 0;
mousePressed = false;
}
break;
default:
break;
}
}
_system->updateScreen();
_system->delayMillis(20);
}
}
void Mouse::spriteMouse(uint16 frameNum, uint8 mouseX, uint8 mouseY) {
_currentCursor = frameNum;
byte *newCursor = _miceData;
newCursor += ((DataFileHeader *)_miceData)->s_sp_size * frameNum;
newCursor += sizeof(DataFileHeader);
uint16 mouseWidth = ((DataFileHeader *)_miceData)->s_width;
uint16 mouseHeight = ((DataFileHeader *)_miceData)->s_height;
CursorMan.replaceCursor(newCursor, mouseWidth, mouseHeight, mouseX, mouseY, 0);
if (frameNum == MOUSE_BLANK)
CursorMan.showMouse(false);
else
CursorMan.showMouse(true);
}
void Mouse::mouseEngine() {
_logicClick = (_mouseB > 0); // click signal is available for Logic for one gamecycle
if (!Logic::_scriptVariables[MOUSE_STOP]) {
if (Logic::_scriptVariables[MOUSE_STATUS] & (1 << 1)) {
pointerEngine(_mouseX + TOP_LEFT_X, _mouseY + TOP_LEFT_Y);
if (Logic::_scriptVariables[MOUSE_STATUS] & (1 << 2)) //buttons enabled?
buttonEngine1();
}
}
_mouseB = 0; //don't save up buttons
}
void Mouse::pointerEngine(uint16 xPos, uint16 yPos) {
uint32 currentListNum = Logic::_scriptVariables[MOUSE_LIST_NO];
uint16 *currentList;
do {
currentList = (uint16 *)_skyCompact->fetchCpt(currentListNum);
while ((*currentList != 0) && (*currentList != 0xFFFF)) {
uint16 itemNum = *currentList;
Compact *itemData = _skyCompact->fetchCpt(itemNum);
currentList++;
if ((itemData->screen == Logic::_scriptVariables[SCREEN]) && (itemData->status & 16)) {
if (itemData->xcood + ((int16)itemData->mouseRelX) > xPos) continue;
if (itemData->xcood + ((int16)itemData->mouseRelX) + itemData->mouseSizeX < xPos) continue;
if (itemData->ycood + ((int16)itemData->mouseRelY) > yPos) continue;
if (itemData->ycood + ((int16)itemData->mouseRelY) + itemData->mouseSizeY < yPos) continue;
// we've hit the item
if (Logic::_scriptVariables[SPECIAL_ITEM] == itemNum)
return;
Logic::_scriptVariables[SPECIAL_ITEM] = itemNum;
if (Logic::_scriptVariables[GET_OFF])
_skyLogic->mouseScript(Logic::_scriptVariables[GET_OFF], itemData);
Logic::_scriptVariables[GET_OFF] = itemData->mouseOff;
if (itemData->mouseOn)
_skyLogic->mouseScript(itemData->mouseOn, itemData);
return;
}
}
if (*currentList == 0xFFFF)
currentListNum = currentList[1];
} while (*currentList != 0);
if (Logic::_scriptVariables[SPECIAL_ITEM] != 0) {
Logic::_scriptVariables[SPECIAL_ITEM] = 0;
if (Logic::_scriptVariables[GET_OFF])
_skyLogic->script((uint16)Logic::_scriptVariables[GET_OFF],(uint16)(Logic::_scriptVariables[GET_OFF] >> 16));
Logic::_scriptVariables[GET_OFF] = 0;
}
}
void Mouse::buttonPressed(uint8 button) {
_mouseB = button;
}
void Mouse::mouseMoved(uint16 mouseX, uint16 mouseY) {
_mouseX = mouseX;
_mouseY = mouseY;
}
void Mouse::buttonEngine1() {
//checks for clicking on special item
//"compare the size of this routine to S1 mouse_button"
if (_mouseB) { //anything pressed?
Logic::_scriptVariables[BUTTON] = _mouseB;
if (Logic::_scriptVariables[SPECIAL_ITEM]) { //over anything?
Compact *item = _skyCompact->fetchCpt(Logic::_scriptVariables[SPECIAL_ITEM]);
if (item->mouseClick)
_skyLogic->mouseScript(item->mouseClick, item);
}
}
}
void Mouse::resetCursor() {
spriteMouse(_currentCursor, 0, 0);
}
uint16 Mouse::findMouseCursor(uint32 itemNum) {
uint8 cnt;
for (cnt = 0; cnt < NO_MAIN_OBJECTS; cnt++) {
if (itemNum == _mouseMainObjects[cnt]) {
return cnt;
}
}
for (cnt = 0; cnt < NO_LINC_OBJECTS; cnt++) {
if (itemNum == _mouseLincObjects[cnt]) {
return cnt;
}
}
return 0;
}
void Mouse::fnOpenCloseHand(bool open) {
if ((!open) && (!Logic::_scriptVariables[OBJECT_HELD])) {
spriteMouse(1, 0, 0);
return;
}
uint16 cursor = findMouseCursor(Logic::_scriptVariables[OBJECT_HELD]) << 1;
if (open)
cursor++;
uint32 size = ((DataFileHeader *)_objectMouseData)->s_sp_size;
uint8 *srcData;
uint8 *destData;
srcData = (uint8 *)_objectMouseData + size * cursor + sizeof(DataFileHeader);
destData = (uint8 *)_miceData + sizeof(DataFileHeader);
memcpy(destData, srcData, size);
spriteMouse(0, 5, 5);
}
bool Mouse::wasClicked() {
if (_logicClick) {
_logicClick = false;
return true;
} else
return false;
}
} // End of namespace Sky

92
engines/sky/mouse.h Normal file
View File

@@ -0,0 +1,92 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef SKY_MOUSE_H
#define SKY_MOUSE_H
#include "common/scummsys.h"
class OSystem;
namespace Sky {
class Disk;
class Logic;
class SkyCompact;
class Mouse {
public:
Mouse(OSystem *system, Disk *skyDisk, SkyCompact *skyCompact);
~Mouse();
void mouseEngine();
void replaceMouseCursors(uint16 fileNo);
bool fnAddHuman();
void fnSaveCoods();
void fnOpenCloseHand(bool open);
uint16 findMouseCursor(uint32 itemNum);
void lockMouse();
void unlockMouse();
void restoreMouseData(uint16 frameNum);
void drawNewMouse();
void spriteMouse(uint16 frameNum, uint8 mouseX, uint8 mouseY);
void useLogicInstance(Logic *skyLogic) { _skyLogic = skyLogic; }
void buttonPressed(uint8 button);
void mouseMoved(uint16 mouseX, uint16 mouseY);
void waitMouseNotPressed(int minDelay = 0);
uint16 giveMouseX() { return _mouseX; }
uint16 giveMouseY() { return _mouseY; }
uint16 giveCurrentMouseType() { return _currentCursor; }
bool wasClicked();
void logicClick() { _logicClick = true; }
void resetCursor();
protected:
void pointerEngine(uint16 xPos, uint16 yPos);
void buttonEngine1();
bool _logicClick;
uint16 _mouseB; //mouse button
uint16 _mouseX; //actual mouse coordinates
uint16 _mouseY;
uint16 _currentCursor;
byte *_miceData; //address of mouse sprites
byte *_objectMouseData; //address of object mouse sprites
static uint32 _mouseMainObjects[24];
static uint32 _mouseLincObjects[21];
OSystem *_system;
Disk *_skyDisk;
Logic *_skyLogic;
SkyCompact *_skyCompact;
};
} // End of namespace Sky
#endif //SKYMOUSE_H

View File

@@ -0,0 +1,316 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#include "common/endian.h"
#include "common/textconsole.h"
#include "audio/fmopl.h"
#include "sky/music/adlibchannel.h"
#include "sky/sky.h"
namespace Sky {
AdLibChannel::AdLibChannel(OPL::OPL *opl, uint8 *pMusicData, uint16 startOfData) {
_opl = opl;
_musicData = pMusicData;
_channelData.loopPoint = startOfData;
_channelData.eventDataPtr = startOfData;
_channelData.channelActive = true;
_channelData.tremoVibro = 0;
_channelData.assignedInstrument = 0xFF;
_channelData.channelVolume = 0x7F;
_channelData.nextEventTime = getNextEventTime();
_channelData.adlibChannelNumber = _channelData.lastCommand = _channelData.note =
_channelData.adlibReg1 = _channelData.adlibReg2 = _channelData.freqOffset = 0;
_channelData.frequency = 0;
_channelData.instrumentData = NULL;
_musicVolume = 128;
uint16 instrumentDataLoc;
if (SkyEngine::_systemVars->gameVersion == 109) {
//instrumentDataLoc = (_musicData[0x11D0] << 8) | _musicData[0x11CF];
//_frequenceTable = (uint16 *)(_musicData + 0x835);
//_registerTable = _musicData + 0xE35;
//_opOutputTable = _musicData + 0xE47;
//_adlibRegMirror = _musicData + 0xF4A;
instrumentDataLoc = READ_LE_UINT16(_musicData + 0x1204);
_frequenceTable = (uint16 *)(_musicData + 0x868);
_registerTable = _musicData + 0xE68;
_opOutputTable = _musicData + 0xE7A;
_adlibRegMirror = _musicData + 0xF7D;
} else if (SkyEngine::_systemVars->gameVersion == 267) {
instrumentDataLoc = READ_LE_UINT16(_musicData + 0x11FB);
_frequenceTable = (uint16 *)(_musicData + 0x7F4);
_registerTable = _musicData + 0xDF4;
_opOutputTable = _musicData + 0xE06;
_adlibRegMirror = _musicData + 0xF55;
} else {
instrumentDataLoc = READ_LE_UINT16(_musicData + 0x1205);
_frequenceTable = (uint16 *)(_musicData + 0x7FE);
_registerTable = _musicData + 0xDFE;
_opOutputTable = _musicData + 0xE10;
_adlibRegMirror = _musicData + 0xF5F;
}
_instrumentMap = _musicData+instrumentDataLoc;
_instruments = (InstrumentStruct *)(_instrumentMap+0x80);
}
AdLibChannel::~AdLibChannel() {
stopNote();
}
bool AdLibChannel::isActive() {
return _channelData.channelActive;
}
void AdLibChannel::updateVolume(uint16 pVolume) {
_musicVolume = pVolume;
}
/* This class uses the same area for the register mirror as the original
asm driver did (_musicData[0xF5F..0x105E]), so the cache is indeed shared
by all instances of the class.
*/
void AdLibChannel::setRegister(uint8 regNum, uint8 value) {
if (_adlibRegMirror[regNum] != value) {
_opl->writeReg(regNum, value);
_adlibRegMirror[regNum] = value;
}
}
void AdLibChannel::stopNote() {
if (_channelData.note & 0x20) {
_channelData.note &= ~0x20;
setRegister(0xB0 | _channelData.adlibChannelNumber, _channelData.note);
}
}
int32 AdLibChannel::getNextEventTime() {
int32 retV = 0;
uint8 cnt, lVal = 0;
for (cnt = 0; cnt < 4; cnt++) {
lVal = _musicData[_channelData.eventDataPtr];
_channelData.eventDataPtr++;
retV = (retV << 7) | (lVal & 0x7F);
if (!(lVal & 0x80))
break;
}
if (lVal & 0x80) {
return -1; // should never happen
} else
return retV;
}
uint8 AdLibChannel::process(uint16 aktTime) {
if (!_channelData.channelActive) {
return 0;
}
uint8 returnVal = 0;
_channelData.nextEventTime -= aktTime;
uint8 opcode;
while ((_channelData.nextEventTime < 0) && (_channelData.channelActive)) {
opcode = _musicData[_channelData.eventDataPtr];
_channelData.eventDataPtr++;
if (opcode & 0x80) {
if (opcode == 0xFF) {
// dummy opcode
} else if (opcode >= 0x90) {
switch (opcode&0xF) {
case 0: com90_caseNoteOff(); break;
case 1: com90_stopChannel(); break;
case 2: com90_setupInstrument(); break;
case 3:
returnVal = com90_updateTempo();
break;
case 5: com90_getFreqOffset(); break;
case 6: com90_getChannelVolume(); break;
case 7: com90_getTremoVibro(); break;
case 8: com90_loopMusic(); break;
case 9: com90_keyOff(); break;
case 12: com90_setLoopPoint(); break;
default:
error("AdLibChannel: Unknown music opcode 0x%02X", opcode);
break;
}
} else {
// new adlib channel assignment
_channelData.adlibChannelNumber = opcode & 0xF;
_channelData.adlibReg1 = _registerTable[((opcode & 0xF) << 1) | 0];
_channelData.adlibReg2 = _registerTable[((opcode & 0xF) << 1) | 1];
}
} else {
_channelData.lastCommand = opcode;
stopNote();
// not sure why this "if" is necessary...either a bug in my
// code or a bug in the music data (section 1, music 2)
if (_channelData.instrumentData || _channelData.tremoVibro) {
setupInstrument(opcode);
opcode = _musicData[_channelData.eventDataPtr];
_channelData.eventDataPtr++;
setupChannelVolume(opcode);
} else
_channelData.eventDataPtr++;
}
if (_channelData.channelActive)
_channelData.nextEventTime += getNextEventTime();
}
return returnVal;
}
void AdLibChannel::setupInstrument(uint8 opcode) {
uint16 nextNote;
if (_channelData.tremoVibro) {
uint8 newInstrument = _instrumentMap[opcode];
if (newInstrument != _channelData.assignedInstrument) {
_channelData.assignedInstrument = newInstrument;
_channelData.instrumentData = _instruments + newInstrument;
adlibSetupInstrument();
}
_channelData.lastCommand = _channelData.instrumentData->bindedEffect;
nextNote = getNextNote(_channelData.lastCommand);
} else {
nextNote = getNextNote(opcode - 0x18 + _channelData.instrumentData->bindedEffect);
}
_channelData.frequency = nextNote;
setRegister(0xA0 | _channelData.adlibChannelNumber, (uint8)nextNote);
setRegister(0xB0 | _channelData.adlibChannelNumber, (uint8)((nextNote >> 8) | 0x20));
_channelData.note = (uint8)((nextNote >> 8) | 0x20);
}
void AdLibChannel::setupChannelVolume(uint8 volume) {
uint8 resultOp;
uint32 resVol = ((volume + 1) * (_channelData.instrumentData->totOutLev_Op2 + 1)) << 1;
resVol &= 0xFFFF;
resVol *= (_channelData.channelVolume + 1) << 1;
resVol >>= 8;
resVol *= _musicVolume << 1;
resVol >>= 16;
assert(resVol < 0x81);
resultOp = ((_channelData.instrumentData->scalingLevel << 6) & 0xC0) | _opOutputTable[resVol];
setRegister(0x40 | _channelData.adlibReg2, resultOp);
if (_channelData.instrumentData->feedBack & 1) {
resVol = ((volume + 1) * (_channelData.instrumentData->totOutLev_Op1 + 1)) << 1;
resVol &= 0xFFFF;
resVol *= (_channelData.channelVolume + 1) << 1;
resVol >>= 8;
resVol *= _musicVolume << 1;
resVol >>= 16;
} else
resVol = _channelData.instrumentData->totOutLev_Op1;
assert(resVol < 0x81);
resultOp = ((_channelData.instrumentData->scalingLevel << 2) & 0xC0) | _opOutputTable[resVol];
setRegister(0x40 | _channelData.adlibReg1, resultOp);
}
void AdLibChannel::adlibSetupInstrument() {
setRegister(0x60 | _channelData.adlibReg1, _channelData.instrumentData->ad_Op1);
setRegister(0x60 | _channelData.adlibReg2, _channelData.instrumentData->ad_Op2);
setRegister(0x80 | _channelData.adlibReg1, _channelData.instrumentData->sr_Op1);
setRegister(0x80 | _channelData.adlibReg2, _channelData.instrumentData->sr_Op2);
setRegister(0xE0 | _channelData.adlibReg1, _channelData.instrumentData->waveSelect_Op1);
setRegister(0xE0 | _channelData.adlibReg2, _channelData.instrumentData->waveSelect_Op2);
setRegister(0xC0 | _channelData.adlibChannelNumber, _channelData.instrumentData->feedBack);
setRegister(0x20 | _channelData.adlibReg1, _channelData.instrumentData->ampMod_Op1);
setRegister(0x20 | _channelData.adlibReg2, _channelData.instrumentData->ampMod_Op2);
}
uint16 AdLibChannel::getNextNote(uint8 param) {
int16 freqIndex = ((int16)_channelData.freqOffset) - 0x40;
if (freqIndex >= 0x3F)
freqIndex++;
freqIndex *= 2;
freqIndex += param << 6;
uint16 freqData = FROM_LE_16(_frequenceTable[freqIndex % 0x300]);
if ((freqIndex % 0x300 >= 0x1C0) || (freqIndex / 0x300 > 0)) {
return (((freqIndex / 0x300) - 1) << 10) + (freqData & 0x7FF);
} else {
// looks like a bug. dunno why. It's what the ASM code says.
return (uint16)(((int16)freqData) >> 1);
}
}
//- command 90h routines
void AdLibChannel::com90_caseNoteOff() {
if (_musicData[_channelData.eventDataPtr] == _channelData.lastCommand)
stopNote();
_channelData.eventDataPtr++;
}
void AdLibChannel::com90_stopChannel() {
stopNote();
_channelData.channelActive = false;
}
void AdLibChannel::com90_setupInstrument() {
_channelData.channelVolume = 0x7F;
_channelData.freqOffset = 0x40;
_channelData.assignedInstrument = _musicData[_channelData.eventDataPtr];
_channelData.eventDataPtr++;
_channelData.instrumentData = _instruments + _channelData.assignedInstrument;
adlibSetupInstrument();
}
uint8 AdLibChannel::com90_updateTempo() {
return _musicData[_channelData.eventDataPtr++];
}
void AdLibChannel::com90_getFreqOffset() {
_channelData.freqOffset = _musicData[_channelData.eventDataPtr++];
if (_channelData.note & 0x20) {
uint16 nextNote = getNextNote(
_channelData.lastCommand - 0x18 + _channelData.instrumentData->bindedEffect);
setRegister(0xA0 | _channelData.adlibChannelNumber, (uint8)nextNote);
setRegister(0xB0 | _channelData.adlibChannelNumber, (uint8)((nextNote >> 8) | 0x20));
_channelData.note = (uint8)(nextNote >> 8) | 0x20;
}
}
void AdLibChannel::com90_getChannelVolume() {
_channelData.channelVolume = _musicData[_channelData.eventDataPtr++];
}
void AdLibChannel::com90_getTremoVibro() {
_channelData.tremoVibro = _musicData[_channelData.eventDataPtr++];
}
void AdLibChannel::com90_loopMusic() {
_channelData.eventDataPtr = _channelData.loopPoint;
}
void AdLibChannel::com90_keyOff() {
stopNote();
}
void AdLibChannel::com90_setLoopPoint() {
_channelData.loopPoint = _channelData.eventDataPtr;
}
} // End of namespace Sky

View File

@@ -0,0 +1,110 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef SKY_MUSIC_ADLIBCHANNEL_H
#define SKY_MUSIC_ADLIBCHANNEL_H
#include "sky/music/musicbase.h"
namespace OPL {
class OPL;
}
namespace Sky {
typedef struct {
uint8 ad_Op1, ad_Op2;
uint8 sr_Op1, sr_Op2;
uint8 ampMod_Op1, ampMod_Op2;
uint8 waveSelect_Op1, waveSelect_Op2;
uint8 bindedEffect;
uint8 feedBack;
uint8 totOutLev_Op1, totOutLev_Op2;
uint8 scalingLevel;
uint8 pad1, pad2, pad3;
} InstrumentStruct;
typedef struct {
uint16 eventDataPtr;
int32 nextEventTime;
uint16 loopPoint;
uint8 adlibChannelNumber;
uint8 lastCommand;
bool channelActive;
uint8 note;
uint8 adlibReg1, adlibReg2;
InstrumentStruct *instrumentData;
uint8 assignedInstrument;
uint8 channelVolume;
uint8 padding; // field_12 / not used by original driver
uint8 tremoVibro;
uint8 freqOffset;
uint16 frequency;
} AdLibChannelType;
class AdLibChannel : public ChannelBase {
public:
AdLibChannel (OPL::OPL *opl, uint8 *pMusicData, uint16 startOfData);
~AdLibChannel() override;
uint8 process(uint16 aktTime) override;
void updateVolume(uint16 pVolume) override;
bool isActive() override;
private:
OPL::OPL *_opl;
uint8 *_musicData;
uint16 _musicVolume;
AdLibChannelType _channelData;
InstrumentStruct *_instruments;
uint16 *_frequenceTable;
uint8 *_instrumentMap;
uint8 *_registerTable, *_opOutputTable;
uint8 *_adlibRegMirror;
// normal subs
void setRegister(uint8 regNum, uint8 value);
int32 getNextEventTime();
uint16 getNextNote(uint8 param);
void adlibSetupInstrument();
void setupInstrument(uint8 opcode);
void setupChannelVolume(uint8 volume);
void stopNote();
// Streamfunctions from Command90hTable
void com90_caseNoteOff(); // 0
void com90_stopChannel(); // 1
void com90_setupInstrument(); // 2
uint8 com90_updateTempo(); // 3
//void com90_dummy(); // 4
void com90_getFreqOffset(); // 5
void com90_getChannelVolume(); // 6
void com90_getTremoVibro(); // 7
void com90_loopMusic(); // 8
void com90_keyOff(); // 9
//void com90_error(); // 10
//void com90_doLodsb(); // 11
void com90_setLoopPoint(); // 12
//void com90_do_two_Lodsb(); // 13
};
} // End of namespace Sky
#endif //ADLIBCHANNEL_H

View File

@@ -0,0 +1,98 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#include "common/endian.h"
#include "common/textconsole.h"
#include "sky/music/adlibmusic.h"
#include "sky/music/adlibchannel.h"
#include "audio/fmopl.h"
#include "sky/sky.h"
namespace Audio {
class Mixer;
}
namespace Sky {
AdLibMusic::AdLibMusic(Audio::Mixer *pMixer, Disk *pDisk) : MusicBase(pMixer, pDisk) {
_driverFileBase = 60202;
_opl = OPL::Config::create();
if (!_opl || !_opl->init())
error("Failed to create OPL");
_opl->start(new Common::Functor0Mem<void, AdLibMusic>(this, &AdLibMusic::onTimer), 50);
}
AdLibMusic::~AdLibMusic() {
delete _opl;
}
void AdLibMusic::onTimer() {
if (_musicData != NULL)
pollMusic();
}
void AdLibMusic::setupPointers() {
if (SkyEngine::_systemVars->gameVersion == 109) {
// disk demo uses a different AdLib driver version, some offsets have changed
//_musicDataLoc = (_musicData[0x11CC] << 8) | _musicData[0x11CB];
//_initSequence = _musicData + 0xEC8;
_musicDataLoc = READ_LE_UINT16(_musicData + 0x1200);
_initSequence = _musicData + 0xEFB;
} else if (SkyEngine::_systemVars->gameVersion == 267) {
_musicDataLoc = READ_LE_UINT16(_musicData + 0x11F7);
_initSequence = _musicData + 0xE87;
} else {
_musicDataLoc = READ_LE_UINT16(_musicData + 0x1201);
_initSequence = _musicData + 0xE91;
}
}
void AdLibMusic::setupChannels(uint8 *channelData) {
_numberOfChannels = channelData[0];
channelData++;
for (uint8 cnt = 0; cnt < _numberOfChannels; cnt++) {
uint16 chDataStart = READ_LE_UINT16((uint16 *)channelData + cnt) + _musicDataLoc;
_channels[cnt] = new AdLibChannel(_opl, _musicData, chDataStart);
// also set proper volume - Fixes bug where intro music plays at full volume
_channels[cnt]->updateVolume(_musicVolume);
}
}
void AdLibMusic::startDriver() {
uint16 cnt = 0;
while (_initSequence[cnt] || _initSequence[cnt + 1]) {
_opl->writeReg(_initSequence[cnt], _initSequence[cnt + 1]);
cnt += 2;
}
}
void AdLibMusic::setVolume(uint16 param) {
_musicVolume = param;
for (uint8 cnt = 0; cnt < _numberOfChannels; cnt++)
_channels[cnt]->updateVolume(_musicVolume);
}
} // End of namespace Sky

View File

@@ -0,0 +1,60 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef SKY_MUSIC_ADLIBMUSIC_H
#define SKY_MUSIC_ADLIBMUSIC_H
#include "sky/music/musicbase.h"
namespace Audio {
class Mixer;
}
namespace OPL {
class OPL;
}
namespace Sky {
class AdLibMusic : public MusicBase {
public:
AdLibMusic(Audio::Mixer *pMixer, Disk *pDisk);
~AdLibMusic() override;
// AudioStream API
void setVolume(uint16 param) override;
private:
OPL::OPL *_opl;
uint8 *_initSequence;
uint32 _sampleRate;
void setupPointers() override;
void setupChannels(uint8 *channelData) override;
void startDriver() override;
void premixerCall(int16 *buf, uint len);
void onTimer();
};
} // End of namespace Sky
#endif //ADLIBMUSIC_H

View File

@@ -0,0 +1,195 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#include "gmchannel.h"
#include "common/util.h"
#include "common/textconsole.h"
#include "audio/mididrv.h"
namespace Sky {
GmChannel::GmChannel(uint8 *pMusicData, uint16 startOfData, MidiDriver *pMidiDrv, const byte *pInstMap, const byte *veloTab) {
_musicData = pMusicData;
_midiDrv = pMidiDrv;
_channelData.midiChannelNumber = 0;
_channelData.loopPoint = startOfData;
_channelData.eventDataPtr = startOfData;
_channelData.channelActive = true;
_channelData.nextEventTime = getNextEventTime();
_instMap = pInstMap;
_veloTab = veloTab;
_musicVolume = 0x7F;
_currentChannelVolume = 0x7F;
}
GmChannel::~GmChannel() {
stopNote();
}
bool GmChannel::isActive() {
return _channelData.channelActive;
}
void GmChannel::updateVolume(uint16 pVolume) {
_musicVolume = pVolume;
if (_musicVolume > 0)
_musicVolume = (_musicVolume * 2) / 3 + 43;
uint8 newVol = (_currentChannelVolume * _musicVolume) >> 7;
_midiDrv->send((0xB0 | _channelData.midiChannelNumber) | 0x700 | (newVol << 16));
}
void GmChannel::stopNote() {
// All Notes Off
_midiDrv->send((0xB0 | _channelData.midiChannelNumber) | 0x7B00 | 0 | 0x79000000);
// Reset the Pitch Wheel. See bug #1742.
_midiDrv->send((0xE0 | _channelData.midiChannelNumber) | 0x400000);
}
int32 GmChannel::getNextEventTime() {
int32 retV = 0;
uint8 cnt, lVal = 0;
for (cnt = 0; cnt < 4; cnt++) {
lVal = _musicData[_channelData.eventDataPtr];
_channelData.eventDataPtr++;
retV = (retV << 7) | (lVal & 0x7F);
if (!(lVal & 0x80))
break;
}
if (lVal & 0x80) { // should never happen
return -1;
} else
return retV;
}
uint8 GmChannel::process(uint16 aktTime) {
if (!_channelData.channelActive)
return 0;
uint8 returnVal = 0;
_channelData.nextEventTime -= aktTime;
uint8 opcode;
while ((_channelData.nextEventTime < 0) && (_channelData.channelActive)) {
opcode = _musicData[_channelData.eventDataPtr];
_channelData.eventDataPtr++;
if (opcode&0x80) {
if (opcode == 0xFF) {
// dummy opcode
} else if (opcode >= 0x90) {
switch (opcode&0xF) {
case 0: com90_caseNoteOff(); break;
case 1: com90_stopChannel(); break;
case 2: com90_setupInstrument(); break;
case 3:
returnVal = com90_updateTempo();
break;
case 5: com90_getPitch(); break;
case 6: com90_getChannelVolume(); break;
case 8: com90_loopMusic(); break;
case 9: com90_keyOff(); break;
case 11: com90_getChannelPanValue(); break;
case 12: com90_setLoopPoint(); break;
case 13: com90_getChannelControl(); break;
default:
error("GmChannel: Unknown music opcode 0x%02X", opcode);
break;
}
} else {
// new midi channel assignment
_channelData.midiChannelNumber = opcode&0xF;
}
} else {
_channelData.note = opcode;
byte velocity = _musicData[_channelData.eventDataPtr];
if (_veloTab)
velocity = _veloTab[velocity];
_channelData.eventDataPtr++;
_midiDrv->send((0x90 | _channelData.midiChannelNumber) | (opcode << 8) | (velocity << 16));
}
if (_channelData.channelActive)
_channelData.nextEventTime += getNextEventTime();
}
return returnVal;
}
//- command 90h routines
void GmChannel::com90_caseNoteOff() {
_midiDrv->send((0x90 | _channelData.midiChannelNumber) | (_musicData[_channelData.eventDataPtr] << 8));
_channelData.eventDataPtr++;
}
void GmChannel::com90_stopChannel() {
stopNote();
_channelData.channelActive = false;
}
void GmChannel::com90_setupInstrument() {
byte instrument = _musicData[_channelData.eventDataPtr];
if (_instMap)
instrument = _instMap[instrument];
_midiDrv->send((0xC0 | _channelData.midiChannelNumber) | (instrument << 8));
_channelData.eventDataPtr++;
}
uint8 GmChannel::com90_updateTempo() {
return _musicData[_channelData.eventDataPtr++];
}
void GmChannel::com90_getPitch() {
_midiDrv->send((0xE0 | _channelData.midiChannelNumber) | 0 | (_musicData[_channelData.eventDataPtr] << 16));
_channelData.eventDataPtr++;
}
void GmChannel::com90_getChannelVolume() {
_currentChannelVolume = _musicData[_channelData.eventDataPtr++];
uint8 newVol = (uint8)((_currentChannelVolume * _musicVolume) >> 7);
_midiDrv->send((0xB0 | _channelData.midiChannelNumber) | 0x700 | (newVol << 16));
}
void GmChannel::com90_loopMusic() {
_channelData.eventDataPtr = _channelData.loopPoint;
}
void GmChannel::com90_keyOff() {
_midiDrv->send((0x90 | _channelData.midiChannelNumber) | (_channelData.note << 8) | 0);
}
void GmChannel::com90_setLoopPoint() {
_channelData.loopPoint = _channelData.eventDataPtr;
}
void GmChannel::com90_getChannelPanValue() {
_midiDrv->send((0xB0 | _channelData.midiChannelNumber) | 0x0A00 | (_musicData[_channelData.eventDataPtr] << 16));
_channelData.eventDataPtr++;
}
void GmChannel::com90_getChannelControl() {
uint8 conNum = _musicData[_channelData.eventDataPtr++];
uint8 conDat = _musicData[_channelData.eventDataPtr++];
_midiDrv->send((0xB0 | _channelData.midiChannelNumber) | (conNum << 8) | (conDat << 16));
}
} // End of namespace Sky

View File

@@ -0,0 +1,82 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef SKY_MUSIC_GMCHANNEL_H
#define SKY_MUSIC_GMCHANNEL_H
#include "sky/music/musicbase.h"
class MidiDriver;
namespace Sky {
typedef struct {
uint16 eventDataPtr;
int32 nextEventTime;
uint16 loopPoint;
uint8 midiChannelNumber;
uint8 note;
bool channelActive;
} MidiChannelType;
class GmChannel : public ChannelBase {
public:
GmChannel(uint8 *pMusicData, uint16 startOfData, MidiDriver *pMidiDrv, const byte *pInstMap, const byte *veloTab);
~GmChannel() override;
virtual void stopNote();
uint8 process(uint16 aktTime) override;
void updateVolume(uint16 pVolume) override;
bool isActive() override;
private:
const byte *_instMap;
const byte *_veloTab;
MidiDriver *_midiDrv;
uint8 *_musicData;
uint16 _musicVolume;
MidiChannelType _channelData;
uint8 _currentChannelVolume;
//- normal subs
void setRegister(uint8 regNum, uint8 value);
int32 getNextEventTime();
uint16 getNextNote(uint8 param);
void adlibSetupInstrument();
void setupInstrument(uint8 opcode);
void setupChannelVolume(uint8 volume);
//- Streamfunctions from Command90hTable
void com90_caseNoteOff(); // 0
void com90_stopChannel(); // 1
void com90_setupInstrument(); // 2
uint8 com90_updateTempo(); // 3
//void com90_dummy(); // 4
void com90_getPitch(); // 5
void com90_getChannelVolume(); // 6
//void com90_skipTremoVibro(); // 7
void com90_loopMusic(); // 8
void com90_keyOff(); // 9
//void com90_error(); // 10
void com90_getChannelPanValue(); // 11
void com90_setLoopPoint(); // 12
void com90_getChannelControl(); // 13
};
} // End of namespace Sky
#endif //SKYGMCHANNEL_H

View File

@@ -0,0 +1,120 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#include "sky/music/gmmusic.h"
#include "sky/music/gmchannel.h"
#include "sky/sky.h"
#include "common/util.h"
#include "common/endian.h"
#include "common/textconsole.h"
#include "audio/mididrv.h"
namespace Sky {
void GmMusic::passTimerFunc(void *param) {
((GmMusic *)param)->timerCall();
}
GmMusic::GmMusic(MidiDriver *pMidiDrv, Audio::Mixer *pMixer, Disk *pDisk) : MusicBase(pMixer, pDisk) {
_driverFileBase = 60200;
_midiDrv = pMidiDrv;
int midiRes = _midiDrv->open();
if (midiRes != 0)
error("Can't open midi device. Errorcode: %d", midiRes);
_timerCount = 0;
_midiDrv->setTimerCallback(this, passTimerFunc);
_midiDrv->sendGMReset();
}
GmMusic::~GmMusic() {
_midiDrv->setTimerCallback(NULL, NULL);
if (_currentMusic)
stopMusic();
// Send All Sound Off and All Notes Off (for external synths)
for (int i = 0; i < 16; i++) {
_midiDrv->send((120 << 8) | 0xB0 | i);
_midiDrv->send((123 << 8) | 0xB0 | i);
}
_midiDrv->close();
delete _midiDrv;
}
void GmMusic::setVolume(uint16 param) {
_musicVolume = param;
for (uint8 cnt = 0; cnt < _numberOfChannels; cnt++)
_channels[cnt]->updateVolume(_musicVolume);
}
void GmMusic::timerCall() {
_timerCount += _midiDrv->getBaseTempo();
if (_timerCount > (1000 * 1000 / 50)) {
// call pollMusic() 50 times per second
_timerCount -= 1000 * 1000 / 50;
if (_musicData != NULL)
pollMusic();
}
}
void GmMusic::setupPointers() {
if (SkyEngine::_systemVars->gameVersion == 109) {
_musicDataLoc = READ_LE_UINT16(_musicData + 0x79B);
_sysExSequence = _musicData + 0x1EF2;
} else {
_musicDataLoc = READ_LE_UINT16(_musicData + 0x7DC);
_sysExSequence = READ_LE_UINT16(_musicData + 0x7E0) + _musicData;
}
}
void GmMusic::setupChannels(uint8 *channelData) {
_numberOfChannels = channelData[0];
channelData++;
for (uint8 cnt = 0; cnt < _numberOfChannels; cnt++) {
uint16 chDataStart = READ_LE_UINT16((uint16 *)channelData + cnt) + _musicDataLoc;
_channels[cnt] = new GmChannel(_musicData, chDataStart, _midiDrv, MidiDriver::_mt32ToGm, _veloTab);
_channels[cnt]->updateVolume(_musicVolume);
}
}
void GmMusic::startDriver() {
// Send GM System On to reset channel parameters etc.
uint8 sysEx[] = { 0x7e, 0x7f, 0x09, 0x01 };
_midiDrv->sysEx(sysEx, sizeof(sysEx));
//_midiDrv->send(0xFF); //ALSA can't handle this.
// skip all sysEx as it can't be handled anyways.
}
const byte GmMusic::_veloTab[128] = {
0x00, 0x40, 0x41, 0x41, 0x42, 0x42, 0x43, 0x43, 0x44, 0x44,
0x45, 0x45, 0x46, 0x46, 0x47, 0x47, 0x48, 0x48, 0x49, 0x49,
0x4A, 0x4A, 0x4B, 0x4B, 0x4C, 0x4C, 0x4D, 0x4D, 0x4E, 0x4E,
0x4F, 0x4F, 0x50, 0x50, 0x51, 0x51, 0x52, 0x52, 0x53, 0x53,
0x54, 0x54, 0x55, 0x55, 0x56, 0x56, 0x57, 0x57, 0x58, 0x58,
0x59, 0x59, 0x5A, 0x5A, 0x5B, 0x5B, 0x5C, 0x5C, 0x5D, 0x5D,
0x5E, 0x5E, 0x5F, 0x5F, 0x60, 0x60, 0x61, 0x61, 0x62, 0x62,
0x63, 0x63, 0x64, 0x64, 0x65, 0x65, 0x66, 0x66, 0x67, 0x67,
0x68, 0x68, 0x69, 0x69, 0x6A, 0x6A, 0x6B, 0x6B, 0x6C, 0x6C,
0x6D, 0x6D, 0x6E, 0x6E, 0x6F, 0x6F, 0x70, 0x70, 0x71, 0x71,
0x72, 0x72, 0x73, 0x73, 0x74, 0x74, 0x75, 0x75, 0x76, 0x76,
0x77, 0x77, 0x78, 0x78, 0x79, 0x79, 0x7A, 0x7A, 0x7B, 0x7B,
0x7C, 0x7C, 0x7D, 0x7D, 0x7E, 0x7E, 0x7F, 0x7F
};
} // End of namespace Sky

View File

@@ -0,0 +1,52 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef SKY_MUSIC_GMMUSIC_H
#define SKY_MUSIC_GMMUSIC_H
#include "sky/music/musicbase.h"
class MidiDriver;
namespace Sky {
class GmMusic : public MusicBase {
public:
GmMusic(MidiDriver *pMidiDrv, Audio::Mixer *pMixer, Disk *pDisk);
~GmMusic() override;
void setVolume(uint16 param) override;
private:
static void passTimerFunc(void *param);
void timerCall();
uint32 _timerCount;
uint8 *_sysExSequence;
MidiDriver *_midiDrv;
static const byte _veloTab[128];
void setupPointers() override;
void setupChannels(uint8 *channelData) override;
void startDriver() override;
};
} // End of namespace Sky
#endif //GMMUSIC_H

View File

@@ -0,0 +1,176 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#include "sky/music/mt32music.h"
#include "sky/music/gmchannel.h"
#include "common/util.h"
#include "common/system.h"
#include "common/endian.h"
#include "common/textconsole.h"
#include "audio/mididrv.h"
namespace Sky {
void MT32Music::passTimerFunc(void *param) {
((MT32Music *)param)->timerCall();
}
MT32Music::MT32Music(MidiDriver *pMidiDrv, Audio::Mixer *pMixer, Disk *pDisk) : MusicBase(pMixer, pDisk) {
_driverFileBase = 60200;
_midiDrv = pMidiDrv;
int midiRes = _midiDrv->open();
if (midiRes != 0)
error("Can't open midi device. Errorcode: %d",midiRes);
_timerCount = 0;
_midiDrv->setTimerCallback(this, passTimerFunc);
_midiDrv->sendMT32Reset();
}
MT32Music::~MT32Music() {
_midiDrv->close();
_midiDrv->setTimerCallback(NULL, NULL);
delete _midiDrv;
}
void MT32Music::timerCall() {
_timerCount += _midiDrv->getBaseTempo();
if (_timerCount > (1000000 / 50)) {
// call pollMusic() 50 times per second
_timerCount -= 1000000 / 50;
if (_musicData != NULL)
pollMusic();
}
}
void MT32Music::setVolume(uint16 volume) {
uint8 sysEx[10] = "\x41\x10\x16\x12\x10\x00\x16\x00\x00";
_musicVolume = volume;
sysEx[7] = (volume > 100) ? 100 : (uint8)volume;
sysEx[8] = 0x00;
for (uint8 cnt = 4; cnt < 8; cnt++)
sysEx[8] -= sysEx[cnt];
sysEx[8] &= 0x7F;
_midiDrv->sysEx(sysEx, 9);
}
void MT32Music::setupPointers() {
_musicDataLoc = READ_LE_UINT16(_musicData + 0x7DC);
_sysExSequence = READ_LE_UINT16(_musicData + 0x7E0) + _musicData;
}
void MT32Music::setupChannels(uint8 *channelData) {
_numberOfChannels = channelData[0];
channelData++;
for (uint8 cnt = 0; cnt < _numberOfChannels; cnt++) {
uint16 chDataStart = READ_LE_UINT16((uint16 *)channelData + cnt) + _musicDataLoc;
_channels[cnt] = new GmChannel(_musicData, chDataStart, _midiDrv, NULL, NULL);
_channels[cnt]->updateVolume(_musicVolume);
}
}
bool MT32Music::processPatchSysEx(uint8 *sysExData) {
uint8 sysExBuf[15];
uint8 crc = 0;
if (sysExData[0] & 0x80)
return false;
// decompress data from stream
sysExBuf[ 0] = 0x41;
sysExBuf[ 1] = 0x10;
sysExBuf[ 2] = 0x16;
sysExBuf[ 3] = 0x12;
sysExBuf[ 4] = 0x5;
sysExBuf[ 5] = sysExData[0] >> 4; // patch offset part 1
sysExBuf[ 6] = (sysExData[0] & 0xF) << 3; // patch offset part 2
sysExBuf[ 7] = sysExData[1] >> 6; // timbre group
sysExBuf[ 8] = sysExData[1] & 0x3F; // timbre num
sysExBuf[ 9] = sysExData[2] & 0x3F; // key shift
sysExBuf[10] = sysExData[3] & 0x7F; // fine tune
sysExBuf[11] = sysExData[4] & 0x7F; // bender range
sysExBuf[12] = sysExData[2] >> 6; // assign mode
sysExBuf[13] = sysExData[3] >> 7; // reverb switch
for (uint8 cnt = 4; cnt < 14; cnt++)
crc -= sysExBuf[cnt];
sysExBuf[14] = crc & 0x7F; // crc
_midiDrv->sysEx(sysExBuf, 15);
// We delay the time it takes to send the sysEx plus an
// additional 40ms, which is required for MT-32 rev00,
// to assure no buffer overflow or missing bytes
g_system->delayMillis(17 * 1000 / 3125 + 40);
return true;
}
void MT32Music::startDriver() {
// setup timbres and patches using SysEx data
uint8* sysExData = _sysExSequence;
uint8 timbreNum = sysExData[0];
uint8 cnt, crc;
sysExData++;
uint8 sendBuf[256];
uint8 len;
sendBuf[0] = 0x41;
sendBuf[1] = 0x10;
sendBuf[2] = 0x16;
sendBuf[3] = 0x12;
for (cnt = 0; cnt < timbreNum; cnt++) {
len = 7;
crc = 0;
// Timbre address
sendBuf[4] = 0x8 | (sysExData[0] >> 6);
sendBuf[5] = (sysExData[0] & 0x3F) << 1;
sendBuf[6] = 0xA;
sysExData++;
crc -= sendBuf[4] + sendBuf[5] + sendBuf[6];
uint8 dataLen = sysExData[0];
sysExData++;
// Timbre data:
do {
uint8 rlVal = 1;
uint8 codeVal = sysExData[0];
sysExData++;
if (codeVal & 0x80) {
codeVal &= 0x7F;
rlVal = sysExData[0];
sysExData++;
dataLen--;
}
for (uint8 cnt2 = 0; cnt2 < rlVal; cnt2++) {
sendBuf[len] = codeVal;
len++;
crc -= codeVal;
}
dataLen--;
} while (dataLen > 0);
sendBuf[len] = crc & 0x7F;
len++;
_midiDrv->sysEx(sendBuf, len);
// We delay the time it takes to send the sysEx plus an
// additional 40ms, which is required for MT-32 rev00,
// to assure no buffer overflow or missing bytes
g_system->delayMillis((len + 2) * 1000 / 3125 + 40);
}
while (processPatchSysEx(sysExData))
sysExData += 5;
}
} // End of namespace Sky

View File

@@ -0,0 +1,52 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef SKY_MUSIC_MT32MUSIC_H
#define SKY_MUSIC_MT32MUSIC_H
#include "sky/music/musicbase.h"
class MidiDriver;
namespace Sky {
class MT32Music : public MusicBase {
public:
MT32Music(MidiDriver *pMidiDrv, Audio::Mixer *pMixer, Disk *pDisk);
~MT32Music() override;
private:
static void passTimerFunc(void *param);
void timerCall();
bool processPatchSysEx(uint8 *sysExData);
void setVolume(uint16 volume) override;
uint32 _timerCount;
uint8 *_sysExSequence;
MidiDriver *_midiDrv;
void setupPointers() override;
void setupChannels(uint8 *channelData) override;
void startDriver() override;
};
} // End of namespace Sky
#endif //MT32MUSIC_H

View File

@@ -0,0 +1,197 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#include "sky/music/musicbase.h"
#include "sky/disk.h"
#include "common/util.h"
#include "common/endian.h"
#include "common/textconsole.h"
#include "audio/audiostream.h"
namespace Sky {
MusicBase::MusicBase(Audio::Mixer *pMixer, Disk *pDisk) {
_musicData = NULL;
_mixer = pMixer;
_skyDisk = pDisk;
_currentMusic = 0;
_musicVolume = 127;
_numberOfChannels = _currentMusic = 0;
}
MusicBase::~MusicBase() {
stopMusic();
free(_musicData);
}
void MusicBase::loadSection(uint8 pSection) {
if (_currentMusic)
stopMusicInternal();
Common::StackLock lock(_mutex);
free(_musicData);
_currentSection = pSection;
_musicData = _skyDisk->loadFile(_driverFileBase + FILES_PER_SECTION * pSection);
_musicTempo0 = 0x78; // init constants taken from idb file, area ~0x1060
_musicTempo1 = 0xC0;
_onNextPoll.musicToProcess = 0;
_onNextPoll.stream = nullptr;
_tempo = _aktTime = 0x10001;
_numberOfChannels = _currentMusic = 0;
setupPointers();
startDriver();
}
bool MusicBase::musicIsPlaying() {
if (_mixer->isSoundHandleActive(_musicHandle))
return true;
for (uint8 cnt = 0; cnt < _numberOfChannels; cnt++)
if (_channels[cnt]->isActive())
return true;
return false;
}
void MusicBase::stopMusic() {
stopMusicInternal();
}
void MusicBase::stopMusicInternal() {
_mixer->stopHandle(_musicHandle);
Common::StackLock lock(_mutex);
for (uint8 cnt = 0; cnt < _numberOfChannels; cnt++)
delete _channels[cnt];
_numberOfChannels = 0;
}
void MusicBase::updateTempo() {
uint16 tempoMul = _musicTempo0 * _musicTempo1;
uint16 divisor = 0x4446390/ 23864;
_tempo = (tempoMul / divisor) << 16;
_tempo |= (((tempoMul % divisor) << 16) | (tempoMul / divisor)) / divisor;
}
void MusicBase::loadNewMusic() {
uint16 musicPos;
if (_onNextPoll.musicToProcess > _musicData[_musicDataLoc]) {
error("Music %d requested but doesn't exist in file.", _onNextPoll.musicToProcess);
return;
}
if (_currentMusic != 0)
stopMusicInternal();
_currentMusic = _onNextPoll.musicToProcess;
if (_currentMusic == 0)
return;
// Try playing digital audio first (from the Music Enhancement Project).
// TODO: This always prefers digital music over the MIDI music types!
Audio::SeekableAudioStream *stream = _onNextPoll.stream;
if (stream) {
_onNextPoll.stream = nullptr;
// not all tracks should loop
bool loops = true;
uint8 section = _currentSection;
uint8 song = _currentMusic;
if ((section == 0 && song == 1)
|| (section == 1 && song == 1) || (section == 1 && song == 4)
|| (section == 2 && song == 1) || (section == 2 && song == 4)
|| (section == 4 && song == 2) || (section == 4 && song == 3)
|| (section == 4 && song == 5) || (section == 4 && song == 6)
|| (section == 4 && song == 11) || (section == 5 && song == 1)
|| (section == 5 && song == 3) || (section == 5 && song == 4))
loops = false;
_mixer->playStream(Audio::Mixer::kMusicSoundType, &_musicHandle, Audio::makeLoopingAudioStream(stream, loops ? 0 : 1));
return;
}
// no digital audio, resort to MIDI playback
musicPos = READ_LE_UINT16(_musicData + _musicDataLoc + 1);
musicPos += _musicDataLoc + ((_currentMusic - 1) << 1);
musicPos = READ_LE_UINT16(_musicData + musicPos) + _musicDataLoc;
_musicTempo0 = _musicData[musicPos];
_musicTempo1 = _musicData[musicPos+1];
setupChannels(_musicData + musicPos + 2);
updateTempo();
}
void MusicBase::pollMusic() {
Common::StackLock lock(_mutex);
uint8 newTempo;
if (_onNextPoll.musicToProcess != _currentMusic)
loadNewMusic();
_aktTime += _tempo;
for (uint8 cnt = 0; cnt < _numberOfChannels; cnt++) {
newTempo = _channels[cnt]->process((uint16)(_aktTime >> 16));
if (newTempo) {
_musicTempo1 = newTempo;
updateTempo();
}
}
_aktTime &= 0xFFFF;
}
void MusicBase::startMusic(uint16 param) {
uint8 song = param & 0xF;
_onNextPoll.musicToProcess = song;
delete _onNextPoll.stream;
_onNextPoll.stream = nullptr;
if (song == 0)
return;
// Load digital audio now if available
// This avoids doing it in the audio thread
uint8 section = _currentSection;
// handle duplicates
if ((section == 2 && song == 1) || (section == 5 && song == 1)) {
section = 1;
song = 1;
} else if ((section == 2 && song == 4) || (section == 5 && song == 4)) {
section = 1;
song = 4;
} else if (section == 5 && song == 6) {
section = 4;
song = 4;
}
Common::Path trackName(Common::String::format("music_%d%02d", section, song));
_onNextPoll.stream = Audio::SeekableAudioStream::openStreamFile(trackName);
}
uint8 MusicBase::giveVolume() {
return (uint8)_musicVolume;
}
uint8 MusicBase::giveCurrentMusic() {
return _currentMusic;
}
} // End of namespace Sky

View File

@@ -0,0 +1,100 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef SKY_MUSIC_MUSICBASE_H
#define SKY_MUSIC_MUSICBASE_H
#include "common/scummsys.h"
#include "common/mutex.h"
#include "audio/mixer.h"
namespace Audio {
class SeekableAudioStream;
}
namespace Sky {
class Disk;
#define FILES_PER_SECTION 4
typedef struct {
uint8 musicToProcess;
Audio::SeekableAudioStream *stream;
} Actions;
class ChannelBase {
public:
virtual ~ChannelBase() {}
virtual uint8 process(uint16 aktTime) = 0;
virtual void updateVolume(uint16 pVolume) = 0;
virtual bool isActive() = 0;
private:
};
class MusicBase {
public:
MusicBase(Audio::Mixer *pMixer, Disk *pDisk);
virtual ~MusicBase();
void loadSection(uint8 pSection);
void startMusic(uint16 param);
void stopMusic();
bool musicIsPlaying();
uint8 giveVolume();
uint8 giveCurrentMusic();
virtual void setVolume(uint16 param) = 0;
protected:
Audio::Mixer *_mixer;
Disk *_skyDisk;
uint8 *_musicData;
uint16 _musicDataLoc;
uint16 _driverFileBase;
uint16 _musicVolume, _numberOfChannels;
uint8 _currentMusic, _currentSection;
uint8 _musicTempo0; // can be changed by music stream
uint8 _musicTempo1; // given once per music
uint32 _tempo; // calculated from musicTempo0 and musicTempo1
uint32 _aktTime;
Actions _onNextPoll;
ChannelBase *_channels[10];
Common::Mutex _mutex;
Audio::SoundHandle _musicHandle;
virtual void setupPointers() = 0;
virtual void setupChannels(uint8 *channelData) = 0;
virtual void startDriver() = 0;
void updateTempo();
void loadNewMusic();
void pollMusic();
void stopMusicInternal();
};
} // End of namespace Sky
#endif //MUSICBASE_H

824
engines/sky/screen.cpp Normal file
View File

@@ -0,0 +1,824 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#include "common/endian.h"
#include "common/events.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "graphics/paletteman.h"
#include "sky/disk.h"
#include "sky/logic.h"
#include "sky/screen.h"
#include "sky/compact.h"
#include "sky/sky.h"
#include "sky/skydefs.h"
#include "sky/struc.h"
namespace Sky {
uint8 Screen::_top16Colors[16*3] = {
0, 0, 0,
38, 38, 38,
63, 63, 63,
0, 0, 0,
0, 0, 0,
0, 0, 0,
0, 0, 0,
54, 54, 54,
45, 47, 49,
32, 31, 41,
29, 23, 37,
23, 18, 30,
49, 11, 11,
39, 5, 5,
29, 1, 1,
63, 63, 63
};
Screen::Screen(OSystem *pSystem, Disk *pDisk, SkyCompact *skyCompact) {
_system = pSystem;
_skyDisk = pDisk;
_skyCompact = skyCompact;
int i;
uint8 tmpPal[VGA_COLORS * 3];
_gameGrid = (uint8 *)malloc(GRID_X * GRID_Y * 2);
forceRefresh();
_currentScreen = NULL;
_scrollScreen = NULL;
//blank the first 240 colors of the palette
memset(tmpPal, 0, GAME_COLORS * 3);
//set the remaining colors
for (i = 0; i < (VGA_COLORS-GAME_COLORS); i++) {
tmpPal[3 * GAME_COLORS + i * 3 + 0] = (_top16Colors[i * 3 + 0] << 2) + (_top16Colors[i * 3 + 0] >> 4);
tmpPal[3 * GAME_COLORS + i * 3 + 1] = (_top16Colors[i * 3 + 1] << 2) + (_top16Colors[i * 3 + 1] >> 4);
tmpPal[3 * GAME_COLORS + i * 3 + 2] = (_top16Colors[i * 3 + 2] << 2) + (_top16Colors[i * 3 + 2] >> 4);
}
//set the palette
_system->getPaletteManager()->setPalette(tmpPal, 0, VGA_COLORS);
_currentPalette = 0;
_seqInfo.nextFrame = _seqInfo.framesLeft = 0;
_seqInfo.seqData = _seqInfo.seqDataPos = NULL;
_seqInfo.running = false;
}
Screen::~Screen() {
free(_gameGrid);
free(_currentScreen);
free(_scrollScreen);
}
void Screen::clearScreen(bool fullscreen) {
memset(_currentScreen, 0, FULL_SCREEN_WIDTH * FULL_SCREEN_HEIGHT);
_system->copyRectToScreen(_currentScreen, GAME_SCREEN_WIDTH, 0, 0, GAME_SCREEN_WIDTH, fullscreen ? FULL_SCREEN_HEIGHT : GAME_SCREEN_HEIGHT);
_system->updateScreen();
}
void Screen::setFocusRectangle(const Common::Rect& rect) {
_system->setFocusRectangle(rect);
}
//set a new palette, pal is a pointer to dos vga rgb components 0..63
void Screen::setPalette(uint8 *pal) {
convertPalette(pal, _palette);
_system->getPaletteManager()->setPalette(_palette, 0, GAME_COLORS);
_system->updateScreen();
}
void Screen::setPaletteEndian(uint8 *pal) {
#ifdef SCUMM_BIG_ENDIAN
uint8 endPalette[VGA_COLORS * 3];
for (uint16 cnt = 0; cnt < VGA_COLORS * 3; cnt++)
endPalette[cnt] = pal[cnt ^ 1];
convertPalette(endPalette, _palette);
#else
convertPalette(pal, _palette);
#endif
_system->getPaletteManager()->setPalette(_palette, 0, GAME_COLORS);
_system->updateScreen();
}
void Screen::halvePalette() {
uint8 halfPalette[VGA_COLORS * 3];
for (uint8 cnt = 0; cnt < GAME_COLORS; cnt++) {
halfPalette[cnt * 3 + 0] = _palette[cnt * 3 + 0] >> 1;
halfPalette[cnt * 3 + 1] = _palette[cnt * 3 + 1] >> 1;
halfPalette[cnt * 3 + 2] = _palette[cnt * 3 + 2] >> 1;
}
_system->getPaletteManager()->setPalette(halfPalette, 0, GAME_COLORS);
}
void Screen::setPalette(uint16 fileNum) {
uint8 *tmpPal = _skyDisk->loadFile(fileNum);
if (tmpPal) {
setPalette(tmpPal);
free(tmpPal);
} else
warning("Screen::setPalette: can't load file nr. %d",fileNum);
}
void Screen::showScreen(uint16 fileNum, bool fullscreen) {
// This is only used for static images in the floppy and cd intro
free(_currentScreen);
_currentScreen = _skyDisk->loadFile(fileNum);
if (!fullscreen) {
// make sure the last 8 lines are forced to black.
memset(_currentScreen + GAME_SCREEN_HEIGHT * GAME_SCREEN_WIDTH, 0, (FULL_SCREEN_HEIGHT - GAME_SCREEN_HEIGHT) * GAME_SCREEN_WIDTH);
}
if (_currentScreen)
showScreen(_currentScreen, fullscreen);
else
warning("Screen::showScreen: can't load file nr. %d",fileNum);
}
void Screen::showScreen(uint8 *pScreen, bool fullscreen) {
_system->copyRectToScreen(pScreen, 320, 0, 0, GAME_SCREEN_WIDTH, fullscreen ? FULL_SCREEN_HEIGHT : GAME_SCREEN_HEIGHT);
_system->updateScreen();
}
//convert 3 byte 0..63 rgb to 3 byte 0..255 rgb
void Screen::convertPalette(uint8 *inPal, uint8* outPal) {
int i;
for (i = 0; i < VGA_COLORS; i++) {
outPal[3 * i + 0] = (inPal[3 * i + 0] << 2) + (inPal[3 * i + 0] >> 4);
outPal[3 * i + 1] = (inPal[3 * i + 1] << 2) + (inPal[3 * i + 1] >> 4);
outPal[3 * i + 2] = (inPal[3 * i + 2] << 2) + (inPal[3 * i + 2] >> 4);
}
}
void Screen::recreate() {
// check the game grid for changed blocks
if (!Logic::_scriptVariables[LAYER_0_ID])
return;
uint8 *gridPos = _gameGrid;
uint8 *screenData = (uint8 *)SkyEngine::fetchItem(Logic::_scriptVariables[LAYER_0_ID]);
if (!screenData) {
error("Screen::recreate():\nSkyEngine::fetchItem(Logic::_scriptVariables[LAYER_0_ID](%X)) returned NULL", Logic::_scriptVariables[LAYER_0_ID]);
}
uint8 *screenPos = _currentScreen;
for (uint8 cnty = 0; cnty < GRID_Y; cnty++) {
for (uint8 cntx = 0; cntx < GRID_X; cntx++) {
if (gridPos[0] & 0x80) {
gridPos[0] &= 0x7F; // reset recreate flag
gridPos[0] |= 1; // set bit for flip routine
uint8 *savedScreenY = screenPos;
for (uint8 gridCntY = 0; gridCntY < GRID_H; gridCntY++) {
memcpy(screenPos, screenData, GRID_W);
screenPos += GAME_SCREEN_WIDTH;
screenData += GRID_W;
}
screenPos = savedScreenY + GRID_W;
} else {
screenPos += GRID_W;
screenData += GRID_W * GRID_H;
}
gridPos++;
}
screenPos += (GRID_H - 1) * GAME_SCREEN_WIDTH;
}
}
void Screen::flip(bool doUpdate) {
uint32 copyX, copyWidth;
copyX = copyWidth = 0;
for (uint8 cnty = 0; cnty < GRID_Y; cnty++) {
for (uint8 cntx = 0; cntx < GRID_X; cntx++) {
if (_gameGrid[cnty * GRID_X + cntx] & 1) {
_gameGrid[cnty * GRID_X + cntx] &= ~1;
if (!copyWidth)
copyX = cntx * GRID_W;
copyWidth += GRID_W;
} else if (copyWidth) {
_system->copyRectToScreen(_currentScreen + cnty * GRID_H * GAME_SCREEN_WIDTH + copyX, GAME_SCREEN_WIDTH, copyX, cnty * GRID_H, copyWidth, GRID_H);
copyWidth = 0;
}
}
if (copyWidth) {
_system->copyRectToScreen(_currentScreen + cnty * GRID_H * GAME_SCREEN_WIDTH + copyX, GAME_SCREEN_WIDTH, copyX, cnty * GRID_H, copyWidth, GRID_H);
copyWidth = 0;
}
}
if (doUpdate)
_system->updateScreen();
}
void Screen::fnDrawScreen(uint32 palette, uint32 scroll) {
// set up the new screen
fnFadeDown(scroll);
forceRefresh();
recreate();
spriteEngine();
flip(false);
fnFadeUp(palette, scroll);
}
void Screen::fnFadeDown(uint32 scroll) {
if (((scroll != 123) && (scroll != 321)) || (SkyEngine::_systemVars->systemFlags & SF_NO_SCROLL)) {
uint32 delayTime = _system->getMillis();
for (uint8 cnt = 0; cnt < 32; cnt++) {
delayTime += 20;
palette_fadedown_helper(_palette, GAME_COLORS);
_system->getPaletteManager()->setPalette(_palette, 0, GAME_COLORS);
_system->updateScreen();
int32 waitTime = (int32)delayTime - _system->getMillis();
if (waitTime < 0)
waitTime = 0;
_system->delayMillis((uint)waitTime);
}
} else {
// scrolling is performed by fnFadeUp. It's just prepared here
_scrollScreen = _currentScreen;
_currentScreen = (uint8 *)malloc(FULL_SCREEN_WIDTH * FULL_SCREEN_HEIGHT);
// the game will draw the new room into _currentScreen which
// will be scrolled into the visible screen by fnFadeUp
// fnFadeUp also frees the _scrollScreen
}
}
void Screen::palette_fadedown_helper(uint8 *pal, uint num) {
do {
if (pal[0] >= 8)
pal[0] -= 8;
else
pal[0] = 0;
if (pal[1] >= 8)
pal[1] -= 8;
else
pal[1] = 0;
if (pal[2] >= 8)
pal[2] -= 8;
else
pal[2] = 0;
pal += 3;
} while (--num);
}
void Screen::paletteFadeUp(uint16 fileNr) {
uint8 *pal = _skyDisk->loadFile(fileNr);
if (pal) {
paletteFadeUp(pal);
free(pal);
} else
warning("Screen::paletteFadeUp: Can't load palette #%d",fileNr);
}
void Screen::paletteFadeUp(uint8 *pal) {
byte tmpPal[VGA_COLORS * 3];
convertPalette(pal, tmpPal);
uint32 delayTime = _system->getMillis();
for (uint8 cnt = 1; cnt <= 32; cnt++) {
delayTime += 20;
for (uint8 colCnt = 0; colCnt < GAME_COLORS; colCnt++) {
_palette[colCnt * 3 + 0] = (tmpPal[colCnt * 3 + 0] * cnt) >> 5;
_palette[colCnt * 3 + 1] = (tmpPal[colCnt * 3 + 1] * cnt) >> 5;
_palette[colCnt * 3 + 2] = (tmpPal[colCnt * 3 + 2] * cnt) >> 5;
}
_system->getPaletteManager()->setPalette(_palette, 0, GAME_COLORS);
_system->updateScreen();
int32 waitTime = (int32)delayTime - _system->getMillis();
if (waitTime < 0)
waitTime = 0;
_system->delayMillis((uint)waitTime);
}
}
void Screen::fnFadeUp(uint32 palNum, uint32 scroll) {
//_currentScreen points to new screen,
//_scrollScreen points to graphic showing old room
if ((scroll != 123) && (scroll != 321))
scroll = 0;
if ((scroll == 0) || (SkyEngine::_systemVars->systemFlags & SF_NO_SCROLL)) {
uint8 *palette = (uint8 *)_skyCompact->fetchCpt(palNum);
if (palette == NULL)
error("Screen::fnFadeUp: can't fetch compact %X", palNum);
#ifdef SCUMM_BIG_ENDIAN
byte tmpPal[VGA_COLORS * 3];
for (uint16 cnt = 0; cnt < VGA_COLORS * 3; cnt++)
tmpPal[cnt] = palette[cnt ^ 1];
paletteFadeUp(tmpPal);
#else
paletteFadeUp(palette);
#endif
} else if (scroll == 123) { // scroll left (going right)
assert(_currentScreen && _scrollScreen);
uint8 *scrNewPtr, *scrOldPtr;
for (uint8 scrollCnt = 0; scrollCnt < (GAME_SCREEN_WIDTH / SCROLL_JUMP) - 1; scrollCnt++) {
scrNewPtr = _currentScreen + scrollCnt * SCROLL_JUMP;
scrOldPtr = _scrollScreen;
for (uint8 lineCnt = 0; lineCnt < GAME_SCREEN_HEIGHT; lineCnt++) {
memmove(scrOldPtr, scrOldPtr + SCROLL_JUMP, GAME_SCREEN_WIDTH - SCROLL_JUMP);
memcpy(scrOldPtr + GAME_SCREEN_WIDTH - SCROLL_JUMP, scrNewPtr, SCROLL_JUMP);
scrNewPtr += GAME_SCREEN_WIDTH;
scrOldPtr += GAME_SCREEN_WIDTH;
}
showScreen(_scrollScreen);
waitForTick();
}
showScreen(_currentScreen);
} else if (scroll == 321) { // scroll right (going left)
assert(_currentScreen && _scrollScreen);
uint8 *scrNewPtr, *scrOldPtr;
for (uint8 scrollCnt = 0; scrollCnt < (GAME_SCREEN_WIDTH / SCROLL_JUMP) - 1; scrollCnt++) {
scrNewPtr = _currentScreen + GAME_SCREEN_WIDTH - (scrollCnt + 1) * SCROLL_JUMP;
scrOldPtr = _scrollScreen;
for (uint8 lineCnt = 0; lineCnt < GAME_SCREEN_HEIGHT; lineCnt++) {
memmove(scrOldPtr + SCROLL_JUMP, scrOldPtr, GAME_SCREEN_WIDTH - SCROLL_JUMP);
memcpy(scrOldPtr, scrNewPtr, SCROLL_JUMP);
scrNewPtr += GAME_SCREEN_WIDTH;
scrOldPtr += GAME_SCREEN_WIDTH;
}
showScreen(_scrollScreen);
waitForTick();
}
showScreen(_currentScreen);
}
if (_scrollScreen) {
free(_scrollScreen);
_scrollScreen = NULL;
}
}
void Screen::waitForTick() {
uint32 start = _system->getMillis();
uint32 end = start + 20 - (start % 20);
uint32 remain;
Common::EventManager *eventMan = _system->getEventManager();
Common::Event event;
while (true) {
while (eventMan->pollEvent(event))
;
start = _system->getMillis();
if (start >= end)
return;
remain = end - start;
if (remain < 10) {
_system->delayMillis(remain);
return;
}
_system->delayMillis(10);
}
}
void Screen::waitForSequence() {
Common::EventManager *eventMan = _system->getEventManager();
Common::Event event;
while (_seqInfo.running) {
processSequence();
_system->delayMillis(20);
while (eventMan->pollEvent(event))
;
}
}
void Screen::startSequence(uint16 fileNum) {
_seqInfo.seqData = _skyDisk->loadFile(fileNum);
_seqInfo.nextFrame = _system->getMillis() + 60;
_seqInfo.framesLeft = _seqInfo.seqData[0];
_seqInfo.seqDataPos = _seqInfo.seqData + 1;
_seqInfo.running = true;
_seqInfo.runningItem = false;
}
void Screen::startSequenceItem(uint16 itemNum) {
_seqInfo.seqData = (uint8 *)SkyEngine::fetchItem(itemNum);
_seqInfo.nextFrame = _system->getMillis() + 60;
_seqInfo.framesLeft = _seqInfo.seqData[0] - 1;
_seqInfo.seqDataPos = _seqInfo.seqData + 1;
_seqInfo.running = true;
_seqInfo.runningItem = true;
}
void Screen::stopSequence() {
_seqInfo.running = false;
waitForTick();
waitForTick();
_seqInfo.nextFrame = _seqInfo.framesLeft = 0;
free(_seqInfo.seqData);
_seqInfo.seqData = _seqInfo.seqDataPos = NULL;
}
void Screen::processSequence() {
if (!_seqInfo.running)
return;
if (_system->getMillis() < _seqInfo.nextFrame)
return;
_seqInfo.nextFrame += 60;
memset(_seqGrid, 0, 12 * 20);
uint32 screenPos = 0;
uint8 nrToSkip, nrToDo, cnt;
do {
do {
nrToSkip = _seqInfo.seqDataPos[0];
_seqInfo.seqDataPos++;
screenPos += nrToSkip;
} while (nrToSkip == 0xFF);
do {
nrToDo = _seqInfo.seqDataPos[0];
_seqInfo.seqDataPos++;
uint8 gridSta = (uint8)((screenPos / (GAME_SCREEN_WIDTH * 16))*20 + ((screenPos % GAME_SCREEN_WIDTH) >> 4));
uint8 gridEnd = (uint8)(((screenPos+nrToDo) / (GAME_SCREEN_WIDTH * 16))*20 + (((screenPos+nrToDo) % GAME_SCREEN_WIDTH) >> 4));
gridSta = MIN(gridSta, (uint8)(12 * 20 - 1));
gridEnd = MIN(gridEnd, (uint8)(12 * 20 - 1));
if (gridEnd >= gridSta)
for (cnt = gridSta; cnt <= gridEnd; cnt++)
_seqGrid[cnt] = 1;
else {
for (cnt = gridSta; cnt < (gridSta / 20 + 1) * 20; cnt++)
_seqGrid[cnt] = 1;
for (cnt = (gridEnd / 20) * 20; cnt <= gridEnd; cnt++)
_seqGrid[cnt] = 1;
}
for (cnt = 0; cnt < nrToDo; cnt++) {
_currentScreen[screenPos] = _seqInfo.seqDataPos[0];
_seqInfo.seqDataPos++;
screenPos++;
}
} while (nrToDo == 0xFF);
} while (screenPos < (GAME_SCREEN_WIDTH * GAME_SCREEN_HEIGHT));
uint8 *gridPtr = _seqGrid; uint8 *scrPtr = _currentScreen; uint8 *rectPtr = NULL;
uint8 rectWid = 0, rectX = 0, rectY = 0;
for (uint8 cnty = 0; cnty < 12; cnty++) {
for (uint8 cntx = 0; cntx < 20; cntx++) {
if (*gridPtr) {
if (!rectWid) {
rectX = cntx;
rectY = cnty;
rectPtr = scrPtr;
}
rectWid++;
} else if (rectWid) {
_system->copyRectToScreen(rectPtr, GAME_SCREEN_WIDTH, rectX << 4, rectY << 4, rectWid << 4, 16);
rectWid = 0;
}
scrPtr += 16;
gridPtr++;
}
if (rectWid) {
_system->copyRectToScreen(rectPtr, GAME_SCREEN_WIDTH, rectX << 4, rectY << 4, rectWid << 4, 16);
rectWid = 0;
}
scrPtr += 15 * GAME_SCREEN_WIDTH;
}
_system->updateScreen();
_seqInfo.framesLeft--;
if (_seqInfo.framesLeft == 0) {
_seqInfo.running = false;
if (!_seqInfo.runningItem)
free(_seqInfo.seqData);
_seqInfo.seqData = _seqInfo.seqDataPos = NULL;
}
}
//- sprites.asm routines
void Screen::spriteEngine() {
doSprites(BACK);
sortSprites();
doSprites(FORE);
}
void Screen::sortSprites() {
StSortList sortList[30];
uint32 currDrawList = DRAW_LIST_NO;
uint32 loadDrawList;
bool nextDrawList = false;
while (Logic::_scriptVariables[currDrawList]) {
// big_sort_loop
uint32 spriteCnt = 0;
loadDrawList = Logic::_scriptVariables[currDrawList];
currDrawList++;
do { // a_new_draw_list:
uint16 *drawListData = (uint16 *)_skyCompact->fetchCpt(loadDrawList);
nextDrawList = false;
while ((!nextDrawList) && (drawListData[0])) {
if (drawListData[0] == 0xFFFF) {
loadDrawList = drawListData[1];
nextDrawList = true;
} else {
// process_this_id:
Compact *spriteComp = _skyCompact->fetchCpt(drawListData[0]);
if ((spriteComp->status & 4) && // is it sortable playfield?(!?!)
(spriteComp->screen == Logic::_scriptVariables[SCREEN])) { // on current screen
DataFileHeader *spriteData =
(DataFileHeader *)SkyEngine::fetchItem(spriteComp->frame >> 6);
if (!spriteData) {
debug(9,"Missing file %d", spriteComp->frame >> 6);
spriteComp->status = 0;
} else {
sortList[spriteCnt].yCood = spriteComp->ycood + spriteData->s_offset_y + spriteData->s_height;
sortList[spriteCnt].compact = spriteComp;
sortList[spriteCnt].sprite = spriteData;
spriteCnt++;
}
}
drawListData++;
}
}
} while (nextDrawList);
// made_list:
if (spriteCnt > 1) { // bubble sort
for (uint32 cnt1 = 0; cnt1 < spriteCnt - 1; cnt1++)
for (uint32 cnt2 = cnt1 + 1; cnt2 < spriteCnt; cnt2++)
if (sortList[cnt1].yCood > sortList[cnt2].yCood) {
StSortList tmp;
tmp.yCood = sortList[cnt1].yCood;
tmp.sprite = sortList[cnt1].sprite;
tmp.compact = sortList[cnt1].compact;
sortList[cnt1].yCood = sortList[cnt2].yCood;
sortList[cnt1].sprite = sortList[cnt2].sprite;
sortList[cnt1].compact = sortList[cnt2].compact;
sortList[cnt2].yCood = tmp.yCood;
sortList[cnt2].sprite = tmp.sprite;
sortList[cnt2].compact = tmp.compact;
}
}
for (uint32 cnt = 0; cnt < spriteCnt; cnt++) {
drawSprite((uint8 *)sortList[cnt].sprite, sortList[cnt].compact);
if (sortList[cnt].compact->status & 8)
vectorToGame(0x81);
else
vectorToGame(1);
if (!(sortList[cnt].compact->status & 0x200))
verticalMask();
}
}
}
void Screen::doSprites(uint8 layer) {
uint16 drawListNum = DRAW_LIST_NO;
uint32 idNum;
uint16* drawList;
while (Logic::_scriptVariables[drawListNum]) { // std sp loop
idNum = Logic::_scriptVariables[drawListNum];
drawListNum++;
drawList = (uint16 *)_skyCompact->fetchCpt(idNum);
while (drawList[0]) {
// new_draw_list:
while ((drawList[0] != 0) && (drawList[0] != 0xFFFF)) {
// back_loop:
// not_new_list
Compact *spriteData = _skyCompact->fetchCpt(drawList[0]);
drawList++;
if ((spriteData->status & (1 << layer)) &&
(spriteData->screen == Logic::_scriptVariables[SCREEN])) {
uint8 *toBeDrawn = (uint8 *)SkyEngine::fetchItem(spriteData->frame >> 6);
if (!toBeDrawn) {
debug(9, "Spritedata %d not loaded", spriteData->frame >> 6);
spriteData->status = 0;
} else {
drawSprite(toBeDrawn, spriteData);
if (layer == BACK)
verticalMask();
if (spriteData->status & 8)
vectorToGame(0x81);
else
vectorToGame(1);
}
}
}
while (drawList[0] == 0xFFFF)
drawList = (uint16 *)_skyCompact->fetchCpt(drawList[1]);
}
}
}
void Screen::drawSprite(uint8 *spriteInfo, Compact *sprCompact) {
if (spriteInfo == NULL) {
warning("Screen::drawSprite Can't draw sprite. Data %d was not loaded", sprCompact->frame >> 6);
sprCompact->status = 0;
return;
}
DataFileHeader *sprDataFile = (DataFileHeader *)spriteInfo;
_sprWidth = sprDataFile->s_width;
_sprHeight = sprDataFile->s_height;
_maskX1 = _maskX2 = 0;
uint8 *spriteData = spriteInfo + (sprCompact->frame & 0x3F) * sprDataFile->s_sp_size;
spriteData += sizeof(DataFileHeader);
int32 spriteY = sprCompact->ycood + sprDataFile->s_offset_y - TOP_LEFT_Y;
if (spriteY < 0) {
spriteY = -spriteY;
if (_sprHeight <= (uint32)spriteY) {
_sprWidth = 0;
return;
}
_sprHeight -= spriteY;
spriteData += sprDataFile->s_width * spriteY;
spriteY = 0;
} else {
int32 botClip = GAME_SCREEN_HEIGHT - sprDataFile->s_height - spriteY;
if (botClip < 0) {
botClip = -botClip;
if (_sprHeight <= (uint32)botClip) {
_sprWidth = 0;
return;
}
_sprHeight -= botClip;
}
}
_sprY = (uint32)spriteY;
int32 spriteX = sprCompact->xcood + sprDataFile->s_offset_x - TOP_LEFT_X;
if (spriteX < 0) {
spriteX = -spriteX;
if (_sprWidth <= (uint32)spriteX) {
_sprWidth = 0;
return;
}
_sprWidth -= spriteX;
_maskX1 = spriteX;
spriteX = 0;
} else {
int32 rightClip = GAME_SCREEN_WIDTH - (sprDataFile->s_width + spriteX);
if (rightClip < 0) {
rightClip = (-rightClip) + 1;
if (_sprWidth <= (uint32)rightClip) {
_sprWidth = 0;
return;
}
_sprWidth -= rightClip;
_maskX2 = rightClip;
}
}
_sprX = (uint32)spriteX;
uint8 *screenPtr = _currentScreen + _sprY * GAME_SCREEN_WIDTH + _sprX;
if ((_sprHeight > 192) || (_sprY > 192)) {
_sprWidth = 0;
return;
}
if ((_sprX + _sprWidth > 320) || (_sprY + _sprHeight > 192)) {
warning("Screen::drawSprite fatal error: got x = %d, y = %d, w = %d, h = %d",_sprX, _sprY, _sprWidth, _sprHeight);
_sprWidth = 0;
return;
}
for (uint16 cnty = 0; cnty < _sprHeight; cnty++) {
for (uint16 cntx = 0; cntx < _sprWidth; cntx++)
if (spriteData[cntx + _maskX1])
screenPtr[cntx] = spriteData[cntx + _maskX1];
spriteData += _sprWidth + _maskX2 + _maskX1;
screenPtr += GAME_SCREEN_WIDTH;
}
// Convert the sprite coordinate/size values to blocks for vertical mask and/or vector to game
_sprWidth += _sprX + GRID_W-1;
_sprHeight += _sprY + GRID_H-1;
_sprX >>= GRID_W_SHIFT;
_sprWidth >>= GRID_W_SHIFT;
_sprY >>= GRID_H_SHIFT;
_sprHeight >>= GRID_H_SHIFT;
_sprWidth -= _sprX;
_sprHeight -= _sprY;
}
void Screen::vectorToGame(uint8 gridVal) {
if (_sprWidth == 0)
return;
uint8 *trgGrid = _gameGrid + _sprY * GRID_X +_sprX;
for (uint32 cnty = 0; cnty < _sprHeight; cnty++) {
for (uint32 cntx = 0; cntx < _sprWidth; cntx++)
trgGrid[cntx] |= gridVal;
trgGrid += GRID_X;
}
}
void Screen::vertMaskSub(uint16 *grid, uint32 gridOfs, uint8 *screenPtr, uint32 layerId) {
for (uint32 cntx = 0; cntx < _sprHeight; cntx++) { // start_x | block_loop
if (grid[gridOfs]) {
if (!(FROM_LE_16(grid[gridOfs]) & 0x8000)) {
uint32 gridVal = FROM_LE_16(grid[gridOfs]) - 1;
gridVal *= GRID_W * GRID_H;
uint8 *dataSrc = (uint8 *)SkyEngine::fetchItem(Logic::_scriptVariables[layerId]) + gridVal;
uint8 *dataTrg = screenPtr;
for (uint32 grdCntY = 0; grdCntY < GRID_H; grdCntY++) {
for (uint32 grdCntX = 0; grdCntX < GRID_W; grdCntX++)
if (dataSrc[grdCntX])
dataTrg[grdCntX] = dataSrc[grdCntX];
dataSrc += GRID_W;
dataTrg += GAME_SCREEN_WIDTH;
}
} // dummy_end:
screenPtr -= GRID_H * GAME_SCREEN_WIDTH;
gridOfs -= GRID_X;
} else
return;
} // next_x
}
void Screen::verticalMask() {
if (_sprWidth == 0)
return;
uint32 startGridOfs = (_sprY + _sprHeight - 1) * GRID_X + _sprX;
uint8 *startScreenPtr = (_sprY + _sprHeight - 1) * GRID_H * GAME_SCREEN_WIDTH + _sprX * GRID_W + _currentScreen;
for (uint32 layerCnt = LAYER_1_ID; layerCnt <= LAYER_3_ID; layerCnt++) {
uint32 gridOfs = startGridOfs;
uint8 *screenPtr = startScreenPtr;
for (uint32 widCnt = 0; widCnt < _sprWidth; widCnt++) { // x_loop
uint32 nLayerCnt = layerCnt;
while (Logic::_scriptVariables[nLayerCnt + 3]) {
uint16 *scrGrid;
scrGrid = (uint16 *)SkyEngine::fetchItem(Logic::_scriptVariables[layerCnt + 3]);
if (scrGrid[gridOfs]) {
vertMaskSub(scrGrid, gridOfs, screenPtr, layerCnt);
break;
} else
nLayerCnt++;
}
// next_x:
screenPtr += GRID_W;
gridOfs++;
}
}
}
void Screen::paintBox(uint16 x, uint16 y) {
uint8 *screenPos = _currentScreen + y * GAME_SCREEN_WIDTH + x;
memset(screenPos, 255, 8);
for (uint8 cnt = 1; cnt < 8; cnt++) {
*(screenPos + cnt * GAME_SCREEN_WIDTH) = 255;
*(screenPos + cnt * GAME_SCREEN_WIDTH + 7) = 255;
}
memset(screenPos + 7 * GAME_SCREEN_WIDTH, 255, 7);
}
void Screen::showGrid(uint8 *gridBuf) {
uint32 gridData = 0;
uint8 bitsLeft = 0;
for (uint16 cnty = 0; cnty < GAME_SCREEN_HEIGHT >> 3; cnty++) {
for (uint16 cntx = 0; cntx < GAME_SCREEN_WIDTH >> 3; cntx++) {
if (!bitsLeft) {
bitsLeft = 32;
gridData = *(uint32 *)gridBuf;
gridBuf += 4;
}
if (gridData & 0x80000000)
paintBox(cntx << 3, cnty << 3);
bitsLeft--;
gridData <<= 1;
}
}
_system->copyRectToScreen(_currentScreen, GAME_SCREEN_WIDTH, 0, 0, GAME_SCREEN_WIDTH, GAME_SCREEN_HEIGHT);
}
} // End of namespace Sky

136
engines/sky/screen.h Normal file
View File

@@ -0,0 +1,136 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef SKY_SCREEN_H
#define SKY_SCREEN_H
#include "common/scummsys.h"
#include "sky/skydefs.h"
class OSystem;
namespace Common {
struct Rect;
}
namespace Sky {
class Disk;
class SkyEngine;
class SkyCompact;
struct Compact;
struct DataFileHeader;
#define SCROLL_JUMP 16
#define VGA_COLORS 256
#define GAME_COLORS 240
#define FORE 1
#define BACK 0
typedef struct {
uint16 yCood;
Compact *compact;
DataFileHeader *sprite;
} StSortList;
class Screen {
public:
Screen(OSystem *pSystem, Disk *pDisk, SkyCompact *skyCompact);
~Screen();
void setPalette(uint8 *pal);
void setPaletteEndian(uint8 *pal);
void setPalette(uint16 fileNum);
void paletteFadeUp(uint8 *pal);
void paletteFadeUp(uint16 fileNr);
void showScreen(uint16 fileNum, bool fullscreen = false);
void showScreen(uint8 *pScreen, bool fullscreen = false);
void handleTimer();
void startSequence(uint16 fileNum);
void startSequenceItem(uint16 itemNum);
void stopSequence();
bool sequenceRunning() { return _seqInfo.running; }
void processSequence();
void waitForSequence();
uint32 seqFramesLeft() { return _seqInfo.framesLeft; }
uint8 *giveCurrent() { return _currentScreen; }
void halvePalette();
//- regular screen.asm routines
void forceRefresh() { memset(_gameGrid, 0x80, GRID_X * GRID_Y); }
void fnFadeUp(uint32 palNum, uint32 scroll);
void fnFadeDown(uint32 scroll);
void fnDrawScreen(uint32 palette, uint32 scroll);
void clearScreen(bool fullscreen = false);
void setFocusRectangle(const Common::Rect& rect);
void recreate();
void flip(bool doUpdate = true);
void spriteEngine();
void paintBox(uint16 x, uint16 y);
void showGrid(uint8 *gridBuf);
private:
OSystem *_system;
Disk *_skyDisk;
SkyCompact *_skyCompact;
static uint8 _top16Colors[16 * 3];
uint8 _palette[VGA_COLORS * 3];
uint32 _currentPalette;
uint8 _seqGrid[20 * 12];
void waitForTick();
uint8 *_gameGrid;
uint8 *_currentScreen;
uint8 *_scrollScreen;
struct {
uint32 nextFrame;
uint32 framesLeft;
uint8 *seqData;
uint8 *seqDataPos;
volatile bool running;
bool runningItem; // when playing an item, don't free it afterwards.
} _seqInfo;
//- more regular screen.asm + layer.asm routines
void convertPalette(uint8 *inPal, uint8* outPal);
void palette_fadedown_helper(uint8 *pal, uint num);
//- sprite.asm routines
// fixme: get rid of these globals
uint32 _sprWidth, _sprHeight, _sprX, _sprY, _maskX1, _maskX2;
void doSprites(uint8 layer);
void sortSprites();
void drawSprite(uint8 *spriteData, Compact *sprCompact);
void verticalMask();
void vertMaskSub(uint16 *grid, uint32 gridOfs, uint8 *screenPtr, uint32 layerId);
void vectorToGame(uint8 gridVal);
};
} // End of namespace Sky
#endif //SKYSCREEN_H

587
engines/sky/sky.cpp Normal file
View File

@@ -0,0 +1,587 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#include "backends/keymapper/keymapper.h"
#include "common/config-manager.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "common/file.h"
#include "common/md5.h"
#include "sky/control.h"
#include "sky/debug.h"
#include "sky/disk.h"
#include "sky/grid.h"
#include "sky/intro.h"
#include "sky/logic.h"
#include "sky/mouse.h"
#include "sky/music/adlibmusic.h"
#include "sky/music/gmmusic.h"
#include "sky/music/mt32music.h"
#include "sky/music/musicbase.h"
#include "sky/screen.h"
#include "sky/sky.h"
#include "sky/skydefs.h"
#include "sky/sound.h"
#include "sky/text.h"
#include "sky/compact.h"
#include "audio/mididrv.h"
#include "audio/mixer.h"
#include "engines/util.h"
/*
At the beginning the reverse engineers were happy, and did rejoice at
their task, for the engine before them did shineth and was full of
promise. But then they did look closer and see'th the awful truth;
its code was assembly and messy (rareth was its comments). And so large
were its includes that did at first seem small; queereth also was its
compact(s). Then they did findeth another version, and this was slightly
different from the first. Then a third, and this was different again.
All different, but not really better, for all were not really compatible.
But, eventually, it did come to pass that Steel Sky was implemented on
a modern platform. And the programmers looked and saw that it was indeed a
miracle. But they were not joyous and instead did weep for nobody knew
just what had been done. Except people who read the source. Hello.
With apologies to the CD32 SteelSky file.
*/
namespace Sky {
void *SkyEngine::_itemList[300];
SystemVars *SkyEngine::_systemVars = nullptr;
const char *SkyEngine::shortcutsKeymapId = "sky-shortcuts";
SkyEngine::SkyEngine(OSystem *syst)
: Engine(syst), _fastMode(0), _debugger(0), _big5Font(nullptr) {
_systemVars = new SystemVars();
_systemVars->systemFlags = 0;
_systemVars->gameVersion = 0;
_systemVars->mouseFlag = 0;
_systemVars->language = 0;
_systemVars->currentPalette = 4316;
_systemVars->gameSpeed = 0;
_systemVars->currentMusic = 0;
_systemVars->pastIntro = false;
_systemVars->paused = false;
_systemVars->textDirRTL = false;
memset (_chineseTraditionalOffsets, 0, sizeof(_chineseTraditionalOffsets));
_chineseTraditionalBlock = nullptr;
_action = kSkyActionNone;
_skyLogic = nullptr;
_skySound = nullptr;
_skyMusic = nullptr;
_skyText = nullptr;
_skyMouse = nullptr;
_skyScreen = nullptr;
_skyDisk = nullptr;
_skyControl = nullptr;
_skyCompact = nullptr;
}
SkyEngine::~SkyEngine() {
delete _skyLogic;
delete _skySound;
delete _skyMusic;
delete _skyText;
delete _skyMouse;
delete _skyScreen;
//_debugger is deleted by Engine
delete _skyDisk;
delete _skyControl;
delete _skyCompact;
for (int i = 0; i < 300; i++)
if (_itemList[i])
free(_itemList[i]);
delete _systemVars;
delete [] _chineseTraditionalBlock;
_chineseTraditionalBlock = nullptr;
delete _big5Font;
_big5Font = nullptr;
}
void SkyEngine::syncSoundSettings() {
Engine::syncSoundSettings();
bool mute = false;
if (ConfMan.hasKey("mute"))
mute = ConfMan.getBool("mute");
if (ConfMan.getBool("sfx_mute")) // set mute sfx status for native options menu (F5)
SkyEngine::_systemVars->systemFlags |= SF_FX_OFF;
if (ConfMan.getBool("music_mute")) { // CD version allows to mute music from native options menu (F5)
SkyEngine::_systemVars->systemFlags |= SF_MUS_OFF;
}
// SkyEngine native sound volume range is [0, 127]
// However, via ScummVM UI, the volume range can be set within [0, 256]
// so we "translate" between them
_skyMusic->setVolume(mute ? 0: CLIP(ConfMan.getInt("music_volume") >> 1, 0, 127));
// write-back to ini file for persistence
ConfMan.flushToDisk();
}
void SkyEngine::initVirgin() {
_skyScreen->setPalette(60111);
_skyScreen->showScreen(60110);
}
void SkyEngine::handleKey() {
if ((_action != kSkyActionNone || _keyPressed.keycode) && _systemVars->paused) {
_skySound->fnUnPauseFx();
_systemVars->paused = false;
_skyScreen->setPaletteEndian((uint8 *)_skyCompact->fetchCpt(SkyEngine::_systemVars->currentPalette));
} else {
switch (_action) {
case kSkyActionToggleFastMode:
_fastMode ^= 1;
break;
case kSkyActionToggleReallyFastMode:
_fastMode ^= 2;
break;
case kSkyActionOpenControlPanel:
_skyControl->doControlPanel();
break;
case kSkyActionSkip:
if (!_systemVars->pastIntro)
_skyControl->restartGame();
break;
case kSkyActionSkipLine:
_skyMouse->logicClick();
break;
case kSkyActionPause:
_skyScreen->halvePalette();
_skySound->fnPauseFx();
_systemVars->paused = true;
break;
default:
break;
}
}
_action = kSkyActionNone;
_keyPressed.reset();
}
Common::Error SkyEngine::go() {
_action = kSkyActionNone;
uint16 result = 0;
if (ConfMan.hasKey("save_slot")) {
int saveSlot = ConfMan.getInt("save_slot");
if (saveSlot >= 0 && saveSlot <= MAX_SAVE_GAMES)
result = _skyControl->quickXRestore(ConfMan.getInt("save_slot"));
}
if (result != GAME_RESTORED) {
bool introSkipped = false;
// Clear pastIntro here (set to false) explicilty
// It should be false already, but better to ensure it
_systemVars->pastIntro = false;
if (_systemVars->gameVersion > 272) { // don't do intro for floppydemos
Intro *skyIntro = new Intro(_skyDisk, _skyScreen, _skyMusic, _skySound, _skyText, _mixer, _system);
bool floppyIntro = ConfMan.getBool("alt_intro");
introSkipped = !skyIntro->doIntro(floppyIntro);
delete skyIntro;
}
if (!shouldQuit()) {
_skyScreen->clearScreen(true);
// restartGame() takes us to the first scene, without showing the
// initial animation where Foster is being chased. initScreen0()
// shows the first scene together with that animation. We can't
// call both, as they both load the same scene.
if (introSkipped) {
// restart game sets the _systemVars->pastIntro = true;
_skyControl->restartGame();
} else {
_skyLogic->initScreen0();
}
}
}
uint32 delayCount = _system->getMillis();
while (!shouldQuit()) {
_skySound->checkFxQueue();
_skyMouse->mouseEngine();
handleKey();
if (_systemVars->paused) {
do {
_system->updateScreen();
delay(50);
handleKey();
} while (_systemVars->paused);
delayCount = _system->getMillis();
}
_skyLogic->engine();
_skyScreen->processSequence();
_skyScreen->recreate();
_skyScreen->spriteEngine();
if (_debugger->showGrid()) {
uint8 *grid = _skyLogic->_skyGrid->giveGrid(Logic::_scriptVariables[SCREEN]);
if (grid) {
_skyScreen->showGrid(grid);
_skyScreen->forceRefresh();
}
}
_skyScreen->flip();
if (_fastMode & 2)
delay(0);
else if (_fastMode & 1)
delay(10);
else {
delayCount += _systemVars->gameSpeed;
int needDelay = delayCount - (int)_system->getMillis();
if ((needDelay < 0) || (needDelay > _systemVars->gameSpeed)) {
needDelay = 0;
delayCount = _system->getMillis();
}
delay(needDelay);
}
}
_skyControl->showGameQuitMsg();
_skyMusic->stopMusic();
ConfMan.flushToDisk();
delay(1500);
return Common::kNoError;
}
static const struct {
// Identification
const char *md5; // File MD5
uint length; // File length
// Main section
// Offset from the beginning of file to virtual address 0
uint32 virtualBase;
// Offset to index of string sections
uint stringSectionIndexOffset;
// Offset to the font
uint fontOffset;
// Next one isn't strictly necessary but makes logic simpler
// by allowing to read string block into memory as whole
// without any parsing. Just has to cover the block containing
// the strings. Reading more (up to whole file) is OK.
// End of strings block.
uint stringBlockEnd;
} chineseExes[] = {
{
// Identification
"7bc128ba9bfaecb9bb4ef328b756057a", 575538,
// Main
0x5191, 0x6427e, 0x54afc,
// Value to simplify code
0x7eee1
}
};
bool SkyEngine::loadChineseTraditional() {
Common::File skyExe;
if (!skyExe.open("sky.exe"))
return false;
uint length = skyExe.size();
Common::String md5 = Common::computeStreamMD5AsString(skyExe, length);
for (uint i = 0; i < ARRAYSIZE(chineseExes); i++) {
if (md5 == chineseExes[i].md5 && length == chineseExes[i].length) {
skyExe.seek(chineseExes[i].stringSectionIndexOffset);
for (uint j = 0; j < 8; j++)
_chineseTraditionalOffsets[j] = skyExe.readUint32LE() + chineseExes[i].virtualBase;
uint32 stringBlockOffset = _chineseTraditionalOffsets[0];
for (uint j = 1; j < 8; j++)
stringBlockOffset = MIN(_chineseTraditionalOffsets[j], stringBlockOffset);
for (uint j = 0; j < 8; j++)
_chineseTraditionalOffsets[j] -= stringBlockOffset;
uint stringBlockLen = chineseExes[i].stringBlockEnd - stringBlockOffset;
_chineseTraditionalBlock = new char[stringBlockLen];
skyExe.seek(stringBlockOffset);
skyExe.read(_chineseTraditionalBlock, stringBlockLen);
skyExe.seek(chineseExes[i].fontOffset);
_big5Font = new Graphics::Big5Font();
_big5Font->loadPrefixedRaw(skyExe, 15);
return true;
}
}
return false;
}
Common::Error SkyEngine::init() {
initGraphics(320, 200);
_skyDisk = new Disk();
_skySound = new Sound(_mixer, _skyDisk, Audio::Mixer::kMaxChannelVolume);
_systemVars->gameVersion = _skyDisk->determineGameVersion();
MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_ADLIB | MDT_MIDI | MDT_PREFER_MT32);
if (MidiDriver::getMusicType(dev) == MT_ADLIB) {
_systemVars->systemFlags |= SF_SBLASTER;
_skyMusic = new AdLibMusic(_mixer, _skyDisk);
} else {
_systemVars->systemFlags |= SF_ROLAND;
if ((MidiDriver::getMusicType(dev) == MT_MT32) || ConfMan.getBool("native_mt32"))
_skyMusic = new MT32Music(MidiDriver::createMidi(dev), _mixer, _skyDisk);
else
_skyMusic = new GmMusic(MidiDriver::createMidi(dev), _mixer, _skyDisk);
}
if (isCDVersion()) {
if (ConfMan.hasKey("nosubtitles")) {
warning("Configuration key 'nosubtitles' is deprecated. Use 'subtitles' instead");
if (!ConfMan.getBool("nosubtitles"))
_systemVars->systemFlags |= SF_ALLOW_TEXT;
}
if (ConfMan.getBool("subtitles"))
_systemVars->systemFlags |= SF_ALLOW_TEXT;
if (!ConfMan.getBool("speech_mute"))
_systemVars->systemFlags |= SF_ALLOW_SPEECH;
} else
_systemVars->systemFlags |= SF_ALLOW_TEXT;
_systemVars->systemFlags |= SF_PLAY_VOCS;
_systemVars->gameSpeed = 80;
_skyCompact = new SkyCompact();
_skyText = new Text(this, _skyDisk, _skyCompact);
_skyMouse = new Mouse(_system, _skyDisk, _skyCompact);
_skyScreen = new Screen(_system, _skyDisk, _skyCompact);
initVirgin();
initItemList();
loadFixedItems();
_skyLogic = new Logic(_skyCompact, _skyScreen, _skyDisk, _skyText, _skyMusic, _skyMouse, _skySound);
_skyMouse->useLogicInstance(_skyLogic);
Common::Keymapper *keymapper = _system->getEventManager()->getKeymapper();
Common::Keymap *shortcutsKeymap = keymapper->getKeymap(shortcutsKeymapId);
assert(shortcutsKeymap);
_skyControl = new Control(this, _saveFileMan, _skyScreen, _skyDisk, _skyMouse, _skyText, _skyMusic, _skyLogic, _skySound, _skyCompact, _system, shortcutsKeymap);
_skyLogic->useControlInstance(_skyControl);
switch (Common::parseLanguage(ConfMan.get("language"))) {
case Common::EN_USA:
_systemVars->language = SKY_USA;
break;
case Common::DE_DEU:
_systemVars->language = SKY_GERMAN;
break;
case Common::FR_FRA:
_systemVars->language = SKY_FRENCH;
break;
case Common::IT_ITA:
_systemVars->language = SKY_ITALIAN;
break;
case Common::PT_BRA:
_systemVars->language = SKY_PORTUGUESE;
break;
case Common::ES_ESP:
_systemVars->language = SKY_SPANISH;
break;
case Common::SV_SWE:
_systemVars->language = SKY_SWEDISH;
break;
case Common::EN_GRB:
_systemVars->language = SKY_ENGLISH;
break;
case Common::ZH_TWN:
_systemVars->language = SKY_CHINESE_TRADITIONAL;
break;
case Common::HE_ISR:
_systemVars->textDirRTL = true;
break;
default:
_systemVars->language = SKY_ENGLISH;
break;
}
if (_systemVars->language == SKY_CHINESE_TRADITIONAL && !loadChineseTraditional()) {
_systemVars->language = SKY_ENGLISH;
}
if (_systemVars->language != SKY_CHINESE_TRADITIONAL &&
!_skyDisk->fileExists(60600 + SkyEngine::_systemVars->language * 8)) {
warning("The language you selected does not exist in your BASS version");
if (_skyDisk->fileExists(60600))
SkyEngine::_systemVars->language = SKY_ENGLISH; // default to GB english if it exists..
else if (_skyDisk->fileExists(60600 + SKY_USA * 8))
SkyEngine::_systemVars->language = SKY_USA; // try US english...
else
for (uint8 cnt = SKY_ENGLISH; cnt <= SKY_SPANISH; cnt++)
if (_skyDisk->fileExists(60600 + cnt * 8)) { // pick the first language we can find
SkyEngine::_systemVars->language = cnt;
break;
}
}
// Setup mixer: Default volume is set to 192 (ScummVM)
// which is 96 (in-game) volume
ConfMan.registerDefault("sfx_volume", 192);
ConfMan.registerDefault("music_volume", 192);
ConfMan.registerDefault("speech_volume", 192);
ConfMan.registerDefault("mute", "false");
syncSoundSettings();
_debugger = new Debugger(_skyLogic, _skyMouse, _skyScreen, _skyCompact);
setDebugger(_debugger);
return Common::kNoError;
}
void SkyEngine::initItemList() {
//See List.asm for (cryptic) item # descriptions
for (int i = 0; i < 300; i++)
_itemList[i] = NULL;
}
void SkyEngine::loadFixedItems() {
_itemList[49] = _skyDisk->loadFile(49);
_itemList[50] = _skyDisk->loadFile(50);
_itemList[73] = _skyDisk->loadFile(73);
_itemList[262] = _skyDisk->loadFile(262);
if (!isDemo()) {
_itemList[36] = _skyDisk->loadFile(36);
_itemList[263] = _skyDisk->loadFile(263);
_itemList[264] = _skyDisk->loadFile(264);
_itemList[265] = _skyDisk->loadFile(265);
_itemList[266] = _skyDisk->loadFile(266);
_itemList[267] = _skyDisk->loadFile(267);
_itemList[269] = _skyDisk->loadFile(269);
_itemList[271] = _skyDisk->loadFile(271);
_itemList[272] = _skyDisk->loadFile(272);
}
}
void *SkyEngine::fetchItem(uint32 num) {
return _itemList[num];
}
void SkyEngine::delay(int32 amount) {
Common::Event event;
uint32 start = _system->getMillis();
_action = kSkyActionNone;
_keyPressed.reset();
if (amount < 0)
amount = 0;
do {
while (_eventMan->pollEvent(event)) {
switch (event.type) {
case Common::EVENT_CUSTOM_ENGINE_ACTION_START:
_action = (SkyAction)event.customType;
break;
case Common::EVENT_KEYDOWN:
_keyPressed = event.kbd;
break;
case Common::EVENT_MOUSEMOVE:
if (!(_systemVars->systemFlags & SF_MOUSE_LOCKED))
_skyMouse->mouseMoved(event.mouse.x, event.mouse.y);
break;
case Common::EVENT_LBUTTONDOWN:
if (!(_systemVars->systemFlags & SF_MOUSE_LOCKED))
_skyMouse->mouseMoved(event.mouse.x, event.mouse.y);
_skyMouse->buttonPressed(2);
break;
case Common::EVENT_RBUTTONDOWN:
if (!(_systemVars->systemFlags & SF_MOUSE_LOCKED))
_skyMouse->mouseMoved(event.mouse.x, event.mouse.y);
_skyMouse->buttonPressed(1);
break;
default:
break;
}
}
_system->updateScreen();
if (amount > 0)
_system->delayMillis((amount > 10) ? 10 : amount);
} while (_system->getMillis() < start + amount);
}
bool SkyEngine::isDemo() {
switch (_systemVars->gameVersion) {
case 109: // PC Gamer demo
case 267: // English floppy demo
case 272: // German floppy demo
case 365: // CD demo
return true;
case 288:
case 303:
case 331:
case 348:
case 368:
case 372:
return false;
default:
error("Unknown game version %d", _systemVars->gameVersion);
}
}
bool SkyEngine::isCDVersion() {
switch (_systemVars->gameVersion) {
case 109:
case 267:
case 272:
case 288:
case 303:
case 331:
case 348:
return false;
case 365:
case 368:
case 372:
return true;
default:
error("Unknown game version %d", _systemVars->gameVersion);
}
}
} // End of namespace Sky

147
engines/sky/sky.h Normal file
View File

@@ -0,0 +1,147 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef SKY_SKY_H
#define SKY_SKY_H
#include "common/array.h"
#include "common/error.h"
#include "common/keyboard.h"
#include "engines/engine.h"
#include "graphics/big5.h"
/**
* This is the namespace of the Sky engine.
*
* Status of this engine: ???
*
* Games using this engine:
* - Beneath a Steel Sky
*/
namespace Sky {
struct SystemVars {
uint32 systemFlags;
uint32 gameVersion;
uint32 mouseFlag;
uint16 language;
uint32 currentPalette;
uint16 gameSpeed;
uint16 currentMusic;
bool pastIntro;
bool paused;
bool textDirRTL;
};
class Sound;
class Disk;
class Text;
class Logic;
class Mouse;
class Screen;
class Control;
class MusicBase;
class Debugger;
class SkyCompact;
enum SkyAction {
kSkyActionNone,
kSkyActionToggleFastMode,
kSkyActionToggleReallyFastMode,
kSkyActionOpenControlPanel,
kSkyActionConfirm,
kSkyActionSkip,
kSkyActionSkipLine,
kSkyActionPause
};
class SkyEngine : public Engine {
protected:
SkyAction _action;
Common::KeyState _keyPressed;
Sound *_skySound;
Disk *_skyDisk;
Text *_skyText;
Logic *_skyLogic;
Mouse *_skyMouse;
Screen *_skyScreen;
Control *_skyControl;
SkyCompact *_skyCompact;
Debugger *_debugger;
MusicBase *_skyMusic;
public:
SkyEngine(OSystem *syst);
~SkyEngine() override;
void syncSoundSettings() override;
static bool isDemo();
static bool isCDVersion();
Common::Error loadGameState(int slot) override;
Common::Error saveGameState(int slot, const Common::String &desc, bool isAutosave = false) override;
bool canLoadGameStateCurrently(Common::U32String *msg = nullptr) override;
bool canSaveGameStateCurrently(Common::U32String *msg = nullptr) override;
Common::String getSaveStateName(int slot) const override {
return Common::String::format("SKY-VM.%03d", slot);
}
static void *fetchItem(uint32 num);
static void *_itemList[300];
static SystemVars *_systemVars;
static const char *shortcutsKeymapId;
uint32 _chineseTraditionalOffsets[8];
char *_chineseTraditionalBlock;
Graphics::Big5Font *_big5Font;
protected:
// Engine APIs
Common::Error init();
bool loadChineseTraditional();
Common::Error go();
Common::Error run() override {
Common::Error err;
err = init();
if (err.getCode() != Common::kNoError)
return err;
return go();
}
bool hasFeature(EngineFeature f) const override;
byte _fastMode;
void delay(int32 amount);
void handleKey();
void initItemList();
void initVirgin();
void loadFixedItems();
};
} // End of namespace Sky
#endif

4317
engines/sky/skydefs.h Normal file

File diff suppressed because it is too large Load Diff

1273
engines/sky/sound.cpp Normal file

File diff suppressed because it is too large Load Diff

96
engines/sky/sound.h Normal file
View File

@@ -0,0 +1,96 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef SKY_SOUND_H
#define SKY_SOUND_H
#include "common/scummsys.h"
#include "audio/mixer.h"
namespace Sky {
class Disk;
enum {
SOUND_CH0 = 0,
SOUND_CH1 = 1,
SOUND_BG = 2,
SOUND_VOICE = 3,
SOUND_SPEECH = 4
};
struct SfxQueue {
uint8 count, fxNo, chan, vol;
};
#define MAX_QUEUED_FX 4
class Sound {
protected:
public:
Audio::Mixer *_mixer;
Audio::SoundHandle _voiceHandle;
Audio::SoundHandle _effectHandle;
Audio::SoundHandle _bgSoundHandle;
Audio::SoundHandle _ingameSound0, _ingameSound1, _ingameSpeech;
uint16 _saveSounds[2];
protected:
void playSound(uint32 id, byte *sound, uint32 size, Audio::SoundHandle *handle);
public:
Sound(Audio::Mixer *mixer, Disk *pDisk, uint8 pVolume);
~Sound();
void loadSection(uint8 pSection);
void playSound(uint16 sound, uint16 volume, uint8 channel);
void fnStartFx(uint32 sound, uint8 channel);
bool startSpeech(uint16 textNum);
bool speechFinished() { return !_mixer->isSoundHandleActive(_ingameSpeech); }
void fnPauseFx();
void fnUnPauseFx();
void fnStopFx();
void stopSpeech();
void checkFxQueue();
void restoreSfx();
uint8 _soundsTotal;
private:
Disk *_skyDisk;
uint16 _sfxBaseOfs;
uint8 *_soundData;
uint8 *_sampleRates, *_sfxInfo;
uint8 _mainSfxVolume;
bool _isPaused;
static uint16 _speechConvertTable[8];
static SfxQueue _sfxQueue[MAX_QUEUED_FX];
};
} // End of namespace Sky
#endif

165
engines/sky/struc.h Normal file
View File

@@ -0,0 +1,165 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef SKY_STRUC_H
#define SKY_STRUC_H
namespace Sky {
struct DisplayedText {
byte *textData;
uint32 textWidth;
uint16 compactNum;
};
#include "common/pack-start.h" // START STRUCT PACKING
struct DataFileHeader {
uint16 flag; // bit 0: set for color data, clear for not
// bit 1: set for compressed, clear for uncompressed
// bit 2: set for 32 colors, clear for 16 colors
uint16 s_x;
uint16 s_y;
uint16 s_width;
uint16 s_height;
uint16 s_sp_size;
uint16 s_tot_size;
uint16 s_n_sprites;
int16 s_offset_x;
int16 s_offset_y;
uint16 s_compressed_size;
} PACKED_STRUCT;
struct TurnTable {
uint16 turnTableUp[5];
uint16 turnTableDown[5];
uint16 turnTableLeft[5];
uint16 turnTableRight[5];
uint16 turnTableTalk[5];
} PACKED_STRUCT;
struct MegaSet {
uint16 gridWidth; // 0
uint16 colOffset; // 1
uint16 colWidth; // 2
uint16 lastChr; // 3
uint16 animUpId; // 4
uint16 animDownId; // 5
uint16 animLeftId; // 6
uint16 animRightId; // 7
uint16 standUpId; // 8
uint16 standDownId; // 9
uint16 standLeftId; // 10
uint16 standRightId; // 11
uint16 standTalkId; // 12
uint16 turnTableId; // 13
} PACKED_STRUCT;
struct Compact {
uint16 logic; // 0: Entry in logic table to run (byte as <256entries in logic table
uint16 status; // 1
uint16 sync; // 2: flag sent to compacts by other things
uint16 screen; // 3: current screen
uint16 place; // 4: so's this one
uint16 getToTableId; // 5: Address of how to get to things table
uint16 xcood; // 6
uint16 ycood; // 7
uint16 frame; // 8
uint16 cursorText; // 9
uint16 mouseOn; // 10
uint16 mouseOff; // 11
uint16 mouseClick; // 12
int16 mouseRelX; // 13
int16 mouseRelY; // 14
uint16 mouseSizeX; // 15
uint16 mouseSizeY; // 16
uint16 actionScript; // 17
uint16 upFlag; // 18: usually holds the Action Mode
uint16 downFlag; // 19: used for passing back
uint16 getToFlag; // 20: used by action script for get to attempts, also frame store (hence word)
uint16 flag; // 21: a use any time flag
uint16 mood; // 22: high level - stood or not
uint16 grafixProgId; // 23
uint16 grafixProgPos;// 24
uint16 offset; // 25
uint16 mode; // 26: which mcode block
uint16 baseSub; // 27: 1st mcode block relative to start of compact
uint16 baseSub_off; // 28
uint16 actionSub; // 29
uint16 actionSub_off;// 30
uint16 getToSub; // 31
uint16 getToSub_off; // 32
uint16 extraSub; // 33
uint16 extraSub_off; // 34
uint16 dir; // 35
uint16 stopScript; // 36
uint16 miniBump; // 37
uint16 leaving; // 38
uint16 atWatch; // 39: pointer to script variable
uint16 atWas; // 40: pointer to script variable
uint16 alt; // 41: alternate script
uint16 request; // 42
uint16 spWidth_xx; // 43
uint16 spColor; // 44
uint16 spTextId; // 45
uint16 spTime; // 46
uint16 arAnimIndex; // 47
uint16 turnProgId; // 48
uint16 turnProgPos; // 49
uint16 waitingFor; // 50
uint16 arTargetX; // 51
uint16 arTargetY; // 52
uint16 animScratchId;// 53: data area for AR
uint16 megaSet; // 54
MegaSet megaSet0; // 55
MegaSet megaSet1; //
MegaSet megaSet2; //
MegaSet megaSet3; //
} PACKED_STRUCT;
#include "common/pack-end.h" // END STRUCT PACKING
} // End of namespace Sky
#endif

572
engines/sky/text.cpp Normal file
View File

@@ -0,0 +1,572 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#include "common/debug.h"
#include "common/endian.h"
#include "common/textconsole.h"
#include "sky/disk.h"
#include "sky/logic.h"
#include "sky/text.h"
#include "sky/sky.h"
#include "sky/skydefs.h"
#include "sky/struc.h"
#include "sky/compact.h"
namespace Sky {
#define FIRST_TEXT_SEC 77
#define FIRST_TEXT_BUFFER 274
#define LAST_TEXT_BUFFER 284
#define NO_OF_TEXT_SECTIONS 8 // 8 sections per language
#define CHAR_SET_FILE 60150
#define MAX_SPEECH_SECTION 7
#define CHAR_SET_HEADER 128
#define MAX_NO_LINES 10
Text::Text(SkyEngine *vm, Disk *skyDisk, SkyCompact *skyCompact) : _skyDisk(skyDisk), _skyCompact(skyCompact), _vm(vm) {
initHuffTree();
_mainCharacterSet.addr = _skyDisk->loadFile(CHAR_SET_FILE);
_mainCharacterSet.charHeight = MAIN_CHAR_HEIGHT;
_mainCharacterSet.charSpacing = 0;
fnSetFont(0);
if (!SkyEngine::isDemo()) {
_controlCharacterSet.addr = _skyDisk->loadFile(60520);
_controlCharacterSet.charHeight = 12;
_controlCharacterSet.charSpacing = 0;
_linkCharacterSet.addr = _skyDisk->loadFile(60521);
_linkCharacterSet.charHeight = 12;
_linkCharacterSet.charSpacing = 1;
} else {
_controlCharacterSet.addr = NULL;
_linkCharacterSet.addr = NULL;
}
}
Text::~Text() {
for (int i = FIRST_TEXT_BUFFER; i <= LAST_TEXT_BUFFER; i++)
if (SkyEngine::_itemList[i]) {
free(SkyEngine::_itemList[i]);
SkyEngine::_itemList[i] = NULL;
}
free(_mainCharacterSet.addr);
free(_controlCharacterSet.addr);
free(_linkCharacterSet.addr);
}
void Text::fnSetFont(uint32 fontNr) {
charSet *newCharSet;
switch (fontNr) {
case 0:
newCharSet = &_mainCharacterSet;
break;
case 1:
newCharSet = &_linkCharacterSet;
break;
case 2:
newCharSet = &_controlCharacterSet;
break;
default:
error("Tried to set invalid font (%d)", fontNr);
}
_curCharSet = fontNr;
_characterSet = newCharSet->addr;
_charHeight = (byte)newCharSet->charHeight;
_dtCharSpacing = newCharSet->charSpacing;
}
void Text::fnTextModule(uint32 textInfoId, uint32 textNo) {
fnSetFont(1);
uint16* msgData = (uint16 *)_skyCompact->fetchCpt(textInfoId);
DisplayedText textId = lowTextManager(textNo, msgData[1], msgData[2], 209, Graphics::kTextAlignStart);
Logic::_scriptVariables[RESULT] = textId.compactNum;
Compact *textCompact = _skyCompact->fetchCpt(textId.compactNum);
textCompact->xcood = msgData[3];
textCompact->ycood = msgData[4];
fnSetFont(0);
}
void Text::getText(uint32 textNr) { //load text #"textNr" into textBuffer
if (patchMessage(textNr))
return;
uint32 sectionNo = (textNr & 0x0F000) >> 12;
if (SkyEngine::_systemVars->language == SKY_CHINESE_TRADITIONAL) {
uint32 sectionOffset = _vm->_chineseTraditionalOffsets[sectionNo];
const char *ptr = _vm->_chineseTraditionalBlock + sectionOffset;
uint nrInBlock = textNr & 0xFFF;
if (sectionNo != 7)
nrInBlock--;
for (uint32 i = 0; i < nrInBlock; i++) {
while (*ptr)
ptr++;
ptr++;
}
char *dest = (char *)_textBuffer;
while (*ptr)
*dest++ = *ptr++;
*dest = 0;
return;
}
if (SkyEngine::_itemList[FIRST_TEXT_SEC + sectionNo] == NULL) { //check if already loaded
debug(5, "Loading Text item(s) for Section %d", (sectionNo >> 2));
uint32 fileNo = sectionNo + ((SkyEngine::_systemVars->language * NO_OF_TEXT_SECTIONS) + 60600);
SkyEngine::_itemList[FIRST_TEXT_SEC + sectionNo] = (void **)_skyDisk->loadFile((uint16)fileNo);
}
uint8 *textDataPtr = (uint8 *)SkyEngine::_itemList[FIRST_TEXT_SEC + sectionNo];
uint32 offset = 0;
uint32 blockNr = textNr & 0xFE0;
textNr &= 0x1F;
if (blockNr) {
uint16 *blockPtr = (uint16 *)(textDataPtr + 4);
uint32 nr32MsgBlocks = blockNr >> 5;
do {
offset += READ_LE_UINT16(blockPtr);
blockPtr++;
} while (--nr32MsgBlocks);
}
if (textNr) {
uint8 *blockPtr = textDataPtr + blockNr + READ_LE_UINT16(textDataPtr);
do {
uint16 skipBytes = *blockPtr++;
if (skipBytes & 0x80) {
skipBytes &= 0x7F;
skipBytes <<= 3;
}
offset += skipBytes;
} while (--textNr);
}
uint32 bitPos = offset & 3;
offset >>= 2;
offset += READ_LE_UINT16(textDataPtr + 2);
textDataPtr += offset;
//bit pointer: 0->8, 1->6, 2->4 ...
bitPos ^= 3;
bitPos++;
bitPos <<= 1;
char *dest = (char *)_textBuffer;
char textChar;
do {
textChar = getTextChar(&textDataPtr, &bitPos);
*dest++ = textChar;
} while (textChar);
}
void Text::fnPointerText(uint32 pointedId, uint16 mouseX, uint16 mouseY) {
Compact *ptrComp = _skyCompact->fetchCpt(pointedId);
DisplayedText text = lowTextManager(ptrComp->cursorText, TEXT_MOUSE_WIDTH, L_CURSOR, 242, Graphics::kTextAlignLeft);
Logic::_scriptVariables[CURSOR_ID] = text.compactNum;
if (Logic::_scriptVariables[MENU]) {
_mouseOfsY = TOP_LEFT_Y - 2;
if (mouseX < 150)
_mouseOfsX = TOP_LEFT_X + 24;
else
_mouseOfsX = TOP_LEFT_X - 8 - text.textWidth;
} else {
_mouseOfsY = TOP_LEFT_Y - 10;
if (mouseX < 150)
_mouseOfsX = TOP_LEFT_X + 13;
else
_mouseOfsX = TOP_LEFT_X - 8 - text.textWidth;
}
Compact *textCompact = _skyCompact->fetchCpt(text.compactNum);
logicCursor(textCompact, mouseX, mouseY);
}
void Text::logicCursor(Compact *textCompact, uint16 mouseX, uint16 mouseY) {
textCompact->xcood = (uint16)(mouseX + _mouseOfsX);
textCompact->ycood = (uint16)(mouseY + _mouseOfsY);
if (textCompact->ycood < TOP_LEFT_Y)
textCompact->ycood = TOP_LEFT_Y;
}
bool Text::getTextBit(uint8 **data, uint32 *bitPos) {
if (*bitPos) {
(*bitPos)--;
} else {
(*data)++;
*bitPos = 7;
}
return (bool)(((**data) >> (*bitPos)) & 1);
}
char Text::getTextChar(uint8 **data, uint32 *bitPos) {
int pos = 0;
while (1) {
if (getTextBit(data, bitPos))
pos = _huffTree[pos].rChild;
else
pos = _huffTree[pos].lChild;
if (_huffTree[pos].lChild == 0 && _huffTree[pos].rChild == 0) {
return _huffTree[pos].value;
}
}
}
DisplayedText Text::displayText(uint32 textNum, uint8 *dest, Graphics::TextAlign align, uint16 pixelWidth, uint8 color) {
//Render text into buffer *dest
getText(textNum);
return displayText(_textBuffer, sizeof(_textBuffer), dest, align, pixelWidth, color);
}
// TODO: Don't use caller-supplied buffer for editing operations
DisplayedText Text::displayText(char *textPtr, uint32 bufLen, uint8 *dest, Graphics::TextAlign align, uint16 pixelWidth, uint8 color) {
//Render text pointed to by *textPtr in buffer *dest
uint32 centerTable[10];
uint16 lineWidth = 0;
uint32 numLines = 0;
_numLetters = 2;
// work around bug #1080 (line width exceeded)
char *tmpPtr = strstr(textPtr, "MUND-BEATMUNG!");
if (tmpPtr)
// We are sure there is at least this space and we replace it by something of same length
Common::strcpy_s(tmpPtr, sizeof("MUND-BEATMUNG!"), "MUND BEATMUNG!");
// work around bug #1940 (line width exceeded when talking to gardener using spanish text)
// This text apparently only is broken in the floppy versions, the CD versions contain
// the correct string "MANIFESTACION - ARTISTICA.", which doesn't break the algorithm/game.
tmpPtr = strstr(textPtr, "MANIFESTACION-ARTISTICA.");
if (tmpPtr)
// We are sure there is at least this space and we replace it by something of same length
Common::strcpy_s(tmpPtr, sizeof("MANIFESTACION-ARTISTICA."), "MANIFESTACION ARTISTICA.");
char *curPos = textPtr;
char *lastSpace = textPtr;
uint8 textChar = (uint8)*curPos++;
bool isBig5 = SkyEngine::_systemVars->language == SKY_CHINESE_TRADITIONAL;
while (textChar >= 0x20) {
bool isDoubleChar = false;
int oldLineWidth = lineWidth;
if (isBig5 && (textChar & 0x80)) {
isDoubleChar = true;
curPos++;
lineWidth += Graphics::Big5Font::kChineseTraditionalWidth;
} else {
if ((_curCharSet == 1) && (textChar >= 0x80) && !SkyEngine::_systemVars->textDirRTL)
textChar = 0x20;
textChar -= 0x20;
if (textChar == 0) {
lastSpace = curPos; //keep track of last space
centerTable[numLines] = lineWidth;
}
lineWidth += _characterSet[textChar]; //add character width
lineWidth += (uint16)_dtCharSpacing; //include character spacing
}
if (pixelWidth <= lineWidth) {
// If no space is found just break here. This is common in e.g. Chinese
// that doesn't use spaces.
if (lastSpace == textPtr || *(lastSpace-1) == 10) {
curPos -= isDoubleChar ? 2 : 1;
if (curPos < textPtr)
curPos = textPtr;
if (strlen(textPtr) + 2 >= bufLen)
error("Ran out of buffer size when word-wrapping");
// Add a place for linebreak
memmove(curPos + 1, curPos, textPtr + bufLen - curPos - 2);
textPtr[bufLen - 1] = 0;
lastSpace = curPos + 1;
centerTable[numLines] = oldLineWidth;
}
*(lastSpace-1) = 10;
lineWidth = 0;
numLines++;
curPos = lastSpace; //go back for new count
}
textChar = (uint8)*curPos++;
_numLetters++;
}
uint32 dtLastWidth = lineWidth; //save width of last line
centerTable[numLines] = lineWidth; //and update centering table
numLines++;
if (numLines > MAX_NO_LINES)
error("Maximum no. of lines exceeded");
int charHeight = isBig5 && _vm->_big5Font ? MAX<int>(_charHeight, _vm->_big5Font->getFontHeight()) : _charHeight;
uint32 dtLineSize = pixelWidth * charHeight;
uint32 numBytes = (dtLineSize * numLines) + sizeof(DataFileHeader) + 4;
if (!dest)
dest = (uint8 *)malloc(numBytes);
// clear text sprite buffer
memset(dest + sizeof(DataFileHeader), 0, numBytes - sizeof(DataFileHeader));
//make the header
((DataFileHeader *)dest)->s_width = pixelWidth;
((DataFileHeader *)dest)->s_height = (uint16)(charHeight * numLines);
((DataFileHeader *)dest)->s_sp_size = (uint16)(pixelWidth * charHeight * numLines);
((DataFileHeader *)dest)->s_offset_x = 0;
((DataFileHeader *)dest)->s_offset_y = 0;
//reset position
curPos = textPtr;
uint8 *curDest = dest + sizeof(DataFileHeader); //point to where pixels start
byte *prevDest = curDest;
uint32 *centerTblPtr = centerTable;
align = Graphics::convertTextAlignH(align, _vm->_systemVars->textDirRTL);
do {
Common::String line("");
byte *lineEnd = curDest + pixelWidth;
if (align == Graphics::kTextAlignCenter) {
uint32 width = (pixelWidth - *centerTblPtr) >> 1;
centerTblPtr++;
curDest += width;
} else if (align == Graphics::kTextAlignRight) {
curDest += pixelWidth - *centerTblPtr - 1;
centerTblPtr++;
}
textChar = (uint8)*curPos++;
while (textChar >= 0x20) {
if (isBig5 && (textChar & 0x80)) {
uint8 trail = *curPos++;
uint16 fullCh = (textChar << 8) | trail;
if (_vm->_big5Font->drawBig5Char(curDest, fullCh, lineEnd - curDest, charHeight, pixelWidth, color, 240)) {
//update position
curDest += Graphics::Big5Font::kChineseTraditionalWidth;
textChar = *curPos++;
continue;
}
textChar = '?';
}
line += textChar - 0x20;
textChar = *curPos++;
}
if (_vm->_systemVars->textDirRTL) {
for (int i = line.size() - 1; i >= 0; --i) {
makeGameCharacter(line[i], _characterSet, curDest, color, pixelWidth);
}
} else {
for (auto &c : line) {
makeGameCharacter(c, _characterSet, curDest, color, pixelWidth);
}
}
prevDest = curDest = prevDest + dtLineSize; //start of last line + start of next
} while (textChar >= 10);
DisplayedText ret;
memset(&ret, 0, sizeof(ret));
ret.textData = dest;
ret.textWidth = dtLastWidth;
return ret;
}
void Text::makeGameCharacter(uint8 textChar, uint8 *charSetPtr, uint8 *&dest, uint8 color, uint16 bufPitch) {
bool maskBit, dataBit;
uint8 charWidth = (uint8)((*(charSetPtr + textChar)) + 1 - _dtCharSpacing);
uint16 data, mask;
byte *charSpritePtr = charSetPtr + (CHAR_SET_HEADER + ((_charHeight << 2) * textChar));
byte *startPos = dest;
byte *curPos = startPos;
for (int i = 0; i < _charHeight; i++) {
byte *prevPos = curPos;
data = READ_BE_UINT16(charSpritePtr);
mask = READ_BE_UINT16(charSpritePtr + 2);
charSpritePtr += 4;
for (int j = 0; j < charWidth; j++) {
maskBit = (mask & 0x8000) != 0; //check mask
mask <<= 1;
dataBit = (data & 0x8000) != 0; //check data
data <<= 1;
if (maskBit) {
if (dataBit)
*curPos = color;
else
*curPos = 240; //black edge
}
curPos++;
}
//advance a line
curPos = prevPos + bufPitch;
}
//update position
dest = startPos + charWidth + _dtCharSpacing * 2 - 1;
}
DisplayedText Text::lowTextManager(uint32 textNum, uint16 width, uint16 logicNum, uint8 color, Graphics::TextAlign align) {
getText(textNum);
DisplayedText textInfo = displayText(_textBuffer, sizeof(_textBuffer), NULL, align, width, color);
uint32 compactNum = FIRST_TEXT_COMPACT;
Compact *cpt = _skyCompact->fetchCpt(compactNum);
while (cpt->status != 0) {
compactNum++;
cpt = _skyCompact->fetchCpt(compactNum);
}
cpt->flag = (uint16)(compactNum - FIRST_TEXT_COMPACT) + FIRST_TEXT_BUFFER;
if (SkyEngine::_itemList[cpt->flag])
free(SkyEngine::_itemList[cpt->flag]);
SkyEngine::_itemList[cpt->flag] = textInfo.textData;
cpt->logic = logicNum;
cpt->status = ST_LOGIC | ST_FOREGROUND | ST_RECREATE;
cpt->screen = (uint16) Logic::_scriptVariables[SCREEN];
textInfo.compactNum = (uint16)compactNum;
return textInfo;
}
void Text::changeTextSpriteColor(uint8 *sprData, uint8 newCol) {
DataFileHeader *header = (DataFileHeader *)sprData;
sprData += sizeof(DataFileHeader);
for (uint16 cnt = 0; cnt < header->s_sp_size; cnt++)
if (sprData[cnt] >= 241)
sprData[cnt] = newCol;
}
uint32 Text::giveCurrentCharSet() {
return _curCharSet;
}
void Text::initHuffTree() {
switch (SkyEngine::_systemVars->gameVersion) {
case 109:
_huffTree = _huffTree_00109;
break;
case 272: // FIXME: Extract data
case 267:
_huffTree = _huffTree_00267;
break;
case 288:
_huffTree = _huffTree_00288;
break;
case 303:
_huffTree = _huffTree_00303;
break;
case 331:
_huffTree = _huffTree_00331;
break;
case 348:
_huffTree = _huffTree_00348;
break;
case 365:
_huffTree = _huffTree_00365;
break;
case 368:
_huffTree = _huffTree_00368;
break;
case 372:
_huffTree = _huffTree_00372;
break;
default:
error("Unknown game version %d", SkyEngine::_systemVars->gameVersion);
}
}
bool Text::patchMessage(uint32 textNum) {
if (SkyEngine::_systemVars->language == SKY_CHINESE_TRADITIONAL)
return false;
uint16 patchIdx = _patchLangIdx[SkyEngine::_systemVars->language];
uint16 patchNum = _patchLangNum[SkyEngine::_systemVars->language];
for (uint16 cnt = 0; cnt < patchNum; cnt++) {
if (_patchedMessages[cnt + patchIdx].textNr == textNum) {
Common::strcpy_s(_textBuffer, _patchedMessages[cnt + patchIdx].text);
return true;
}
}
return false;
}
const PatchMessage Text::_patchedMessages[NUM_PATCH_MSG] = {
{ 28724, "Testo e Parlato" }, // - italian
{ 28707, "Solo Testo" },
{ 28693, "Solo Parlato" },
{ 28724, "Text och tal" }, // - swedish
{ 28707, "Endast text" },
{ 28693, "Endast tal" },
{ 28686, "Musikvolym" },
{ 4336, "Wir befinden uns EINHUNDERTZWANZIG METER #ber dem ERBODEN!" }, // - german
{ 28686, "Volume de musique" }, // - french
};
const uint16 Text::_patchLangIdx[8] = {
0xFFFF, // SKY_ENGLISH
7, // SKY_GERMAN
8, // SKY_FRENCH
0xFFFF, // SKY_USA
3, // SKY_SWEDISH
0, // SKY_ITALIAN
0xFFFF, // SKY_PORTUGUESE
0xFFFF // SKY_SPANISH
};
const uint16 Text::_patchLangNum[8] = {
0, // SKY_ENGLISH
1, // SKY_GERMAN
1, // SKY_FRENCH
0, // SKY_USA
4, // SKY_SWEDISH
3, // SKY_ITALIAN
0, // SKY_PORTUGUESE
0 // SKY_SPANISH
};
} // End of namespace Sky

112
engines/sky/text.h Normal file
View File

@@ -0,0 +1,112 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef SKY_TEXT_H
#define SKY_TEXT_H
#include "common/scummsys.h"
#include "graphics/font.h"
#include "sky/sky.h"
namespace Sky {
struct Compact;
class Disk;
class SkyCompact;
struct HuffTree {
unsigned char lChild;
unsigned char rChild;
unsigned char value;
};
#define NUM_PATCH_MSG 9
struct PatchMessage {
uint32 textNr;
char text[100];
};
class Text {
public:
Text(SkyEngine *vm, Disk *skyDisk, SkyCompact *skyCompact);
~Text();
struct DisplayedText displayText(uint32 textNum, uint8 *dest, Graphics::TextAlign align, uint16 pixelWidth, uint8 color);
struct DisplayedText displayText(char *textPtr, uint32 bufLen, uint8 *dest, Graphics::TextAlign align, uint16 pixelWidth, uint8 color);
struct DisplayedText lowTextManager(uint32 textNum, uint16 width, uint16 logicNum, uint8 color, Graphics::TextAlign align);
void fnSetFont(uint32 fontNr);
void fnTextModule(uint32 textInfoId, uint32 textNo);
void fnPointerText(uint32 pointedId, uint16 mouseX, uint16 mouseY);
void logicCursor(Compact *textCompact, uint16 mouseX, uint16 mouseY);
void changeTextSpriteColor(uint8 *sprData, uint8 newCol);
uint32 giveCurrentCharSet();
uint32 _numLetters; //no of chars in message
private:
void initHuffTree();
void getText(uint32 textNr);
char getTextChar(uint8 **data, uint32 *bitPos);
bool getTextBit(uint8 **data, uint32 *bitPos);
void makeGameCharacter(uint8 textChar, uint8 *charSetPtr, uint8 *&data, uint8 color, uint16 bufPitch);
void makeChineseGameCharacter(uint16 textChar, uint8 *charSetPtr, uint8 *&dest, uint8 color, uint16 bufPitch);
bool patchMessage(uint32 textNum);
Disk *_skyDisk;
SkyCompact *_skyCompact;
SkyEngine *_vm;
const HuffTree *_huffTree;
struct charSet {
uint8 *addr;
uint32 charHeight;
uint32 charSpacing;
} _mainCharacterSet, _linkCharacterSet, _controlCharacterSet;
uint32 _curCharSet;
uint8 *_characterSet;
uint8 _charHeight;
char _textBuffer[1024];
uint32 _dtCharSpacing; //character separation adjustment
uint32 _mouseOfsX, _mouseOfsY;
static const PatchMessage _patchedMessages[NUM_PATCH_MSG];
static const uint16 _patchLangIdx[8];
static const uint16 _patchLangNum[8];
static const HuffTree _huffTree_00109[]; // trees moved to hufftext.cpp
static const HuffTree _huffTree_00267[];
static const HuffTree _huffTree_00288[];
static const HuffTree _huffTree_00303[];
static const HuffTree _huffTree_00331[];
static const HuffTree _huffTree_00348[];
static const HuffTree _huffTree_00365[];
static const HuffTree _huffTree_00368[];
static const HuffTree _huffTree_00372[];
};
} // End of namespace Sky
#endif