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

4
engines/sword2/POTFILES Normal file
View File

@@ -0,0 +1,4 @@
engines/sword2/animation.cpp
engines/sword2/detection_tables.h
engines/sword2/metaengine.cpp
engines/sword2/sword2.cpp

View File

@@ -0,0 +1,477 @@
/* 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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/file.h"
#include "common/mutex.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "common/translation.h"
#include "sword2/sword2.h"
#include "sword2/defs.h"
#include "sword2/header.h"
#include "sword2/logic.h"
#include "sword2/maketext.h"
#include "sword2/resman.h"
#include "sword2/sound.h"
#include "sword2/screen.h"
#include "sword2/animation.h"
#include "graphics/paletteman.h"
#include "gui/message.h"
#ifdef USE_MPEG2
#include "video/avi_decoder.h"
#endif
#include "video/dxa_decoder.h"
#include "video/smk_decoder.h"
#include "video/psx_decoder.h"
#include "engines/util.h"
namespace Sword2 {
///////////////////////////////////////////////////////////////////////////////
// Basic movie player
///////////////////////////////////////////////////////////////////////////////
MoviePlayer::MoviePlayer(Sword2Engine *vm, OSystem *system, Video::VideoDecoder *decoder, DecoderType decoderType)
: _vm(vm), _system(system), _modeChange(false) {
_decoderType = decoderType;
_decoder = decoder;
_white = 255;
_black = 0;
}
MoviePlayer::~MoviePlayer() {
delete _decoder;
}
/**
* Plays an animated cutscene.
* @param id the id of the file
*/
Common::Error MoviePlayer::load(const char *name) {
_textSurface = nullptr;
Common::String filename;
switch (_decoderType) {
case kVideoDecoderDXA:
filename = Common::String::format("%s.dxa", name);
break;
case kVideoDecoderSMK:
filename = Common::String::format("%s.smk", name);
break;
case kVideoDecoderPSX:
filename = Common::String::format("%s.str", name);
break;
case kVideoDecoderMP2:
filename = Common::String::format("%s.mp2", name);
break;
default:
break;
}
if (!_decoder->loadFile(Common::Path(filename))) {
return Common::Error(Common::kPathDoesNotExist, filename);
}
// Need to switch to true color for PSX/MP2 videos
if (!_decoder->getPixelFormat().isCLUT8()) {
if (!_decoder->setOutputPixelFormats(g_system->getSupportedFormats()))
return Common::kUnsupportedColorMode;
Graphics::PixelFormat format = _decoder->getPixelFormat();
initGraphics(g_system->getWidth(), g_system->getHeight(), &format);
if (g_system->getScreenFormat() != format) {
return Common::kUnsupportedColorMode;
} else {
_modeChange = true;
}
}
// For DXA/MP2, also add the external sound file
if (_decoderType == kVideoDecoderDXA || _decoderType == kVideoDecoderMP2)
_decoder->addStreamFileTrack(name);
_decoder->start();
return Common::kNoError;
}
void MoviePlayer::play(MovieText *movieTexts, uint32 numMovieTexts, uint32 leadIn, uint32 leadOut) {
_leadOutFrame = _decoder->getFrameCount();
if (_leadOutFrame > 60)
_leadOutFrame -= 60;
_movieTexts = movieTexts;
_numMovieTexts = numMovieTexts;
_currentMovieText = 0;
_leadOut = leadOut;
if (leadIn)
_vm->_sound->playMovieSound(leadIn, kLeadInSound);
bool terminated = !playVideo();
closeTextObject(_currentMovieText, nullptr, 0);
if (terminated) {
_vm->_sound->stopMovieSounds();
_vm->_sound->stopSpeech();
}
// Need to jump back to paletted color
if (_modeChange) {
initGraphics(640, 480);
_modeChange = false;
}
}
void MoviePlayer::openTextObject(uint32 index) {
MovieText *text = &_movieTexts[index];
// Pull out the text line to get the official text number (for WAV id)
uint32 res = text->_textNumber / SIZE;
uint32 localText = text->_textNumber & 0xffff;
// Open text resource and get the line
byte *textData = _vm->fetchTextLine(_vm->_resman->openResource(res), localText);
text->_speechId = READ_LE_UINT16(textData);
// Is it speech or subtitles, or both?
// If we want subtitles, or there was no sound
if (_vm->getSubtitles() || !text->_speechId) {
text->_textMem = _vm->_fontRenderer->makeTextSprite(textData + 2, 600, 255, _vm->_speechFontId, 1);
}
_vm->_resman->closeResource(res);
if (text->_textMem) {
FrameHeader frame;
frame.read(text->_textMem);
text->_textSprite.x = 320 - frame.width / 2;
text->_textSprite.y = 440 - frame.height;
text->_textSprite.w = frame.width;
text->_textSprite.h = frame.height;
text->_textSprite.type = RDSPR_DISPLAYALIGN | RDSPR_NOCOMPRESSION;
text->_textSprite.data = text->_textMem + FrameHeader::size();
text->_textSprite.isText = true;
_vm->_screen->createSurface(&text->_textSprite, &_textSurface);
_textX = 320 - text->_textSprite.w / 2;
_textY = 420 - text->_textSprite.h;
}
}
void MoviePlayer::closeTextObject(uint32 index, Graphics::Surface *screen, uint16 pitch) {
if (index < _numMovieTexts) {
MovieText *text = &_movieTexts[index];
free(text->_textMem);
text->_textMem = nullptr;
if (_textSurface) {
if (screen) {
// If the frame doesn't cover the entire
// screen, we have to erase the subtitles
// manually.
int frameWidth = _decoder->getWidth();
int frameHeight = _decoder->getHeight();
if (_decoderType == kVideoDecoderPSX)
frameHeight *= 2;
int frameX = (_system->getWidth() - frameWidth) / 2;
int frameY = (_system->getHeight() - frameHeight) / 2;
uint32 black = getBlackColor();
for (int y = 0; y < text->_textSprite.h; y++) {
if (_textY + y < frameY || _textY + y >= frameY + frameHeight) {
screen->hLine(_textX, _textY + y, _textX + text->_textSprite.w, black);
} else {
if (frameX > _textX)
screen->hLine(_textX, _textY + y, frameX, black);
if (frameX + frameWidth < _textX + text->_textSprite.w)
screen->hLine(frameX + frameWidth, _textY + y, text->_textSprite.w, black);
}
}
}
_vm->_screen->deleteSurface(_textSurface);
_textSurface = nullptr;
}
}
}
#define PUT_PIXEL(c) \
switch (screen->format.bytesPerPixel) { \
case 1: \
*dst = (c); \
break; \
case 2: \
WRITE_UINT16(dst, (c)); \
break; \
case 4: \
WRITE_UINT32(dst, (c)); \
break; \
default: \
break; \
}
void MoviePlayer::drawTextObject(uint32 index, Graphics::Surface *screen, uint16 pitch) {
MovieText *text = &_movieTexts[index];
uint32 white = getWhiteColor();
uint32 black = getBlackColor();
if (text->_textMem && _textSurface) {
byte *src = text->_textSprite.data;
uint16 width = text->_textSprite.w;
uint16 height = text->_textSprite.h;
// Resize text sprites for PSX version
byte *psxSpriteBuffer = 0;
if (Sword2Engine::isPsx()) {
height *= 2;
psxSpriteBuffer = (byte *)malloc(width * height);
Screen::resizePsxSprite(psxSpriteBuffer, src, width, height);
src = psxSpriteBuffer;
}
for (int y = 0; y < height; y++) {
byte *dst = (byte *)screen->getBasePtr(_textX, _textY + y);
for (int x = 0; x < width; x++) {
if (src[x] == 1) {
PUT_PIXEL(black);
} else if (src[x] == 255) {
PUT_PIXEL(white);
}
dst += screen->format.bytesPerPixel;
}
src += width;
}
// Free buffer used to resize psx sprite
if (Sword2Engine::isPsx())
free(psxSpriteBuffer);
}
}
#undef PUT_PIXEL
void MoviePlayer::performPostProcessing(Graphics::Surface *screen, uint16 pitch) {
MovieText *text;
int frame = _decoder->getCurFrame();
if (_currentMovieText < _numMovieTexts) {
text = &_movieTexts[_currentMovieText];
} else {
text = nullptr;
}
if (text && frame == text->_startFrame) {
if ((_vm->getSubtitles() || !text->_speechId) && _currentMovieText < _numMovieTexts) {
openTextObject(_currentMovieText);
}
}
if (text && frame >= text->_startFrame) {
if (text->_speechId && !text->_played && _vm->_sound->amISpeaking() == RDSE_QUIET) {
text->_played = true;
_vm->_sound->playCompSpeech(text->_speechId, 16, 0);
}
if (frame < text->_endFrame) {
drawTextObject(_currentMovieText, screen, pitch);
} else {
closeTextObject(_currentMovieText, screen, pitch);
_currentMovieText++;
}
}
if (_leadOut && _decoder->getCurFrame() == _leadOutFrame) {
_vm->_sound->playMovieSound(_leadOut, kLeadOutSound);
}
}
bool MoviePlayer::playVideo() {
uint16 x = (g_system->getWidth() - _decoder->getWidth()) / 2;
uint16 y = (g_system->getHeight() - _decoder->getHeight()) / 2;
while (!_vm->shouldQuit() && !_decoder->endOfVideo()) {
if (_decoder->needsUpdate()) {
const Graphics::Surface *frame = _decoder->decodeNextFrame();
if (frame) {
if (_decoderType == kVideoDecoderPSX)
drawFramePSX(frame);
else
_vm->_system->copyRectToScreen(frame->getPixels(), frame->pitch, x, y, frame->w, frame->h);
}
if (_decoder->hasDirtyPalette()) {
_vm->_system->getPaletteManager()->setPalette(_decoder->getPalette(), 0, 256);
uint32 maxWeight = 0;
uint32 minWeight = 0xFFFFFFFF;
uint32 weight;
byte r, g, b;
const byte *palette = _decoder->getPalette();
for (int i = 0; i < 256; i++) {
r = *palette++;
g = *palette++;
b = *palette++;
weight = 3 * r * r + 6 * g * g + 2 * b * b;
if (weight >= maxWeight) {
maxWeight = weight;
_white = i;
}
if (weight <= minWeight) {
minWeight = weight;
_black = i;
}
}
}
Graphics::Surface *screen = _vm->_system->lockScreen();
performPostProcessing(screen, screen->pitch);
_vm->_system->unlockScreen();
_vm->_system->updateScreen();
}
Common::Event event;
while (_vm->_system->getEventManager()->pollEvent(event))
if ((event.type == Common::EVENT_KEYDOWN && event.kbd.keycode == Common::KEYCODE_ESCAPE) || event.type == Common::EVENT_LBUTTONUP)
return false;
_vm->_system->delayMillis(10);
}
return !_vm->shouldQuit();
}
uint32 MoviePlayer::getBlackColor() {
return _modeChange ? g_system->getScreenFormat().RGBToColor(0x00, 0x00, 0x00) : _black;
}
uint32 MoviePlayer::getWhiteColor() {
return _modeChange ? g_system->getScreenFormat().RGBToColor(0xFF, 0xFF, 0xFF) : _white;
}
void MoviePlayer::drawFramePSX(const Graphics::Surface *frame) {
// The PSX videos have half resolution
Graphics::Surface scaledFrame;
scaledFrame.create(frame->w, frame->h * 2, frame->format);
for (int y = 0; y < scaledFrame.h; y++)
memcpy(scaledFrame.getBasePtr(0, y), frame->getBasePtr(0, y / 2), scaledFrame.w * scaledFrame.format.bytesPerPixel);
uint16 x = (g_system->getWidth() - scaledFrame.w) / 2;
uint16 y = (g_system->getHeight() - scaledFrame.h) / 2;
_vm->_system->copyRectToScreen(scaledFrame.getPixels(), scaledFrame.pitch, x, y, scaledFrame.w, scaledFrame.h);
scaledFrame.free();
}
///////////////////////////////////////////////////////////////////////////////
// Factory function for creating the appropriate cutscene player
///////////////////////////////////////////////////////////////////////////////
MoviePlayer *makeMoviePlayer(const char *name, Sword2Engine *vm, OSystem *system, uint32 frameCount) {
// This happens when quitting during the "eye" cutscene.
if (vm->shouldQuit())
return nullptr;
Common::String filename;
filename = Common::String::format("%s.str", name);
if (Common::File::exists(Common::Path(filename))) {
Video::VideoDecoder *psxDecoder = new Video::PSXStreamDecoder(Video::PSXStreamDecoder::kCD2x, frameCount);
return new MoviePlayer(vm, system, psxDecoder, kVideoDecoderPSX);
}
filename = Common::String::format("%s.smk", name);
if (Common::File::exists(Common::Path(filename))) {
Video::SmackerDecoder *smkDecoder = new Video::SmackerDecoder();
return new MoviePlayer(vm, system, smkDecoder, kVideoDecoderSMK);
}
filename = Common::String::format("%s.dxa", name);
if (Common::File::exists(Common::Path(filename))) {
Video::DXADecoder *dxaDecoder = new Video::DXADecoder();
return new MoviePlayer(vm, system, dxaDecoder, kVideoDecoderDXA);
}
// Old MPEG2 cutscenes
filename = Common::String::format("%s.mp2", name);
if (Common::File::exists(Common::Path(filename))) {
#ifdef USE_MPEG2
// HACK: Old ScummVM builds ignored the AVI frame rate field and forced the video
// to be played back at 12fps.
Video::AVIDecoder *aviDecoder = new Video::AVIDecoder(12);
return new MoviePlayer(vm, system, aviDecoder, kVideoDecoderMP2);
#else
GUI::MessageDialog dialog(_("MPEG-2 cutscenes found but ScummVM has been built without MPEG-2 support"));
dialog.runModal();
return nullptr;
#endif
}
// The demo tries to play some cutscenes that aren't there, so make those warnings more discreet.
// In addition, some of the later re-releases of the game don't have the "eye" Virgin logo movie.
if (!vm->_logic->readVar(DEMO) && strcmp(name, "eye") != 0) {
Common::U32String buf = Common::U32String::format(_("Cutscene '%s' not found"), name);
GUI::MessageDialog dialog(buf);
dialog.runModal();
} else
warning("Cutscene '%s' not found", name);
return nullptr;
}
} // End of namespace Sword2

103
engines/sword2/animation.h Normal file
View File

@@ -0,0 +1,103 @@
/* 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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 SWORD2_ANIMATION_H
#define SWORD2_ANIMATION_H
#include "sword2/screen.h"
namespace Graphics {
struct Surface;
}
namespace Video {
class VideoDecoder;
}
namespace Sword2 {
enum DecoderType {
kVideoDecoderDXA = 0,
kVideoDecoderSMK = 1,
kVideoDecoderPSX = 2,
kVideoDecoderMP2 = 3
};
struct MovieText {
uint16 _startFrame;
uint16 _endFrame;
uint32 _textNumber;
byte *_textMem;
SpriteInfo _textSprite;
uint16 _speechId;
bool _played;
void reset() {
_textMem = nullptr;
_speechId = 0;
_played = false;
}
};
class MoviePlayer {
public:
MoviePlayer(Sword2Engine *vm, OSystem *system, Video::VideoDecoder *decoder, DecoderType decoderType);
virtual ~MoviePlayer();
Common::Error load(const char *name);
void play(MovieText *movieTexts, uint32 numMovieTexts, uint32 leadIn, uint32 leadOut);
protected:
Sword2Engine *_vm;
OSystem *_system;
MovieText *_movieTexts;
uint32 _numMovieTexts;
uint32 _currentMovieText;
byte *_textSurface;
int _textX, _textY;
byte _white, _black;
DecoderType _decoderType;
bool _modeChange;
Video::VideoDecoder *_decoder;
uint32 _leadOut;
int _leadOutFrame;
void performPostProcessing(Graphics::Surface *screen, uint16 pitch);
bool playVideo();
void drawFramePSX(const Graphics::Surface *frame);
void openTextObject(uint32 index);
void closeTextObject(uint32 index, Graphics::Surface *screen, uint16 pitch);
void drawTextObject(uint32 index, Graphics::Surface *screen, uint16 pitch);
uint32 getBlackColor();
uint32 getWhiteColor();
};
MoviePlayer *makeMoviePlayer(const char *name, Sword2Engine *vm, OSystem *system, uint32 frameCount);
} // End of namespace Sword2
#endif

176
engines/sword2/anims.cpp Normal file
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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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/>.
*/
// ---------------------------------------------------------------------------
// A more intelligent version of the old ANIMS.C
// All this stuff by James
// DON'T TOUCH!
// ---------------------------------------------------------------------------
#include "common/file.h"
#include "sword2/sword2.h"
#include "sword2/defs.h"
#include "sword2/header.h"
#include "sword2/screen.h"
#include "sword2/interpreter.h"
#include "sword2/logic.h"
#include "sword2/maketext.h"
#include "sword2/resman.h"
#include "sword2/router.h"
#include "sword2/sound.h"
#include "sword2/animation.h"
namespace Sword2 {
int Router::doAnimate(byte *ob_logic, byte *ob_graph, int32 animRes, bool reverse) {
AnimHeader anim_head;
byte *anim_file;
ObjectLogic obLogic(ob_logic);
ObjectGraphic obGraph(ob_graph);
if (obLogic.getLooping() == 0) {
// This is the start of the anim - set up the first frame
// For testing all anims!
// A script loop can send every resource number to the anim
// function & it will only run the valid ones. See
// 'testing_routines' object in George's Player Character
// section of linc
if (_vm->_logic->readVar(SYSTEM_TESTING_ANIMS)) {
if (!_vm->_resman->checkValid(animRes)) {
// Not a valid resource number. Switch off
// the sprite. Don't animate - just continue
// script next cycle.
setSpriteStatus(ob_graph, NO_SPRITE);
return IR_STOP;
}
// if it's not an animation file
if (_vm->_resman->fetchType(animRes) != ANIMATION_FILE) {
// switch off the sprite
// don't animate - just continue
// script next cycle
setSpriteStatus(ob_graph, NO_SPRITE);
return IR_STOP;
}
// switch on the sprite
setSpriteStatus(ob_graph, SORT_SPRITE);
}
assert(animRes);
// open anim file
anim_file = _vm->_resman->openResource(animRes);
assert(_vm->_resman->fetchType(animRes) == ANIMATION_FILE);
// point to anim header
anim_head.read(_vm->fetchAnimHeader(anim_file));
// now running an anim, looping back to this call again
obLogic.setLooping(1);
obGraph.setAnimResource(animRes);
if (reverse)
obGraph.setAnimPc(anim_head.noAnimFrames - 1);
else
obGraph.setAnimPc(0);
} else if (_vm->_logic->getSync() != -1) {
// We've received a sync - return to script immediately
debug(5, "**sync stopped %d**", _vm->_logic->readVar(ID));
// If sync received, anim finishes right now (remaining on
// last frame). Quit animation, but continue script.
obLogic.setLooping(0);
return IR_CONT;
} else {
// Not first frame, and no sync received - set up the next
// frame of the anim.
// open anim file and point to anim header
anim_file = _vm->_resman->openResource(obGraph.getAnimResource());
anim_head.read(_vm->fetchAnimHeader(anim_file));
if (reverse)
obGraph.setAnimPc(obGraph.getAnimPc() - 1);
else
obGraph.setAnimPc(obGraph.getAnimPc() + 1);
}
// check for end of anim
if (reverse) {
if (obGraph.getAnimPc() == 0)
obLogic.setLooping(0);
} else {
if (obGraph.getAnimPc() == anim_head.noAnimFrames - 1)
obLogic.setLooping(0);
}
// close the anim file
_vm->_resman->closeResource(obGraph.getAnimResource());
// check if we want the script to loop back & call this function again
return obLogic.getLooping() ? IR_REPEAT : IR_STOP;
}
int Router::megaTableAnimate(byte *ob_logic, byte *ob_graph, byte *ob_mega, byte *animTable, bool reverse) {
int32 animRes = 0;
// If this is the start of the anim, read the anim table to get the
// appropriate anim resource
ObjectLogic obLogic(ob_logic);
if (obLogic.getLooping() == 0) {
ObjectMega obMega(ob_mega);
// Appropriate anim resource is in 'table[direction]'
animRes = READ_LE_UINT32(animTable + 4 * obMega.getCurDir());
}
return doAnimate(ob_logic, ob_graph, animRes, reverse);
}
void Router::setSpriteStatus(byte *ob_graph, uint32 type) {
ObjectGraphic obGraph(ob_graph);
// Remove the previous status, but don't affect the shading upper-word
obGraph.setType((obGraph.getType() & 0xffff0000) | type);
}
void Router::setSpriteShading(byte *ob_graph, uint32 type) {
ObjectGraphic obGraph(ob_graph);
// Remove the previous shading, but don't affect the status lower-word.
// Note that mega frames may still be shaded automatically, even when
// not sent 'RDSPR_SHADOW'.
obGraph.setType((obGraph.getType() & 0x0000ffff) | type);
}
} // End of namespace Sword2

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 sword2 "Broken Sword II" yes "" "" "highres" "mpeg2"

803
engines/sword2/console.cpp Normal file
View File

@@ -0,0 +1,803 @@
/* 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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/memstream.h"
#include "common/rect.h"
#include "common/system.h"
#include "sword2/sword2.h"
#include "sword2/defs.h"
#include "sword2/header.h"
#include "sword2/console.h"
#include "sword2/logic.h"
#include "sword2/maketext.h"
#include "sword2/memory.h"
#include "sword2/mouse.h"
#include "sword2/resman.h"
#include "sword2/screen.h"
#include "sword2/sound.h"
namespace Sword2 {
Debugger::Debugger(Sword2Engine *vm)
: GUI::Debugger() {
_vm = vm;
memset(_debugTextBlocks, 0, sizeof(_debugTextBlocks));
memset(_showVar, 0, sizeof(_showVar));
_displayDebugText = false; // "INFO"
_displayWalkGrid = false; // "WALKGRID"
_displayMouseMarker = false; // "MOUSE"
_displayTime = false; // "TIME"
_displayPlayerMarker = false; // "PLAYER"
_displayTextNumbers = false; // "TEXT"
_definingRectangles = false; // "RECT"
_draggingRectangle = 0; // 0 = waiting to start new rect
// 1 = currently dragging a rectangle
_rectX1 = _rectY1 = 0;
_rectX2 = _rectY2 = 0;
_rectFlicker = false;
_testingSnR = false; // "SAVEREST" - for system to kill all
// object resources (except player) in
// fnAddHuman()
_speechScriptWaiting = 0; // The id of whoever we're waiting for
// in a speech script. See fnTheyDo(),
// fnTheyDoWeWait(), fnWeWait(), and
// fnTimedWait().
_startTime = 0; // "TIMEON" & "TIMEOFF" - system start
// time
_textNumber = 0; // Current system text line number
_graphNoFrames = 0; // No. of frames in currently displayed
// anim
// Register commands
registerCmd("continue", WRAP_METHOD(Debugger, cmdExit));
registerCmd("q", WRAP_METHOD(Debugger, cmdExit));
registerCmd("mem", WRAP_METHOD(Debugger, Cmd_Mem));
registerCmd("tony", WRAP_METHOD(Debugger, Cmd_Tony));
registerCmd("res", WRAP_METHOD(Debugger, Cmd_Res));
registerCmd("reslist", WRAP_METHOD(Debugger, Cmd_ResList));
registerCmd("starts", WRAP_METHOD(Debugger, Cmd_Starts));
registerCmd("start", WRAP_METHOD(Debugger, Cmd_Start));
registerCmd("s", WRAP_METHOD(Debugger, Cmd_Start));
registerCmd("info", WRAP_METHOD(Debugger, Cmd_Info));
registerCmd("walkgrid", WRAP_METHOD(Debugger, Cmd_WalkGrid));
registerCmd("mouse", WRAP_METHOD(Debugger, Cmd_Mouse));
registerCmd("player", WRAP_METHOD(Debugger, Cmd_Player));
registerCmd("reslook", WRAP_METHOD(Debugger, Cmd_ResLook));
registerCmd("cur", WRAP_METHOD(Debugger, Cmd_CurrentInfo));
registerCmd("runlist", WRAP_METHOD(Debugger, Cmd_RunList));
registerCmd("kill", WRAP_METHOD(Debugger, Cmd_Kill));
registerCmd("nuke", WRAP_METHOD(Debugger, Cmd_Nuke));
registerCmd("var", WRAP_METHOD(Debugger, Cmd_Var));
registerCmd("rect", WRAP_METHOD(Debugger, Cmd_Rect));
registerCmd("clear", WRAP_METHOD(Debugger, Cmd_Clear));
registerCmd("debugon", WRAP_METHOD(Debugger, Cmd_DebugOn));
registerCmd("debugoff", WRAP_METHOD(Debugger, Cmd_DebugOff));
registerCmd("saverest", WRAP_METHOD(Debugger, Cmd_SaveRest));
registerCmd("timeon", WRAP_METHOD(Debugger, Cmd_TimeOn));
registerCmd("timeoff", WRAP_METHOD(Debugger, Cmd_TimeOff));
registerCmd("text", WRAP_METHOD(Debugger, Cmd_Text));
registerCmd("showvar", WRAP_METHOD(Debugger, Cmd_ShowVar));
registerCmd("hidevar", WRAP_METHOD(Debugger, Cmd_HideVar));
registerCmd("version", WRAP_METHOD(Debugger, Cmd_Version));
registerCmd("animtest", WRAP_METHOD(Debugger, Cmd_AnimTest));
registerCmd("texttest", WRAP_METHOD(Debugger, Cmd_TextTest));
registerCmd("linetest", WRAP_METHOD(Debugger, Cmd_LineTest));
registerCmd("events", WRAP_METHOD(Debugger, Cmd_Events));
registerCmd("sfx", WRAP_METHOD(Debugger, Cmd_Sfx));
registerCmd("english", WRAP_METHOD(Debugger, Cmd_English));
registerCmd("finnish", WRAP_METHOD(Debugger, Cmd_Finnish));
registerCmd("polish", WRAP_METHOD(Debugger, Cmd_Polish));
registerCmd("fxq", WRAP_METHOD(Debugger, Cmd_FxQueue));
}
void Debugger::varGet(int var) {
debugPrintf("%d\n", _vm->_logic->readVar(var));
}
void Debugger::varSet(int var, int val) {
debugPrintf("was %d, ", _vm->_logic->readVar(var));
_vm->_logic->writeVar(var, val);
debugPrintf("now %d\n", _vm->_logic->readVar(var));
}
void Debugger::preEnter() {
// Pause sound output
if (_vm->_sound) {
_vm->_sound->pauseFx();
_vm->_sound->pauseSpeech();
_vm->_sound->pauseMusic();
}
}
void Debugger::postEnter() {
if (_vm->_sound) {
// Resume previous sound state
_vm->_sound->unpauseFx();
_vm->_sound->unpauseSpeech();
_vm->_sound->unpauseMusic();
}
if (_vm->_mouse) {
// Restore old mouse cursor
_vm->_mouse->drawMouse();
}
}
// Now the fun stuff: Commands
static int compare_blocks(const void *p1, const void *p2) {
const MemBlock *m1 = *(const MemBlock * const *)p1;
const MemBlock *m2 = *(const MemBlock * const *)p2;
if (m1->size < m2->size)
return 1;
if (m1->size > m2->size)
return -1;
return 0;
}
bool Debugger::Cmd_Mem(int argc, const char **argv) {
int16 numBlocks = _vm->_memory->getNumBlocks();
MemBlock *memBlocks = _vm->_memory->getMemBlocks();
MemBlock **blocks = (MemBlock **)malloc(numBlocks * sizeof(MemBlock *));
int i, j;
for (i = 0, j = 0; i < MAX_MEMORY_BLOCKS; i++) {
if (memBlocks[i].ptr)
blocks[j++] = &memBlocks[i];
}
qsort(blocks, numBlocks, sizeof(MemBlock *), compare_blocks);
debugPrintf(" size id res type name\n");
debugPrintf("---------------------------------------------------------------------------\n");
for (i = 0; i < numBlocks; i++) {
const char *type;
switch (_vm->_resman->fetchType(blocks[i]->ptr)) {
case ANIMATION_FILE:
type = "ANIMATION_FILE";
break;
case SCREEN_FILE:
type = "SCREEN_FILE";
break;
case GAME_OBJECT:
type = "GAME_OBJECT";
break;
case WALK_GRID_FILE:
type = "WALK_GRID_FILE";
break;
case GLOBAL_VAR_FILE:
type = "GLOBAL_VAR_FILE";
break;
case PARALLAX_FILE_null:
type = "PARALLAX_FILE_null";
break;
case RUN_LIST:
type = "RUN_LIST";
break;
case TEXT_FILE:
type = "TEXT_FILE";
break;
case SCREEN_MANAGER:
type = "SCREEN_MANAGER";
break;
case MOUSE_FILE:
type = "MOUSE_FILE";
break;
case WAV_FILE:
type = "WAV_FILE";
break;
case ICON_FILE:
type = "ICON_FILE";
break;
case PALETTE_FILE:
type = "PALETTE_FILE";
break;
default:
type = "<unknown>";
break;
}
debugPrintf("%9d %-3d %-4d %-20s %s\n",
blocks[i]->size, blocks[i]->id, blocks[i]->uid,
type, _vm->_resman->fetchName(blocks[i]->ptr));
}
free(blocks);
debugPrintf("---------------------------------------------------------------------------\n");
debugPrintf("%9d\n", _vm->_memory->getTotAlloc());
return true;
}
bool Debugger::Cmd_Tony(int argc, const char **argv) {
debugPrintf("What about him?\n");
return true;
}
bool Debugger::Cmd_Res(int argc, const char **argv) {
uint32 numClusters = _vm->_resman->getNumClusters();
if (!numClusters) {
debugPrintf("Argh! No resources!\n");
return true;
}
ResourceFile *resFiles = _vm->_resman->getResFiles();
for (uint i = 0; i < numClusters; i++) {
const char *locStr[3] = { "HDD", "CD1", "CD2" };
debugPrintf("%-20s %s\n", resFiles[i].fileName, locStr[resFiles[i].cd]);
}
debugPrintf("%d resources\n", _vm->_resman->getNumResFiles());
return true;
}
bool Debugger::Cmd_ResList(int argc, const char **argv) {
// By default, list only resources that are being held open.
uint32 minCount = 1;
if (argc > 1)
minCount = atoi(argv[1]);
uint32 numResFiles = _vm->_resman->getNumResFiles();
Resource *resList = _vm->_resman->getResList();
for (uint i = 0; i < numResFiles; i++) {
if (resList[i].ptr && resList[i].refCount >= minCount) {
debugPrintf("%-4d: %-35s refCount: %-3d\n", i, _vm->_resman->fetchName(resList[i].ptr), resList[i].refCount);
}
}
return true;
}
bool Debugger::Cmd_Starts(int argc, const char **argv) {
uint32 numStarts = _vm->getNumStarts();
if (!numStarts) {
debugPrintf("Sorry - no startup positions registered?\n");
uint32 numScreenManagers = _vm->getNumScreenManagers();
if (!numScreenManagers)
debugPrintf("There is a problem with startup.inf\n");
else
debugPrintf(" (%d screen managers found in startup.inf)\n", numScreenManagers);
return true;
}
StartUp *startList = _vm->getStartList();
for (uint i = 0; i < numStarts; i++)
debugPrintf("%d (%s)\n", i, startList[i].description);
return true;
}
bool Debugger::Cmd_Start(int argc, const char **argv) {
uint8 pal[3] = { 255, 255, 255 };
if (argc != 2) {
debugPrintf("Usage: %s number\n", argv[0]);
return true;
}
uint32 numStarts = _vm->getNumStarts();
if (!numStarts) {
debugPrintf("Sorry - there are no startups!\n");
return true;
}
int start = atoi(argv[1]);
if (start < 0 || start >= (int)numStarts) {
debugPrintf("Not a legal start position\n");
return true;
}
debugPrintf("Running start %d\n", start);
_vm->runStart(start);
_vm->_screen->setPalette(187, 1, pal, RDPAL_INSTANT);
return true;
}
bool Debugger::Cmd_Info(int argc, const char **argv) {
_displayDebugText = !_displayDebugText;
if (_displayDebugText)
debugPrintf("Info text on\n");
else
debugPrintf("Info Text off\n");
return true;
}
bool Debugger::Cmd_WalkGrid(int argc, const char **argv) {
_displayWalkGrid = !_displayWalkGrid;
if (_displayWalkGrid)
debugPrintf("Walk-grid display on\n");
else
debugPrintf("Walk-grid display off\n");
return true;
}
bool Debugger::Cmd_Mouse(int argc, const char **argv) {
_displayMouseMarker = !_displayMouseMarker;
if (_displayMouseMarker)
debugPrintf("Mouse marker on\n");
else
debugPrintf("Mouse marker off\n");
return true;
}
bool Debugger::Cmd_Player(int argc, const char **argv) {
_displayPlayerMarker = !_displayPlayerMarker;
if (_displayPlayerMarker)
debugPrintf("Player feet marker on\n");
else
debugPrintf("Player feet marker off\n");
return true;
}
bool Debugger::Cmd_ResLook(int argc, const char **argv) {
if (argc != 2) {
debugPrintf("Usage: %s number\n", argv[0]);
return true;
}
int res = atoi(argv[1]);
uint32 numResFiles = _vm->_resman->getNumResFiles();
if (res < 0 || res >= (int)numResFiles) {
debugPrintf("Illegal resource %d. There are %d resources, 0-%d.\n",
res, numResFiles, numResFiles - 1);
return true;
}
if (!_vm->_resman->checkValid(res)) {
debugPrintf("%d is a null & void resource number\n", res);
return true;
}
// Open up the resource and take a look inside!
uint8 type = _vm->_resman->fetchType(res);
switch (type) {
case ANIMATION_FILE:
debugPrintf("<anim> %s\n", _vm->_resman->fetchName(res));
break;
case SCREEN_FILE:
debugPrintf("<layer> %s\n", _vm->_resman->fetchName(res));
break;
case GAME_OBJECT:
debugPrintf("<game object> %s\n", _vm->_resman->fetchName(res));
break;
case WALK_GRID_FILE:
debugPrintf("<walk grid> %s\n", _vm->_resman->fetchName(res));
break;
case GLOBAL_VAR_FILE:
debugPrintf("<global variables> %s\n", _vm->_resman->fetchName(res));
break;
case PARALLAX_FILE_null:
debugPrintf("<parallax file NOT USED!> %s\n", _vm->_resman->fetchName(res));
break;
case RUN_LIST:
debugPrintf("<run list> %s\n", _vm->_resman->fetchName(res));
break;
case TEXT_FILE:
debugPrintf("<text file> %s\n", _vm->_resman->fetchName(res));
break;
case SCREEN_MANAGER:
debugPrintf("<screen manager> %s\n", _vm->_resman->fetchName(res));
break;
case MOUSE_FILE:
debugPrintf("<mouse pointer> %s\n", _vm->_resman->fetchName(res));
break;
case ICON_FILE:
debugPrintf("<menu icon> %s\n", _vm->_resman->fetchName(res));
break;
default:
debugPrintf("unrecognized fileType %d\n", type);
break;
}
return true;
}
bool Debugger::Cmd_CurrentInfo(int argc, const char **argv) {
// prints general stuff about the screen, etc.
ScreenInfo *screenInfo = _vm->_screen->getScreenInfo();
if (screenInfo->background_layer_id) {
debugPrintf("background layer id %d\n", screenInfo->background_layer_id);
debugPrintf("%d wide, %d high\n", screenInfo->screen_wide, screenInfo->screen_deep);
debugPrintf("%d normal layers\n", screenInfo->number_of_layers);
Cmd_RunList(argc, argv);
} else
debugPrintf("No screen\n");
return true;
}
bool Debugger::Cmd_RunList(int argc, const char **argv) {
uint32 runList = _vm->_logic->getRunList();
if (runList) {
Common::MemoryReadStream readS(_vm->_resman->openResource(runList), _vm->_resman->fetchLen(runList));
readS.seek(ResHeader::size());
debugPrintf("Runlist number %d\n", runList);
while (1) {
uint32 res = readS.readUint32LE();
if (!res)
break;
debugPrintf("%d %s\n", res, _vm->_resman->fetchName(res));
}
_vm->_resman->closeResource(runList);
} else
debugPrintf("No run list set\n");
return true;
}
bool Debugger::Cmd_Kill(int argc, const char **argv) {
if (argc != 2) {
debugPrintf("Usage: %s number\n", argv[0]);
return true;
}
int res = atoi(argv[1]);
uint32 numResFiles = _vm->_resman->getNumResFiles();
if (res < 0 || res >= (int)numResFiles) {
debugPrintf("Illegal resource %d. There are %d resources, 0-%d.\n",
res, numResFiles, numResFiles - 1);
return true;
}
Resource *resList = _vm->_resman->getResList();
if (!resList[res].ptr) {
debugPrintf("Resource %d is not in memory\n", res);
return true;
}
if (resList[res].refCount) {
debugPrintf("Resource %d is open - cannot remove\n", res);
return true;
}
_vm->_resman->remove(res);
debugPrintf("Trashed %d\n", res);
return true;
}
bool Debugger::Cmd_Nuke(int argc, const char **argv) {
debugPrintf("Killing all resources except variable file and player object\n");
_vm->_resman->killAll(true);
return true;
}
bool Debugger::Cmd_Var(int argc, const char **argv) {
switch (argc) {
case 2:
varGet(atoi(argv[1]));
break;
case 3:
varSet(atoi(argv[1]), atoi(argv[2]));
break;
default:
debugPrintf("Usage: %s number value\n", argv[0]);
break;
}
return true;
}
bool Debugger::Cmd_Rect(int argc, const char **argv) {
uint32 filter = _vm->setInputEventFilter(0);
_definingRectangles = !_definingRectangles;
if (_definingRectangles) {
_vm->setInputEventFilter(filter & ~(RD_LEFTBUTTONUP | RD_RIGHTBUTTONUP));
debugPrintf("Mouse rectangles enabled\n");
} else {
_vm->setInputEventFilter(filter | RD_LEFTBUTTONUP | RD_RIGHTBUTTONUP);
debugPrintf("Mouse rectangles disabled\n");
}
_draggingRectangle = 0;
return true;
}
bool Debugger::Cmd_Clear(int argc, const char **argv) {
_vm->_resman->killAllObjects(true);
return true;
}
bool Debugger::Cmd_DebugOn(int argc, const char **argv) {
_displayDebugText = true;
_displayWalkGrid = true;
_displayMouseMarker = true;
_displayPlayerMarker = true;
_displayTextNumbers = true;
debugPrintf("Enabled all on-screen debug info\n");
return true;
}
bool Debugger::Cmd_DebugOff(int argc, const char **argv) {
_displayDebugText = false;
_displayWalkGrid = false;
_displayMouseMarker = false;
_displayPlayerMarker = false;
_displayTextNumbers = false;
debugPrintf("Disabled all on-screen debug info\n");
return true;
}
bool Debugger::Cmd_SaveRest(int argc, const char **argv) {
_testingSnR = !_testingSnR;
if (_testingSnR)
debugPrintf("Enabled S&R logic_script stability checking\n");
else
debugPrintf("Disabled S&R logic_script stability checking\n");
return true;
}
bool Debugger::Cmd_TimeOn(int argc, const char **argv) {
if (argc == 2)
_startTime = _vm->_system->getMillis() - atoi(argv[1]) * 1000;
else if (_startTime == 0)
_startTime = _vm->_system->getMillis();
_displayTime = true;
debugPrintf("Timer display on\n");
return true;
}
bool Debugger::Cmd_TimeOff(int argc, const char **argv) {
_displayTime = false;
debugPrintf("Timer display off\n");
return true;
}
bool Debugger::Cmd_Text(int argc, const char **argv) {
_displayTextNumbers = !_displayTextNumbers;
if (_displayTextNumbers)
debugPrintf("Text numbers on\n");
else
debugPrintf("Text numbers off\n");
return true;
}
bool Debugger::Cmd_ShowVar(int argc, const char **argv) {
int32 showVarNo = 0;
int32 varNo;
if (argc != 2) {
debugPrintf("Usage: %s number\n", argv[0]);
return true;
}
varNo = atoi(argv[1]);
// search for a spare slot in the watch-list, but also watch out for
// this variable already being in the list
while (showVarNo < MAX_SHOWVARS && _showVar[showVarNo] != 0 && _showVar[showVarNo] != varNo)
showVarNo++;
// if we've found a spare slot or the variable's already there
if (showVarNo < MAX_SHOWVARS) {
if (_showVar[showVarNo] == 0) {
// empty slot - add it to the list at this slot
_showVar[showVarNo] = varNo;
debugPrintf("var(%d) added to the watch-list\n", varNo);
} else
debugPrintf("var(%d) already in the watch-list!\n", varNo);
} else
debugPrintf("Sorry - no more allowed - hide one or extend the system watch-list\n");
return true;
}
bool Debugger::Cmd_HideVar(int argc, const char **argv) {
int32 showVarNo = 0;
int32 varNo;
if (argc != 2) {
debugPrintf("Usage: %s number\n", argv[0]);
return true;
}
varNo = atoi(argv[1]);
// search for 'varNo' in the watch-list
while (showVarNo < MAX_SHOWVARS && _showVar[showVarNo] != varNo)
showVarNo++;
if (showVarNo < MAX_SHOWVARS) {
// We've found 'varNo' in the list - clear this slot
_showVar[showVarNo] = 0;
debugPrintf("var(%d) removed from watch-list\n", varNo);
} else
debugPrintf("Sorry - can't find var(%d) in the list\n", varNo);
return true;
}
bool Debugger::Cmd_Version(int argc, const char **argv) {
// This function used to print more information, but nothing we
// particularly care about.
debugPrintf("\"Broken Sword II\" (c) Revolution Software 1997.\n");
return true;
}
bool Debugger::Cmd_AnimTest(int argc, const char **argv) {
if (argc != 2) {
debugPrintf("Usage: %s value\n", argv[0]);
return true;
}
// Automatically do "s 32" to run the animation testing start script
_vm->runStart(32);
// Same as typing "VAR 912 <value>" at the console
varSet(912, atoi(argv[1]));
debugPrintf("Setting flag 'system_testing_anims'\n");
return true;
}
bool Debugger::Cmd_TextTest(int argc, const char **argv) {
if (argc != 2) {
debugPrintf("Usage: %s value\n", argv[0]);
return true;
}
// Automatically do "s 33" to run the text/speech testing start script
_vm->runStart(33);
// Same as typing "VAR 1230 <value>" at the console
varSet(1230, atoi(argv[1]));
_displayTextNumbers = true;
debugPrintf("Setting flag 'system_testing_text'\n");
debugPrintf("Text numbers on\n");
return true;
}
bool Debugger::Cmd_LineTest(int argc, const char **argv) {
if (argc != 3) {
debugPrintf("Usage: %s value1 value2\n", argv[0]);
return true;
}
// Automatically do "s 33" to run the text/speech testing start script
_vm->runStart(33);
// Same as typing "VAR 1230 <value>" at the console
varSet(1230, atoi(argv[1]));
// Same as typing "VAR 1264 <value>" at the console
varSet(1264, atoi(argv[2]));
_displayTextNumbers = true;
debugPrintf("Setting flag 'system_testing_text'\n");
debugPrintf("Setting flag 'system_test_line_no'\n");
debugPrintf("Text numbers on\n");
return true;
}
bool Debugger::Cmd_Events(int argc, const char **argv) {
EventUnit *eventList = _vm->_logic->getEventList();
debugPrintf("EVENT LIST:\n");
for (uint32 i = 0; i < MAX_events; i++) {
if (eventList[i].id) {
uint32 target = eventList[i].id;
uint32 script = eventList[i].interact_id;
debugPrintf("slot %2d: id = %s (%d)\n", i, _vm->_resman->fetchName(target), target);
debugPrintf(" script = %s (%d) pos %d\n", _vm->_resman->fetchName(script / 65536), script / 65536, script % 65536);
}
}
return true;
}
bool Debugger::Cmd_Sfx(int argc, const char **argv) {
_vm->_wantSfxDebug = !_vm->_wantSfxDebug;
if (_vm->_wantSfxDebug)
debugPrintf("SFX logging activated\n");
else
debugPrintf("SFX logging deactivated\n");
return true;
}
bool Debugger::Cmd_English(int argc, const char **argv) {
_vm->initializeFontResourceFlags(DEFAULT_TEXT);
debugPrintf("Default fonts selected\n");
return true;
}
bool Debugger::Cmd_Finnish(int argc, const char **argv) {
_vm->initializeFontResourceFlags(FINNISH_TEXT);
debugPrintf("Finnish fonts selected\n");
return true;
}
bool Debugger::Cmd_Polish(int argc, const char **argv) {
_vm->initializeFontResourceFlags(POLISH_TEXT);
debugPrintf("Polish fonts selected\n");
return true;
}
bool Debugger::Cmd_FxQueue(int argc, const char **argv) {
_vm->_sound->printFxQueue();
return true;
}
} // End of namespace Sword2

129
engines/sword2/console.h Normal file
View File

@@ -0,0 +1,129 @@
/* 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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 SWORD2_CONSOLE_H
#define SWORD2_CONSOLE_H
#include "gui/debugger.h"
#include "sword2/debug.h"
namespace Sword2 {
class Debugger : public GUI::Debugger {
private:
void varGet(int var);
void varSet(int var, int val);
bool _displayDebugText;
bool _displayWalkGrid;
bool _displayMouseMarker;
bool _displayTime;
bool _displayPlayerMarker;
bool _displayTextNumbers;
bool _rectFlicker;
int32 _startTime;
int32 _showVar[MAX_SHOWVARS];
byte _debugTextBlocks[MAX_DEBUG_TEXTS];
void clearDebugTextBlocks();
void makeDebugTextBlock(char *text, int16 x, int16 y);
void plotCrossHair(int16 x, int16 y, uint8 pen);
void drawRect(int16 x, int16 y, int16 x2, int16 y2, uint8 pen);
public:
Debugger(Sword2Engine *vm);
int16 _rectX1, _rectY1;
int16 _rectX2, _rectY2;
uint8 _draggingRectangle;
bool _definingRectangles;
bool _testingSnR;
int32 _speechScriptWaiting;
int32 _textNumber;
int32 _graphType;
int32 _graphAnimRes;
int32 _graphAnimPc;
uint32 _graphNoFrames;
void buildDebugText();
void drawDebugGraphics();
private:
void preEnter() override;
void postEnter() override;
private:
Sword2Engine *_vm;
// Commands
bool Cmd_Mem(int argc, const char **argv);
bool Cmd_Tony(int argc, const char **argv);
bool Cmd_Res(int argc, const char **argv);
bool Cmd_ResList(int argc, const char **argv);
bool Cmd_Starts(int argc, const char **argv);
bool Cmd_Start(int argc, const char **argv);
bool Cmd_Info(int argc, const char **argv);
bool Cmd_WalkGrid(int argc, const char **argv);
bool Cmd_Mouse(int argc, const char **argv);
bool Cmd_Player(int argc, const char **argv);
bool Cmd_ResLook(int argc, const char **argv);
bool Cmd_CurrentInfo(int argc, const char **argv);
bool Cmd_RunList(int argc, const char **argv);
bool Cmd_Kill(int argc, const char **argv);
bool Cmd_Nuke(int argc, const char **argv);
bool Cmd_Var(int argc, const char **argv);
bool Cmd_Rect(int argc, const char **argv);
bool Cmd_Clear(int argc, const char **argv);
bool Cmd_DebugOn(int argc, const char **argv);
bool Cmd_DebugOff(int argc, const char **argv);
bool Cmd_SaveRest(int argc, const char **argv);
bool Cmd_TimeOn(int argc, const char **argv);
bool Cmd_TimeOff(int argc, const char **argv);
bool Cmd_Text(int argc, const char **argv);
bool Cmd_ShowVar(int argc, const char **argv);
bool Cmd_HideVar(int argc, const char **argv);
bool Cmd_Version(int argc, const char **argv);
bool Cmd_AnimTest(int argc, const char **argv);
bool Cmd_TextTest(int argc, const char **argv);
bool Cmd_LineTest(int argc, const char **argv);
bool Cmd_Events(int argc, const char **argv);
bool Cmd_Sfx(int argc, const char **argv);
bool Cmd_English(int argc, const char **argv);
bool Cmd_Finnish(int argc, const char **argv);
bool Cmd_Polish(int argc, const char **argv);
bool Cmd_FxQueue(int argc, const char **argv);
};
} // End of namespace Sword2
#endif

1444
engines/sword2/controls.cpp Normal file

File diff suppressed because it is too large Load Diff

186
engines/sword2/controls.h Normal file
View File

@@ -0,0 +1,186 @@
/* 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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 SWORD2_CONTROL_H
#define SWORD2_CONTROL_H
#include "sword2/defs.h"
#include "sword2/saveload.h"
#define MAX_WIDGETS 25
namespace Sword2 {
class Sword2Engine;
class FontRendererGui;
class Widget;
class Switch;
class Slider;
class Button;
class ScrollButton;
class Slot;
enum {
kSaveDialog,
kRestoreDialog
};
/**
* Base class for all dialogs.
*/
class Dialog {
private:
int _numWidgets;
Widget *_widgets[MAX_WIDGETS];
bool _finish;
int _result;
public:
Sword2Engine *_vm;
Dialog(Sword2Engine *vm);
virtual ~Dialog();
void registerWidget(Widget *widget);
virtual void paint();
virtual void setResult(int result);
virtual int runModal();
virtual void onAction(Widget *widget, int result = 0) {}
};
class OptionsDialog : public Dialog {
private:
FontRendererGui *_fr;
Widget *_panel;
Switch *_objectLabelsSwitch;
Switch *_subtitlesSwitch;
Switch *_reverseStereoSwitch;
Switch *_musicSwitch;
Switch *_speechSwitch;
Switch *_fxSwitch;
Slider *_musicSlider;
Slider *_speechSlider;
Slider *_fxSlider;
Slider *_gfxSlider;
Widget *_gfxPreview;
Button *_okButton;
Button *_cancelButton;
Audio::Mixer *_mixer;
public:
OptionsDialog(Sword2Engine *vm);
~OptionsDialog() override;
void paint() override;
void onAction(Widget *widget, int result = 0) override;
};
class SaveRestoreDialog : public Dialog {
private:
int _mode, _selectedSlot;
byte _editBuffer[SAVE_DESCRIPTION_LEN];
int _editPos, _firstPos;
int _cursorTick;
FontRendererGui *_fr1;
FontRendererGui *_fr2;
Widget *_panel;
Slot *_slotButton[8];
ScrollButton *_zupButton;
ScrollButton *_upButton;
ScrollButton *_downButton;
ScrollButton *_zdownButton;
Button *_okButton;
Button *_cancelButton;
public:
SaveRestoreDialog(Sword2Engine *vm, int mode);
~SaveRestoreDialog() override;
void updateSlots();
void drawEditBuffer(Slot *slot);
void onAction(Widget *widget, int result = 0) override;
void paint() override;
void setResult(int result) override;
int runModal() override;
};
/**
* A "mini" dialog is usually a yes/no question, but also used for the
* restart/restore dialog at the beginning of the game.
*/
class MiniDialog : public Dialog {
private:
uint32 _headerTextId;
uint32 _okTextId;
uint32 _cancelTextId;
FontRendererGui *_fr;
Widget *_panel;
Button *_okButton;
Button *_cancelButton;
public:
MiniDialog(Sword2Engine *vm, uint32 headerTextId, uint32 okTextId = TEXT_OK, uint32 cancelTextId = TEXT_CANCEL);
~MiniDialog() override;
void paint() override;
void onAction(Widget *widget, int result = 0) override;
};
class StartDialog : public MiniDialog {
public:
StartDialog(Sword2Engine *vm);
int runModal() override;
};
class RestartDialog : public MiniDialog {
public:
RestartDialog(Sword2Engine *vm);
int runModal() override;
};
class QuitDialog : public MiniDialog {
public:
QuitDialog(Sword2Engine *vm);
int runModal() override;
};
class SaveDialog : public SaveRestoreDialog {
public:
SaveDialog(Sword2Engine *vm) : SaveRestoreDialog(vm, kSaveDialog) {}
};
class RestoreDialog : public SaveRestoreDialog {
public:
RestoreDialog(Sword2Engine *vm) : SaveRestoreDialog(vm, kRestoreDialog) {}
};
} // End of namespace Sword2
#endif

View File

@@ -0,0 +1,5 @@
begin_section("Sword2");
add_person("Torbj&ouml;rn Andersson", "eriktorbjorn", "");
add_person("Fabio Battaglia", "Hkz", "PSX version support");
add_person("Jonathan Gray", "khalek", "(retired)");
end_section();

372
engines/sword2/debug.cpp Normal file
View File

@@ -0,0 +1,372 @@
/* 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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/rect.h"
#include "sword2/sword2.h"
#include "sword2/defs.h"
#include "sword2/header.h"
#include "sword2/console.h"
#include "sword2/logic.h"
#include "sword2/maketext.h"
#include "sword2/memory.h"
#include "sword2/mouse.h"
#include "sword2/resman.h"
#include "sword2/router.h"
#include "sword2/screen.h"
namespace Sword2 {
void Debugger::clearDebugTextBlocks() {
uint8 blockNo = 0;
while (blockNo < MAX_DEBUG_TEXTS && _debugTextBlocks[blockNo] > 0) {
// kill the system text block
_vm->_fontRenderer->killTextBloc(_debugTextBlocks[blockNo]);
// clear this element of our array of block numbers
_debugTextBlocks[blockNo] = 0;
blockNo++;
}
}
void Debugger::makeDebugTextBlock(char *text, int16 x, int16 y) {
uint8 blockNo = 0;
while (blockNo < MAX_DEBUG_TEXTS && _debugTextBlocks[blockNo] > 0)
blockNo++;
assert(blockNo < MAX_DEBUG_TEXTS);
_debugTextBlocks[blockNo] = _vm->_fontRenderer->buildNewBloc((byte *)text, x, y, 640 - x, 0, RDSPR_DISPLAYALIGN, CONSOLE_FONT_ID, NO_JUSTIFICATION);
}
void Debugger::buildDebugText() {
char buf[128];
int32 showVarNo; // for variable watching
int32 showVarPos;
int32 varNo;
ScreenInfo *screenInfo = _vm->_screen->getScreenInfo();
// clear the array of text block numbers for the debug text
clearDebugTextBlocks();
// mouse coords
// print mouse coords beside mouse-marker, if it's being displayed
if (_displayMouseMarker) {
int mouseX, mouseY;
_vm->_mouse->getPos(mouseX, mouseY);
Common::sprintf_s(buf, "%d,%d", mouseX + screenInfo->scroll_offset_x, mouseY + screenInfo->scroll_offset_y);
if (mouseX > 560)
makeDebugTextBlock(buf, mouseX - 50, mouseY - 15);
else
makeDebugTextBlock(buf, mouseX + 5, mouseY - 15);
}
// mouse area coords
// defining a mouse area the easy way, by creating a box on-screen
if (_draggingRectangle || _vm->_logic->readVar(SYSTEM_TESTING_ANIMS)) {
// so we can see what's behind the lines
_rectFlicker = !_rectFlicker;
Common::sprintf_s(buf, "x1=%d", _rectX1);
makeDebugTextBlock(buf, 0, 120);
Common::sprintf_s(buf, "y1=%d", _rectY1);
makeDebugTextBlock(buf, 0, 135);
Common::sprintf_s(buf, "x2=%d", _rectX2);
makeDebugTextBlock(buf, 0, 150);
Common::sprintf_s(buf, "y2=%d", _rectY2);
makeDebugTextBlock(buf, 0, 165);
}
// testingSnR indicator
if (_testingSnR) { // see fnAddHuman()
Common::sprintf_s(buf, "TESTING LOGIC STABILITY!");
makeDebugTextBlock(buf, 0, 105);
}
// debug info at top of screen - enabled/disabled as one complete unit
if (_displayTime) {
int32 time = _vm->getMillis();
if ((time - _startTime) / 1000 >= 10000)
_startTime = time;
time -= _startTime;
Common::sprintf_s(buf, "Time %.2d:%.2d:%.2d.%.3d", (time / 3600000) % 60, (time / 60000) % 60, (time / 1000) % 60, time % 1000);
makeDebugTextBlock(buf, 500, 360);
Common::sprintf_s(buf, "Game %d", _vm->_gameCycle);
makeDebugTextBlock(buf, 500, 380);
}
// current text number & speech-sample resource id
if (_displayTextNumbers) {
if (_textNumber) {
if (_vm->_logic->readVar(SYSTEM_TESTING_TEXT)) {
if (_vm->_logic->readVar(SYSTEM_WANT_PREVIOUS_LINE))
Common::sprintf_s(buf, "backwards");
else
Common::sprintf_s(buf, "forwards");
makeDebugTextBlock(buf, 0, 340);
}
Common::sprintf_s(buf, "res: %d", _textNumber / SIZE);
makeDebugTextBlock(buf, 0, 355);
Common::sprintf_s(buf, "pos: %d", _textNumber & 0xffff);
makeDebugTextBlock(buf, 0, 370);
Common::sprintf_s(buf, "TEXT: %d", _vm->_logic->_officialTextNumber);
makeDebugTextBlock(buf, 0, 385);
}
}
// resource number currently being checking for animation
if (_vm->_logic->readVar(SYSTEM_TESTING_ANIMS)) {
Common::sprintf_s(buf, "trying resource %d", _vm->_logic->readVar(SYSTEM_TESTING_ANIMS));
makeDebugTextBlock(buf, 0, 90);
}
// general debug info
if (_displayDebugText) {
/*
// CD in use
Common::sprintf_s(buf, "CD-%d", currentCD);
makeDebugTextBlock(buf, 0, 0);
*/
// mouse coords & object pointed to
if (_vm->_logic->readVar(CLICKED_ID))
Common::sprintf_s(buf, "last click at %d,%d (id %d: %s)",
_vm->_logic->readVar(MOUSE_X),
_vm->_logic->readVar(MOUSE_Y),
_vm->_logic->readVar(CLICKED_ID),
_vm->_resman->fetchName(_vm->_logic->readVar(CLICKED_ID)));
else
Common::sprintf_s(buf, "last click at %d,%d (---)",
_vm->_logic->readVar(MOUSE_X),
_vm->_logic->readVar(MOUSE_Y));
makeDebugTextBlock(buf, 0, 15);
uint32 mouseTouching = _vm->_mouse->getMouseTouching();
int mouseX, mouseY;
_vm->_mouse->getPos(mouseX, mouseY);
if (mouseTouching)
Common::sprintf_s(buf, "mouse %d,%d (id %d: %s)",
mouseX + screenInfo->scroll_offset_x,
mouseY + screenInfo->scroll_offset_y,
mouseTouching,
_vm->_resman->fetchName(mouseTouching));
else
Common::sprintf_s(buf, "mouse %d,%d (not touching)",
mouseX + screenInfo->scroll_offset_x,
mouseY + screenInfo->scroll_offset_y);
makeDebugTextBlock(buf, 0, 30);
// player coords & graphic info
// if player objct has a graphic
if (_graphAnimRes)
Common::sprintf_s(buf, "player %d,%d %s (%d) #%d/%d",
screenInfo->player_feet_x,
screenInfo->player_feet_y,
_vm->_resman->fetchName(_graphAnimRes),
_graphAnimRes,
_graphAnimPc,
_graphNoFrames);
else
Common::sprintf_s(buf, "player %d,%d --- %d",
screenInfo->player_feet_x,
screenInfo->player_feet_y,
_graphAnimPc);
makeDebugTextBlock(buf, 0, 45);
// frames-per-second counter
Common::sprintf_s(buf, "fps %d", _vm->_screen->getFps());
makeDebugTextBlock(buf, 440, 0);
// location number
Common::sprintf_s(buf, "location=%d", _vm->_logic->readVar(LOCATION));
makeDebugTextBlock(buf, 440, 15);
// "result" variable
Common::sprintf_s(buf, "result=%d", _vm->_logic->readVar(RESULT));
makeDebugTextBlock(buf, 440, 30);
// no. of events in event list
Common::sprintf_s(buf, "events=%d", _vm->_logic->countEvents());
makeDebugTextBlock(buf, 440, 45);
// sprite list usage
Common::sprintf_s(buf, "bgp0: %d/%d", _vm->_screen->getCurBgp0(), MAX_bgp0_sprites);
makeDebugTextBlock(buf, 560, 0);
Common::sprintf_s(buf, "bgp1: %d/%d", _vm->_screen->getCurBgp1(), MAX_bgp1_sprites);
makeDebugTextBlock(buf, 560, 15);
Common::sprintf_s(buf, "back: %d/%d", _vm->_screen->getCurBack(), MAX_back_sprites);
makeDebugTextBlock(buf, 560, 30);
Common::sprintf_s(buf, "sort: %d/%d", _vm->_screen->getCurSort(), MAX_sort_sprites);
makeDebugTextBlock(buf, 560, 45);
Common::sprintf_s(buf, "fore: %d/%d", _vm->_screen->getCurFore(), MAX_fore_sprites);
makeDebugTextBlock(buf, 560, 60);
Common::sprintf_s(buf, "fgp0: %d/%d", _vm->_screen->getCurFgp0(), MAX_fgp0_sprites);
makeDebugTextBlock(buf, 560, 75);
Common::sprintf_s(buf, "fgp1: %d/%d", _vm->_screen->getCurFgp1(), MAX_fgp1_sprites);
makeDebugTextBlock(buf, 560, 90);
// largest layer & sprite
// NB. Strings already constructed in Build_display.cpp
makeDebugTextBlock(_vm->_screen->getLargestLayerInfo(), 0, 60);
makeDebugTextBlock(_vm->_screen->getLargestSpriteInfo(), 0, 75);
// "waiting for person" indicator - set form fnTheyDo and
// fnTheyDoWeWait
if (_speechScriptWaiting) {
Common::sprintf_s(buf, "script waiting for %s (%d)",
_vm->_resman->fetchName(_speechScriptWaiting),
_speechScriptWaiting);
makeDebugTextBlock(buf, 0, 90);
}
// variable watch display
showVarPos = 115; // y-coord for first showVar
for (showVarNo = 0; showVarNo < MAX_SHOWVARS; showVarNo++) {
varNo = _showVar[showVarNo]; // get variable number
// if non-zero ie. cannot watch 'id' but not needed
// anyway because it changes throughout the logic loop
if (varNo) {
Common::sprintf_s(buf, "var(%d) = %d", varNo, _vm->_logic->readVar(varNo));
makeDebugTextBlock(buf, 530, showVarPos);
showVarPos += 15; // next line down
}
}
// memory indicator - this should come last, to show all the
// sprite blocks above!
uint32 totAlloc = _vm->_memory->getTotAlloc();
int16 numBlocks = _vm->_memory->getNumBlocks();
if (totAlloc < 1024)
Common::sprintf_s(buf, "%u bytes in %d memory blocks", totAlloc, numBlocks);
else if (totAlloc < 1024 * 1024)
Common::sprintf_s(buf, "%uK in %d memory blocks", totAlloc / 1024, numBlocks);
else
Common::sprintf_s(buf, "%.02fM in %d memory blocks", totAlloc / 1048576., numBlocks);
makeDebugTextBlock(buf, 0, 0);
}
}
void Debugger::drawDebugGraphics() {
ScreenInfo *screenInfo = _vm->_screen->getScreenInfo();
// walk-grid
if (_displayWalkGrid)
_vm->_logic->_router->plotWalkGrid();
// player feet coord marker
if (_displayPlayerMarker)
plotCrossHair(screenInfo->player_feet_x, screenInfo->player_feet_y, 215);
// mouse marker & coords
if (_displayMouseMarker) {
int mouseX, mouseY;
_vm->_mouse->getPos(mouseX, mouseY);
plotCrossHair(mouseX + screenInfo->scroll_offset_x, mouseY + screenInfo->scroll_offset_y, 215);
}
// mouse area rectangle / sprite box rectangle when testing anims
if (_vm->_logic->readVar(SYSTEM_TESTING_ANIMS)) {
// draw box around current frame
drawRect(_rectX1, _rectY1, _rectX2, _rectY2, 184);
} else if (_draggingRectangle) {
// defining a mouse area the easy way, by creating a box
// on-screen
if (_rectFlicker)
drawRect(_rectX1, _rectY1, _rectX2, _rectY2, 184);
}
}
void Debugger::plotCrossHair(int16 x, int16 y, uint8 pen) {
_vm->_screen->plotPoint(x, y, pen);
_vm->_screen->drawLine(x - 2, y, x - 5, y, pen);
_vm->_screen->drawLine(x + 2, y, x + 5, y, pen);
_vm->_screen->drawLine(x, y - 2, x, y - 5, pen);
_vm->_screen->drawLine(x, y + 2, x, y + 5, pen);
}
void Debugger::drawRect(int16 x1, int16 y1, int16 x2, int16 y2, uint8 pen) {
_vm->_screen->drawLine(x1, y1, x2, y1, pen); // top edge
_vm->_screen->drawLine(x1, y2, x2, y2, pen); // bottom edge
_vm->_screen->drawLine(x1, y1, x1, y2, pen); // left edge
_vm->_screen->drawLine(x2, y1, x2, y2, pen); // right edge
}
} // End of namespace Sword2

35
engines/sword2/debug.h Normal file
View File

@@ -0,0 +1,35 @@
/* 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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 SWORD2_DEBUG_H
#define SWORD2_DEBUG_H
// FIXME: I don't know how large this constant used to be
#define MAX_DEBUG_TEXTS 55
#define MAX_SHOWVARS 15
namespace Sword2 {
} // End of namespace Sword2
#endif

207
engines/sword2/defs.h Normal file
View File

@@ -0,0 +1,207 @@
/* 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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 SWORD2_DEFS_H
#define SWORD2_DEFS_H
#define SIZE 0x10000 // 65536 items per section
#define NuSIZE 0xffff // & with this
// Return codes
enum {
// Generic error codes
RD_OK = 0x00000000,
RDERR_UNKNOWN = 0x00000001,
RDERR_OUTOFMEMORY = 0x00000003,
RDERR_INVALIDFILENAME = 0x00000004,
// Drawing error codes
RDERR_DECOMPRESSION = 0x00010007,
// Sprite drawing error codes
RDERR_NOTIMPLEMENTED = 0x00060001,
RDERR_NOTCLOSED = 0x00050005,
RDERR_NOTOPEN = 0x00050006,
// Menubar error codes
RDERR_INVALIDMENU = 0x00060000,
RDERR_INVALIDPOCKET = 0x00060001,
RDERR_INVALIDCOMMAND = 0x00060002,
// Palette fading error codes
RDERR_FADEINCOMPLETE = 0x00070000,
// Sound engine error codes
RDERR_SPEECHPLAYING = 0x00080004,
RDERR_SPEECHNOTPLAYING = 0x00080005,
RDERR_INVALIDWAV = 0x00080006,
RDERR_FXALREADYOPEN = 0x00080009,
RDERR_FXNOTOPEN = 0x0008000B,
RDERR_INVALIDID = 0x0008000D
};
// Text ids for the control panel etc.
enum {
TEXT_OK = 0x08EB0000,
TEXT_CANCEL = 0x08EB0001,
TEXT_RESTORE = 0x08EB0002,
TEXT_SAVE = 0x08EB0003,
TEXT_QUIT = 0x08EB0004,
TEXT_RESTART = 0x08EB0005,
TEXT_OPTIONS = 0x08EB000A,
TEXT_SUBTITLES = 0x08EB000B,
TEXT_OBJECT_LABELS = 0x08EB000C,
TEXT_MUSIC_VOLUME = 0x08EB000E,
TEXT_SPEECH_VOLUME = 0x08EB000F,
TEXT_FX_VOLUME = 0x08EB0010,
TEXT_GFX_QUALITY = 0x08EB0011,
TEXT_REVERSE_STEREO = 0x08EB0015,
TEXT_RESTORE_CANT_OPEN = 0x0CBA017E,
TEXT_RESTORE_INCOMPATIBLE = 0x0CBA017F,
TEXT_RESTORE_FAILED = 0x0CBA0181,
TEXT_SAVE_CANT_OPEN = 0x0CBA0182,
TEXT_SAVE_FAILED = 0x0CBA0184
};
// Always 8 (George object used for Nico player character as well)
#define CUR_PLAYER_ID 8
// Global variable references
enum {
ID = 0,
RESULT = 1,
PLAYER_ACTION = 2,
// CUR_PLAYER_ID = 3,
PLAYER_ID = 305,
TALK_FLAG = 13,
MOUSE_X = 4,
MOUSE_Y = 5,
LEFT_BUTTON = 109,
RIGHT_BUTTON = 110,
CLICKED_ID = 178,
IN_SUBJECT = 6,
COMBINE_BASE = 7,
OBJECT_HELD = 14,
SPEECH_ID = 9,
INS1 = 10,
INS2 = 11,
INS3 = 12,
INS4 = 60,
INS5 = 61,
INS_COMMAND = 59,
PLAYER_FEET_X = 141,
PLAYER_FEET_Y = 142,
PLAYER_CUR_DIR = 937,
// for debug.cpp
LOCATION = 62,
// so scripts can force scroll offsets
SCROLL_X = 345,
SCROLL_Y = 346,
EXIT_CLICK_ID = 710,
EXIT_FADING = 713,
SYSTEM_TESTING_ANIMS = 912,
SYSTEM_TESTING_TEXT = 1230,
SYSTEM_WANT_PREVIOUS_LINE = 1245,
// 1=on 0=off (set in fnAddHuman and fnNoHuman)
MOUSE_AVAILABLE = 686,
// used in fnChoose
AUTO_SELECTED = 1115,
// see fnStartConversation and fnChooser
CHOOSER_COUNT_FLAG = 15,
// signifies a demo mode
DEMO = 1153,
// Indicates to script whether this is the Playstation version.
// PSXFLAG = 1173,
// for the poor PSX so it knows what language is running.
// GAME_LANGUAGE = 111,
// 1 = dead
DEAD = 1256,
// If set indicates that the speech anim is to run through only once.
SPEECHANIMFLAG = 1278,
// for the engine
SCROLL_OFFSET_X = 1314
};
// Resource IDs
enum {
// mouse mointers - It's pretty much safe to do it like this
NORMAL_MOUSE_ID = 17,
SCROLL_LEFT_MOUSE_ID = 1440,
SCROLL_RIGHT_MOUSE_ID = 1441,
// Console Font - does not use game text - only English required
CONSOLE_FONT_ID = 340,
// Speech Font
ENGLISH_SPEECH_FONT_ID = 341,
FINNISH_SPEECH_FONT_ID = 956,
POLISH_SPEECH_FONT_ID = 955,
// Control Panel Font (and un-selected savegame descriptions)
ENGLISH_CONTROLS_FONT_ID = 2005,
FINNISH_CONTROLS_FONT_ID = 959,
POLISH_CONTROLS_FONT_ID = 3686,
// Red Font (for selected savegame descriptions)
// BS2 doesn't draw selected savegames in red, so I guess this is a
// left-over from BS1
ENGLISH_RED_FONT_ID = 2005, // 1998 // Redfont
FINNISH_RED_FONT_ID = 959, // 960 // FinRedFn
POLISH_RED_FONT_ID = 3686, // 3688 // PolRedFn
// Control panel palette resource id
CONTROL_PANEL_PALETTE = 261,
// res id's of the system menu icons
OPTIONS_ICON = 344,
QUIT_ICON = 335,
SAVE_ICON = 366,
RESTORE_ICON = 364,
RESTART_ICON = 342,
// conversation exit icon, 'EXIT' menu icon (used in fnChoose)
EXIT_ICON = 65
};
#endif

View File

@@ -0,0 +1,76 @@
/* 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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/advancedDetector.h"
#include "engines/obsolete.h"
#include "sword2/detection.h"
#include "sword2/obsolete.h" // Obsolete ID table.
static const PlainGameDescriptor sword2Games[] = {
{"sword2", "Broken Sword II: The Smoking Mirror"},
{nullptr, nullptr}
};
#include "sword2/detection_tables.h"
static const char *const directoryGlobs[] = {
"clusters",
"smacks",
"sub",
"video",
"extras", // GOG.com
nullptr
};
class Sword2MetaEngineDetection : public AdvancedMetaEngineDetection<ADGameDescription> {
public:
Sword2MetaEngineDetection() : AdvancedMetaEngineDetection(Sword2::gameDescriptions, sword2Games) {
_guiOptions = GUIO3(GUIO_NOMIDI, GUIO_NOASPECT, GAMEOPTION_OBJECT_LABELS);
_maxScanDepth = 2;
_directoryGlobs = directoryGlobs;
}
PlainGameDescriptor findGame(const char *gameId) const override {
return Engines::findGameID(gameId, _gameIds, obsoleteGameIDsTable);
}
Common::Error identifyGame(DetectedGame &game, const void **descriptor) override {
Engines::upgradeTargetIfNecessary(obsoleteGameIDsTable);
return AdvancedMetaEngineDetection::identifyGame(game, descriptor);
}
const char *getName() const override {
return "sword2";
}
const char *getEngineName() const override {
return "Broken Sword II: The Smoking Mirror";
}
const char *getOriginalCopyright() const override {
return "Broken Sword II: The Smoking Mirror (C) Revolution";
}
};
REGISTER_PLUGIN_STATIC(SWORD2_DETECTION, PLUGIN_TYPE_ENGINE_DETECTION, Sword2MetaEngineDetection);

View File

@@ -0,0 +1,39 @@
/* 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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 SWORD2_DETECTION_H
#define SWORD2_DETECTION_H
#include "engines/advancedDetector.h"
namespace Sword2 {
enum {
GF_SPANISHDEMO = 1 << 0
};
#define GAMEOPTION_OBJECT_LABELS GUIO_GAMEOPTIONS1
} // End of namespace Sword2
#endif // SWORD2_DETECTION_H

View File

@@ -0,0 +1,444 @@
/* 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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/translation.h"
namespace Sword2 {
static const ADGameDescription gameDescriptions[] = {
{
"sword2",
"Demo",
AD_ENTRY2s("general.clu", "11e824864a75195652610e8b397382a6", 8030769,
"docks.clu", "b39246fbb5b955a29f9a207c69bfc318", 20262263),
Common::EN_ANY,
Common::kPlatformWindows,
ADGF_DEMO,
GUIO0()
},
{
"sword2",
"PC Gamer Demo",
AD_ENTRY2s("general.clu", "522ecd261027f0b55315a32aaef97295", 4519015,
"docks.clu", "b3583fcf8a8f02109f3f528a4f64c1e6", 21017250),
Common::EN_ANY,
Common::kPlatformWindows,
ADGF_DEMO,
GUIO0()
},
{
"sword2",
"Demo",
AD_ENTRY2s("players.clu", "5068815a62ba932afba7267bafc9786d", 9904289,
"docks.clu", "c46d1150d826fff7f343edcc1cc430f7", 20375248),
Common::ES_ESP,
Common::kPlatformWindows,
ADGF_DEMO | GF_SPANISHDEMO,
GUIO0()
},
{
"sword2",
"Demo",
AD_ENTRY2s("players.clu", "e8786804d399310bda3fcbf897bc44f7", 3085812,
"docks.clu", "14470523a50333defc82c78afdf87b6b", 5818340),
Common::EN_ANY,
Common::kPlatformPSX,
ADGF_DEMO,
GUIO0()
},
{
"sword2",
"",
AD_ENTRY4s("general.clu", "31db8564f9187538f24d9fda0677f666", 7059728,
"text.clu", "9b344d976ca8d19a1cf5aa4413397f6b", 304968,
"speech1.clu", "a403904a0e825356107d228f8f74092e", 176260048,
"docks.clu", "b39246fbb5b955a29f9a207c69bfc318", 20262263),
Common::EN_ANY,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO0()
},
{ // Korean fan translation
"sword2",
"",
AD_ENTRY6s("general.clu", "31db8564f9187538f24d9fda0677f666", 7059728,
"text.clu", "9b344d976ca8d19a1cf5aa4413397f6b", 304968,
"speech1.clu", "a403904a0e825356107d228f8f74092e", 176260048,
"docks.clu", "b39246fbb5b955a29f9a207c69bfc318", 20262263,
"bs2k.fnt", nullptr, 1222000,
"korean.clu", nullptr, AD_NO_SIZE),
Common::KO_KOR,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO0()
},
{ // GOG.com release version 2.0.0.6
"sword2",
"GOG",
AD_ENTRY5s("general.clu", "31db8564f9187538f24d9fda0677f666", 7059728,
"text.clu", "9b344d976ca8d19a1cf5aa4413397f6b", 304968,
"speech1.clu", "a403904a0e825356107d228f8f74092e", 176260048,
"docks.clu", "b39246fbb5b955a29f9a207c69bfc318", 20262263,
"eye.dxa", "7aef7fcb4faae760e82e0c7d3b336ac9", 7052599),
Common::EN_ANY,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO0()
},
{
"sword2",
"",
AD_ENTRY5s("general.clu", "31db8564f9187538f24d9fda0677f666", 7059728,
"text.clu", "9b344d976ca8d19a1cf5aa4413397f6b", 304968,
"speech1.clu", "a403904a0e825356107d228f8f74092e", 176260048,
"docks.clu", "b39246fbb5b955a29f9a207c69bfc318", 20262263,
"bs2.dat", "c8238e7d017faa3b48d98df3f42a63e6", 336246),
Common::ZH_CHN,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO0()
},
{
"sword2",
"1CD release",
AD_ENTRY3s("general.clu", "31db8564f9187538f24d9fda0677f666", 7059728,
"resource.tab", "ee4c0a8a2b8821ca113ea4176968b857", 16588,
"docks.clu", "b39246fbb5b955a29f9a207c69bfc318", 20262263),
Common::EN_ANY,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO0()
},
{
"sword2",
"EU",
AD_ENTRY4s("general.clu", "31db8564f9187538f24d9fda0677f666", 7059728,
"text.clu", "9b344d976ca8d19a1cf5aa4413397f6b", 304968,
"speech1.clg", "d49a5f3683b734d1129cbf6a0f95ae83", 57935499,
"docks.clu", "b39246fbb5b955a29f9a207c69bfc318", 20262263),
Common::EN_ANY,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO0()
},
{ // USA English, Windows 2-CD. Bugreport #15287
"sword2",
"USA 2-CD",
AD_ENTRY3s("general.clu", "11e824864a75195652610e8b397382a6", 8030769,
"text.clu", "2b3ff1803200fc155c1de09e9b2875b5", 337938,
"docks.clu", "b39246fbb5b955a29f9a207c69bfc318", 20262263),
Common::EN_USA,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO0()
},
{
"sword2",
"",
AD_ENTRY3s("general.clu", "31db8564f9187538f24d9fda0677f666", 7059728,
"text.clu", "d0cafb4d2982613ca4cf0574a0e4e079", 418165,
"docks.clu", "b39246fbb5b955a29f9a207c69bfc318", 20262263),
Common::FR_FRA,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO0()
},
{
"sword2",
"",
AD_ENTRY3s("general.clu", "31db8564f9187538f24d9fda0677f666", 7059728,
"text.clu", "5771f52410745029d7f71af05072d3d6", 556961,
"docks.clu", "b39246fbb5b955a29f9a207c69bfc318", 20262263),
Common::DE_DEU,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO0()
},
{
"sword2",
"",
AD_ENTRY3s("general.clu", "31db8564f9187538f24d9fda0677f666", 7059728,
"text.clu", "56c1197e72249473538c30c912607d01", 418165,
"docks.clu", "b39246fbb5b955a29f9a207c69bfc318", 20262263),
Common::ES_ESP,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO0()
},
{
"sword2",
"",
AD_ENTRY3s("general.clu", "31db8564f9187538f24d9fda0677f666", 7059728,
"text.clu", "c141e9903e4a1f45252dd1500498b6e2", 488745,
"docks.clu", "b39246fbb5b955a29f9a207c69bfc318", 20262263),
Common::IT_ITA,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO0()
},
{
"sword2",
"English speech",
AD_ENTRY3s("general.clu", "31db8564f9187538f24d9fda0677f666", 7059728,
"text.clu", "bc45e00cfb737ad61fada3ca6b1b2bfc", 279042,
"docks.clu", "b39246fbb5b955a29f9a207c69bfc318", 20262263),
Common::CS_CZE,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO0()
},
{
"sword2",
"English speech",
AD_ENTRY3s("general.clu", "11e824864a75195652610e8b397382a6", 8030769,
"text.clu", "9867bb6dfc850bfa165812f0827a5508", 454229,
"docks.clu", "b39246fbb5b955a29f9a207c69bfc318", 20262263),
Common::FI_FIN,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO0()
},
{
"sword2",
"Fanmade/English speech/V1",
AD_ENTRY3s("general.clu", "31db8564f9187538f24d9fda0677f666", 7059728,
"text.clu", "93ea23ccf78dc746ed9a027fcf66d58d", 248692,
"docks.clu", "b39246fbb5b955a29f9a207c69bfc318", 20262263),
Common::HE_ISR,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO0()
},
{
"sword2",
"Fanmade/English speech/V2",
AD_ENTRY3s("general.clu", "31db8564f9187538f24d9fda0677f666", 7059728,
"text.clu", "b5442676e958309bf1a4817dd3893aab", 248702,
"docks.clu", "b39246fbb5b955a29f9a207c69bfc318", 20262263),
Common::HE_ISR,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO0()
},
{
"sword2",
"English speech",
AD_ENTRY3s("general.clu", "31db8564f9187538f24d9fda0677f666", 7059728,
"text.clu", "82714fa70516486174cddc2754958cd4", 304968,
"docks.clu", "b39246fbb5b955a29f9a207c69bfc318", 20262263),
Common::HU_HUN,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO0()
},
{
"sword2",
"English speech",
AD_ENTRY3s("general.clu", "11e824864a75195652610e8b397382a6", 8030769,
"text.clu", "f1cf2aaa7e56d8bf6572c9b25267931e", 373704,
"docks.clu", "b39246fbb5b955a29f9a207c69bfc318", 20262263),
Common::PL_POL,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO0()
},
{ // Alternate version. Bugreport #14277
"sword2",
"English speech/alternate version",
AD_ENTRY3s("general.clu", "31db8564f9187538f24d9fda0677f666", 7059728,
"text.clu", "77fb6f58acad0f9c4eebeb5527b32861", 410707,
"docks.clu", "b39246fbb5b955a29f9a207c69bfc318", 20262263),
Common::PL_POL,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO0()
},
{ // Alternate version (2 CD). Bugreport #16449
"sword2",
"English speech/alternate version",
AD_ENTRY3s("general.clu", "11e824864a75195652610e8b397382a6", 8030769,
"text.clu", "77fb6f58acad0f9c4eebeb5527b32861", 410707,
"docks.clu", "b39246fbb5b955a29f9a207c69bfc318", 20262263),
Common::PL_POL,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO0()
},
{
"sword2",
"English speech",
AD_ENTRY3s("general.clu", "31db8564f9187538f24d9fda0677f666", 7059728,
"text.clu", "cda6306bedfa63ac4386ff82977bfcd6", 410949,
"docks.clu", "b39246fbb5b955a29f9a207c69bfc318", 20262263),
Common::PT_BRA,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO0()
},
{
"sword2",
"Fargus",
AD_ENTRY3s("general.clu", "98e43a4fd93227b1d5d44e664eeede0c", 7320908,
"text.clu", "33a2645498ef1f4e63c4f6a50da4a3e2", 288998,
"docks.clu", "b39246fbb5b955a29f9a207c69bfc318", 20262263),
Common::RU_RUS,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO0()
},
{
"sword2",
"Mediahauz/English speech",
AD_ENTRY3s("general.clu", "31db8564f9187538f24d9fda0677f666", 7059728,
"text.clu", "e85c148037b8bfc02c968d4d22fda5e1", 315178,
"docks.clu", "b39246fbb5b955a29f9a207c69bfc318", 20262263),
Common::RU_RUS,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO0()
},
{ // TRAC report #14550
"sword2",
"Novy Disk",
AD_ENTRY2s("general.clu", "60d17ec7eb80fec561e1278ff5a32faa", 7093894,
"docks.clu", "b39246fbb5b955a29f9a207c69bfc318", 20262263),
Common::RU_RUS,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUIO0()
},
{
"sword2",
"",
AD_ENTRY3s("general.clu", "7ef0353ca03338d59b4f4e3d01a38df1", 2095780,
"text.clu", "06691fc9f749f3f7ad0f622fbfe79467", 302756,
"docks.clu", "95ee20f4c61de6acc3243ba4632e37d8", 5654572),
Common::EN_USA,
Common::kPlatformPSX,
ADGF_NO_FLAGS,
GUIO0()
},
{
"sword2",
"",
AD_ENTRY3s("general.clu", "7ef0353ca03338d59b4f4e3d01a38df1", 2095780,
"text.clu", "be8ad3f1d9d3ddd8881169b16aa23970", 838392,
"docks.clu", "95ee20f4c61de6acc3243ba4632e37d8", 5654572),
Common::EN_GRB,
Common::kPlatformPSX,
ADGF_NO_FLAGS,
GUIO0()
},
{
"sword2",
"",
AD_ENTRY3s("general.clu", "7ef0353ca03338d59b4f4e3d01a38df1", 2095780,
"text.clu", "0920f1aec8bc9d02f8c94f73965c8006", 327668,
"docks.clu", "95ee20f4c61de6acc3243ba4632e37d8", 5654572),
Common::FR_FRA,
Common::kPlatformPSX,
ADGF_NO_FLAGS,
GUIO0()
},
{
"sword2",
"",
AD_ENTRY3s("general.clu", "7ef0353ca03338d59b4f4e3d01a38df1", 2095780,
"text.clu", "5ce53dfc154b80d4ca64b60df808e411", 347456,
"docks.clu", "95ee20f4c61de6acc3243ba4632e37d8", 5654572),
Common::DE_DEU,
Common::kPlatformPSX,
ADGF_NO_FLAGS,
GUIO0()
},
{
"sword2",
"",
AD_ENTRY3s("general.clu", "7ef0353ca03338d59b4f4e3d01a38df1", 2095780,
"text.clu", "03ffcd1eec48f74a3d16d1b7751cee0b", 316124,
"docks.clu", "95ee20f4c61de6acc3243ba4632e37d8", 5654572),
Common::ES_ESP,
Common::kPlatformPSX,
ADGF_NO_FLAGS,
GUIO0()
},
{
"sword2",
"",
AD_ENTRY3s("general.clu", "7ef0353ca03338d59b4f4e3d01a38df1", 2095780,
"text.clu", "298bd6eef464780bf6b0830805eef220", 334784,
"docks.clu", "95ee20f4c61de6acc3243ba4632e37d8", 5654572),
Common::IT_ITA,
Common::kPlatformPSX,
ADGF_NO_FLAGS,
GUIO0()
},
{ // Remastered version, not supported
"sword2",
_s("Remastered edition is not supported. Please, use the classic version"),
AD_ENTRY2s("general.clu", "5b237f3d0bbe05ceb94e271616c6e560", 33964,
"docks.clu", "9b5ddad1fb436b4897df9c6632cccbbe", 21641864),
Common::UNK_LANG,
Common::kPlatformUnknown,
ADGF_UNSUPPORTED,
GUIO0()
},
AD_TABLE_END_MARKER
};
} // End of namespace Sword2

104
engines/sword2/events.cpp Normal file
View File

@@ -0,0 +1,104 @@
/* 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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/textconsole.h"
#include "sword2/sword2.h"
#include "sword2/defs.h"
#include "sword2/header.h"
#include "sword2/logic.h"
namespace Sword2 {
void Logic::sendEvent(uint32 id, uint32 interact_id) {
for (int i = 0; i < ARRAYSIZE(_eventList); i++) {
if (_eventList[i].id == id || !_eventList[i].id) {
_eventList[i].id = id;
_eventList[i].interact_id = interact_id;
return;
}
}
error("sendEvent() ran out of event slots");
}
void Logic::setPlayerActionEvent(uint32 id, uint32 interact_id) {
// Full script id of action script number 2
sendEvent(id, (interact_id << 16) | 2);
}
int Logic::checkEventWaiting() {
for (int i = 0; i < ARRAYSIZE(_eventList); i++) {
if (_eventList[i].id == readVar(ID))
return 1;
}
return 0;
}
void Logic::startEvent() {
// call this from stuff like fnWalk
// you must follow with a return IR_TERMINATE
for (int i = 0; i < ARRAYSIZE(_eventList); i++) {
if (_eventList[i].id == readVar(ID)) {
logicOne(_eventList[i].interact_id);
_eventList[i].id = 0;
return;
}
}
error("startEvent() can't find event for id %d", readVar(ID));
}
void Logic::clearEvent(uint32 id) {
for (int i = 0; i < ARRAYSIZE(_eventList); i++) {
if (_eventList[i].id == id) {
_eventList[i].id = 0;
return;
}
}
}
void Logic::killAllIdsEvents(uint32 id) {
for (int i = 0; i < ARRAYSIZE(_eventList); i++) {
if (_eventList[i].id == id)
_eventList[i].id = 0;
}
}
// For the debugger
uint32 Logic::countEvents() {
uint32 count = 0;
for (int i = 0; i < ARRAYSIZE(_eventList); i++) {
if (_eventList[i].id)
count++;
}
return count;
}
} // End of namespace Sword2

2494
engines/sword2/function.cpp Normal file

File diff suppressed because it is too large Load Diff

360
engines/sword2/header.cpp Normal file
View File

@@ -0,0 +1,360 @@
/* 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 "sword2/header.h"
#include "sword2/object.h"
#include "sword2/screen.h"
#include "sword2/sword2.h"
#include "common/memstream.h"
#include "common/endian.h"
namespace Sword2 {
void ResHeader::read(const byte *addr) {
Common::MemoryReadStream readS(addr, size());
fileType = readS.readByte();
compType = readS.readByte();
compSize = readS.readUint32LE();
decompSize = readS.readUint32LE();
readS.read(name, NAME_LEN);
}
void ResHeader::write(byte *addr) {
Common::MemoryWriteStream writeS(addr, size());
writeS.writeByte(fileType);
writeS.writeByte(compType);
writeS.writeUint32LE(compSize);
writeS.writeUint32LE(decompSize);
writeS.write(name, NAME_LEN);
}
void AnimHeader::read(const byte *addr) {
Common::MemoryReadStream readS(addr, size());
if (Sword2Engine::isPsx()) {
noAnimFrames = readS.readUint16LE();
feetStartX = readS.readUint16LE();
feetStartY = readS.readUint16LE();
feetEndX = readS.readUint16LE();
feetEndY = readS.readUint16LE();
blend = readS.readUint16LE();
runTimeComp = readS.readByte();
feetStartDir = readS.readByte();
feetEndDir = readS.readByte();
} else {
runTimeComp = readS.readByte();
noAnimFrames = readS.readUint16LE();
feetStartX = readS.readUint16LE();
feetStartY = readS.readUint16LE();
feetStartDir = readS.readByte();
feetEndX = readS.readUint16LE();
feetEndY = readS.readUint16LE();
feetEndDir = readS.readByte();
blend = readS.readUint16LE();
}
}
void AnimHeader::write(byte *addr) {
Common::MemoryWriteStream writeS(addr, size());
writeS.writeByte(runTimeComp);
writeS.writeUint16LE(noAnimFrames);
writeS.writeUint16LE(feetStartX);
writeS.writeUint16LE(feetStartY);
writeS.writeByte(feetStartDir);
writeS.writeUint16LE(feetEndX);
writeS.writeUint16LE(feetEndY);
writeS.writeByte(feetEndDir);
writeS.writeUint16LE(blend);
}
int CdtEntry::size() {
if (Sword2Engine::isPsx())
return 12;
else
return 9;
}
void CdtEntry::read(const byte *addr) {
Common::MemoryReadStream readS(addr, size());
if (Sword2Engine::isPsx()) {
readS.readByte(); // Skip a byte in psx version
x = readS.readUint16LE();
y = readS.readUint16LE();
frameOffset = readS.readUint32LE();
frameType = readS.readByte();
} else {
x = readS.readUint16LE();
y = readS.readUint16LE();
frameOffset = readS.readUint32LE();
frameType = readS.readByte();
}
}
void CdtEntry::write(byte *addr) {
Common::MemoryWriteStream writeS(addr, size());
writeS.writeUint16LE(x);
writeS.writeUint16LE(y);
writeS.writeUint32LE(frameOffset);
writeS.writeByte(frameType);
}
void FrameHeader::read(const byte *addr) {
Common::MemoryReadStream readS(addr, size());
compSize = readS.readUint32LE();
width = readS.readUint16LE();
height = readS.readUint16LE();
if (Sword2Engine::isPsx()) { // In PSX version, frames are half height
height *= 2;
width = (width % 2) ? width + 1 : width;
}
}
void FrameHeader::write(byte *addr) {
Common::MemoryWriteStream writeS(addr, size());
writeS.writeUint32LE(compSize);
writeS.writeUint16LE(width);
writeS.writeUint16LE(height);
}
void MultiScreenHeader::read(const byte *addr) {
Common::MemoryReadStream readS(addr, size());
palette = readS.readUint32LE();
bg_parallax[0] = readS.readUint32LE();
bg_parallax[1] = readS.readUint32LE();
screen = readS.readUint32LE();
fg_parallax[0] = readS.readUint32LE();
fg_parallax[1] = readS.readUint32LE();
layers = readS.readUint32LE();
paletteTable = readS.readUint32LE();
maskOffset = readS.readUint32LE();
}
void MultiScreenHeader::write(byte *addr) {
Common::MemoryWriteStream writeS(addr, size());
writeS.writeUint32LE(palette);
writeS.writeUint32LE(bg_parallax[0]);
writeS.writeUint32LE(bg_parallax[1]);
writeS.writeUint32LE(screen);
writeS.writeUint32LE(fg_parallax[0]);
writeS.writeUint32LE(fg_parallax[1]);
writeS.writeUint32LE(layers);
writeS.writeUint32LE(paletteTable);
writeS.writeUint32LE(maskOffset);
}
void ScreenHeader::read(const byte *addr) {
Common::MemoryReadStream readS(addr, size());
width = readS.readUint16LE();
height = readS.readUint16LE();
noLayers = readS.readUint16LE();
}
void ScreenHeader::write(byte *addr) {
Common::MemoryWriteStream writeS(addr, size());
writeS.writeUint16LE(width);
writeS.writeUint16LE(height);
writeS.writeUint16LE(noLayers);
}
void LayerHeader::read(const byte *addr) {
Common::MemoryReadStream readS(addr, size());
x = readS.readUint16LE();
y = readS.readUint16LE();
width = readS.readUint16LE();
height = readS.readUint16LE();
maskSize = readS.readUint32LE();
offset = readS.readUint32LE();
}
void LayerHeader::write(byte *addr) {
Common::MemoryWriteStream writeS(addr, size());
writeS.writeUint16LE(x);
writeS.writeUint16LE(y);
writeS.writeUint16LE(width);
writeS.writeUint16LE(height);
writeS.writeUint32LE(maskSize);
writeS.writeUint32LE(offset);
}
void TextHeader::read(const byte *addr) {
Common::MemoryReadStream readS(addr, size());
noOfLines = readS.readUint32LE();
}
void TextHeader::write(byte *addr) {
Common::MemoryWriteStream writeS(addr, size());
writeS.writeUint32LE(noOfLines);
}
void PSXScreensEntry::read(const byte *addr) {
Common::MemoryReadStream readS(addr, size());
bgPlxXres = readS.readUint16LE();
bgPlxYres = readS.readUint16LE();
bgPlxOffset = readS.readUint32LE();
bgPlxSize = readS.readUint32LE();
bgXres = readS.readUint16LE();
bgYres = readS.readUint16LE();
bgOffset = readS.readUint32LE();
bgSize = readS.readUint32LE();
fgPlxXres = readS.readUint16LE();
fgPlxYres = readS.readUint16LE();
fgPlxOffset = readS.readUint32LE();
fgPlxSize = readS.readUint32LE();
}
void PSXScreensEntry::write(byte *addr) {
Common::MemoryWriteStream writeS(addr, size());
writeS.writeUint16LE(bgPlxXres);
writeS.writeUint16LE(bgPlxYres);
writeS.writeUint32LE(bgPlxOffset);
writeS.writeUint32LE(bgPlxSize);
writeS.writeUint16LE(bgXres);
writeS.writeUint16LE(bgYres);
writeS.writeUint32LE(bgOffset);
writeS.writeUint32LE(bgSize);
writeS.writeUint16LE(fgPlxXres);
writeS.writeUint16LE(fgPlxYres);
writeS.writeUint32LE(fgPlxOffset);
writeS.writeUint32LE(fgPlxSize);
}
void PSXFontEntry::read(const byte *addr) {
Common::MemoryReadStream readS(addr, size());
offset = readS.readUint16LE() / 2;
skipLines = readS.readUint16LE();
charWidth = readS.readUint16LE() / 2;
charHeight = readS.readUint16LE();
}
void PSXFontEntry::write(byte *addr) {
Common::MemoryWriteStream writeS(addr, size());
writeS.writeUint16LE(offset);
writeS.writeUint16LE(skipLines);
writeS.writeUint16LE(charWidth);
writeS.writeUint16LE(charHeight);
}
void Parallax::read(const byte *addr) {
Common::MemoryReadStream readS(addr, size());
w = readS.readUint16LE();
h = readS.readUint16LE();
}
void Parallax::write(byte *addr) {
Common::MemoryWriteStream writeS(addr, size());
writeS.writeUint16LE(w);
writeS.writeUint16LE(h);
}
void ObjectMouse::read(const byte *addr) {
Common::MemoryReadStream readS(addr, size());
x1 = readS.readSint32LE();
y1 = readS.readSint32LE();
x2 = readS.readSint32LE();
y2 = readS.readSint32LE();
priority = readS.readSint32LE();
pointer = readS.readSint32LE();
}
void ObjectMouse::write(byte *addr) {
Common::MemoryWriteStream writeS(addr, size());
writeS.writeSint32LE(x1);
writeS.writeSint32LE(y1);
writeS.writeSint32LE(x2);
writeS.writeSint32LE(y2);
writeS.writeSint32LE(priority);
writeS.writeSint32LE(pointer);
}
void ObjectWalkdata::read(const byte *addr) {
Common::MemoryReadStream readS(addr, size());
nWalkFrames = readS.readUint32LE();
usingStandingTurnFrames = readS.readUint32LE();
usingWalkingTurnFrames = readS.readUint32LE();
usingSlowInFrames = readS.readUint32LE();
usingSlowOutFrames = readS.readUint32LE();
int i;
for (i = 0; i < ARRAYSIZE(nSlowInFrames); i++)
nSlowInFrames[i] = readS.readUint32LE();
for (i = 0; i < ARRAYSIZE(leadingLeg); i++)
leadingLeg[i] = readS.readUint32LE();
for (i = 0; i < ARRAYSIZE(dx); i++)
dx[i] = readS.readUint32LE();
for (i = 0; i < ARRAYSIZE(dy); i++)
dy[i] = readS.readUint32LE();
}
void ObjectWalkdata::write(byte *addr) {
Common::MemoryWriteStream writeS(addr, size());
writeS.writeUint32LE(nWalkFrames);
writeS.writeUint32LE(usingStandingTurnFrames);
writeS.writeUint32LE(usingWalkingTurnFrames);
writeS.writeUint32LE(usingSlowInFrames);
writeS.writeUint32LE(usingSlowOutFrames);
int i;
for (i = 0; i < ARRAYSIZE(nSlowInFrames); i++)
writeS.writeUint32LE(nSlowInFrames[i]);
for (i = 0; i < ARRAYSIZE(leadingLeg); i++)
writeS.writeUint32LE(leadingLeg[i]);
for (i = 0; i < ARRAYSIZE(dx); i++)
writeS.writeUint32LE(dx[i]);
for (i = 0; i < ARRAYSIZE(dy); i++)
writeS.writeUint32LE(dy[i]);
}
} // End of namespace Sword2

422
engines/sword2/header.h Normal file
View File

@@ -0,0 +1,422 @@
/* 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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 SWORD2_HEADER_H
#define SWORD2_HEADER_H
#include "common/endian.h"
namespace Sword2 {
//----------------------------------------------------------
// SYSTEM FILE & FRAME HEADERS
//----------------------------------------------------------
//----------------------------------------------------------
// ALL FILES
//----------------------------------------------------------
// Standard File Header
#define NAME_LEN 34
struct ResHeader {
uint8 fileType; // Byte to define file type (see below)
uint8 compType; // Type of file compression used ie.
// on whole file (see below)
uint32 compSize; // Length of compressed file (ie.
// length on disk)
uint32 decompSize; // Length of decompressed file held in
// memory (NB. frames still held
// compressed)
byte name[NAME_LEN]; // Name of object
static int size() {
return 44;
}
void read(const byte *addr);
void write(byte *addr);
};
// fileType
enum {
// 0 something's wrong!
ANIMATION_FILE = 1, // All normal animations & sprites
// including mega-sets & font files
// which are the same format (but all
// frames always uncompressed)
SCREEN_FILE = 2, // Each contains background, palette,
// layer sprites, parallax layers &
// shading mask
GAME_OBJECT = 3, // Each contains object hub +
// structures + script data
WALK_GRID_FILE = 4, // Walk-grid data
GLOBAL_VAR_FILE = 5, // All the global script variables in
// one file; "there can be only one"
PARALLAX_FILE_null = 6, // NOT USED
RUN_LIST = 7, // Each contains a list of object
// resource id's
TEXT_FILE = 8, // Each contains all the lines of text
// for a location or a character's
// conversation script
SCREEN_MANAGER = 9, // One for each location; this contains
// special startup scripts
MOUSE_FILE = 10, // Mouse pointers and luggage icons
// (sprites in General / Mouse pointers
// & Luggage icons)
WAV_FILE = 11, // WAV file
ICON_FILE = 12, // Menu icon (sprites in General / Menu
// icons)
PALETTE_FILE = 13 // separate palette file (see also
// _paletteHeader)
};
// compType
enum {
NO_COMPRESSION = 0,
FILE_COMPRESSION = 1 // standard whole-file compression
// (not yet devised!)
};
//----------------------------------------------------------
// (1) ANIMATION FILES
//----------------------------------------------------------
// an animation file consists of:
//
// standard file header
// animation header
// a string of CDT entries (one per frame of the anim)
// a 16-byte color table ONLY if (runTimeComp==RLE16)
// a string of groups of (frame header + frame data)
// Animation Header
struct AnimHeader {
uint8 runTimeComp; // Type of runtime compression used for the
// frame data (see below)
uint16 noAnimFrames; // Number of frames in the anim (ie. no. of
// CDT entries)
uint16 feetStartX; // Start coords for mega to walk to, before
uint16 feetStartY; // running anim
uint8 feetStartDir; // Direction to start in before running anim
uint16 feetEndX; // End coords for mega to stand at after
uint16 feetEndY; // running anim (vital if anim starts from an
// off-screen position, or ends in a different
// place from the start)
uint8 feetEndDir; // Direction to start in after running anim
uint16 blend;
static int size() {
return 15;
}
void read(const byte *addr);
void write(byte *addr);
};
// runtimeComp - compression used on each frame of the anim
enum {
NONE = 0, // No frame compression
RLE256 = 1, // James's RLE for 256-color sprites
RLE16 = 2 // James's RLE for 16- or 17-color sprites
// (raw blocks have max 16 colors for 2 pixels
// per byte, so '0's are encoded only as FLAT
// for 17-color sprites eg. George's mega-set)
};
// CDT Entry
struct CdtEntry {
int16 x; // sprite x-coord OR offset to add to mega's
// feet x-coord to calc sprite y-coord
int16 y; // sprite y-coord OR offset to add to mega's
// feet y-coord to calc sprite y-coord
uint32 frameOffset; // points to start of frame header (from start
// of file header)
uint8 frameType; // 0 = print sprite normally with top-left
// corner at (x,y), otherwise see below...
static int size();
void read(const byte *addr);
void write(byte *addr);
};
// 'frameType' bit values
enum {
FRAME_OFFSET = 1, // Print at (feetX + x, feetY + y), with
// scaling according to feetY
FRAME_FLIPPED = 2, // Print the frame flipped Left->Right
FRAME_256_FAST = 4 // Frame has been compressed using Pauls fast
// RLE 256 compression.
};
// Frame Header
struct FrameHeader {
uint32 compSize; // Compressed size of frame - NB. compression
// type is now in Anim Header
uint16 width; // Dimensions of frame
uint16 height;
static int size() {
return 8;
}
void read(const byte *addr);
void write(byte *addr);
};
//----------------------------------------------------------
// (2) SCREEN FILES
//----------------------------------------------------------
// a screen file consists of:
//
// standard file header
// multi screen header
// 4 * 256 bytes of palette data
// 256k palette match table
// 2 background parallax layers
// 1 background layer with screen header
// 2 foreground parallax layers
// a string of layer headers
// a string of layer masks
// Multi screen header
// Goes at the beginning of a screen file after the standard header.
// Gives offsets from start of table of each of the components
struct MultiScreenHeader {
uint32 palette;
uint32 bg_parallax[2];
uint32 screen;
uint32 fg_parallax[2];
uint32 layers;
uint32 paletteTable;
uint32 maskOffset;
static int size() {
return 36;
}
void read(const byte *addr);
void write(byte *addr);
};
// Screen Header
struct ScreenHeader {
uint16 width; // dimensions of the background screen
uint16 height;
uint16 noLayers; // number of layer areas
static int size() {
return 6;
}
void read(const byte *addr);
void write(byte *addr);
};
// Layer Header
// Note that all the layer headers are kept together, rather than being placed
// before each layer mask, in order to simplify the sort routine.
struct LayerHeader {
uint16 x; // coordinates of top-left pixel of area
uint16 y;
uint16 width;
uint16 height;
uint32 maskSize;
uint32 offset; // where to find mask data (from start of
// standard file header)
static int size() {
return 16;
}
void read(const byte *addr);
void write(byte *addr);
};
//----------------------------------------------------------
// (3) SCRIPT OBJECT FILES
//----------------------------------------------------------
// a script object file consists of:
//
// standard file header
// script object header
// script object data
//----------------------------------------------------------
// (5) PALETTE FILES
//----------------------------------------------------------
// a palette file consists of:
//
// standard file header
// 4 * 256 bytes of palette data
// 256k palette match table
// an object hub - which represents all that remains of the compact concept
class ObjectHub {
// int32 type; // type of object
// uint32 logic_level; // what level?
// uint32 logic[3] // NOT USED
// uint32 script_id[3] // need this if script
// uint32 script_pc[3] // need this also
private:
byte *_addr;
public:
ObjectHub() {
_addr = nullptr;
}
static int size() {
return 44;
}
byte *data() {
return _addr;
}
void setAddress(byte *addr) {
_addr = addr;
}
byte *getScriptPcPtr(int level) {
return _addr + 32 + 4 * level;
}
uint32 getLogicLevel() {
return READ_LE_UINT32(_addr + 4);
}
uint32 getScriptId(int level) {
return READ_LE_UINT32(_addr + 20 + 4 * level);
}
uint32 getScriptPc(int level) {
return READ_LE_UINT32(_addr + 32 + 4 * level);
}
void setLogicLevel(uint32 x) {
WRITE_LE_UINT32(_addr + 4, x);
}
void setScriptId(int level, uint32 x) {
WRITE_LE_UINT32(_addr + 20 + 4 * level, x);
}
void setScriptPc(int level, uint32 x) {
WRITE_LE_UINT32(_addr + 32 + 4 * level, x);
}
};
// (6) text module header
struct TextHeader {
uint32 noOfLines; // how many lines of text are there in this
// module
static int size() {
return 4;
}
void read(const byte *addr);
void write(byte *addr);
};
// a text file has:
//
// ResHeader
// TextHeader
// look up table, to
// line of text,0
// line of text,0
//----------------------------------------------------------
// SCREENS.CLU file
//----------------------------------------------------------
// This file is present in PSX version of the game only.
// It keeps parallax and background images, aligned at 1024 bytes
// for faster access by the psx cd drive.
//
// SCREENS.CLU structure:
// In first 2048 Bytes there's an offset table. Each entry is an
// 32bit offset for a background/parallax group. If entry is 0, screen
// does not exist.
// To find matching screen for the location, you must count LOCATION_NO
// words and then go to the corresponding offset indicated by last 32bit
// word.
// Each screen then begins with a PSXScreensEntry entry:
struct PSXScreensEntry {
uint16 fgPlxXres; // If these values are 0, subsequent fgPlx* values must be
uint16 fgPlxYres; // ignored, as this means foreground parallax is not present.
uint32 fgPlxOffset; // This offset is relative, counting from the beginning of Resource Header
uint32 fgPlxSize; // Size of parallax, the size is aligned at 1024 bytes.
// fgPlxSize/1024 gives number of sector the parallax is divided into.
uint16 bgXres;
uint16 bgYres;
uint32 bgOffset; // relative
uint32 bgSize;
uint16 bgPlxXres; // same considerations for fg parallaxes apply
uint16 bgPlxYres;
uint32 bgPlxOffset; // relative
uint32 bgPlxSize;
static int size() {
return 36;
}
void read(const byte *addr);
void write(byte *addr);
};
// PSXFontEntry is present in font resource file, it is used
// to address a single char in the character atlas image.
struct PSXFontEntry {
uint16 offset;
uint16 skipLines;
uint16 charWidth;
uint16 charHeight;
static int size() {
return 8;
}
void read(const byte *addr);
void write(byte *addr);
};
} // End of namespace Sword2
#endif

234
engines/sword2/icons.cpp Normal file
View File

@@ -0,0 +1,234 @@
/* 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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/memstream.h"
#include "common/rect.h"
#include "sword2/sword2.h"
#include "sword2/header.h"
#include "sword2/defs.h"
#include "sword2/logic.h"
#include "sword2/mouse.h"
#include "sword2/resman.h"
namespace Sword2 {
void Mouse::addMenuObject(byte *ptr) {
assert(_totalTemp < TOTAL_engine_pockets);
Common::MemoryReadStream readS(ptr, 2 * sizeof(int32));
_tempList[_totalTemp].icon_resource = readS.readSint32LE();
_tempList[_totalTemp].luggage_resource = readS.readSint32LE();
_totalTemp++;
}
void Mouse::addSubject(int32 id, int32 ref) {
uint32 in_subject = _vm->_logic->readVar(IN_SUBJECT);
if (in_subject == 0) {
// This is the start of the new subject list. Set the default
// repsonse id to zero in case we're never passed one.
_defaultResponseId = 0;
}
if (id == -1) {
// Id -1 is used for setting the default response, i.e. the
// response when someone uses an object on a person and he
// doesn't know anything about it. See fnChoose().
_defaultResponseId = ref;
} else {
debug(5, "fnAddSubject res %d, uid %d", id, ref);
_subjectList[in_subject].res = id;
_subjectList[in_subject].ref = ref;
_vm->_logic->writeVar(IN_SUBJECT, in_subject + 1);
}
}
/**
* Create and start the inventory (bottom) menu
*/
void Mouse::buildMenu() {
uint32 i, j;
byte menuIconWidth;
if (Sword2Engine::isPsx())
menuIconWidth = RDMENU_PSXICONWIDE;
else
menuIconWidth = RDMENU_ICONWIDE;
// Clear the temporary inventory list, since we are going to build a
// new one from scratch.
for (i = 0; i < TOTAL_engine_pockets; i++)
_tempList[i].icon_resource = 0;
_totalTemp = 0;
// Run the 'build_menu' script in the 'menu_master' object. This will
// register all carried menu objects.
_vm->_logic->runResScript(MENU_MASTER_OBJECT, 0);
// Create a new master list based on the old master inventory list and
// the new temporary inventory list. The purpose of all this is, as
// far as I can tell, that the new list is ordered in the same way as
// the old list, with new objects added to the end of it.
// Compare new with old. Anything in master that's not in new gets
// removed from master - if found in new too, remove from temp
for (i = 0; i < _totalMasters; i++) {
bool found_in_temp = false;
for (j = 0; j < TOTAL_engine_pockets; j++) {
if (_masterMenuList[i].icon_resource == _tempList[j].icon_resource) {
// We alread know about this object, so kill it
// in the temporary list.
_tempList[j].icon_resource = 0;
found_in_temp = true;
break;
}
}
if (!found_in_temp) {
// The object is in the master list, but not in the
// temporary list. The player must have lost the object
// since the last time we checked, so kill it in the
// master list.
_masterMenuList[i].icon_resource = 0;
}
}
// Eliminate blank entries from the master list.
_totalMasters = 0;
for (i = 0; i < TOTAL_engine_pockets; i++) {
if (_masterMenuList[i].icon_resource) {
if (i != _totalMasters) {
memcpy(&_masterMenuList[_totalMasters], &_masterMenuList[i], sizeof(MenuObject));
_masterMenuList[i].icon_resource = 0;
}
_totalMasters++;
}
}
// Add the new objects - i.e. the ones still in the temporary list but
// not yet in the master list - to the end of the master.
for (i = 0; i < TOTAL_engine_pockets; i++) {
if (_tempList[i].icon_resource) {
memcpy(&_masterMenuList[_totalMasters++], &_tempList[i], sizeof(MenuObject));
}
}
// Initialize the menu from the master list.
for (i = 0; i < 15; i++) {
uint32 res = _masterMenuList[i].icon_resource;
byte *icon = nullptr;
if (res) {
bool icon_colored;
uint32 object_held = _vm->_logic->readVar(OBJECT_HELD);
uint32 combine_base = _vm->_logic->readVar(COMBINE_BASE);
if (_examiningMenuIcon) {
// When examining an object, that object is
// colored. The rest are greyed out.
icon_colored = (res == object_held);
} else if (combine_base) {
// When combining two menu object (i.e. using
// one on another), both are colored. The rest
// are greyed out.
icon_colored = (res == object_held || combine_base);
} else {
// If an object is selected but we are not yet
// doing anything with it, the selected object
// is greyed out. The rest are colored.
icon_colored = (res != object_held);
}
icon = _vm->_resman->openResource(res) + ResHeader::size();
// The colored icon is stored directly after the
// greyed out one.
if (icon_colored)
icon += (menuIconWidth * RDMENU_ICONDEEP);
}
setMenuIcon(RDMENU_BOTTOM, i, icon);
if (res)
_vm->_resman->closeResource(res);
}
showMenu(RDMENU_BOTTOM);
}
/**
* Build a fresh system (top) menu.
*/
void Mouse::buildSystemMenu() {
uint32 icon_list[5] = {
OPTIONS_ICON,
QUIT_ICON,
SAVE_ICON,
RESTORE_ICON,
RESTART_ICON
};
byte menuIconWidth;
if (Sword2Engine::isPsx())
menuIconWidth = RDMENU_PSXICONWIDE;
else
menuIconWidth = RDMENU_ICONWIDE;
// Build them all high in full color - when one is clicked on all the
// rest will grey out.
for (int i = 0; i < ARRAYSIZE(icon_list); i++) {
byte *icon = _vm->_resman->openResource(icon_list[i]) + ResHeader::size();
// The only case when an icon is grayed is when the player
// is dead. Then SAVE is not available.
if (!_vm->_logic->readVar(DEAD) || icon_list[i] != SAVE_ICON)
icon += (menuIconWidth * RDMENU_ICONDEEP);
setMenuIcon(RDMENU_TOP, i, icon);
_vm->_resman->closeResource(icon_list[i]);
}
showMenu(RDMENU_TOP);
}
} // End of namespace Sword2

View File

@@ -0,0 +1,771 @@
/* 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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/util.h"
#include "common/stack.h"
#include "common/textconsole.h"
#include "sword2/sword2.h"
#include "sword2/header.h"
#include "sword2/defs.h"
#include "sword2/interpreter.h"
#include "sword2/logic.h"
#include "sword2/memory.h"
#include "sword2/resman.h"
namespace Sword2 {
#define STACK_SIZE 10
// The machine code table
#ifndef REDUCE_MEMORY_USAGE
# define OPCODE(x) { &Logic::x, #x }
#else
# define OPCODE(x) { &Logic::x, "" }
#endif
void Logic::setupOpcodes() {
static const OpcodeEntry opcodes[] = {
/* 00 */
OPCODE(fnTestFunction),
OPCODE(fnTestFlags),
OPCODE(fnRegisterStartPoint),
OPCODE(fnInitBackground),
/* 04 */
OPCODE(fnSetSession),
OPCODE(fnBackSprite),
OPCODE(fnSortSprite),
OPCODE(fnForeSprite),
/* 08 */
OPCODE(fnRegisterMouse),
OPCODE(fnAnim),
OPCODE(fnRandom),
OPCODE(fnPreLoad),
/* 0C */
OPCODE(fnAddSubject),
OPCODE(fnInteract),
OPCODE(fnChoose),
OPCODE(fnWalk),
/* 10 */
OPCODE(fnWalkToAnim),
OPCODE(fnTurn),
OPCODE(fnStandAt),
OPCODE(fnStand),
/* 14 */
OPCODE(fnStandAfterAnim),
OPCODE(fnPause),
OPCODE(fnMegaTableAnim),
OPCODE(fnAddMenuObject),
/* 18 */
OPCODE(fnStartConversation),
OPCODE(fnEndConversation),
OPCODE(fnSetFrame),
OPCODE(fnRandomPause),
/* 1C */
OPCODE(fnRegisterFrame),
OPCODE(fnNoSprite),
OPCODE(fnSendSync),
OPCODE(fnUpdatePlayerStats),
/* 20 */
OPCODE(fnPassGraph),
OPCODE(fnInitFloorMouse),
OPCODE(fnPassMega),
OPCODE(fnFaceXY),
/* 24 */
OPCODE(fnEndSession),
OPCODE(fnNoHuman),
OPCODE(fnAddHuman),
OPCODE(fnWeWait),
/* 28 */
OPCODE(fnTheyDoWeWait),
OPCODE(fnTheyDo),
OPCODE(fnWalkToTalkToMega),
OPCODE(fnFadeDown),
/* 2C */
OPCODE(fnISpeak),
OPCODE(fnTotalRestart),
OPCODE(fnSetWalkGrid),
OPCODE(fnSpeechProcess),
/* 30 */
OPCODE(fnSetScaling),
OPCODE(fnStartEvent),
OPCODE(fnCheckEventWaiting),
OPCODE(fnRequestSpeech),
/* 34 */
OPCODE(fnGosub),
OPCODE(fnTimedWait),
OPCODE(fnPlayFx),
OPCODE(fnStopFx),
/* 38 */
OPCODE(fnPlayMusic),
OPCODE(fnStopMusic),
OPCODE(fnSetValue),
OPCODE(fnNewScript),
/* 3C */
OPCODE(fnGetSync),
OPCODE(fnWaitSync),
OPCODE(fnRegisterWalkGrid),
OPCODE(fnReverseMegaTableAnim),
/* 40 */
OPCODE(fnReverseAnim),
OPCODE(fnAddToKillList),
OPCODE(fnSetStandbyCoords),
OPCODE(fnBackPar0Sprite),
/* 44 */
OPCODE(fnBackPar1Sprite),
OPCODE(fnForePar0Sprite),
OPCODE(fnForePar1Sprite),
OPCODE(fnSetPlayerActionEvent),
/* 48 */
OPCODE(fnSetScrollCoordinate),
OPCODE(fnStandAtAnim),
OPCODE(fnSetScrollLeftMouse),
OPCODE(fnSetScrollRightMouse),
/* 4C */
OPCODE(fnColor),
OPCODE(fnFlash),
OPCODE(fnPreFetch),
OPCODE(fnGetPlayerSaveData),
/* 50 */
OPCODE(fnPassPlayerSaveData),
OPCODE(fnSendEvent),
OPCODE(fnAddWalkGrid),
OPCODE(fnRemoveWalkGrid),
/* 54 */
OPCODE(fnCheckForEvent),
OPCODE(fnPauseForEvent),
OPCODE(fnClearEvent),
OPCODE(fnFaceMega),
/* 58 */
OPCODE(fnPlaySequence),
OPCODE(fnShadedSprite),
OPCODE(fnUnshadedSprite),
OPCODE(fnFadeUp),
/* 5C */
OPCODE(fnDisplayMsg),
OPCODE(fnSetObjectHeld),
OPCODE(fnAddSequenceText),
OPCODE(fnResetGlobals),
/* 60 */
OPCODE(fnSetPalette),
OPCODE(fnRegisterPointerText),
OPCODE(fnFetchWait),
OPCODE(fnRelease),
/* 64 */
OPCODE(fnPrepareMusic),
OPCODE(fnSoundFetch),
OPCODE(fnPrepareMusic), // Again, apparently
OPCODE(fnSmackerLeadIn),
/* 68 */
OPCODE(fnSmackerLeadOut),
OPCODE(fnStopAllFx),
OPCODE(fnCheckPlayerActivity),
OPCODE(fnResetPlayerActivityDelay),
/* 6C */
OPCODE(fnCheckMusicPlaying),
OPCODE(fnPlayCredits),
OPCODE(fnSetScrollSpeedNormal),
OPCODE(fnSetScrollSpeedSlow),
/* 70 */
OPCODE(fnRemoveChooser),
OPCODE(fnSetFxVolAndPan),
OPCODE(fnSetFxVol),
OPCODE(fnRestoreGame),
/* 74 */
OPCODE(fnRefreshInventory),
OPCODE(fnChangeShadows)
};
_numOpcodes = ARRAYSIZE(opcodes);
_opcodes = opcodes;
}
int Logic::runResScript(uint32 scriptRes, uint32 offset) {
byte *scriptAddr;
int result;
scriptAddr = _vm->_resman->openResource(scriptRes);
result = runScript(scriptAddr, scriptAddr, offset);
_vm->_resman->closeResource(scriptRes);
return result;
}
int Logic::runResObjScript(uint32 scriptRes, uint32 objRes, uint32 offset) {
byte *scriptAddr, *objAddr;
int result;
scriptAddr = _vm->_resman->openResource(scriptRes);
objAddr = _vm->_resman->openResource(objRes);
result = runScript(scriptAddr, objAddr, offset);
_vm->_resman->closeResource(objRes);
_vm->_resman->closeResource(scriptRes);
return result;
}
int Logic::runScript(byte *scriptData, byte *objectData, uint32 offset) {
byte pc[4];
WRITE_LE_UINT32(pc, offset);
return runScript2(scriptData, objectData, pc);
}
// This form of the runScript function is only called directly from
// the processSession() function, which uses it to update the script PC in the
// current object hub. For reasons which I do not understand, I couldn't get it
// to work if I called the function first with a dummy offset variable, and
// and then updated the object hub myself.
int Logic::runScript2(byte *scriptData, byte *objectData, byte *offsetPtr) {
int i;
// Interestingly, unlike our BASS engine the stack is a local variable.
// I don't know whether or not this is relevant to the working of the
// BS2 engine.
Common::FixedStack<int32, STACK_SIZE> stack;
int32 opcodeParams[STACK_SIZE];
uint32 offset = READ_LE_UINT32(offsetPtr);
ResHeader header;
header.read(scriptData);
scriptData += ResHeader::size() + ObjectHub::size();
// The script data format:
// int32_TYPE 1 Size of variable space in bytes
// ... The variable space
// int32_TYPE 1 numberOfScripts
// int32_TYPE numberOfScripts The offsets for each script
// Initialize some stuff
uint32 ip = 0; // Code pointer
int scriptNumber;
// Get the start of variables and start of code
byte *localVars = scriptData + 4;
byte *code = scriptData + READ_LE_UINT32(scriptData) + 4;
uint32 noScripts = READ_LE_UINT32(code);
code += 4;
byte *offsetTable = code;
if (offset < noScripts) {
ip = READ_LE_UINT32(offsetTable + offset * 4);
scriptNumber = offset;
debug(8, "Starting script %d from %d", scriptNumber, ip);
} else {
ip = offset;
for (i = 1; i < (int)noScripts; i++) {
if (READ_LE_UINT32(offsetTable + 4 * i) >= ip)
break;
}
scriptNumber = i - 1;
debug(8, "Resuming script %d from %d", scriptNumber, ip);
}
// There are a couple of known script bugs related to interacting with
// certain objects. We try to work around a few of them.
bool checkMopBug = false;
bool checkPyramidBug = false;
bool checkElevatorBug = false;
bool checkPearlBug = false;
if (scriptNumber == 2) {
if (strcmp((char *)header.name, "mop_73") == 0)
checkMopBug = true;
else if (strcmp((char *)header.name, "titipoco_81") == 0)
checkPyramidBug = true;
else if (strcmp((char *)header.name, "lift_82") == 0)
checkElevatorBug = true;
else if (strcmp((char *)header.name, "pearl_31") == 0)
checkPearlBug = true;
}
code += noScripts * 4;
// Code should now be pointing at an identifier and a checksum
byte *checksumBlock = code;
code += 4 * 3;
if (READ_LE_UINT32(checksumBlock) != 12345678) {
error("Invalid script in object %s", header.name);
//return 0;
}
int32 codeLen = READ_LE_UINT32(checksumBlock + 4);
int32 checksum = 0;
for (i = 0; i < codeLen; i++)
checksum += (unsigned char)code[i];
if (checksum != (int32)READ_LE_UINT32(checksumBlock + 8)) {
debug(1, "Checksum error in object %s", header.name);
// This could be bad, but there has been a report about someone
// who had problems running the German version because of
// checksum errors. Could there be a version where checksums
// weren't properly calculated?
}
bool runningScript = true;
int parameterReturnedFromMcodeFunction = 0; // Allow scripts to return things
int savedStartOfMcode = 0; // For saving start of mcode commands
while (runningScript) {
int32 a, b;
int curCommand, parameter, value; // Command and parameter variables
int retVal;
int caseCount;
bool foundCase;
byte *ptr;
curCommand = code[ip++];
switch (curCommand) {
// Script-related opcodes
case CP_END_SCRIPT:
// End the script
runningScript = false;
// WORKAROUND: The dreaded pyramid bug makes the torch
// untakeable when you speak to Titipoco. This is
// because one of the conditions for the torch to be
// takeable is that Titipoco isn't doing anything out
// of the ordinary. Global variable 913 has to be 0 to
// signify that he is in his "idle" state.
//
// Unfortunately, simply the act of speaking to him
// sets variable 913 to 1 (probably to stop him from
// turning around every now and then). The script may
// then go on to set the variable to different values
// to trigger various behaviors in him, but if you
// have run out of these cases the script won't ever
// set it back to 0 again.
//
// So if his click hander finishes, and variable 913 is
// 1, we set it back to 0 manually.
if (checkPyramidBug && readVar(913) == 1) {
warning("Working around pyramid bug: Resetting Titipoco's state");
writeVar(913, 0);
}
// WORKAROUND: The not-so-known-but-should-be-dreaded
// elevator bug.
//
// The click handler for the top of the elevator only
// handles using the elevator, not examining it. When
// examining it, the mouse cursor is removed but never
// restored.
if (checkElevatorBug && readVar(RIGHT_BUTTON)) {
warning("Working around elevator bug: Restoring mouse pointer");
fnAddHuman(nullptr);
}
debug(9, "CP_END_SCRIPT");
break;
case CP_QUIT:
// Quit out for a cycle
WRITE_LE_UINT32(offsetPtr, ip);
debug(9, "CP_QUIT");
return 0;
case CP_TERMINATE:
// Quit out immediately without affecting the offset
// pointer
debug(9, "CP_TERMINATE");
return 3;
case CP_RESTART_SCRIPT:
// Start the script again
ip = FROM_LE_32(offsetTable[scriptNumber]);
debug(9, "CP_RESTART_SCRIPT");
break;
// Stack-related opcodes
case CP_PUSH_INT32:
// Push a long word value on to the stack
Read32ip(parameter);
stack.push(parameter);
debug(9, "CP_PUSH_INT32: %d", parameter);
break;
case CP_PUSH_LOCAL_VAR32:
// Push the contents of a local variable
Read16ip(parameter);
stack.push(READ_LE_UINT32(localVars + parameter));
debug(9, "CP_PUSH_LOCAL_VAR32: localVars[%d] => %d", parameter / 4, READ_LE_UINT32(localVars + parameter));
break;
case CP_PUSH_GLOBAL_VAR32:
// Push a global variable
Read16ip(parameter);
stack.push(readVar(parameter));
debug(9, "CP_PUSH_GLOBAL_VAR32: scriptVars[%d] => %d", parameter, readVar(parameter));
break;
case CP_PUSH_LOCAL_ADDR:
// Push the address of a local variable
// From what I understand, some scripts store data
// (e.g. mouse pointers) in their local variable space
// from the very beginning, and use this mechanism to
// pass that data to the opcode function. I don't yet
// know the conceptual difference between this and the
// CP_PUSH_DEREFERENCED_STRUCTURE opcode.
Read16ip(parameter);
stack.push(_vm->_memory->encodePtr(localVars + parameter));
debug(9, "CP_PUSH_LOCAL_ADDR: &localVars[%d] => %p", parameter / 4, (void *)(localVars + parameter));
break;
case CP_PUSH_STRING:
// Push the address of a string on to the stack
// Get the string size
Read8ip(parameter);
// ip now points to the string
ptr = code + ip;
stack.push(_vm->_memory->encodePtr(ptr));
debug(9, "CP_PUSH_STRING: \"%s\"", ptr);
ip += (parameter + 1);
break;
case CP_PUSH_DEREFERENCED_STRUCTURE:
// Push the address of a dereferenced structure
Read32ip(parameter);
ptr = objectData + 4 + ResHeader::size() + ObjectHub::size() + parameter;
stack.push(_vm->_memory->encodePtr(ptr));
debug(9, "CP_PUSH_DEREFERENCED_STRUCTURE: %d => %p", parameter, (void *)ptr);
break;
case CP_POP_LOCAL_VAR32:
// Pop a value into a local word variable
Read16ip(parameter);
value = stack.pop();
WRITE_LE_UINT32(localVars + parameter, value);
debug(9, "CP_POP_LOCAL_VAR32: localVars[%d] = %d", parameter / 4, value);
break;
case CP_POP_GLOBAL_VAR32:
// Pop a global variable
Read16ip(parameter);
value = stack.pop();
// WORKAROUND for bug #2058: The not-at-all dreaded
// mop bug.
//
// At the London Docks, global variable 1003 keeps
// track of Nico:
//
// 0: Hiding behind the first crate.
// 1: Hiding behind the second crate.
// 2: Standing in plain view on the deck.
// 3: Hiding on the roof.
//
// The bug happens when trying to pick up the mop while
// hiding on the roof. Nico climbs down, the mop is
// picked up, but the variable remains set to 3.
// Visually, everything looks ok. But as far as the
// scripts are concerned, she's still hiding up on the
// roof. This is not fatal, but leads to a number of
// glitches until the state is corrected. E.g. trying
// to climb back up the ladder will cause Nico to climb
// down again.
//
// Global variable 1017 keeps track of the mop. Setting
// it to 2 means that the mop has been picked up. We
// use that as the signal that Nico's state needs to be
// updated as well.
if (checkMopBug && parameter == 1017 && readVar(1003) != 2) {
warning("Working around mop bug: Setting Nico's state");
writeVar(1003, 2);
}
writeVar(parameter, value);
debug(9, "CP_POP_GLOBAL_VAR32: scriptsVars[%d] = %d", parameter, value);
break;
case CP_ADDNPOP_LOCAL_VAR32:
Read16ip(parameter);
value = READ_LE_UINT32(localVars + parameter) + stack.pop();
WRITE_LE_UINT32(localVars + parameter, value);
debug(9, "CP_ADDNPOP_LOCAL_VAR32: localVars[%d] => %d", parameter / 4, value);
break;
case CP_SUBNPOP_LOCAL_VAR32:
Read16ip(parameter);
value = READ_LE_UINT32(localVars + parameter) - stack.pop();
WRITE_LE_UINT32(localVars + parameter, value);
debug(9, "CP_SUBNPOP_LOCAL_VAR32: localVars[%d] => %d", parameter / 4, value);
break;
case CP_ADDNPOP_GLOBAL_VAR32:
// Add and pop a global variable
Read16ip(parameter);
value = readVar(parameter) + stack.pop();
writeVar(parameter, value);
debug(9, "CP_ADDNPOP_GLOBAL_VAR32: scriptVars[%d] => %d", parameter, value);
break;
case CP_SUBNPOP_GLOBAL_VAR32:
// Sub and pop a global variable
Read16ip(parameter);
value = readVar(parameter) - stack.pop();
writeVar(parameter, value);
debug(9, "CP_SUBNPOP_GLOBAL_VAR32: scriptVars[%d] => %d", parameter, value);
break;
// Jump opcodes
case CP_SKIPONTRUE:
// Skip if the value on the stack is true
Read32ipLeaveip(parameter);
value = stack.pop();
if (!value) {
ip += 4;
debug(9, "CP_SKIPONTRUE: %d (IS FALSE (NOT SKIPPED))", parameter);
} else {
ip += parameter;
debug(9, "CP_SKIPONTRUE: %d (IS TRUE (SKIPPED))", parameter);
}
break;
case CP_SKIPONFALSE:
// Skip if the value on the stack is false
Read32ipLeaveip(parameter);
value = stack.pop();
if (value) {
ip += 4;
debug(9, "CP_SKIPONFALSE: %d (IS TRUE (NOT SKIPPED))", parameter);
} else {
ip += parameter;
debug(9, "CP_SKIPONFALSE: %d (IS FALSE (SKIPPED))", parameter);
}
break;
case CP_SKIPALWAYS:
// skip a block
Read32ipLeaveip(parameter);
ip += parameter;
debug(9, "CP_SKIPALWAYS: %d", parameter);
break;
case CP_SWITCH:
// switch
value = stack.pop();
Read32ip(caseCount);
// Search the cases
foundCase = false;
for (i = 0; i < caseCount && !foundCase; i++) {
if (value == (int32)READ_LE_UINT32(code + ip)) {
// We have found the case, so lets
// jump to it
foundCase = true;
ip += READ_LE_UINT32(code + ip + 4);
} else
ip += 4 * 2;
}
// If we found no matching case then use the default
if (!foundCase)
ip += READ_LE_UINT32(code + ip);
debug(9, "CP_SWITCH: [SORRY, NO DEBUG INFO]");
break;
case CP_SAVE_MCODE_START:
// Save the start position on an mcode instruction in
// case we need to restart it again
savedStartOfMcode = ip - 1;
debug(9, "CP_SAVE_MCODE_START");
break;
case CP_CALL_MCODE:
// Call an mcode routine
Read16ip(parameter);
assert(parameter < _numOpcodes);
// amount to adjust stack by (no of parameters)
Read8ip(value);
debug(9, "CP_CALL_MCODE: '%s', %d", _opcodes[parameter].desc, value);
// The scripts do not always call the mcode command
// with as many parameters as it can accept. To keep
// things predictable, initialize the remaining
// parameters to 0.
for (i = STACK_SIZE - 1; i >= value; i--) {
opcodeParams[i] = 0;
}
while (--value >= 0) {
opcodeParams[value] = stack.pop();
}
retVal = (this->*_opcodes[parameter].proc)(opcodeParams);
switch (retVal & 7) {
case IR_STOP:
// Quit out for a cycle
WRITE_LE_UINT32(offsetPtr, ip);
return 0;
case IR_CONT:
// Continue as normal
break;
case IR_TERMINATE:
if (checkPearlBug && readVar(1290) == 0) {
// Pearl's interaction script will wait
// until global(1290) is no longer 0
// before doing anything. But if the
// script was terminated prematurely,
// that never happens.
warning("Working around Pearl bug: Resetting Pearl's state");
writeVar(1290, 1);
}
// Return without updating the offset
return 2;
case IR_REPEAT:
// Return setting offset to start of this
// function call
WRITE_LE_UINT32(offsetPtr, savedStartOfMcode);
return 0;
case IR_GOSUB:
// that's really neat
WRITE_LE_UINT32(offsetPtr, ip);
return 2;
default:
error("Bad return code (%d) from '%s'", retVal & 7, _opcodes[parameter].desc);
}
parameterReturnedFromMcodeFunction = retVal >> 3;
break;
case CP_JUMP_ON_RETURNED:
// Jump to a part of the script depending on
// the return value from an mcode routine
// Get the maximum value
Read8ip(parameter);
debug(9, "CP_JUMP_ON_RETURNED: %d => %d",
parameterReturnedFromMcodeFunction,
READ_LE_UINT32(code + ip + parameterReturnedFromMcodeFunction * 4));
ip += READ_LE_UINT32(code + ip + parameterReturnedFromMcodeFunction * 4);
break;
// Operators
case OP_ISEQUAL:
b = stack.pop();
a = stack.pop();
stack.push(a == b);
debug(9, "OP_ISEQUAL: RESULT = %d", a == b);
break;
case OP_NOTEQUAL:
b = stack.pop();
a = stack.pop();
stack.push(a != b);
debug(9, "OP_NOTEQUAL: RESULT = %d", a != b);
break;
case OP_GTTHAN:
b = stack.pop();
a = stack.pop();
stack.push(a > b);
debug(9, "OP_GTTHAN: RESULT = %d", a > b);
break;
case OP_LSTHAN:
b = stack.pop();
a = stack.pop();
stack.push(a < b);
debug(9, "OP_LSTHAN: RESULT = %d", a < b);
break;
case OP_GTTHANE:
b = stack.pop();
a = stack.pop();
stack.push(a >= b);
debug(9, "OP_GTTHANE: RESULT = %d", a >= b);
break;
case OP_LSTHANE:
b = stack.pop();
a = stack.pop();
stack.push(a <= b);
debug(9, "OP_LSTHANE: RESULT = %d", a <= b);
break;
case OP_PLUS:
b = stack.pop();
a = stack.pop();
stack.push(a + b);
debug(9, "OP_PLUS: RESULT = %d", a + b);
break;
case OP_MINUS:
b = stack.pop();
a = stack.pop();
stack.push(a - b);
debug(9, "OP_MINUS: RESULT = %d", a - b);
break;
case OP_TIMES:
b = stack.pop();
a = stack.pop();
stack.push(a * b);
debug(9, "OP_TIMES: RESULT = %d", a * b);
break;
case OP_DIVIDE:
b = stack.pop();
a = stack.pop();
stack.push(a / b);
debug(9, "OP_DIVIDE: RESULT = %d", a / b);
break;
case OP_ANDAND:
b = stack.pop();
a = stack.pop();
stack.push(a && b);
debug(9, "OP_ANDAND: RESULT = %d", a && b);
break;
case OP_OROR:
b = stack.pop();
a = stack.pop();
stack.push(a || b);
debug(9, "OP_OROR: RESULT = %d", a || b);
break;
// Debugging opcodes, I think
case CP_DEBUGON:
debug(9, "CP_DEBUGON");
break;
case CP_DEBUGOFF:
debug(9, "CP_DEBUGOFF");
break;
case CP_TEMP_TEXT_PROCESS:
Read32ip(parameter);
debug(9, "CP_TEMP_TEXT_PROCESS: %d", parameter);
break;
default:
error("Invalid script command %d", curCommand);
return 3; // for compilers that don't support NORETURN
}
}
return 1;
}
} // End of namespace Sword2

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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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 SWORD2_INTERPRETER_H
#define SWORD2_INTERPRETER_H
#include "common/endian.h"
namespace Sword2 {
// Interpreter return codes
enum {
IR_STOP = 0, // Quit for a cycle
IR_CONT = 1, // Continue as normal
IR_TERMINATE = 2, // Return without updating the offset
IR_REPEAT = 3, // Return; offset at start of function call
IR_GOSUB = 4 // Return with updated offset
};
// Get parameter fix so that the playstation version can handle words not on
// word boundaries
#define Read8ip(var) { var = code[ip]; ip++; }
#define Read16ip(var) { var = (int16)READ_LE_UINT16(code + ip); ip += 2; }
#define Read32ip(var) { var = (int32)READ_LE_UINT32(code + ip); ip += 4; }
#define Read32ipLeaveip(var) { var = (int32)READ_LE_UINT32(code + ip); }
enum {
// Compiled tokens
CP_END_SCRIPT = 0,
CP_PUSH_LOCAL_VAR32 = 1, // Push a local variable on to the stack
CP_PUSH_GLOBAL_VAR32 = 2, // Push a global variable
CP_POP_LOCAL_VAR32 = 3, // Pop a local variable from the stack
CP_CALL_MCODE = 4, // Call a machine code function
CP_PUSH_LOCAL_ADDR = 5, // Push the address of a local variable
CP_PUSH_INT32 = 6, // Adjust the stack after calling an fn function
CP_SKIPONFALSE = 7, // Skip if the bottom value on the stack is false
CP_SKIPALWAYS = 8, // Skip a block of code
CP_SWITCH = 9, // Switch on last stack value
CP_ADDNPOP_LOCAL_VAR32 = 10, // Add to a local varible
CP_SUBNPOP_LOCAL_VAR32 = 11, // Subtract from a local variable
CP_SKIPONTRUE = 12, // Skip if the bottom value on the stack is true
CP_POP_GLOBAL_VAR32 = 13, // Pop a global variable
CP_ADDNPOP_GLOBAL_VAR32 = 14, // Add to a global variable
CP_SUBNPOP_GLOBAL_VAR32 = 15, // Subtract from a global variable
CP_DEBUGON = 16, // Turn debugging on
CP_DEBUGOFF = 17, // Turn debugging off
CP_QUIT = 18, // Quit for a cycle
CP_TERMINATE = 19, // Quit script completely
// Operators
OP_ISEQUAL = 20, // '=='
OP_PLUS = 21, // '+'
OP_MINUS = 22, // '-'
OP_TIMES = 23, // '*'
OP_DIVIDE = 24, // '/'
OP_NOTEQUAL = 25, // '=='
OP_ANDAND = 26, // '&&'
OP_GTTHAN = 27, // '>'
OP_LSTHAN = 28, // '<'
// More tokens, mixed types
CP_JUMP_ON_RETURNED = 29, // Use table of jumps with value returned from fn_mcode
CP_TEMP_TEXT_PROCESS = 30, // A dummy text process command for me
CP_SAVE_MCODE_START = 31, // Save the mcode code start for restarting when necessary
CP_RESTART_SCRIPT = 32, // Start the script from the beginning
CP_PUSH_STRING = 33, // Push a pointer to a string on the stack
CP_PUSH_DEREFERENCED_STRUCTURE = 34, // Push the address of a structure thing
OP_GTTHANE = 35, // >=
OP_LSTHANE = 36, // <=
OP_OROR = 37 // || or OR
};
} // End of namespace Sword2
#endif

295
engines/sword2/layers.cpp Normal file
View File

@@ -0,0 +1,295 @@
/* 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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/>.
*/
// high level layer initializing
// the system supports:
// 1 optional background parallax layer
// 1 not optional normal backdrop layer
// 3 normal sorted layers
// up to 2 foreground parallax layers
#include "common/rect.h"
#include "common/textconsole.h"
#include "sword2/sword2.h"
#include "sword2/defs.h"
#include "sword2/header.h"
#include "sword2/logic.h"
#include "sword2/mouse.h"
#include "sword2/resman.h"
#include "sword2/screen.h"
#include "sword2/sound.h"
namespace Sword2 {
/**
* This function is called when entering a new room.
* @param res resource id of the normal background layer
* @param new_palette 1 for new palette, otherwise 0
*/
void Screen::initBackground(int32 res, int32 new_palette) {
int i;
assert(res);
_vm->_sound->clearFxQueue(false);
waitForFade();
debug(1, "CHANGED TO LOCATION \"%s\"", _vm->_resman->fetchName(res));
// We have to clear this. Otherwise, if an exit warps back to the same
// room (e.g. the jungle maze), clicking on the same exit again will be
// misinterpreted as a double-click, and that only works if we're
// actually walking towards that exit. Otherwise, the game would hang.
_vm->_logic->writeVar(EXIT_CLICK_ID, 0);
// if last screen was using a shading mask (see below)
if (_thisScreen.mask_flag) {
if (closeLightMask() != RD_OK)
error("Could not close light mask");
}
// Close the previous screen, if one is open
if (_thisScreen.background_layer_id)
closeBackgroundLayer();
_thisScreen.background_layer_id = res;
_thisScreen.new_palette = new_palette;
// ok, now read the resource and pull out all the normal sort layer
// info/and set them up at the beginning of the sort list - why do it
// each cycle
byte *file = _vm->_resman->openResource(_thisScreen.background_layer_id);
ScreenHeader screen_head;
screen_head.read(_vm->fetchScreenHeader(file));
// set number of special sort layers
_thisScreen.number_of_layers = screen_head.noLayers;
_thisScreen.screen_wide = screen_head.width;
_thisScreen.screen_deep = screen_head.height;
debug(2, "layers=%d width=%d depth=%d", screen_head.noLayers, screen_head.width, screen_head.height);
// initialize the driver back buffer
setLocationMetrics(screen_head.width, screen_head.height);
for (i = 0; i < screen_head.noLayers; i++) {
debug(3, "init layer %d", i);
LayerHeader layer;
layer.read(_vm->fetchLayerHeader(file, i));
// Add the layer to the sort list. We only provide just enough
// information so that it's clear that it's a layer, and where
// to sort it in relation to other things in the list.
_sortList[i].layer_number = i + 1;
_sortList[i].sort_y = layer.y + layer.height;
}
// reset scroll offsets
_thisScreen.scroll_offset_x = 0;
_thisScreen.scroll_offset_y = 0;
if (screen_head.width > _screenWide || screen_head.height > _screenDeep) {
// The layer is larger than the physical screen. Switch on
// scrolling. (2 means first time on screen)
_thisScreen.scroll_flag = 2;
// Note: if we've already set the player up then we could do
// the initial scroll set here
// Calculate the maximum scroll offsets to prevent scrolling
// off the edge. The minimum offsets are both 0.
_thisScreen.max_scroll_offset_x = screen_head.width - _screenWide;
_thisScreen.max_scroll_offset_y = screen_head.height - (_screenDeep - (MENUDEEP * 2));
} else {
// The later fits on the phyiscal screen. Switch off scrolling.
_thisScreen.scroll_flag = 0;
}
resetRenderEngine();
// These are the physical screen coords where the system will try to
// maintain George's actual feet coords.
_thisScreen.feet_x = 320;
_thisScreen.feet_y = 340;
// shading mask
MultiScreenHeader screenLayerTable;
screenLayerTable.read(file + ResHeader::size());
if (screenLayerTable.maskOffset) {
SpriteInfo spriteInfo;
spriteInfo.x = 0;
spriteInfo.y = 0;
spriteInfo.w = screen_head.width;
spriteInfo.h = screen_head.height;
spriteInfo.scale = 0;
spriteInfo.scaledWidth = 0;
spriteInfo.scaledHeight = 0;
spriteInfo.type = 0;
spriteInfo.blend = 0;
spriteInfo.data = _vm->fetchShadingMask(file);
spriteInfo.colorTable = 0;
if (openLightMask(&spriteInfo) != RD_OK)
error("Could not open light mask");
// so we know to close it later! (see above)
_thisScreen.mask_flag = true;
} else {
// no need to close a mask later
_thisScreen.mask_flag = false;
}
// Background parallax layers
for (i = 0; i < 2; i++) {
if (screenLayerTable.bg_parallax[i])
initializeBackgroundLayer(_vm->fetchBackgroundParallaxLayer(file, i));
else
initializeBackgroundLayer(nullptr);
}
// Normal backround layer
initializeBackgroundLayer(_vm->fetchBackgroundLayer(file));
// Foreground parallax layers
for (i = 0; i < 2; i++) {
if (screenLayerTable.fg_parallax[i])
initializeBackgroundLayer(_vm->fetchForegroundParallaxLayer(file, i));
else
initializeBackgroundLayer(nullptr);
}
_vm->_resman->closeResource(_thisScreen.background_layer_id);
}
/**
* This function is called when entering a new room, PSX edition
* @param res resource id of the normal background layer
* @param new_palette 1 for new palette, otherwise 0
*/
void Screen::initPsxBackground(int32 res, int32 new_palette) {
int i;
assert(res);
_vm->_sound->clearFxQueue(false);
waitForFade();
debug(1, "CHANGED TO LOCATION \"%s\"", _vm->_resman->fetchName(res));
_vm->_logic->writeVar(EXIT_CLICK_ID, 0);
// Close the previous screen, if one is open
if (_thisScreen.background_layer_id)
closeBackgroundLayer();
_thisScreen.background_layer_id = res;
_thisScreen.new_palette = new_palette;
// ok, now read the resource and pull out all the normal sort layer
// info/and set them up at the beginning of the sort list - why do it
// each cycle
byte *file = _vm->_resman->openResource(_thisScreen.background_layer_id);
ScreenHeader screen_head;
screen_head.read(_vm->fetchScreenHeader(file));
screen_head.height *= 2;
// set number of special sort layers
_thisScreen.number_of_layers = screen_head.noLayers;
_thisScreen.screen_wide = screen_head.width;
_thisScreen.screen_deep = screen_head.height;
debug(2, "layers=%d width=%d depth=%d", screen_head.noLayers, screen_head.width, screen_head.height);
// initialize the driver back buffer
setLocationMetrics(screen_head.width, screen_head.height);
for (i = 0; i < screen_head.noLayers; i++) {
debug(3, "init layer %d", i);
LayerHeader layer;
layer.read(_vm->fetchLayerHeader(file, i));
_sortList[i].layer_number = i + 1;
_sortList[i].sort_y = layer.y + layer.height;
}
// reset scroll offsets
_thisScreen.scroll_offset_x = 0;
_thisScreen.scroll_offset_y = 0;
if (screen_head.width > _screenWide || screen_head.height > _screenDeep) {
_thisScreen.scroll_flag = 2;
_thisScreen.max_scroll_offset_x = screen_head.width - _screenWide;
_thisScreen.max_scroll_offset_y = screen_head.height - (_screenDeep - (MENUDEEP * 2));
} else {
// The later fits on the phyiscal screen. Switch off scrolling.
_thisScreen.scroll_flag = 0;
}
resetRenderEngine();
// These are the physical screen coords where the system will try to
// maintain George's actual feet coords.
_thisScreen.feet_x = 320;
_thisScreen.feet_y = 340;
// Background parallax layers
initializePsxParallaxLayer(_vm->fetchBackgroundParallaxLayer(file, 0));
initializePsxParallaxLayer(nullptr);
// Normal backround layer
initializePsxBackgroundLayer(_vm->fetchBackgroundLayer(file));
// Foreground parallax layers
initializePsxParallaxLayer(_vm->fetchForegroundParallaxLayer(file, 1));
initializePsxParallaxLayer(nullptr);
_vm->_resman->closeResource(_thisScreen.background_layer_id);
}
} // End of namespace Sword2

285
engines/sword2/logic.cpp Normal file
View File

@@ -0,0 +1,285 @@
/* 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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/textconsole.h"
#include "sword2/sword2.h"
#include "sword2/defs.h"
#include "sword2/header.h"
#include "sword2/logic.h"
#include "sword2/resman.h"
#include "sword2/router.h"
#include "sword2/sound.h"
namespace Sword2 {
Logic::Logic(Sword2Engine *vm) :
_vm(vm), _kills(0), _currentRunList(0), _moviePlayer(0),
_smackerLeadIn(0), _smackerLeadOut(0), _sequenceTextLines(0),
_speechTime(0), _animId(0), _speechAnimType(0), _leftClickDelay(0),
_rightClickDelay(0), _officialTextNumber(0), _speechTextBlocNo(0) {
_scriptVars = nullptr;
memset(_eventList, 0, sizeof(_eventList));
memset(_syncList, 0, sizeof(_syncList));
_router = new Router(_vm);
_cycleSkip = false;
_speechRunning = false;
setupOpcodes();
}
Logic::~Logic() {
delete _router;
}
/**
* Do one cycle of the current session.
*/
int Logic::processSession() {
// might change during the session, so take a copy here
uint32 run_list = _currentRunList;
_pc = 0; // first object in list
// by minusing the pc we can cause an immediate cessation of logic
// processing on the current list
while (_pc != 0xffffffff) {
byte *game_object_list, *head, *raw_script_ad, *raw_data_ad;
uint32 level, ret, script, id;
game_object_list = _vm->_resman->openResource(run_list) + ResHeader::size();
assert(_vm->_resman->fetchType(run_list) == RUN_LIST);
// read the next id
id = READ_LE_UINT32(game_object_list + 4 * _pc);
_pc++;
writeVar(ID, id);
_vm->_resman->closeResource(run_list);
if (!id) {
// End of list - end the session naturally
return 0;
}
assert(_vm->_resman->fetchType(id) == GAME_OBJECT);
head = _vm->_resman->openResource(id);
_curObjectHub.setAddress(head + ResHeader::size());
level = _curObjectHub.getLogicLevel();
debug(5, "Level %d id(%d) pc(%d)",
level,
_curObjectHub.getScriptId(level),
_curObjectHub.getScriptPc(level));
// Do the logic for this object. We keep going until a function
// says to stop - remember, system operations are run via
// function calls to drivers now.
do {
// There is a distinction between running one of our
// own scripts and that of another object.
level = _curObjectHub.getLogicLevel();
script = _curObjectHub.getScriptId(level);
if (script / SIZE == readVar(ID)) {
// It's our own script
debug(5, "Run script %d pc=%d",
script / SIZE,
_curObjectHub.getScriptPc(level));
// This is the script data. Script and data
// object are the same.
raw_script_ad = head;
ret = runScript2(raw_script_ad, raw_script_ad, _curObjectHub.getScriptPcPtr(level));
} else {
// We're running the script of another game
// object - get our data object address.
uint8 type = _vm->_resman->fetchType(script / SIZE);
assert(type == GAME_OBJECT || type == SCREEN_MANAGER);
raw_script_ad = _vm->_resman->openResource(script / SIZE);
raw_data_ad = head;
ret = runScript2(raw_script_ad, raw_data_ad, _curObjectHub.getScriptPcPtr(level));
_vm->_resman->closeResource(script / SIZE);
// reset to us for service script
raw_script_ad = raw_data_ad;
}
if (ret == 1) {
level = _curObjectHub.getLogicLevel();
// The script finished - drop down a level
if (level) {
_curObjectHub.setLogicLevel(level - 1);
} else {
// Hmmm, level 0 terminated :-| Let's
// be different this time and simply
// let it restart next go :-)
// Note that this really does happen a
// lot, so don't make it a warning.
debug(5, "object %d script 0 terminated", id);
// reset to rerun, drop out for a cycle
_curObjectHub.setScriptPc(level, _curObjectHub.getScriptId(level) & 0xffff);
ret = 0;
}
} else if (ret > 2) {
error("processSession: illegal script return type %d", ret);
}
// if ret == 2 then we simply go around again - a new
// script or subroutine will kick in and run
// keep processing scripts until 0 for quit is returned
} while (ret);
// Any post logic system requests to go here
// Clear any syncs that were waiting for this character - it
// has used them or now looses them
clearSyncs(readVar(ID));
if (_pc != 0xffffffff) {
// The session is still valid so run the graphics/mouse
// service script
runScript(raw_script_ad, raw_script_ad, 0);
}
// and that's it so close the object resource
_vm->_resman->closeResource(readVar(ID));
}
// Leaving a room so remove all ids that must reboot correctly. Then
// restart the loop.
for (uint32 i = 0; i < _kills; i++)
_vm->_resman->remove(_objectKillList[i]);
resetKillList();
return 1;
}
/**
* Bring an immediate halt to the session and cause a new one to start without
* a screen update.
*/
void Logic::expressChangeSession(uint32 sesh_id) {
// Set new session and force the old one to quit.
_currentRunList = sesh_id;
_pc = 0xffffffff;
// Reset now in case we double-clicked an exit prior to changing screen
writeVar(EXIT_FADING, 0);
// We're trashing the list - presumably to change room. In theory,
// sync waiting in the list could be left behind and never removed -
// so we trash the lot
memset(_syncList, 0, sizeof(_syncList));
// Various clean-ups
_router->clearWalkGridList();
_vm->_sound->clearFxQueue(false);
_router->freeAllRouteMem();
}
/**
* @return The private _currentRunList variable.
*/
uint32 Logic::getRunList() {
return _currentRunList;
}
/**
* Move the current object up a level. Called by fnGosub command. Remember:
* only the logic object has access to _curObjectHub.
*/
void Logic::logicUp(uint32 new_script) {
debug(5, "new pc = %d", new_script & 0xffff);
// going up a level - and we'll keep going this cycle
_curObjectHub.setLogicLevel(_curObjectHub.getLogicLevel() + 1);
assert(_curObjectHub.getLogicLevel() < 3); // Can be 0, 1, 2
logicReplace(new_script);
}
/**
* Force the level to one.
*/
void Logic::logicOne(uint32 new_script) {
_curObjectHub.setLogicLevel(1);
logicReplace(new_script);
}
/**
* Change current logic. Script must quit with a TERMINATE directive, which
* does not write to &pc
*/
void Logic::logicReplace(uint32 new_script) {
uint32 level = _curObjectHub.getLogicLevel();
_curObjectHub.setScriptId(level, new_script);
_curObjectHub.setScriptPc(level, new_script & 0xffff);
}
void Logic::resetKillList() {
_kills = 0;
}
/**
* Read current location number from script vars
*/
uint32 Logic::getLocationNum() {
return readVar(LOCATION);
}
} // End of namespace Sword2

322
engines/sword2/logic.h Normal file
View File

@@ -0,0 +1,322 @@
/* 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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/>.
*/
// logic management
#ifndef SWORD2_LOGIC_H
#define SWORD2_LOGIC_H
#include "common/endian.h"
#include "sword2/animation.h"
#include "sword2/memory.h"
namespace Sword2 {
#define MAX_events 10
#define TREE_SIZE 3
// This must allow for the largest number of objects in a screen
#define OBJECT_KILL_LIST_SIZE 50
#define MAX_SEQUENCE_TEXT_LINES 15
class Sword2Engine;
class Router;
struct EventUnit {
uint32 id;
uint32 interact_id;
};
class Logic {
private:
Sword2Engine *_vm;
inline byte *decodePtr(int32 n) {
return _vm->_memory->decodePtr(n);
}
uint32 _objectKillList[OBJECT_KILL_LIST_SIZE];
// keeps note of no. of objects in the kill list
uint32 _kills;
// denotes the res id of the game-object-list in current use
uint32 _currentRunList;
//pc during logic loop
uint32 _pc;
// each object has one of these tacked onto the beginning
ObjectHub _curObjectHub;
EventUnit _eventList[MAX_events];
MoviePlayer *_moviePlayer;
// Resource id of the wav to use as lead-in/lead-out from smacker
uint32 _smackerLeadIn;
uint32 _smackerLeadOut;
// keeps count of number of text lines to disaply during the sequence
uint32 _sequenceTextLines;
MovieText _sequenceTextList[MAX_SEQUENCE_TEXT_LINES];
// when not playing a wav we calculate the speech time based upon
// length of ascii
uint32 _speechTime;
uint32 _animId;
// 0 lip synced and repeating - 1 normal once through
uint32 _speechAnimType;
uint32 _leftClickDelay; // click-delay for LEFT mouse button
uint32 _rightClickDelay; // click-delay for RIGHT mouse button
// calculated by locateTalker() for use in speech-panning & text-sprite
// positioning
int16 _textX, _textY;
void locateTalker(int32 *params);
void formText(int32 *params);
bool wantSpeechForLine(uint32 wavId);
// Set by fnPassMega()
byte _engineMega[56];
bool _cycleSkip;
bool _speechRunning;
public:
Logic(Sword2Engine *vm);
~Logic();
EventUnit *getEventList() { return _eventList; }
byte *getEngineMega() { return _engineMega; }
byte _saveLogic[8];
byte _saveGraphic[12];
byte _saveMega[56];
// Point to the global variable data
byte *_scriptVars;
// "TEXT" - current official text line number - will match the wav
// filenames
int16 _officialTextNumber;
// so speech text cleared when running a new start-script
uint32 _speechTextBlocNo;
uint32 readVar(int n) {
return READ_LE_UINT32(_scriptVars + 4 * n);
}
void writeVar(int n, uint32 value) {
WRITE_LE_UINT32(_scriptVars + 4 * n, value);
}
int runResScript(uint32 scriptRes, uint32 offset);
int runResObjScript(uint32 scriptRes, uint32 objRes, uint32 offset);
int runScript(byte *scriptData, byte *objectData, uint32 offset);
int runScript2(byte *scriptData, byte *objectData, byte *offset);
void sendEvent(uint32 id, uint32 interact_id);
void setPlayerActionEvent(uint32 id, uint32 interact_id);
void startEvent();
int checkEventWaiting();
void clearEvent(uint32 id);
void killAllIdsEvents(uint32 id);
uint32 countEvents();
struct SyncUnit {
uint32 id;
uint32 sync;
};
// There won't be many, will there? Probably 2 at most i reckon
SyncUnit _syncList[10];
void clearSyncs(uint32 id);
void sendSync(uint32 id, uint32 sync);
int getSync();
Router *_router;
typedef int32 (Logic::*OpcodeProc)(int32 *);
struct OpcodeEntry {
OpcodeProc proc;
const char *desc;
};
const OpcodeEntry *_opcodes;
int _numOpcodes;
void setupOpcodes();
int32 fnTestFunction(int32 *params);
int32 fnTestFlags(int32 *params);
int32 fnRegisterStartPoint(int32 *params);
int32 fnInitBackground(int32 *params);
int32 fnSetSession(int32 *params);
int32 fnBackSprite(int32 *params);
int32 fnSortSprite(int32 *params);
int32 fnForeSprite(int32 *params);
int32 fnRegisterMouse(int32 *params);
int32 fnAnim(int32 *params);
int32 fnRandom(int32 *params);
int32 fnPreLoad(int32 *params);
int32 fnAddSubject(int32 *params);
int32 fnInteract(int32 *params);
int32 fnChoose(int32 *params);
int32 fnWalk(int32 *params);
int32 fnWalkToAnim(int32 *params);
int32 fnTurn(int32 *params);
int32 fnStandAt(int32 *params);
int32 fnStand(int32 *params);
int32 fnStandAfterAnim(int32 *params);
int32 fnPause(int32 *params);
int32 fnMegaTableAnim(int32 *params);
int32 fnAddMenuObject(int32 *params);
int32 fnStartConversation(int32 *params);
int32 fnEndConversation(int32 *params);
int32 fnSetFrame(int32 *params);
int32 fnRandomPause(int32 *params);
int32 fnRegisterFrame(int32 *params);
int32 fnNoSprite(int32 *params);
int32 fnSendSync(int32 *params);
int32 fnUpdatePlayerStats(int32 *params);
int32 fnPassGraph(int32 *params);
int32 fnInitFloorMouse(int32 *params);
int32 fnPassMega(int32 *params);
int32 fnFaceXY(int32 *params);
int32 fnEndSession(int32 *params);
int32 fnNoHuman(int32 *params);
int32 fnAddHuman(int32 *params);
int32 fnWeWait(int32 *params);
int32 fnTheyDoWeWait(int32 *params);
int32 fnTheyDo(int32 *params);
int32 fnWalkToTalkToMega(int32 *params);
int32 fnFadeDown(int32 *params);
int32 fnISpeak(int32 *params);
int32 fnTotalRestart(int32 *params);
int32 fnSetWalkGrid(int32 *params);
int32 fnSpeechProcess(int32 *params);
int32 fnSetScaling(int32 *params);
int32 fnStartEvent(int32 *params);
int32 fnCheckEventWaiting(int32 *params);
int32 fnRequestSpeech(int32 *params);
int32 fnGosub(int32 *params);
int32 fnTimedWait(int32 *params);
int32 fnPlayFx(int32 *params);
int32 fnStopFx(int32 *params);
int32 fnPlayMusic(int32 *params);
int32 fnStopMusic(int32 *params);
int32 fnSetValue(int32 *params);
int32 fnNewScript(int32 *params);
int32 fnGetSync(int32 *params);
int32 fnWaitSync(int32 *params);
int32 fnRegisterWalkGrid(int32 *params);
int32 fnReverseMegaTableAnim(int32 *params);
int32 fnReverseAnim(int32 *params);
int32 fnAddToKillList(int32 *params);
int32 fnSetStandbyCoords(int32 *params);
int32 fnBackPar0Sprite(int32 *params);
int32 fnBackPar1Sprite(int32 *params);
int32 fnForePar0Sprite(int32 *params);
int32 fnForePar1Sprite(int32 *params);
int32 fnSetPlayerActionEvent(int32 *params);
int32 fnSetScrollCoordinate(int32 *params);
int32 fnStandAtAnim(int32 *params);
int32 fnSetScrollLeftMouse(int32 *params);
int32 fnSetScrollRightMouse(int32 *params);
int32 fnColor(int32 *params);
int32 fnFlash(int32 *params);
int32 fnPreFetch(int32 *params);
int32 fnGetPlayerSaveData(int32 *params);
int32 fnPassPlayerSaveData(int32 *params);
int32 fnSendEvent(int32 *params);
int32 fnAddWalkGrid(int32 *params);
int32 fnRemoveWalkGrid(int32 *params);
int32 fnCheckForEvent(int32 *params);
int32 fnPauseForEvent(int32 *params);
int32 fnClearEvent(int32 *params);
int32 fnFaceMega(int32 *params);
int32 fnPlaySequence(int32 *params);
int32 fnShadedSprite(int32 *params);
int32 fnUnshadedSprite(int32 *params);
int32 fnFadeUp(int32 *params);
int32 fnDisplayMsg(int32 *params);
int32 fnSetObjectHeld(int32 *params);
int32 fnAddSequenceText(int32 *params);
int32 fnResetGlobals(int32 *params);
int32 fnSetPalette(int32 *params);
int32 fnRegisterPointerText(int32 *params);
int32 fnFetchWait(int32 *params);
int32 fnRelease(int32 *params);
int32 fnPrepareMusic(int32 *params);
int32 fnSoundFetch(int32 *params);
int32 fnSmackerLeadIn(int32 *params);
int32 fnSmackerLeadOut(int32 *params);
int32 fnStopAllFx(int32 *params);
int32 fnCheckPlayerActivity(int32 *params);
int32 fnResetPlayerActivityDelay(int32 *params);
int32 fnCheckMusicPlaying(int32 *params);
int32 fnPlayCredits(int32 *params);
int32 fnSetScrollSpeedNormal(int32 *params);
int32 fnSetScrollSpeedSlow(int32 *params);
int32 fnRemoveChooser(int32 *params);
int32 fnSetFxVolAndPan(int32 *params);
int32 fnSetFxVol(int32 *params);
int32 fnRestoreGame(int32 *params);
int32 fnRefreshInventory(int32 *params);
int32 fnChangeShadows(int32 *params);
// do one cycle of the current session
int processSession();
// cause the logic loop to terminate and drop out
void expressChangeSession(uint32 sesh_id);
uint32 getRunList();
// setup script_id and script_pc in _curObjectHub - called by fnGosub()
void logicUp(uint32 new_script);
void logicReplace(uint32 new_script);
void logicOne(uint32 new_script);
void resetKillList();
// Read location number from script vars
uint32 getLocationNum();
};
} // End of namespace Sword2
#endif

924
engines/sword2/maketext.cpp Normal file
View File

@@ -0,0 +1,924 @@
/* 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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/>.
*/
// MAKETEXT - Constructs a single-frame text sprite: returns a handle to a
// FLOATING memory block containing the sprite, given a
// null-terminated string, max width allowed, pen color and
// pointer to required character set.
//
// NB 1) The routine does not create a standard file header or
// an anim header for the text sprite - the data simply begins
// with the frame header.
//
// NB 2) If pen color is zero, it copies the characters into
// the sprite without remapping the colors.
// ie. It can handle both the standard 2-color font for speech
// and any multicolored fonts for control panels, etc.
//
// Based on textsprt.c as used for Broken Sword 1, but updated
// for new system by JEL on 9oct96 and updated again (for font
// as a resource) on 5dec96.
#include "common/system.h"
#include "common/unicode-bidi.h"
#include "common/textconsole.h"
#include "common/file.h"
#include "sword2/sword2.h"
#include "sword2/defs.h"
#include "sword2/header.h"
#include "sword2/logic.h"
#include "sword2/maketext.h"
#include "sword2/resman.h"
#include "sword2/screen.h"
namespace Sword2 {
#define MAX_LINES 30 // max character lines in output sprite
#define BORDER_COL 200 // source color for character border (only
// needed for remapping colors)
#define LETTER_COL 193 // source color for bulk of character ( " )
#define LETTER_COL_PSX1 33
#define LETTER_COL_PSX2 34
#define SPACE ' '
#define FIRST_CHAR SPACE // first character in character set
#define LAST_CHAR 255 // last character in character set
#define DUD 64 // the first "chequered flag" (dud) symbol in
// our character set is in the '@' position
#define KOREAN_CHAR_WIDTH 20
#define KOREAN_CHAR_HEIGHT 26
namespace {
Common::String readLine(Common::ReadStream &stream) {
Common::String ret = stream.readString('\n');
if (ret.hasSuffix("\r"))
ret = ret.substr(0, ret.size() - 1);
return ret;
}
} // end of anonymous namespace
void FontRenderer::loadTranslations() {
Common::File bs2en, bs2, font;
if(bs2en.open("sub/bs2_en.dat") && bs2.open("sub/bs2.dat") && font.open("font/bs1.fnt")) {
while (!bs2.eos() && !bs2en.eos()) {
Common::String id = readLine(bs2);
Common::String val = readLine(bs2);
Common::String valen = readLine(bs2en);
Common::String iden = readLine(bs2en);
if (val.empty() || valen.empty())
continue;
debug(5, "id: %s->%s", Common::U32String(iden, Common::CodePage::kWindows936).encode().c_str(),
Common::U32String(id, Common::CodePage::kWindows936).encode().c_str());
debug(5, "val: %s->%s", Common::U32String(valen, Common::CodePage::kWindows936).encode().c_str(),
Common::U32String(val, Common::CodePage::kWindows936).encode().c_str());
_subTranslations[valen] = val;
}
while (!font.eos()) {
ChineseGlyph glyph;
if (font.read(glyph.bitmap, sizeof (glyph.bitmap)) != sizeof (glyph.bitmap))
break;
_chineseFont.push_back(glyph);
}
}
}
/**
* This function creates a new text sprite. The sprite data contains a
* FrameHeader, but not a standard file header.
*
* @param sentence pointer to a null-terminated string
* @param maxWidth the maximum allowed text sprite width in pixels
* @param pen the text color, or zero to use the source colors
* @param fontRes the font resource id
* @param border the border color; black by default
* @return a handle to a floating memory block containing the text sprite
* @note The sentence must contain no leading, trailing or extra spaces.
* Out-of-range characters in the string are replaced by a special
* error-signal character (chequered flag)
*/
byte *FontRenderer::makeTextSprite(const byte *sentence, uint16 maxWidth, uint8 pen, uint32 fontRes, uint8 border) {
// Keep this at the function scope to make sure we hold a reference even in case of unintentional copies.
// Normally we should keep using the copy from hashmap.
Common::String translatedSentence;
debug(5, "makeTextSprite(\"%s\", maxWidth=%u)", sentence, maxWidth);
bool isTranslated = false;
_borderPen = border;
if (!_subTranslations.empty()) {
if (_subTranslations.tryGetVal(Common::String((const char *)sentence), translatedSentence)) {
isTranslated = true;
debug(5, "Translating <%s> -> <%s>", sentence, translatedSentence.decode(Common::CodePage::kWindows936).encode().c_str());
sentence = (const byte *)translatedSentence.c_str();
} else {
debug(5, "Keeping <%s> untranslated", sentence);
}
}
// Line- and character spacing are hard-wired, rather than being part
// of the resource.
if (fontRes == _vm->_speechFontId) {
if (Sword2Engine::isPsx())
_lineSpacing = -4; // Text would be unreadable with psx font if linespacing is higher
else
_lineSpacing = -6;
_charSpacing = -3;
} else if (fontRes == CONSOLE_FONT_ID) {
_lineSpacing = 0;
_charSpacing = 1;
} else {
_lineSpacing = 0;
_charSpacing = 0;
}
// Allocate memory for array of lineInfo structures
byte *line = (byte *)malloc(MAX_LINES * sizeof(LineInfo));
// Get details of sentence breakdown into array of LineInfo structures
// and get the number of lines involved
uint16 noOfLines = analyzeSentence(sentence, maxWidth, fontRes, (LineInfo *)line, isTranslated);
// Construct the sprite based on the info gathered - returns floating
// mem block
byte *textSprite = buildTextSprite(sentence, fontRes, pen, (LineInfo *)line, noOfLines, isTranslated);
free(line);
return textSprite;
}
uint16 FontRenderer::analyzeSentence(const byte *sentence, uint16 maxWidth, uint32 fontRes, LineInfo *line, bool isChinese) {
// joinWidth = how much extra space is needed to append a word to a
// line. NB. SPACE requires TWICE the '_charSpacing' to join a word
// to line
uint16 joinWidth = charWidth(SPACE, fontRes) + 2 * _charSpacing;
uint16 lineNo = 0;
uint16 pos = 0;
bool firstWord = true;
line[0].skipSpace = false;
line[0].width = 0;
line[0].length = 0;
do {
uint16 wordWidth = 0;
uint16 wordLength = 0;
// Calculate the width of the word.
while (1) {
byte ch = sentence[pos];
if (ch == 0 || ch == SPACE)
break;
int w = 0, l = 0;
if (isChinese && (ch & 0x80)) {
w = kChineseWidth + _charSpacing;
l = 2;
} else if (isKoreanChar(ch, sentence[pos+1], fontRes)) {
w += wcharWidth(ch, sentence[pos+1], fontRes) + _charSpacing;
l = 2;
} else {
w = charWidth(ch, fontRes) + _charSpacing;
l = 1;
}
wordWidth += w;
wordLength += l;
pos += l;
}
// Don't include any character spacing at the end of the word.
wordWidth -= _charSpacing;
// 'ch' is now the SPACE or NULL following the word
// 'pos' indexes to the position following 'ch'
// Word longer than line. Happens for Chinese since it doesn't use spaces.
if (wordWidth > maxWidth) {
pos -= wordLength;
// Add separator if needed.
if (!firstWord) {
byte ch = sentence[pos];
uint16 spaceNeededForOneCharacter = joinWidth;
if (isChinese && (ch & 0x80)) {
spaceNeededForOneCharacter += kChineseWidth + _charSpacing;
}
spaceNeededForOneCharacter += charWidth(ch, fontRes) + _charSpacing;
if (line[lineNo].width + spaceNeededForOneCharacter <= maxWidth) {
// The separator fits on this line.
line[lineNo].width += spaceNeededForOneCharacter;
line[lineNo].length += (1 + spaceNeededForOneCharacter);
line[lineNo].skipSpace = false;
} else {
// The word spills over to the next line, i.e.
// no separating space.
line[lineNo].skipSpace = true;
lineNo++;
assert(lineNo < MAX_LINES);
line[lineNo].width = wordWidth;
line[lineNo].length = wordLength;
line[lineNo].skipSpace = false;
}
}
while (1) {
byte ch = sentence[pos];
if (ch == 0 || ch == SPACE)
break;
int w = 0, l = 0;
if (isChinese && (ch & 0x80)) {
w = kChineseWidth + _charSpacing;
l = 2;
} else {
w = charWidth(ch, fontRes) + _charSpacing;
l = 1;
}
if (line[lineNo].width + w <= maxWidth) {
line[lineNo].width += w;
line[lineNo].length += l;
} else {
line[lineNo].skipSpace = false;
lineNo++;
line[lineNo].skipSpace = false;
line[lineNo].width = w;
line[lineNo].length = l;
}
pos += l;
}
continue;
}
while (sentence[pos] == SPACE)
pos++;
if (firstWord) {
// This is the first word on the line, so no separating
// space is needed.
line[0].width = wordWidth;
line[0].length = wordLength;
line[0].skipSpace = false;
firstWord = false;
} else {
// See how much extra space this word will need to
// fit on current line (with a separating space
// character - also overlapped)
uint16 spaceNeeded = joinWidth + wordWidth;
if (line[lineNo].width + spaceNeeded <= maxWidth) {
// The word fits on this line.
line[lineNo].width += spaceNeeded;
line[lineNo].length += (1 + wordLength);
} else {
// The word spills over to the next line, i.e.
// no separating space.
line[lineNo].skipSpace = true;
lineNo++;
assert(lineNo < MAX_LINES);
line[lineNo].width = wordWidth;
line[lineNo].length = wordLength;
line[lineNo].skipSpace = false;
}
}
} while (sentence[pos]);
return lineNo + 1;
}
/**
* This function creates a new text sprite in a movable memory block. It must
* be locked before use, i.e. lock, draw sprite, unlock/free. The sprite data
* contains a FrameHeader, but not a standard file header.
*
* @param sentence pointer to a null-terminated string
* @param fontRes the font resource id
* @param pen the text color, or zero to use the source colors
* @param line array of LineInfo structures, created by analyzeSentence()
* @param noOfLines the number of lines, i.e. the number of elements in 'line'
* @return a handle to a floating memory block containing the text sprite
* @note The sentence must contain no leading, trailing or extra spaces.
* Out-of-range characters in the string are replaced by a special
* error-signal character (chequered flag)
*/
byte *FontRenderer::buildTextSprite(const byte *sentence, uint32 fontRes, uint8 pen, LineInfo *line, uint16 noOfLines, bool isChinese) {
uint16 i;
// Find the width of the widest line in the output text
uint16 spriteWidth = 0;
for (i = 0; i < noOfLines; i++)
if (line[i].width > spriteWidth)
spriteWidth = line[i].width;
// Check that text sprite has even horizontal resolution in PSX version
// (needed to work around a problem in some sprites, which reports an odd
// number as horiz resolution, but then have the next even number as true width)
if (Sword2Engine::isPsx())
spriteWidth = (spriteWidth % 2) ? spriteWidth + 1 : spriteWidth;
// Find the total height of the text sprite: the total height of the
// text lines, plus the total height of the spacing between them.
uint16 char_height = charHeight(fontRes);
uint16 spriteHeight = char_height * noOfLines + _lineSpacing * (noOfLines - 1);
// Allocate memory for the text sprite
uint32 sizeOfSprite = spriteWidth * spriteHeight;
byte *textSprite = (byte *)malloc(FrameHeader::size() + sizeOfSprite);
// At this stage, textSprite points to an unmovable memory block. Set
// up the frame header.
FrameHeader frame_head;
frame_head.compSize = 0;
frame_head.width = spriteWidth;
frame_head.height = spriteHeight;
// Normally for PSX frame header we double the height
// of the sprite artificially to regain correct aspect
// ratio, but this is an "artificially generated" text
// sprite, which gets created with correct aspect, so
// fix the height.
if (Sword2Engine::isPsx())
frame_head.height /= 2;
frame_head.write(textSprite);
debug(4, "Text sprite size: %ux%u", spriteWidth, spriteHeight);
// Clear the entire sprite to make it transparent.
byte *linePtr = textSprite + FrameHeader::size();
memset(linePtr, 0, sizeOfSprite);
byte *charSet = _vm->_resman->openResource(fontRes);
// Build the sprite, one line at a time
for (i = 0; i < noOfLines; i++) {
// Center each line
byte *spritePtr = linePtr + (spriteWidth - line[i].width) / 2;
const byte *currTxtLine = sentence;
Common::String reversedString;
if (_vm->_isRTL) {
const Common::String textLogical((const char *)currTxtLine, line[i].length);
reversedString = Common::convertBiDiString(textLogical, Common::kWindows1255);
currTxtLine = reinterpret_cast<const byte *>(reversedString.c_str());
}
// copy the sprite for each character in this line to the
// text sprite and inc the sprite ptr by the character's
// width minus the 'overlap'
for (uint j = 0; j < line[i].length; j++) {
byte ch = *currTxtLine++;
if (isChinese && (ch & 0x80)) {
byte low = *currTxtLine++;
int fullidx;
j++;
if (ch >= 0xa1 && ch <= 0xfe
&& low >= 0xa1 && low <= 0xfe)
fullidx = (ch - 0xa1) * 94 + (low - 0xa1);
else
fullidx = -1;
if (fullidx < 0 || fullidx >= (int)_chineseFont.size())
fullidx = 2 * 94 + 30; // Question mark
assert(kChineseHeight == char_height);
copyCharRaw(_chineseFont[fullidx].bitmap, kChineseWidth, kChineseHeight, spritePtr, spriteWidth, pen);
spritePtr += kChineseWidth + _charSpacing;
continue;
}
byte *charPtr = nullptr;
if (isKoreanChar(ch, *currTxtLine, fontRes)) {
charPtr = findWChar(ch, *currTxtLine++, charSet);
j++;
frame_head.width = KOREAN_CHAR_WIDTH;
copyWChar(charPtr, spritePtr, spriteWidth, pen);
} else {
charPtr = findChar(ch, charSet);
frame_head.read(charPtr);
assert(frame_head.height == char_height);
copyChar(charPtr, spritePtr, spriteWidth, pen);
}
// We must remember to free memory for generated character in psx,
// as it is extracted differently than pc version (copyed from a
// char atlas).
if (Sword2Engine::isPsx())
free(charPtr);
spritePtr += frame_head.width + _charSpacing;
}
sentence += line[i].length;
// Skip space at end of last word in this line
if (line[i].skipSpace)
sentence++;
if (Sword2Engine::isPsx())
linePtr += (char_height / 2 + _lineSpacing) * spriteWidth;
else
linePtr += (char_height + _lineSpacing) * spriteWidth;
}
_vm->_resman->closeResource(fontRes);
return textSprite;
}
/**
* @param ch the ASCII code of the character
* @param fontRes the font resource id
* @return the width of the character
*/
uint16 FontRenderer::charWidth(byte ch, uint32 fontRes) {
byte *charSet = _vm->_resman->openResource(fontRes);
byte *charBuf;
FrameHeader frame_head;
charBuf = findChar(ch, charSet);
frame_head.read(charBuf);
if (Sword2Engine::isPsx())
free(charBuf);
_vm->_resman->closeResource(fontRes);
return frame_head.width;
}
/**
* @param hi the KSX1001 code upper byte of the character
* @param lo the KSX1001 code lower byte of the character
* @param fontRes the font resource id
* @return the width of the character
*/
uint16 FontRenderer::wcharWidth(byte hi, byte lo, uint32 fontRes) {
if (isKoreanChar(hi, lo, fontRes)) {
return KOREAN_CHAR_WIDTH;
}
return charWidth(hi, fontRes) + charWidth(lo, fontRes);
}
/**
* @param fontRes the font resource id
* @return the height of a character sprite
* @note All characters in a font are assumed to have the same height, so
* there is no need to specify which one to look at.
*/
// Returns the height of a character sprite, given the character's ASCII code
// and a pointer to the start of the character set.
uint16 FontRenderer::charHeight(uint32 fontRes) {
byte *charSet = _vm->_resman->openResource(fontRes);
byte *charbuf;
FrameHeader frame_head;
charbuf = findChar(FIRST_CHAR, charSet);
frame_head.read(charbuf);
if (Sword2Engine::isPsx())
free(charbuf);
_vm->_resman->closeResource(fontRes);
return frame_head.height;
}
/**
* @param ch the ASCII code of the character to find
* @param charSet pointer to the start of the character set
* @return pointer to the requested character or, if it's out of range, the
* 'dud' character (chequered flag)
*/
byte *FontRenderer::findChar(byte ch, byte *charSet) {
// PSX version doesn't use an animation table to keep all letters,
// instead a big sprite (char atlas) is used, and the single char
// must be extracted from that.
if (Sword2Engine::isPsx()) {
byte *buffer;
PSXFontEntry header;
FrameHeader bogusHeader;
charSet += ResHeader::size() + 2;
if (ch < FIRST_CHAR)
ch = DUD;
// Read font entry of the corresponding char.
header.read(charSet + PSXFontEntry::size() * (ch - 32));
// We have no such character, generate an empty one
// on the fly, size 6x12.
if (header.charWidth == 0) {
// Prepare a "bogus" FrameHeader to be returned with
// "empty" character data.
bogusHeader.compSize = 0;
bogusHeader.width = 6;
bogusHeader.height = 12;
buffer = (byte *)malloc(24 * 3 + FrameHeader::size());
memset(buffer, 0, 24 * 3 + FrameHeader::size());
bogusHeader.write(buffer);
return buffer;
}
buffer = (byte *)malloc(FrameHeader::size() + header.charWidth * header.charHeight * 4);
byte *tempchar = (byte *)malloc(header.charWidth * header.charHeight);
// Prepare the "bogus" header to be returned with character
bogusHeader.compSize = 0;
bogusHeader.width = header.charWidth * 2;
bogusHeader.height = header.charHeight;
// Go to the beginning of char atlas
charSet += 2062;
memset(buffer, 0, FrameHeader::size() + header.charWidth * header.charHeight * 4);
bogusHeader.write(buffer);
// Copy and stretch the char into destination buffer
for (int idx = 0; idx < header.charHeight; idx++) {
memcpy(tempchar + header.charWidth * idx, charSet + header.offset + 128 * (header.skipLines + idx), header.charWidth);
}
for (int line = 0; line < header.charHeight; line++) {
for (int col = 0; col < header.charWidth; col++) {
*(buffer + FrameHeader::size() + line * bogusHeader.width + col * 2) = *(tempchar + line * header.charWidth + col);
*(buffer + FrameHeader::size() + line * bogusHeader.width + col * 2 + 1) = *(tempchar + line * header.charWidth + col);
}
}
free(tempchar);
return buffer;
} else {
if (ch < FIRST_CHAR)
ch = DUD;
return _vm->fetchFrameHeader(charSet, ch - FIRST_CHAR);
}
}
byte *FontRenderer::findWChar(byte hi, byte lo, byte *charSet) {
uint16 frameWidth = KOREAN_CHAR_WIDTH;
uint16 frameHeight = KOREAN_CHAR_HEIGHT;
FrameHeader frame_head;
byte *charPtr = findChar(0xFF, charSet);
frame_head.read(charPtr);
return charPtr + frame_head.size() + frame_head.width * frame_head.height + ((hi - 0xB0) * 94 + (lo - 0xA1)) * frameWidth * frameHeight;
}
/**
* Copies a character sprite to the sprite buffer.
* @param charPtr pointer to the character sprite
* @param spritePtr pointer to the sprite buffer
* @param spriteWidth the width of the character
* @param pen If zero, copy the data directly. Otherwise remap the
* sprite's colors from BORDER_COL to _borderPen and from
* LETTER_COL to pen.
*/
void FontRenderer::copyChar(const byte *charPtr, byte *spritePtr, uint16 spriteWidth, uint8 pen) {
FrameHeader frame;
frame.read(charPtr);
copyCharRaw(charPtr + FrameHeader::size(), frame.width, frame.height, spritePtr, spriteWidth, pen);
}
void FontRenderer::copyWChar(const byte *charPtr, byte *spritePtr, uint16 spriteWidth, uint8 pen) {
copyCharRaw(charPtr, KOREAN_CHAR_WIDTH, KOREAN_CHAR_HEIGHT, spritePtr, spriteWidth, pen);
}
void FontRenderer::copyCharRaw(const byte *source, uint16 charWidth, uint16 charHeight, byte *spritePtr, uint16 spriteWidth, uint8 pen) {
byte *rowPtr = spritePtr;
for (uint i = 0; i < charHeight; i++) {
byte *dest = rowPtr;
if (pen) {
// Use the specified colors
for (uint j = 0; j < charWidth; j++) {
switch (*source++) {
case 0:
// Do nothing if source pixel is zero,
// ie. transparent
break;
case LETTER_COL_PSX1: // Values for colored zone
case LETTER_COL_PSX2:
case LETTER_COL:
*dest = pen;
break;
case BORDER_COL:
default:
// Don't do a border pixel if there's
// already a bit of another character
// underneath (for overlapping!)
if (!*dest)
*dest = _borderPen;
break;
}
dest++;
}
} else {
// Pen is zero, so just copy character sprites
// directly into text sprite without remapping colors.
// Apparently overlapping is never considered here?
memcpy(dest, source, charWidth);
source += charWidth;
}
rowPtr += spriteWidth;
}
}
bool FontRenderer::isKoreanChar(byte hi, byte lo, uint32 fontRes) {
if (!_vm->_isKorTrs || fontRes != ENGLISH_SPEECH_FONT_ID)
return false;
if (hi >= 0xB0 && hi <= 0xC8 && lo >= 0xA1 && lo <= 0xFE)
return true;
return false;
}
// Distance to keep speech text from edges of screen
#define TEXT_MARGIN 12
/**
* Creates a text bloc in the list and returns the bloc number. The list of
* blocs is read and blitted at render time. Choose alignment type
* RDSPR_DISPLAYALIGN or 0
*/
uint32 FontRenderer::buildNewBloc(byte *ascii, int16 x, int16 y, uint16 width, uint8 pen, uint32 type, uint32 fontRes, uint8 justification) {
uint32 i = 0;
while (i < MAX_text_blocs && _blocList[i].text_mem)
i++;
assert(i < MAX_text_blocs);
// Create and position the sprite
_blocList[i].text_mem = makeTextSprite(ascii, width, pen, fontRes);
// 'NO_JUSTIFICATION' means print sprite with top-left at (x,y)
// without margin checking - used for debug text
if (justification != NO_JUSTIFICATION) {
FrameHeader frame_head;
frame_head.read(_blocList[i].text_mem);
switch (justification) {
case POSITION_AT_CENTER_OF_BASE:
// This one is always used for SPEECH TEXT; possibly
// also for pointer text
x -= (frame_head.width / 2);
y -= frame_head.height;
break;
case POSITION_AT_CENTER_OF_TOP:
x -= (frame_head.width / 2);
break;
case POSITION_AT_CENTER_OF_CENTER:
x -= (frame_head.width / 2);
y -= (frame_head.height) / 2;
break;
case POSITION_AT_LEFT_OF_TOP:
// The given coords are already correct for this!
break;
case POSITION_AT_RIGHT_OF_TOP:
x -= frame_head.width;
break;
case POSITION_AT_LEFT_OF_BASE:
y -= frame_head.height;
break;
case POSITION_AT_RIGHT_OF_BASE:
x -= frame_head.width;
y -= frame_head.height;
break;
case POSITION_AT_LEFT_OF_CENTER:
y -= (frame_head.height / 2);
break;
case POSITION_AT_RIGHT_OF_CENTER:
x -= frame_head.width;
y -= (frame_head.height) / 2;
break;
default:
break;
}
// Ensure text sprite is a few pixels inside the visible screen
// remember - it's RDSPR_DISPLAYALIGN
uint16 text_left_margin = TEXT_MARGIN;
uint16 text_right_margin = 640 - TEXT_MARGIN - frame_head.width;
uint16 text_top_margin = TEXT_MARGIN;
uint16 text_bottom_margin = 400 - TEXT_MARGIN - frame_head.height;
// Move if too far left or too far right
if (x < text_left_margin)
x = text_left_margin;
else if (x > text_right_margin)
x = text_right_margin;
// Move if too high or too low
if (y < text_top_margin)
y = text_top_margin;
else if (y > text_bottom_margin)
y = text_bottom_margin;
}
// The sprite is always uncompressed
_blocList[i].type = type | RDSPR_NOCOMPRESSION;
_blocList[i].x = x;
_blocList[i].y = y;
return i + 1;
}
/**
* Called by buildDisplay()
*/
void FontRenderer::printTextBlocs() {
for (uint i = 0; i < MAX_text_blocs; i++) {
if (_blocList[i].text_mem) {
FrameHeader frame_head;
SpriteInfo spriteInfo;
frame_head.read(_blocList[i].text_mem);
spriteInfo.x = _blocList[i].x;
spriteInfo.y = _blocList[i].y;
spriteInfo.w = frame_head.width;
spriteInfo.h = frame_head.height;
spriteInfo.scale = 0;
spriteInfo.scaledWidth = 0;
spriteInfo.scaledHeight = 0;
spriteInfo.type = _blocList[i].type;
spriteInfo.blend = 0;
spriteInfo.data = _blocList[i].text_mem + FrameHeader::size();
spriteInfo.colorTable = 0;
spriteInfo.isText = true;
uint32 rv = _vm->_screen->drawSprite(&spriteInfo);
if (rv)
error("Driver Error %.8x in printTextBlocs", rv);
}
}
}
void FontRenderer::killTextBloc(uint32 bloc_number) {
bloc_number--;
free(_blocList[bloc_number].text_mem);
_blocList[bloc_number].text_mem = nullptr;
}
// Resource 3258 contains text from location script for 152 (install, save &
// restore text, etc)
#define TEXT_RES 3258
// Local line number of "save" (actor no. 1826)
#define SAVE_LINE_NO 1
void Sword2Engine::initializeFontResourceFlags() {
byte *textFile = _resman->openResource(TEXT_RES);
// If language is Polish or Finnish it requires alternate fonts.
// Otherwise, use regular fonts
// "tallenna" Finnish for "save"
// "zapisz" Polish for "save"
// Get the text line (& skip the 2 chars containing the wavId)
char *textLine = (char *)fetchTextLine(textFile, SAVE_LINE_NO) + 2;
if (strcmp(textLine, "tallenna") == 0)
initializeFontResourceFlags(FINNISH_TEXT);
else if (strcmp(textLine, "zapisz") == 0)
initializeFontResourceFlags(POLISH_TEXT);
else
initializeFontResourceFlags(DEFAULT_TEXT);
// Get the game name for the windows application
// According to the GetGameName(), which was never called and has
// therefore been removed, the name of the game is:
//
// ENGLISH: "Broken Sword II"
// AMERICAN: "Circle of Blood II"
// GERMAN: "Baphomet's Fluch II"
// default: "Some game or other, part 86"
//
// But we get it from the text resource instead.
if (_logic->readVar(DEMO))
textLine = (char *)fetchTextLine(textFile, 451) + 2;
else
textLine = (char *)fetchTextLine(textFile, 54) + 2;
_system->setWindowCaption(Common::U32String(textLine, _isRTL ? Common::kWindows1255 : Common::kUtf8));
_resman->closeResource(TEXT_RES);
}
/**
* Called from initializeFontResourceFlags(), and also from console.cpp
*/
void Sword2Engine::initializeFontResourceFlags(uint8 language) {
switch (language) {
case FINNISH_TEXT:
_speechFontId = FINNISH_SPEECH_FONT_ID;
_controlsFontId = FINNISH_CONTROLS_FONT_ID;
_redFontId = FINNISH_RED_FONT_ID;
break;
case POLISH_TEXT:
_speechFontId = POLISH_SPEECH_FONT_ID;
_controlsFontId = POLISH_CONTROLS_FONT_ID;
_redFontId = POLISH_RED_FONT_ID;
break;
default:
_speechFontId = ENGLISH_SPEECH_FONT_ID;
_controlsFontId = ENGLISH_CONTROLS_FONT_ID;
_redFontId = ENGLISH_RED_FONT_ID;
break;
}
}
} // End of namespace Sword2

138
engines/sword2/maketext.h Normal file
View File

@@ -0,0 +1,138 @@
/* 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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 SWORD2_MAKETEXT_H
#define SWORD2_MAKETEXT_H
#include "sword2/debug.h"
namespace Sword2 {
// Output color for character border - should be be black but note that we
// have to use a different pen number during sequences
#define BORDER_PEN 194
// Usually the only texts on screen are the subtitles and the mouse-over text,
// but there can also be a considerable number of debugging messages...
#define MAX_text_blocs MAX_DEBUG_TEXTS + 1
enum {
// Doesn't keep the text inside the screen - only for debug text!
NO_JUSTIFICATION = 0,
// These all force text inside the screen edge margin when necessary
POSITION_AT_CENTER_OF_BASE = 1,
POSITION_AT_CENTER_OF_TOP = 2,
POSITION_AT_LEFT_OF_TOP = 3,
POSITION_AT_RIGHT_OF_TOP = 4,
POSITION_AT_LEFT_OF_BASE = 5,
POSITION_AT_RIGHT_OF_BASE = 6,
POSITION_AT_LEFT_OF_CENTER = 7,
POSITION_AT_RIGHT_OF_CENTER = 8,
POSITION_AT_CENTER_OF_CENTER = 9
};
enum {
DEFAULT_TEXT = 0,
FINNISH_TEXT = 1,
POLISH_TEXT = 2
};
// Info about the text, used to create the SpriteInfo struct
struct TextBloc {
int16 x;
int16 y;
uint16 type;
byte *text_mem;
};
// Info for each line of words in the output text sprite
struct LineInfo {
uint16 width; // Width in pixels
uint16 length; // Length in characters
bool skipSpace; // Whether there is a trailing space
};
class FontRenderer {
private:
Sword2Engine *_vm;
TextBloc _blocList[MAX_text_blocs];
// Layout variables - these used to be defines, but now we're dealing
// with three character sets
int8 _lineSpacing; // no. of pixels to separate lines of
// characters in the output sprite - negative
// for overlap
int8 _charSpacing; // no. of pixels to separate characters along
// each line - negative for overlap
uint8 _borderPen; // output pen color of character borders
Common::HashMap<Common::String, Common::String> _subTranslations;
static const int kChineseWidth = 20;
static const int kChineseHeight = 26;
struct ChineseGlyph {
byte bitmap[kChineseWidth * kChineseHeight];
};
Common::Array<ChineseGlyph> _chineseFont;
uint16 analyzeSentence(const byte *sentence, uint16 maxWidth, uint32 fontRes, LineInfo *line, bool isChinese);
byte *buildTextSprite(const byte *sentence, uint32 fontRes, uint8 pen, LineInfo *line, uint16 noOfLines, bool isChinese);
uint16 charWidth(byte ch, uint32 fontRes);
uint16 wcharWidth(byte hi, byte lo, uint32 fontRes);
uint16 charHeight(uint32 fontRes);
byte *findChar(byte ch, byte *charSet);
byte *findWChar(byte hi, byte lo, byte *charSet);
void copyChar(const byte *charPtr, byte *spritePtr, uint16 spriteWidth, uint8 pen);
void copyWChar(const byte *charPtr, byte *spritePtr, uint16 spriteWidth, uint8 pen);
void copyCharRaw(const byte *source, uint16 charWidth, uint16 charHeight, byte *spritePtr, uint16 spriteWidth, uint8 pen);
bool isKoreanChar(const byte hi, const byte lo, const uint32 fontRes);
public:
FontRenderer(Sword2Engine *vm) : _vm(vm) {
for (int i = 0; i < MAX_text_blocs; i++)
_blocList[i].text_mem = nullptr;
}
~FontRenderer() {
for (int i = 0; i < MAX_text_blocs; i++)
free(_blocList[i].text_mem);
}
byte *makeTextSprite(const byte *sentence, uint16 maxWidth, uint8 pen, uint32 fontRes, uint8 border = BORDER_PEN);
void killTextBloc(uint32 bloc_number);
void printTextBlocs();
uint32 buildNewBloc(byte *ascii, int16 x, int16 y, uint16 width, uint8 pen, uint32 type, uint32 fontRes, uint8 justification);
void loadTranslations();
};
} // End of namespace Sword2
#endif

247
engines/sword2/memory.cpp Normal file
View File

@@ -0,0 +1,247 @@
/* 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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/>.
*/
// The new memory manager, now only used by the resource manager. The original
// one would allocated a 12 MB memory pool at startup, which may have been
// appropriate for the original Playstation version but didn't work very well
// with our PocketPC version.
//
// There is one thing that prevents us from replacing the whole memory manager
// with the standard memory allocation functions: Broken Sword II absolutely,
// positively needs to be able to encode pointers as 32-bit integers. The
// original engine did this simply by casting between pointers and integers,
// but as far as I know that's not a very portable thing to do.
//
// If it had only used pointers as opcode parameters it would have been
// possible, albeit messy, to extend the stack data type. However, there is
// code in walker.cpp that obviously violates that assumption, and there are
// probably other cases as well.
//
// Instead, we take advantage of the fact that the original memory manager
// could only handle up to 999 blocks of memory. That means we can encode a
// pointer as a 10-bit id and a 22-bit offset into the block. Judging by early
// testing, both should be plenty.
//
// The number zero is used to represent the NULL pointer.
#include "common/textconsole.h"
#include "sword2/sword2.h"
#include "sword2/memory.h"
namespace Sword2 {
MemoryManager::MemoryManager() {
// The id stack contains all the possible ids for the memory blocks.
// We use this to ensure that no two blocks ever have the same id.
// The memory blocks are stored in an array, indexed on the block's
// id. This means that given a block id we can find the pointer with a
// simple array lookup.
// The memory block index is an array of pointers to the memory block
// array, sorted on the memory block's pointer. This means that given
// a pointer into a memory block we can find its id with binary
// searching.
//
// A balanced tree might have been more efficient - the index has to
// be re-sorted every time a block is allocated or freed - but such
// beasts are tricky to implement. Anyway, it wouldn't have made
// encoding or decoding pointers any faster, and these are by far the
// most common operations.
_idStack = (int16 *)malloc(MAX_MEMORY_BLOCKS * sizeof(int16));
_memBlocks = (MemBlock *)malloc(MAX_MEMORY_BLOCKS * sizeof(MemBlock));
_memBlockIndex = (MemBlock **)malloc(MAX_MEMORY_BLOCKS * sizeof(MemBlock *));
_totAlloc = 0;
_numBlocks = 0;
for (int i = 0; i < MAX_MEMORY_BLOCKS; i++) {
_idStack[i] = MAX_MEMORY_BLOCKS - i - 1;
_memBlocks[i].ptr = nullptr;
_memBlockIndex[i] = nullptr;
}
_idStackPtr = MAX_MEMORY_BLOCKS;
}
MemoryManager::~MemoryManager() {
for (int i = 0; i < MAX_MEMORY_BLOCKS; i++)
free(_memBlocks[i].ptr);
free(_memBlocks);
free(_memBlockIndex);
free(_idStack);
}
int32 MemoryManager::encodePtr(byte *ptr) {
if (ptr == nullptr)
return 0;
int idx = findPointerInIndex(ptr);
assert(idx != -1);
uint32 id = _memBlockIndex[idx]->id;
uint32 offset = ptr - _memBlocks[id].ptr;
assert(id < 0x03ff);
assert(offset <= 0x003fffff);
assert(offset < _memBlocks[id].size);
return ((id + 1) << 22) | (ptr - _memBlocks[id].ptr);
}
byte *MemoryManager::decodePtr(int32 n) {
if (n == 0)
return nullptr;
uint32 id = ((n & 0xffc00000) >> 22) - 1;
uint32 offset = n & 0x003fffff;
assert(_memBlocks[id].ptr);
assert(offset < _memBlocks[id].size);
return _memBlocks[id].ptr + offset;
}
int16 MemoryManager::findExactPointerInIndex(byte *ptr) {
int left = 0;
int right = _numBlocks - 1;
while (right >= left) {
int n = (left + right) / 2;
if (_memBlockIndex[n]->ptr == ptr)
return n;
if (_memBlockIndex[n]->ptr > ptr)
right = n - 1;
else
left = n + 1;
}
return -1;
}
int16 MemoryManager::findPointerInIndex(byte *ptr) {
int left = 0;
int right = _numBlocks - 1;
while (right >= left) {
int n = (left + right) / 2;
if (_memBlockIndex[n]->ptr <= ptr && _memBlockIndex[n]->ptr + _memBlockIndex[n]->size > ptr)
return n;
if (_memBlockIndex[n]->ptr > ptr)
right = n - 1;
else
left = n + 1;
}
return -1;
}
int16 MemoryManager::findInsertionPointInIndex(byte *ptr) {
if (_numBlocks == 0)
return 0;
int left = 0;
int right = _numBlocks - 1;
int n = 0;
while (right >= left) {
n = (left + right) / 2;
if (_memBlockIndex[n]->ptr == ptr)
return -1;
if (_memBlockIndex[n]->ptr > ptr)
right = n - 1;
else
left = n + 1;
}
if (_memBlockIndex[n]->ptr < ptr)
n++;
return n;
}
byte *MemoryManager::memAlloc(uint32 size, int16 uid) {
assert(_idStackPtr > 0);
// Get the new block's id from the stack.
int16 id = _idStack[--_idStackPtr];
// Allocate the new memory block
byte *ptr = (byte *)malloc(size);
assert(ptr);
_memBlocks[id].id = id;
_memBlocks[id].uid = uid;
_memBlocks[id].ptr = ptr;
_memBlocks[id].size = size;
// Update the memory block index.
int16 idx = findInsertionPointInIndex(ptr);
assert(idx != -1);
for (int i = _numBlocks; i > idx; i--)
_memBlockIndex[i] = _memBlockIndex[i - 1];
_memBlockIndex[idx] = &_memBlocks[id];
_numBlocks++;
_totAlloc += size;
return _memBlocks[id].ptr;
}
void MemoryManager::memFree(byte *ptr) {
int16 idx = findExactPointerInIndex(ptr);
if (idx == -1) {
warning("Freeing non-allocated pointer %p", (void *)ptr);
return;
}
// Put back the id on the stack
_idStack[_idStackPtr++] = _memBlockIndex[idx]->id;
// Release the memory block
free(_memBlockIndex[idx]->ptr);
_memBlockIndex[idx]->ptr = nullptr;
_totAlloc -= _memBlockIndex[idx]->size;
// Remove the memory block from the index
_numBlocks--;
for (int i = idx; i < _numBlocks; i++)
_memBlockIndex[i] = _memBlockIndex[i + 1];
}
} // End of namespace Sword2

72
engines/sword2/memory.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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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 SWORD2_MEMORY_H
#define SWORD2_MEMORY_H
enum {
MAX_MEMORY_BLOCKS = 999
};
namespace Sword2 {
struct MemBlock {
int16 id;
int16 uid;
byte *ptr;
uint32 size;
};
class MemoryManager {
private:
MemBlock *_memBlocks;
MemBlock **_memBlockIndex;
int16 _numBlocks;
uint32 _totAlloc;
int16 *_idStack;
int16 _idStackPtr;
int16 findExactPointerInIndex(byte *ptr);
int16 findPointerInIndex(byte *ptr);
int16 findInsertionPointInIndex(byte *ptr);
public:
MemoryManager();
~MemoryManager();
int16 getNumBlocks() { return _numBlocks; }
uint32 getTotAlloc() { return _totAlloc; }
MemBlock *getMemBlocks() { return _memBlocks; }
int32 encodePtr(byte *ptr);
byte *decodePtr(int32 n);
byte *memAlloc(uint32 size, int16 uid);
void memFree(byte *ptr);
};
} // End of namespace Sword2
#endif

330
engines/sword2/menu.cpp Normal file
View File

@@ -0,0 +1,330 @@
/* 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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/rect.h"
#include "sword2/sword2.h"
#include "sword2/defs.h"
#include "sword2/header.h"
#include "sword2/mouse.h"
#include "sword2/screen.h"
namespace Sword2 {
#define MENUDEEP 40
#define MAXMENUANIMS 8
void Mouse::clearIconArea(int menu, int pocket, Common::Rect *r) {
byte *buf = _vm->_screen->getScreen();
int16 screenWide = _vm->_screen->getScreenWide();
byte menuIconWidth;
// Initialize menu icon width at correct size
// depending if we are using pc or psx version.
if (Sword2Engine::isPsx())
menuIconWidth = RDMENU_PSXICONWIDE;
else
menuIconWidth = RDMENU_ICONWIDE;
r->top = menu * (RENDERDEEP + MENUDEEP) + (MENUDEEP - RDMENU_ICONDEEP) / 2;
r->bottom = r->top + RDMENU_ICONDEEP;
r->left = RDMENU_ICONSTART + pocket * (menuIconWidth + RDMENU_ICONSPACING);
r->right = r->left + menuIconWidth;
byte *dst = buf + r->top * screenWide + r->left;
for (int i = 0; i < RDMENU_ICONDEEP; i++) {
memset(dst, 0, menuIconWidth);
dst += screenWide;
}
}
/**
* This function should be called regularly to process the menubar system. The
* rate at which this function is called will dictate how smooth the menu
* system is.
*/
void Mouse::processMenu() {
uint8 menu;
uint8 i, j;
uint8 frameCount;
Common::Rect r1, r2;
static int32 lastTime = 0;
byte *buf = _vm->_screen->getScreen();
int16 screenWide = _vm->_screen->getScreenWide();
byte menuIconWidth;
if (Sword2Engine::isPsx())
menuIconWidth = RDMENU_PSXICONWIDE;
else
menuIconWidth = RDMENU_ICONWIDE;
if (lastTime == 0) {
lastTime = _vm->getMillis();
frameCount = 1;
} else {
int32 delta = _vm->getMillis() - lastTime;
if (delta > 250) {
lastTime += delta;
delta = 250;
frameCount = 1;
} else {
frameCount = (uint8) ((_iconCount + 8) * delta / 750);
lastTime += frameCount * 750 / (_iconCount + 8);
}
}
// Note: The "almost hidden" menu state exists only so that the menu
// will be redrawn one last time before it's completely hidden. We do
// not need a corresponding "almost shown" state because the menu will
// always be redrawn while it's shown anyway. (We may want to change
// this later.)
while (frameCount-- > 0) {
for (menu = RDMENU_TOP; menu <= RDMENU_BOTTOM; menu++) {
if (_menuStatus[menu] == RDMENU_HIDDEN || _menuStatus[menu] == RDMENU_ALMOST_HIDDEN || _menuStatus[menu] == RDMENU_SHOWN)
continue;
int target, direction, nextState;
if (_menuStatus[menu] == RDMENU_OPENING) {
target = MAXMENUANIMS;
direction = 1;
nextState = RDMENU_SHOWN;
} else {
target = 0;
direction = -1;
nextState = RDMENU_ALMOST_HIDDEN;
}
bool complete = true;
// Propagate animation from the first icon...
for (i = RDMENU_MAXPOCKETS - 1; i > 0; i--) {
_pocketStatus[menu][i] = _pocketStatus[menu][i - 1];
if (_pocketStatus[menu][i] != target)
complete = false;
}
if (_pocketStatus[menu][i] != target)
complete = false;
// ...and animate the first icon
if (_pocketStatus[menu][0] != target)
_pocketStatus[menu][0] += direction;
if (complete)
_menuStatus[menu] = nextState;
}
}
for (menu = RDMENU_TOP; menu <= RDMENU_BOTTOM; menu++) {
if (_menuStatus[menu] == RDMENU_HIDDEN)
continue;
if (_menuStatus[menu] == RDMENU_ALMOST_HIDDEN)
_menuStatus[menu] = RDMENU_HIDDEN;
// Draw the menu here.
int32 curx = RDMENU_ICONSTART + menuIconWidth / 2;
int32 cury = (MENUDEEP / 2) + (RENDERDEEP + MENUDEEP) * menu;
for (i = 0; i < RDMENU_MAXPOCKETS; i++) {
if (_icons[menu][i]) {
int32 xoff, yoff;
// Since we no longer clear the screen after
// each frame we need to clear the icon area.
clearIconArea(menu, i, &r1);
if (_pocketStatus[menu][i] == MAXMENUANIMS) {
xoff = (menuIconWidth / 2);
r2.left = curx - xoff;
r2.right = r2.left + menuIconWidth;
yoff = (RDMENU_ICONDEEP / 2);
r2.top = cury - yoff;
r2.bottom = r2.top + RDMENU_ICONDEEP;
} else {
xoff = (menuIconWidth / 2) * _pocketStatus[menu][i] / MAXMENUANIMS;
r2.left = curx - xoff;
r2.right = curx + xoff;
yoff = (RDMENU_ICONDEEP / 2) * _pocketStatus[menu][i] / MAXMENUANIMS;
r2.top = cury - yoff;
r2.bottom = cury + yoff;
}
if (xoff != 0 && yoff != 0) {
byte *dst = buf + r2.top * screenWide + r2.left;
byte *src = _icons[menu][i];
if (_pocketStatus[menu][i] != MAXMENUANIMS) {
_vm->_screen->scaleImageFast(
dst, screenWide, r2.right - r2.left, r2.bottom - r2.top,
src, menuIconWidth, menuIconWidth, RDMENU_ICONDEEP);
} else {
for (j = 0; j < RDMENU_ICONDEEP; j++) {
memcpy(dst, src, menuIconWidth);
src += menuIconWidth;
dst += screenWide;
}
}
}
_vm->_screen->updateRect(&r1);
}
curx += (RDMENU_ICONSPACING + menuIconWidth);
}
}
}
/**
* This function brings a specified menu into view.
* @param menu RDMENU_TOP or RDMENU_BOTTOM, depending on which menu to show
* @return RD_OK, or an error code
*/
int32 Mouse::showMenu(uint8 menu) {
// Do not show menu in PSX version, as there was really
// nothing similar in the original game (menu was started
// using SELECT button in psx pad)
if (Sword2Engine::isPsx() && menu == RDMENU_TOP)
return RD_OK;
// Check for invalid menu parameter
if (menu > RDMENU_BOTTOM)
return RDERR_INVALIDMENU;
// Check that the menu is not currently shown, or in the process of
// being shown.
if (_menuStatus[menu] == RDMENU_SHOWN || _menuStatus[menu] == RDMENU_OPENING)
return RDERR_INVALIDCOMMAND;
_menuStatus[menu] = RDMENU_OPENING;
return RD_OK;
}
/**
* This function hides a specified menu.
* @param menu RDMENU_TOP or RDMENU_BOTTOM depending on which menu to hide
* @return RD_OK, or an error code
*/
int32 Mouse::hideMenu(uint8 menu) {
// In PSX version, do nothing. There is no such menu.
if (Sword2Engine::isPsx() && menu == RDMENU_TOP)
return RD_OK;
// Check for invalid menu parameter
if (menu > RDMENU_BOTTOM)
return RDERR_INVALIDMENU;
// Check that the menu is not currently hidden, or in the process of
// being hidden.
if (_menuStatus[menu] == RDMENU_HIDDEN || _menuStatus[menu] == RDMENU_CLOSING)
return RDERR_INVALIDCOMMAND;
_menuStatus[menu] = RDMENU_CLOSING;
return RD_OK;
}
/**
* This function hides both menus immediately.
*/
void Mouse::closeMenuImmediately() {
Common::Rect r;
int i;
_menuStatus[RDMENU_TOP] = RDMENU_HIDDEN;
_menuStatus[RDMENU_BOTTOM] = RDMENU_HIDDEN;
for (i = 0; i < RDMENU_MAXPOCKETS; i++) {
if (_icons[RDMENU_TOP][i]) {
clearIconArea(RDMENU_TOP, i, &r);
_vm->_screen->updateRect(&r);
}
if (_icons[RDMENU_BOTTOM][i]) {
clearIconArea(RDMENU_BOTTOM, i, &r);
_vm->_screen->updateRect(&r);
}
}
memset(_pocketStatus, 0, sizeof(uint8) * 2 * RDMENU_MAXPOCKETS);
}
/**
* This function sets a menubar icon.
* @param menu RDMENU_TOP or RDMENU_BOTTOM, depending on which menu to change
* @param pocket the menu pocket to change
* @param icon icon data, or NULL to clear the icon
* @return RD_OK, or an error code
*/
int32 Mouse::setMenuIcon(uint8 menu, uint8 pocket, byte *icon) {
Common::Rect r;
byte menuIconWidth;
if (Sword2Engine::isPsx())
menuIconWidth = RDMENU_PSXICONWIDE;
else
menuIconWidth = RDMENU_ICONWIDE;
// Check for invalid menu parameter.
if (menu > RDMENU_BOTTOM)
return RDERR_INVALIDMENU;
// Check for invalid pocket parameter
if (pocket >= RDMENU_MAXPOCKETS)
return RDERR_INVALIDPOCKET;
// If there is an icon in the requested menu/pocket, clear it out.
if (_icons[menu][pocket]) {
_iconCount--;
free(_icons[menu][pocket]);
_icons[menu][pocket] = nullptr;
clearIconArea(menu, pocket, &r);
_vm->_screen->updateRect(&r);
}
// Only put the icon in the pocket if it is not NULL
if (icon != nullptr) {
_iconCount++;
_icons[menu][pocket] = (byte *)malloc(menuIconWidth * RDMENU_ICONDEEP);
if (_icons[menu][pocket] == nullptr)
return RDERR_OUTOFMEMORY;
memcpy(_icons[menu][pocket], icon, menuIconWidth * RDMENU_ICONDEEP);
}
return RD_OK;
}
} // End of namespace Sword2

View File

@@ -0,0 +1,139 @@
/* 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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/advancedDetector.h"
#include "common/config-manager.h"
#include "common/events.h"
#include "common/file.h"
#include "common/fs.h"
#include "common/savefile.h"
#include "common/system.h"
#include "common/translation.h"
#include "sword2/sword2.h"
#include "sword2/saveload.h"
namespace Sword2 {
static const ADExtraGuiOptionsMap optionsList[] = {
{
GAMEOPTION_OBJECT_LABELS,
{
_s("Show object labels"),
_s("Show labels for objects on mouse hover"),
"object_labels",
false,
0,
0
}
},
AD_EXTRA_GUI_OPTIONS_TERMINATOR
};
} // End of namespace Sword2
class Sword2MetaEngine : public AdvancedMetaEngine<ADGameDescription> {
public:
const char *getName() const override {
return "sword2";
}
const ADExtraGuiOptionsMap *getAdvancedExtraGuiOptions() const override {
return Sword2::optionsList;
}
bool hasFeature(MetaEngineFeature f) const override;
SaveStateList listSaves(const char *target) const override;
int getMaximumSaveSlot() const override;
bool removeSaveState(const char *target, int slot) const override;
Common::Error createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const override;
};
bool Sword2MetaEngine::hasFeature(MetaEngineFeature f) const {
return
(f == kSupportsListSaves) ||
(f == kSupportsLoadingDuringStartup) ||
(f == kSupportsDeleteSave) ||
(f == kSimpleSavesNames);
}
bool Sword2::Sword2Engine::hasFeature(EngineFeature f) const {
return
(f == kSupportsReturnToLauncher) ||
(f == kSupportsSubtitleOptions) ||
(f == kSupportsSavingDuringRuntime) ||
(f == kSupportsLoadingDuringRuntime);
}
SaveStateList Sword2MetaEngine::listSaves(const char *target) const {
Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
Common::StringArray filenames;
char saveDesc[SAVE_DESCRIPTION_LEN];
Common::String pattern = target;
pattern += ".###";
filenames = saveFileMan->listSavefiles(pattern);
SaveStateList saveList;
for (const auto &filename : filenames) {
// Obtain the last 3 digits of the filename, since they correspond to the save slot
int slotNum = atoi(filename.c_str() + filename.size() - 3);
if (slotNum >= 0 && slotNum <= 999) {
Common::InSaveFile *in = saveFileMan->openForLoading(filename);
if (in) {
in->readUint32LE();
in->read(saveDesc, SAVE_DESCRIPTION_LEN);
saveList.push_back(SaveStateDescriptor(this, slotNum, saveDesc));
delete in;
}
}
}
// Sort saves based on slot number.
Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator());
return saveList;
}
int Sword2MetaEngine::getMaximumSaveSlot() const { return 999; }
bool Sword2MetaEngine::removeSaveState(const char *target, int slot) const {
Common::String filename = target;
filename += Common::String::format(".%03d", slot);
return g_system->getSavefileManager()->removeSavefile(filename);
}
Common::Error Sword2MetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const {
*engine = new Sword2::Sword2Engine(syst, desc);
return Common::kNoError;
}
#if PLUGIN_ENABLED_DYNAMIC(SWORD2)
REGISTER_PLUGIN_DYNAMIC(SWORD2, PLUGIN_TYPE_ENGINE, Sword2MetaEngine);
#else
REGISTER_PLUGIN_STATIC(SWORD2, PLUGIN_TYPE_ENGINE, Sword2MetaEngine);
#endif

47
engines/sword2/module.mk Normal file
View File

@@ -0,0 +1,47 @@
MODULE := engines/sword2
MODULE_OBJS := \
animation.o \
anims.o \
console.o \
controls.o \
debug.o \
events.o \
function.o \
header.o \
icons.o \
interpreter.o \
layers.o \
logic.o \
maketext.o \
memory.o \
menu.o \
metaengine.o \
mouse.o \
music.o \
palette.o \
protocol.o \
render.o \
resman.o \
router.o \
saveload.o \
screen.o \
scroll.o \
sound.o \
speech.o \
sprite.o \
startup.o \
sword2.o \
sync.o \
walker.o
# This module can be built as a plugin
ifeq ($(ENABLE_SWORD2), DYNAMIC_PLUGIN)
PLUGIN := 1
endif
# Include common rules
include $(srcdir)/rules.mk
# Detection objects
DETECT_OBJS += $(MODULE)/detection.o

1739
engines/sword2/mouse.cpp Normal file

File diff suppressed because it is too large Load Diff

288
engines/sword2/mouse.h Normal file
View File

@@ -0,0 +1,288 @@
/* 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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 SWORD2_MOUSE_H
#define SWORD2_MOUSE_H
#include "common/rect.h"
#define MENU_MASTER_OBJECT 44
#define MAX_SUBJECT_LIST 30 // is that enough?
#define TOTAL_subjects (375 - 256 + 1) // the speech subject bar
#define TOTAL_engine_pockets (15 + 10) // +10 for overflow
#define TOTAL_mouse_list 50
namespace Sword2 {
struct MenuObject;
struct BuildUnit;
// Menubar defines.
#define RDMENU_TOP 0
#define RDMENU_BOTTOM 1
enum {
MOUSE_normal = 0, // normal in game
MOUSE_menu = 1, // menu chooser
MOUSE_drag = 2, // dragging luggage
MOUSE_system_menu = 3, // system menu chooser
MOUSE_holding = 4 // special
};
enum {
RDMOUSE_NOFLASH,
RDMOUSE_FLASH
};
enum {
RDMENU_HIDDEN,
RDMENU_SHOWN,
RDMENU_OPENING,
RDMENU_CLOSING,
RDMENU_ALMOST_HIDDEN
};
#define RDMENU_ICONWIDE 35
#define RDMENU_PSXICONWIDE 36
#define RDMENU_ICONDEEP 30
#define RDMENU_ICONSTART 24
#define RDMENU_ICONSPACING 5
#define RDMENU_MAXPOCKETS 15
#define MOUSE_ANIM_HEADER_SIZE 6
struct MouseAnim {
uint8 runTimeComp; // type of runtime compression used for the
// frame data
uint8 noAnimFrames; // number of frames in the anim
int8 xHotSpot;
int8 yHotSpot;
uint8 mousew;
uint8 mouseh;
byte *data;
};
// The MOUSE_holding mode is entered when the conversation menu is closed, and
// exited when the mouse cursor moves off that menu area. I don't know why yet.
// mouse unit - like ObjectMouse, but with anim resource & pc (needed if
// sprite is to act as mouse detection mask)
struct MouseUnit {
// Basically the same information as in ObjectMouse, except the
// coordinates are adjusted to conform to standard ScummVM usage.
Common::Rect rect;
int32 priority;
int32 pointer;
// In addition, we need an id when checking the mouse list, and a
// text id for mouse-overs.
int32 id;
int32 pointer_text;
void clear() {
rect.top = 0;
rect.left = 0;
rect.bottom = 0;
rect.right = 0;
priority = 0;
pointer = 0;
id = 0;
pointer_text = 0;
}
};
// Array of these for subject menu build up
struct SubjectUnit {
uint32 res;
uint32 ref;
};
// define these in a script and then register them with the system
struct MenuObject {
int32 icon_resource; // icon graphic graphic
int32 luggage_resource; // luggage icon resource (for attaching to
// mouse pointer)
};
class Mouse {
private:
Sword2Engine *_vm;
MouseUnit _mouseList[TOTAL_mouse_list];
uint32 _curMouse;
MenuObject _tempList[TOTAL_engine_pockets];
uint32 _totalTemp;
MenuObject _masterMenuList[TOTAL_engine_pockets];
uint32 _totalMasters;
SubjectUnit _subjectList[MAX_SUBJECT_LIST];
// ref number for default response when luggage icon is used on a
// person & it doesn't match any of the icons which would have been in
// the chooser
uint32 _defaultResponseId;
// could alternately use logic->looping of course
bool _choosing;
uint8 _menuStatus[2];
byte *_icons[2][RDMENU_MAXPOCKETS];
uint8 _pocketStatus[2][RDMENU_MAXPOCKETS];
uint8 _iconCount;
// If it's NORMAL_MOUSE_ID (ie. normal pointer) then it's over a floor
// area (or hidden hot-zone)
uint32 _mousePointerRes;
MouseAnim _mouseAnim;
MouseAnim _luggageAnim;
uint8 _mouseFrame;
uint32 _mouseMode;
bool _mouseStatus; // Human 0 on/1 off
bool _mouseModeLocked; // 0 not !0 mode cannot be changed from
// normal mouse to top menu (i.e. when
// carrying big objects)
uint32 _realLuggageItem; // Last minute for pause mode
uint32 _currentLuggageResource;
uint32 _oldButton; // For the re-click stuff - must be
// the same button you see
uint32 _buttonClick;
uint32 _pointerTextBlocNo;
uint32 _playerActivityDelay; // Player activity delay counter
bool _examiningMenuIcon;
// Set by checkMouseList()
uint32 _mouseTouching;
uint32 _oldMouseTouching;
bool _objectLabels;
uint32 _menuSelectedPos;
void decompressMouse(byte *decomp, byte *comp, uint8 frame, int width, int height, int pitch, int xOff = 0, int yOff = 0);
int32 setMouseAnim(byte *ma, int32 size, int32 mouseFlash);
int32 setLuggageAnim(byte *la, int32 size);
void clearIconArea(int menu, int pocket, Common::Rect *r);
public:
Mouse(Sword2Engine *vm);
~Mouse();
void getPos(int &x, int &y);
int getX();
int getY();
bool getObjectLabels() { return _objectLabels; }
void setObjectLabels(bool b) { _objectLabels = b; }
bool getMouseStatus() { return _mouseStatus; }
uint32 getMouseTouching() { return _mouseTouching; }
void setMouseTouching(uint32 touching) { _mouseTouching = touching; }
void pauseEngine(bool pause);
void setMouse(uint32 res);
void setLuggage(uint32 res);
void setObjectHeld(uint32 res);
void resetMouseList();
void registerMouse(byte *ob_mouse, BuildUnit *build_unit);
void registerPointerText(int32 text_id);
void createPointerText(uint32 text_id, uint32 pointer_res);
void clearPointerText();
void drawMouse();
int32 animateMouse();
void processMenu();
void addMenuObject(byte *ptr);
void addSubject(int32 id, int32 ref);
void buildMenu();
void buildSystemMenu();
int32 showMenu(uint8 menu);
int32 hideMenu(uint8 menu);
int32 setMenuIcon(uint8 menu, uint8 pocket, byte *icon);
void closeMenuImmediately();
void refreshInventory();
void startConversation();
void endConversation();
void hideMouse();
void noHuman();
void addHuman();
void resetPlayerActivityDelay() { _playerActivityDelay = 0; }
void monitorPlayerActivity();
void checkPlayerActivity(uint32 seconds);
void mouseOnOff();
uint32 checkMouseList();
void mouseEngine();
void normalMouse();
void menuMouse();
void dragMouse();
void systemMenuMouse();
bool isChoosing() { return _choosing; }
uint32 chooseMouse();
int menuClick(int menu_items);
int getMouseMode();
void setMouseMode(int mouseMode); // Used to force mouse mode
};
} // End of namespace Sword2
#endif

849
engines/sword2/music.cpp Normal file
View File

@@ -0,0 +1,849 @@
/* 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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/>.
*/
// One feature still missing is the original's DipMusic() function which, as
// far as I can understand, softened the music volume when someone was
// speaking, but only (?) if the music was playing loudly at the time.
//
// All things considered, I think this is more bother than it's worth.
#include "common/file.h"
#include "common/memstream.h"
#include "common/substream.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "audio/audiostream.h"
#include "audio/mixer.h"
#include "audio/decoders/mp3.h"
#include "audio/decoders/vorbis.h"
#include "audio/decoders/flac.h"
#include "audio/decoders/xa.h"
#include "audio/rate.h"
#include "sword2/sword2.h"
#include "sword2/defs.h"
#include "sword2/header.h"
#include "sword2/resman.h"
#include "sword2/sound.h"
namespace Sword2 {
static Audio::AudioStream *makeCLUStream(Common::File *fp, int size);
static Audio::AudioStream *makePSXCLUStream(Common::File *fp, int size);
static Audio::AudioStream *getAudioStream(SoundFileHandle *fh, const char *base, int cd, uint32 id, uint32 *numSamples) {
bool alreadyOpen;
if (!fh->file.isOpen()) {
alreadyOpen = false;
struct {
const char *ext;
int mode;
} file_types[] = {
#ifdef USE_FLAC
{ "clf", kFLACMode },
#endif
#ifdef USE_VORBIS
{ "clg", kVorbisMode },
#endif
#ifdef USE_MAD
{ "cl3", kMP3Mode },
#endif
{ "clu", kCLUMode }
};
int soundMode = 0;
char filename[20];
for (int i = 0; i < ARRAYSIZE(file_types); i++) {
Common::sprintf_s(filename, "%s%d.%s", base, cd, file_types[i].ext);
if (Common::File::exists(filename)) {
soundMode = file_types[i].mode;
break;
}
Common::sprintf_s(filename, "%s.%s", base, file_types[i].ext);
if (Common::File::exists(filename)) {
soundMode = file_types[i].mode;
break;
}
}
if (soundMode == 0)
return nullptr;
fh->file.open(filename);
fh->fileType = soundMode;
if (!fh->file.isOpen()) {
warning("BS2 getAudioStream: Failed opening file '%s'", filename);
return nullptr;
}
if (fh->fileSize != fh->file.size()) {
free(fh->idxTab);
fh->idxTab = nullptr;
}
} else
alreadyOpen = true;
uint32 entrySize = (fh->fileType == kCLUMode) ? 2 : 3;
if (!fh->idxTab) {
fh->file.seek(0);
fh->idxLen = fh->file.readUint32LE();
fh->file.seek(entrySize * 4);
fh->idxTab = (uint32 *)malloc(fh->idxLen * 3 * sizeof(uint32));
for (uint32 cnt = 0; cnt < fh->idxLen; cnt++) {
fh->idxTab[cnt * 3 + 0] = fh->file.readUint32LE();
fh->idxTab[cnt * 3 + 1] = fh->file.readUint32LE();
if (fh->fileType == kCLUMode) {
fh->idxTab[cnt * 3 + 2] = fh->idxTab[cnt * 3 + 1];
fh->idxTab[cnt * 3 + 1]--;
} else
fh->idxTab[cnt * 3 + 2] = fh->file.readUint32LE();
}
}
// FIXME: In the forest maze on Zombie Island, the scripts will often
// try to play song 451, which doesn't exist. We could easily substitute
// another for it here, but which one? There are roughly 250 musical
// cues to choose from.
uint32 pos = fh->idxTab[id * 3 + 0];
uint32 len = fh->idxTab[id * 3 + 1];
uint32 enc_len = fh->idxTab[id * 3 + 2];
if (numSamples)
*numSamples = len;
if (!pos || !len) {
// We couldn't find the sound. Possibly as a result of a bad
// installation (e.g. using the music file from CD 2 as the
// first music file). Don't close the file if it was already
// open though, because something is playing from it.
warning("getAudioStream: Could not find %s ID %d! Possibly the wrong file", base, id);
if (!alreadyOpen)
fh->file.close();
return nullptr;
}
fh->file.seek(pos, SEEK_SET);
switch (fh->fileType) {
case kCLUMode:
if (Sword2Engine::isPsx())
return makePSXCLUStream(&fh->file, enc_len);
else
return makeCLUStream(&fh->file, enc_len);
#ifdef USE_MAD
case kMP3Mode: {
Common::SafeSeekableSubReadStream *tmp = new Common::SafeSeekableSubReadStream(&fh->file, pos, pos + enc_len);
return Audio::makeMP3Stream(tmp, DisposeAfterUse::YES);
}
#endif
#ifdef USE_VORBIS
case kVorbisMode: {
Common::SafeSeekableSubReadStream *tmp = new Common::SafeSeekableSubReadStream(&fh->file, pos, pos + enc_len);
return Audio::makeVorbisStream(tmp, DisposeAfterUse::YES);
}
#endif
#ifdef USE_FLAC
case kFLACMode: {
Common::SafeSeekableSubReadStream *tmp = new Common::SafeSeekableSubReadStream(&fh->file, pos, pos + enc_len);
return Audio::makeFLACStream(tmp, DisposeAfterUse::YES);
}
#endif
default:
return nullptr;
}
}
// ----------------------------------------------------------------------------
// Custom AudioStream class to handle Broken Sword 2's audio compression.
// ----------------------------------------------------------------------------
#define GetCompressedShift(n) (((n) >> 4) & 0x0F)
#define GetCompressedSign(n) ((n) & 0x08)
#define GetCompressedAmplitude(n) ((n) & 0x07)
CLUInputStream::CLUInputStream(Common::File *file, int size)
: _file(file), _firstTime(true), _bufferEnd(_outbuf + BUFFER_SIZE) {
// Determine the end position.
_file_pos = _file->pos();
_end_pos = _file_pos + size;
// Read in initial data
refill();
}
CLUInputStream::~CLUInputStream() {
}
int CLUInputStream::readBuffer(int16 *buffer, const int numSamples) {
int samples = 0;
while (samples < numSamples && !eosIntern()) {
const int len = MIN(numSamples - samples, (int)(_bufferEnd - _pos));
memcpy(buffer, _pos, len * 2);
buffer += len;
_pos += len;
samples += len;
if (_pos >= _bufferEnd) {
refill();
}
}
return samples;
}
void CLUInputStream::refill() {
byte *in = _inbuf;
int16 *out = _outbuf;
_file->seek(_file_pos, SEEK_SET);
uint len_left = _file->read(in, MIN((uint32)BUFFER_SIZE, _end_pos - (uint32)_file->pos()));
_file_pos = _file->pos();
while (len_left > 0) {
uint16 sample;
if (_firstTime) {
_firstTime = false;
_prev = READ_LE_UINT16(in);
sample = _prev;
len_left -= 2;
in += 2;
} else {
uint16 delta = GetCompressedAmplitude(*in) << GetCompressedShift(*in);
if (GetCompressedSign(*in))
sample = _prev - delta;
else
sample = _prev + delta;
_prev = sample;
len_left--;
in++;
}
*out++ = (int16)sample;
}
_pos = _outbuf;
_bufferEnd = out;
}
Audio::AudioStream *makeCLUStream(Common::File *file, int size) {
return new CLUInputStream(file, size);
}
Audio::AudioStream *makePSXCLUStream(Common::File *file, int size) {
// Buffer audio file data, and ask MemoryReadStream to dispose of it
// when not needed anymore.
byte *buffer = (byte *)malloc(size);
file->read(buffer, size);
return Audio::makeXAStream(new Common::MemoryReadStream(buffer, size, DisposeAfterUse::YES), 11025);
}
// ----------------------------------------------------------------------------
// Another custom AudioStream class, to wrap around the various AudioStream
// classes used for music decompression, and to add looping, fading, etc.
// ----------------------------------------------------------------------------
// The length of a fade-in/out, in milliseconds.
#define FADE_LENGTH 3000
MusicInputStream::MusicInputStream(int cd, SoundFileHandle *fh, uint32 musicId, bool looping) {
_cd = cd;
_fh = fh;
_musicId = musicId;
_looping = looping;
_bufferEnd = _buffer + BUFFER_SIZE;
_remove = false;
_fading = 0;
_decoder = getAudioStream(_fh, "music", _cd, _musicId, &_numSamples);
if (_decoder) {
_samplesLeft = _numSamples;
_fadeSamples = (getRate() * FADE_LENGTH) / 1000;
fadeUp();
// Read in initial data
refill();
}
}
MusicInputStream::~MusicInputStream() {
delete _decoder;
_decoder = nullptr;
}
int MusicInputStream::readBuffer(int16 *buffer, const int numSamples) {
if (!_decoder)
return 0;
int samples = 0;
while (samples < numSamples && !eosIntern()) {
const int len = MIN(numSamples - samples, (int)(_bufferEnd - _pos));
memcpy(buffer, _pos, len * 2);
buffer += len;
_pos += len;
samples += len;
if (_pos >= _bufferEnd) {
refill();
}
}
return samples;
}
void MusicInputStream::refill() {
int16 *buf = _buffer;
uint32 numSamples = 0;
uint32 len_left;
bool endFade = false;
len_left = BUFFER_SIZE;
if (_fading > 0 && (uint32)_fading < len_left)
len_left = _fading;
if (_samplesLeft < len_left)
len_left = _samplesLeft;
if (!_looping) {
// Non-looping music is faded out at the end. If this fade
// out would have started somewhere within the len_left samples
// to read, we only read up to that point. This way, we can
// treat this fade as any other.
if (!_fading) {
uint32 currentlyAt = _numSamples - _samplesLeft;
uint32 fadeOutAt = _numSamples - _fadeSamples;
uint32 readTo = currentlyAt + len_left;
if (fadeOutAt == currentlyAt)
fadeDown();
else if (fadeOutAt > currentlyAt && fadeOutAt <= readTo) {
len_left = fadeOutAt - currentlyAt;
endFade = true;
}
}
}
int desired = len_left - numSamples;
int len = _decoder->readBuffer(buf, desired);
// Shouldn't happen, but if it does it could cause an infinite loop.
// Of course there were bugs that caused it to happen several times
// during development. :-)
if (len < desired) {
warning("Expected %d samples, but got %d", desired, len);
_samplesLeft = len;
}
buf += len;
numSamples += len;
len_left -= len;
_samplesLeft -= len;
int16 *ptr;
if (_fading > 0) {
// Fade down
for (ptr = _buffer; ptr < buf; ptr++) {
if (_fading > 0) {
_fading--;
*ptr = (*ptr * _fading) / _fadeSamples;
}
if (_fading == 0) {
_looping = false;
_remove = true;
*ptr = 0;
}
}
} else if (_fading < 0) {
// Fade up
for (ptr = _buffer; ptr < buf; ptr++) {
_fading--;
*ptr = -(*ptr * _fading) / _fadeSamples;
if (_fading <= -_fadeSamples) {
_fading = 0;
break;
}
}
}
if (endFade)
fadeDown();
if (!_samplesLeft) {
if (_looping) {
delete _decoder;
_decoder = getAudioStream(_fh, "music", _cd, _musicId, &_numSamples);
_samplesLeft = _numSamples;
} else
_remove = true;
}
_pos = _buffer;
_bufferEnd = buf;
}
void MusicInputStream::fadeUp() {
if (_fading > 0)
_fading = -_fading;
else if (_fading == 0)
_fading = -1;
}
void MusicInputStream::fadeDown() {
if (_fading < 0)
_fading = -_fading;
else if (_fading == 0)
_fading = _fadeSamples;
}
bool MusicInputStream::readyToRemove() {
return _remove;
}
int32 MusicInputStream::getTimeRemaining() {
// This is far from exact, but it doesn't have to be.
return (_samplesLeft + BUFFER_SIZE) / getRate();
}
// ----------------------------------------------------------------------------
// Main sound class
// ----------------------------------------------------------------------------
// AudioStream API
int Sound::readBuffer(int16 *buffer, const int numSamples) {
Common::StackLock lock(_mutex);
int i;
if (_musicPaused)
return 0;
for (i = 0; i < MAXMUS; i++) {
if (_music[i] && _music[i]->readyToRemove()) {
delete _music[i];
_music[i] = nullptr;
}
}
memset(buffer, 0, 2 * numSamples);
if (!_mixBuffer || numSamples > _mixBufferLen) {
if (_mixBuffer) {
int16 *newBuffer = (int16 *)realloc(_mixBuffer, 2 * numSamples);
if (newBuffer) {
_mixBuffer = newBuffer;
} else {
// We can't use the old buffer any more. It's too small.
free(_mixBuffer);
_mixBuffer = 0;
}
} else
_mixBuffer = (int16 *)malloc(2 * numSamples);
_mixBufferLen = numSamples;
}
if (!_mixBuffer)
return 0;
for (i = 0; i < MAXMUS; i++) {
if (!_music[i])
continue;
int len = _music[i]->readBuffer(_mixBuffer, numSamples);
if (!_musicMuted) {
for (int j = 0; j < len; j++) {
Audio::clampedAdd(buffer[j], _mixBuffer[j]);
}
}
}
bool inUse[MAXMUS];
for (i = 0; i < MAXMUS; i++)
inUse[i] = false;
for (i = 0; i < MAXMUS; i++) {
if (_music[i]) {
if (_music[i]->getCD() == 1)
inUse[0] = true;
else
inUse[1] = true;
}
}
for (i = 0; i < MAXMUS; i++) {
if (!inUse[i] && !_musicFile[i].inUse && _musicFile[i].file.isOpen())
_musicFile[i].file.close();
}
return numSamples;
}
bool Sound::endOfData() const {
// The music never stops. It just goes quiet.
return false;
}
// ----------------------------------------------------------------------------
// MUSIC
// ----------------------------------------------------------------------------
/**
* Stops the music dead in its tracks. Any music that is currently being
* streamed is paused.
*/
void Sound::pauseMusic() {
Common::StackLock lock(_mutex);
_musicPaused = true;
}
/**
* Restarts the music from where it was stopped.
*/
void Sound::unpauseMusic() {
Common::StackLock lock(_mutex);
_musicPaused = false;
}
/**
* Fades out and stops the music.
*/
void Sound::stopMusic(bool immediately) {
Common::StackLock lock(_mutex);
_loopingMusicId = 0;
for (int i = 0; i < MAXMUS; i++) {
if (_music[i]) {
if (immediately) {
delete _music[i];
_music[i] = nullptr;
} else
_music[i]->fadeDown();
}
}
}
/**
* Streams music from a cluster file.
* @param musicId the id of the music to stream
* @param loop true if the music is to loop back to the start
* @return RD_OK or an error code
*/
int32 Sound::streamCompMusic(uint32 musicId, bool loop) {
Common::StackLock lock(_mutex);
int cd = _vm->_resman->getCD();
if (loop)
_loopingMusicId = musicId;
else
_loopingMusicId = 0;
int primary = -1;
int secondary = -1;
// If both music streams are active, one of them will have to go.
if (_music[0] && _music[1]) {
int32 fade0 = _music[0]->isFading();
int32 fade1 = _music[1]->isFading();
if (!fade0 && !fade1) {
// Neither is fading. This shouldn't happen, so just
// pick one and be done with it.
primary = 0;
} else if (fade0 && !fade1) {
// Stream 0 is fading, so pick that one.
primary = 0;
} else if (!fade0 && fade1) {
// Stream 1 is fading, so pick that one.
primary = 1;
} else {
// Both streams are fading. Pick the one that is
// closest to silent.
if (ABS(fade0) < ABS(fade1))
primary = 0;
else
primary = 1;
}
delete _music[primary];
_music[primary] = nullptr;
}
// Pick the available music stream. If no music is playing it doesn't
// matter which we use.
if (_music[0] || _music[1]) {
if (_music[0]) {
primary = 1;
secondary = 0;
} else {
primary = 0;
secondary = 1;
}
} else
primary = 0;
// Don't start streaming if the volume is off.
if (isMusicMute()) {
return RD_OK;
}
if (secondary != -1)
_music[secondary]->fadeDown();
SoundFileHandle *fh = (cd == 1) ? &_musicFile[0] : &_musicFile[1];
fh->inUse = true;
MusicInputStream *tmp = new MusicInputStream(cd, fh, musicId, loop);
if (tmp->isReady()) {
_music[primary] = tmp;
fh->inUse = false;
return RD_OK;
} else {
fh->inUse = false;
delete tmp;
return RDERR_INVALIDFILENAME;
}
}
/**
* @return the time left for the current music, in seconds.
*/
int32 Sound::musicTimeRemaining() {
Common::StackLock lock(_mutex);
for (int i = 0; i < MAXMUS; i++) {
if (_music[i] && _music[i]->isFading() <= 0)
return _music[i]->getTimeRemaining();
}
return 0;
}
// ----------------------------------------------------------------------------
// SPEECH
// ----------------------------------------------------------------------------
/**
* Mutes/Unmutes the speech.
* @param mute If mute is false, restore the volume to the last set master
* level. Otherwise the speech is muted (volume 0).
*/
void Sound::muteSpeech(bool mute) {
_speechMuted = mute;
if (_vm->_mixer->isSoundHandleActive(_soundHandleSpeech)) {
uint volume = mute ? 0 : Audio::Mixer::kMaxChannelVolume;
_vm->_mixer->setChannelVolume(_soundHandleSpeech, volume);
}
}
/**
* Stops the speech dead in its tracks.
*/
void Sound::pauseSpeech() {
if (!_speechPaused) {
_speechPaused = true;
_vm->_mixer->pauseHandle(_soundHandleSpeech, true);
}
}
/**
* Restarts the speech from where it was stopped.
*/
void Sound::unpauseSpeech() {
if (_speechPaused) {
_speechPaused = false;
_vm->_mixer->pauseHandle(_soundHandleSpeech, false);
}
}
/**
* Stops the speech from playing.
*/
int32 Sound::stopSpeech() {
if (_vm->_mixer->isSoundHandleActive(_soundHandleSpeech)) {
_vm->_mixer->stopHandle(_soundHandleSpeech);
return RD_OK;
}
return RDERR_SPEECHNOTPLAYING;
}
/**
* @return Either RDSE_SAMPLEPLAYING or RDSE_SAMPLEFINISHED
*/
int32 Sound::getSpeechStatus() {
return _vm->_mixer->isSoundHandleActive(_soundHandleSpeech) ? RDSE_SAMPLEPLAYING : RDSE_SAMPLEFINISHED;
}
/**
* Returns either RDSE_QUIET or RDSE_SPEAKING
*/
int32 Sound::amISpeaking() {
if (!_speechMuted && !_speechPaused && _vm->_mixer->isSoundHandleActive(_soundHandleSpeech))
return RDSE_SPEAKING;
return RDSE_QUIET;
}
/**
* This function loads, decompresses and plays a line of speech. An error
* occurs if speech is already playing.
* @param speechId the text line id used to reference the speech
* @param vol volume, 0 (minimum) to 16 (maximum)
* @param pan panning, -16 (full left) to 16 (full right)
*/
int32 Sound::playCompSpeech(uint32 speechId, uint8 vol, int8 pan) {
if (_speechMuted)
return RD_OK;
if (getSpeechStatus() == RDERR_SPEECHPLAYING)
return RDERR_SPEECHPLAYING;
int cd = _vm->_resman->getCD();
SoundFileHandle *fh = (cd == 1) ? &_speechFile[0] : &_speechFile[1];
Audio::AudioStream *input = getAudioStream(fh, "speech", cd, speechId, nullptr);
if (!input)
return RDERR_INVALIDID;
// Modify the volume according to the master volume
byte volume = _speechMuted ? 0 : vol * Audio::Mixer::kMaxChannelVolume / 16;
int8 p = (pan * 127) / 16;
if (isReverseStereo())
p = -p;
// Start the speech playing
_vm->_mixer->playStream(Audio::Mixer::kSpeechSoundType, &_soundHandleSpeech, input, -1, volume, p);
return RD_OK;
}
// ----------------------------------------------------------------------------
// SOUND EFFECTS
// ----------------------------------------------------------------------------
/**
* Mutes/Unmutes the sound effects.
* @param mute If mute is false, restore the volume to the last set master
* level. Otherwise the sound effects are muted (volume 0).
*/
void Sound::muteFx(bool mute) {
_fxMuted = mute;
// Now update the volume of any fxs playing
for (int i = 0; i < FXQ_LENGTH; i++) {
if (_fxQueue[i].resource) {
_vm->_mixer->setChannelVolume(_fxQueue[i].handle, mute ? 0 : _fxQueue[i].volume);
}
}
}
/**
* Sets the volume and pan of the sample which is currently playing
* @param id the id of the sample
* @param vol volume
* @param pan panning
*/
int32 Sound::setFxIdVolumePan(int32 id, int vol, int pan) {
if (!_fxQueue[id].resource)
return RDERR_FXNOTOPEN;
if (vol > 16)
vol = 16;
_fxQueue[id].volume = (vol * Audio::Mixer::kMaxChannelVolume) / 16;
if (pan != 255) {
if (isReverseStereo())
pan = -pan;
_fxQueue[id].pan = (pan * 127) / 16;
}
if (!_fxMuted && _vm->_mixer->isSoundHandleActive(_fxQueue[id].handle)) {
_vm->_mixer->setChannelVolume(_fxQueue[id].handle, _fxQueue[id].volume);
if (pan != -1)
_vm->_mixer->setChannelBalance(_fxQueue[id].handle, _fxQueue[id].pan);
}
return RD_OK;
}
void Sound::pauseFx() {
if (!_fxPaused) {
for (int i = 0; i < FXQ_LENGTH; i++) {
if (_fxQueue[i].resource)
_vm->_mixer->pauseHandle(_fxQueue[i].handle, true);
}
_fxPaused = true;
}
}
void Sound::unpauseFx() {
if (_fxPaused) {
for (int i = 0; i < FXQ_LENGTH; i++)
if (_fxQueue[i].resource)
_vm->_mixer->pauseHandle(_fxQueue[i].handle, false);
_fxPaused = false;
}
}
} // End of namespace Sword2

280
engines/sword2/object.h 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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 SWORD2_OBJECT_H
#define SWORD2_OBJECT_H
#include "common/endian.h"
namespace Sword2 {
// these structures represent the broken up compact components
// these here declared to the system must be the same as those declared to
// LINC (or it won't work)
// mouse structure - defines mouse detection area, detection priority &
// 'type' flag
struct ObjectMouse {
int32 x1; // Top-left and bottom-right of mouse
int32 y1; // area. (These coords are inclusive.)
int32 x2;
int32 y2;
int32 priority;
int32 pointer; // type (or resource id?) of pointer used over this area
static int size() {
return 24;
}
void read(const byte *addr);
void write(byte *addr);
};
// logic structure - contains fields used in logic script processing
class ObjectLogic {
// int32 looping; // 0 when first calling fn<function>;
// 1 when calling subsequent times in
// same loop
// int32 pause; // pause count, used by fnPause()
private:
byte *_addr;
public:
ObjectLogic(byte *addr) {
_addr = addr;
}
static int size() {
return 8;
}
byte *data() {
return _addr;
}
int32 getLooping() { return READ_LE_UINT32(_addr); }
int32 getPause() { return READ_LE_UINT32(_addr + 4); }
void setLooping(int32 x) { WRITE_LE_UINT32(_addr, x); }
void setPause(int32 x) { WRITE_LE_UINT32(_addr + 4, x); }
};
// status bits for 'type' field of ObjectGraphic)
// in low word:
#define NO_SPRITE 0x00000000 // don't print
#define BGP0_SPRITE 0x00000001 // fixed to background parallax[0]
#define BGP1_SPRITE 0x00000002 // fixed to background parallax[1]
#define BACK_SPRITE 0x00000004 // 'background' sprite, fixed to main background
#define SORT_SPRITE 0x00000008 // 'sorted' sprite, fixed to main background
#define FORE_SPRITE 0x00000010 // 'foreground' sprite, fixed to main background
#define FGP0_SPRITE 0x00000020 // fixed to foreground parallax[0]
#define FGP1_SPRITE 0x00000040 // fixed to foreground parallax[0]
// in high word:
#define UNSHADED_SPRITE 0x00000000 // not to be shaded
#define SHADED_SPRITE 0x00010000 // to be shaded, based on shading mask
// graphic structure - contains fields appropriate to sprite output
class ObjectGraphic {
// int32 type; // see above
// int32 anim_resource; // resource id of animation file
// int32 anim_pc; // current frame number of animation
private:
byte *_addr;
public:
ObjectGraphic(byte *addr) {
_addr = addr;
}
static int size() {
return 12;
}
byte *data() {
return _addr;
}
int32 getType() { return READ_LE_UINT32(_addr); }
int32 getAnimResource() { return READ_LE_UINT32(_addr + 4); }
int32 getAnimPc() { return READ_LE_UINT32(_addr + 8); }
void setType(int32 x) { WRITE_LE_UINT32(_addr, x); }
void setAnimResource(int32 x) { WRITE_LE_UINT32(_addr + 4, x); }
void setAnimPc(int32 x) { WRITE_LE_UINT32(_addr + 8, x); }
};
// speech structure - contains fields used by speech scripts & text output
class ObjectSpeech {
// int32 pen; // color to use for body of characters
// int32 width; // max width of text sprite
// int32 command; // speech script command id
// int32 ins1; // speech script instruction parameters
// int32 ins2;
// int32 ins3;
// int32 ins4;
// int32 ins5;
// int32 wait_state; // 0 not waiting,
// 1 waiting for next speech command
private:
byte *_addr;
public:
ObjectSpeech(byte *addr) {
_addr = addr;
}
static int size() {
return 36;
}
byte *data() {
return _addr;
}
int32 getPen() { return READ_LE_UINT32(_addr); }
int32 getWidth() { return READ_LE_UINT32(_addr + 4); }
int32 getCommand() { return READ_LE_UINT32(_addr + 8); }
int32 getIns1() { return READ_LE_UINT32(_addr + 12); }
int32 getIns2() { return READ_LE_UINT32(_addr + 16); }
int32 getIns3() { return READ_LE_UINT32(_addr + 20); }
int32 getIns4() { return READ_LE_UINT32(_addr + 24); }
int32 getIns5() { return READ_LE_UINT32(_addr + 28); }
int32 getWaitState() { return READ_LE_UINT32(_addr + 32); }
void setPen(int32 x) { WRITE_LE_UINT32(_addr, x); }
void setWidth(int32 x) { WRITE_LE_UINT32(_addr + 4, x); }
void setCommand(int32 x) { WRITE_LE_UINT32(_addr + 8, x); }
void setIns1(int32 x) { WRITE_LE_UINT32(_addr + 12, x); }
void setIns2(int32 x) { WRITE_LE_UINT32(_addr + 16, x); }
void setIns3(int32 x) { WRITE_LE_UINT32(_addr + 20, x); }
void setIns4(int32 x) { WRITE_LE_UINT32(_addr + 24, x); }
void setIns5(int32 x) { WRITE_LE_UINT32(_addr + 28, x); }
void setWaitState(int32 x) { WRITE_LE_UINT32(_addr + 32, x); }
};
// mega structure - contains fields used for mega-character & mega-set
// processing
class ObjectMega {
// int32 NOT_USED_1; // only free roaming megas need to
// check this before registering their
// graphics for drawing
// int32 NOT_USED_2; // id of floor on which we are standing
// int32 NOT_USED_3; // id of object which we are getting to
// int32 NOT_USED_4; // pixel distance to stand from player
// character when in conversation
// int32 currently_walking; // number given us by the auto router
// int32 walk_pc; // current frame number of walk-anim
// int32 scale_a; // current scale factors, taken from
// int32 scale_b; // floor data
// int32 feet_x; // mega feet coords - frame-offsets are
// int32 feet_y; // added to these position mega frames
// int32 current_dir; // current dirction faced by mega; used
// by autorouter to determine turns
// required
// int32 NOT_USED_5; // means were currently avoiding a
// collision (see fnWalk)
// int32 megaset_res; // resource id of mega-set file
// int32 NOT_USED_6; // NOT USED
private:
byte *_addr;
public:
ObjectMega(byte *addr) {
_addr = addr;
}
static int size() {
return 56;
}
byte *data() {
return _addr;
}
int32 getIsWalking() { return READ_LE_UINT32(_addr + 16); }
int32 getWalkPc() { return READ_LE_UINT32(_addr + 20); }
int32 getScaleA() { return READ_LE_UINT32(_addr + 24); }
int32 getScaleB() { return READ_LE_UINT32(_addr + 28); }
int32 getFeetX() { return READ_LE_UINT32(_addr + 32); }
int32 getFeetY() { return READ_LE_UINT32(_addr + 36); }
int32 getCurDir() { return READ_LE_UINT32(_addr + 40); }
int32 getMegasetRes() { return READ_LE_UINT32(_addr + 48); }
void setIsWalking(int32 x) { WRITE_LE_UINT32(_addr + 16, x); }
void setWalkPc(int32 x) { WRITE_LE_UINT32(_addr + 20, x); }
void setScaleA(int32 x) { WRITE_LE_UINT32(_addr + 24, x); }
void setScaleB(int32 x) { WRITE_LE_UINT32(_addr + 28, x); }
void setFeetX(int32 x) { WRITE_LE_UINT32(_addr + 32, x); }
void setFeetY(int32 x) { WRITE_LE_UINT32(_addr + 36, x); }
void setCurDir(int32 x) { WRITE_LE_UINT32(_addr + 40, x); }
void setMegasetRes(int32 x) { WRITE_LE_UINT32(_addr + 48, x); }
int32 calcScale() {
// Calc scale at which to print the sprite, based on feet
// y-coord & scaling constants (NB. 'scale' is actually
// 256 * true_scale, to maintain accuracy)
// Ay+B gives 256 * scale ie. 256 * 256 * true_scale for even
// better accuracy, ie. scale = (Ay + B) / 256
return (getScaleA() * getFeetY() + getScaleB()) / 256;
}
};
// walk-data structure - contains details of layout of frames in the
// mega-set, and how they are to be used
struct ObjectWalkdata {
int32 nWalkFrames; // no. of frames per walk-cycle
int32 usingStandingTurnFrames; // 0 = no 1 = yes
int32 usingWalkingTurnFrames; // 0 = no 1 = yes
int32 usingSlowInFrames; // 0 = no 1 = yes
int32 usingSlowOutFrames; // 0 = no !0 = number of slow-out frames in each direction
int32 nSlowInFrames[8]; // no. of slow-in frames in each direction
int32 leadingLeg[8]; // leading leg for walk in each direction (0 = left 1 = right)
int32 dx[8 * (12 + 1)]; // walk step distances in x direction
int32 dy[8 * (12 + 1)]; // walk step distances in y direction
static int size() {
return 916;
}
void read(const byte *addr);
void write(byte *addr);
};
} // End of namespace Sword2
#endif

36
engines/sword2/obsolete.h Normal file
View File

@@ -0,0 +1,36 @@
/* 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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 SWORD2_OBSOLETE_H
#define SWORD2_OBSOLETE_H
static const Engines::ObsoleteGameID obsoleteGameIDsTable[] = {
{"sword2alt", "sword2", Common::kPlatformWindows},
{"sword2psx", "sword2", Common::kPlatformPSX},
{"sword2psxdemo", "sword2", Common::kPlatformPSX},
{"sword2demo", "sword2", Common::kPlatformWindows},
{"sword2demo-es", "sword2", Common::kPlatformWindows},
{0, 0, Common::kPlatformUnknown}
};
#endif // SWORD2_OBSOLETE_H

303
engines/sword2/palette.cpp Normal file
View File

@@ -0,0 +1,303 @@
/* 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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/system.h"
#include "common/textconsole.h"
#include "graphics/paletteman.h"
#include "sword2/sword2.h"
#include "sword2/defs.h"
#include "sword2/header.h"
#include "sword2/logic.h"
#include "sword2/resman.h"
#include "sword2/screen.h"
namespace Sword2 {
/**
* Start layer palette fading up
*/
void Screen::startNewPalette() {
// If the screen is still fading down then wait for black - could
// happen when everythings cached into a large memory model
waitForFade();
byte *screenFile = _vm->_resman->openResource(_thisScreen.background_layer_id);
// Don't fetch palette match table while using PSX version,
// because it is not present.
if (!Sword2Engine::isPsx())
memcpy(_paletteMatch, _vm->fetchPaletteMatchTable(screenFile), PALTABLESIZE);
_vm->fetchPalette(screenFile, _palette);
setPalette(0, 256, _palette, RDPAL_FADE);
// Indicating that it's a screen palette
_lastPaletteRes = 0;
_vm->_resman->closeResource(_thisScreen.background_layer_id);
fadeUp();
_thisScreen.new_palette = 0;
}
void Screen::setFullPalette(int32 palRes) {
// fudge for hut interior
// - unpausing should restore last palette as normal (could be screen
// palette or 'dark_palette_13')
// - but restoring the screen palette after 'dark_palette_13' should
// now work properly too!
// "Hut interior" refers to the watchman's hut in Marseille, and this
// is apparently needed for the palette to be restored properly when
// you turn the light off. (I didn't even notice the light switch!)
if (_vm->_logic->readVar(LOCATION) == 13) {
// unpausing
if (palRes == -1) {
// restore whatever palette was last set (screen
// palette or 'dark_palette_13')
palRes = _lastPaletteRes;
}
} else {
// check if we're just restoring the current screen palette
// because we might actually need to use a separate palette
// file anyway eg. for pausing & unpausing during the eclipse
// unpausing (fudged for location 13)
if (palRes == -1) {
// we really meant '0'
palRes = 0;
}
if (palRes == 0 && _lastPaletteRes)
palRes = _lastPaletteRes;
}
// If non-zero, set palette to this separate palette file. Otherwise,
// set palette to current screen palette.
if (palRes) {
byte *pal = _vm->_resman->openResource(palRes);
assert(_vm->_resman->fetchType(pal) == PALETTE_FILE);
pal += ResHeader::size();
// always set color 0 to black because most background screen
// palettes have a bright color 0 although it should come out
// as black in the game!
_palette[0] = 0;
_palette[1] = 0;
_palette[2] = 0;
for (uint i = 4, j = 3; i < 4 * 256; i += 4, j += 3) {
_palette[j + 0] = pal[i + 0];
_palette[j + 1] = pal[i + 1];
_palette[j + 2] = pal[i + 2];
}
setPalette(0, 256, _palette, RDPAL_INSTANT);
_vm->_resman->closeResource(palRes);
} else {
if (_thisScreen.background_layer_id) {
byte *data = _vm->_resman->openResource(_thisScreen.background_layer_id);
// Do not fetch palette match table when using PSX version,
// because it is not present.
if (!Sword2Engine::isPsx())
memcpy(_paletteMatch, _vm->fetchPaletteMatchTable(data), PALTABLESIZE);
_vm->fetchPalette(data, _palette);
setPalette(0, 256, _palette, RDPAL_INSTANT);
_vm->_resman->closeResource(_thisScreen.background_layer_id);
} else
error("setFullPalette(0) called, but no current screen available");
}
if (palRes != CONTROL_PANEL_PALETTE)
_lastPaletteRes = palRes;
}
/**
* Matches a color triplet to a palette index.
* @param r red color component
* @param g green color component
* @param b blue color component
* @return the palette index of the closest matching color in the palette
*/
// FIXME: This used to be inlined - probably a good idea - but the
// linker complained when I tried to use it in sprite.cpp.
uint8 Screen::quickMatch(uint8 r, uint8 g, uint8 b) {
return _paletteMatch[((int32)(r >> 2) << 12) + ((int32)(g >> 2) << 6) + (b >> 2)];
}
/**
* Sets the palette.
* @param startEntry the first color entry to set
* @param noEntries the number of color entries to set
* @param colorTable the new color entries
* @param fadeNow whether to perform the change immediately or delayed
*/
void Screen::setPalette(int16 startEntry, int16 noEntries, byte *colorTable, uint8 fadeNow) {
assert(noEntries > 0);
memmove(&_palette[3 * startEntry], colorTable, noEntries * 3);
if (fadeNow == RDPAL_INSTANT) {
setSystemPalette(_palette, startEntry, noEntries);
setNeedFullRedraw();
}
}
void Screen::dimPalette(bool dim) {
if (getFadeStatus() != RDFADE_NONE)
return;
if (dim != _dimPalette) {
_dimPalette = dim;
setSystemPalette(_palette, 0, 256);
setNeedFullRedraw();
}
}
/**
* Fades the palette up from black to the current palette.
* @param time the time it will take the palette to fade up
*/
int32 Screen::fadeUp(float time) {
if (getFadeStatus() != RDFADE_BLACK && getFadeStatus() != RDFADE_NONE)
return RDERR_FADEINCOMPLETE;
_fadeTotalTime = (int32)(time * 1000);
_fadeStatus = RDFADE_UP;
_fadeStartTime = getTick();
return RD_OK;
}
/**
* Fades the palette down to black from the current palette.
* @param time the time it will take the palette to fade down
*/
int32 Screen::fadeDown(float time) {
if (getFadeStatus() != RDFADE_BLACK && getFadeStatus() != RDFADE_NONE)
return RDERR_FADEINCOMPLETE;
_fadeTotalTime = (int32)(time * 1000);
_fadeStatus = RDFADE_DOWN;
_fadeStartTime = getTick();
return RD_OK;
}
/**
* Get the current fade status
* @return RDFADE_UP (fading up), RDFADE_DOWN (fading down), RDFADE_NONE
* (not faded), or RDFADE_BLACK (completely faded down)
*/
uint8 Screen::getFadeStatus() {
return _fadeStatus;
}
void Screen::waitForFade() {
while (getFadeStatus() != RDFADE_NONE && getFadeStatus() != RDFADE_BLACK && !_vm->shouldQuit()) {
updateDisplay();
_vm->_system->delayMillis(20);
}
}
void Screen::fadeServer() {
static int32 previousTime = 0;
byte fadePalette[256 * 3];
byte *newPalette = fadePalette;
int32 currentTime;
int16 fadeMultiplier;
int16 i;
// If we're not in the process of fading, do nothing.
if (getFadeStatus() != RDFADE_UP && getFadeStatus() != RDFADE_DOWN)
return;
// I don't know if this is necessary, but let's limit how often the
// palette is updated, just to be safe.
currentTime = getTick();
if (currentTime - previousTime <= 25)
return;
previousTime = currentTime;
if (getFadeStatus() == RDFADE_UP) {
if (currentTime >= _fadeStartTime + _fadeTotalTime) {
_fadeStatus = RDFADE_NONE;
newPalette = _palette;
} else {
fadeMultiplier = (int16)(((int32)(currentTime - _fadeStartTime) * 256) / _fadeTotalTime);
for (i = 0; i < 256; i++) {
newPalette[i * 3 + 0] = (_palette[i * 3 + 0] * fadeMultiplier) >> 8;
newPalette[i * 3 + 1] = (_palette[i * 3 + 1] * fadeMultiplier) >> 8;
newPalette[i * 3 + 2] = (_palette[i * 3 + 2] * fadeMultiplier) >> 8;
}
}
} else {
if (currentTime >= _fadeStartTime + _fadeTotalTime) {
_fadeStatus = RDFADE_BLACK;
memset(newPalette, 0, sizeof(fadePalette));
} else {
fadeMultiplier = (int16)(((int32)(_fadeTotalTime - (currentTime - _fadeStartTime)) * 256) / _fadeTotalTime);
for (i = 0; i < 256; i++) {
newPalette[i * 3 + 0] = (_palette[i * 3 + 0] * fadeMultiplier) >> 8;
newPalette[i * 3 + 1] = (_palette[i * 3 + 1] * fadeMultiplier) >> 8;
newPalette[i * 3 + 2] = (_palette[i * 3 + 2] * fadeMultiplier) >> 8;
}
}
}
setSystemPalette(newPalette, 0, 256);
setNeedFullRedraw();
}
void Screen::setSystemPalette(const byte *colors, uint start, uint num) {
if (_dimPalette) {
byte pal[256 * 3];
for (uint i = start * 3; i < 3 * (start + num); i++)
pal[i] = colors[i] / 2;
_vm->_system->getPaletteManager()->setPalette(pal, start, num);
} else {
_vm->_system->getPaletteManager()->setPalette(colors, start, num);
}
}
} // End of namespace Sword2

447
engines/sword2/protocol.cpp Normal file
View File

@@ -0,0 +1,447 @@
/* 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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/file.h"
#include "common/endian.h"
#include "sword2/sword2.h"
#include "sword2/header.h"
#include "sword2/resman.h"
#include "sword2/logic.h"
namespace Sword2 {
/**
* Returns a pointer to the first palette entry, given the pointer to the start
* of the screen file.
*/
void Sword2Engine::fetchPalette(byte *screenFile, byte *palBuffer) {
byte *palette;
if (isPsx()) { // PSX version doesn't have a "MultiScreenHeader", instead there's a ScreenHeader and a tag
palette = screenFile + ResHeader::size() + ScreenHeader::size() + 2;
} else {
MultiScreenHeader mscreenHeader;
mscreenHeader.read(screenFile + ResHeader::size());
palette = screenFile + ResHeader::size() + mscreenHeader.palette;
}
// Always set color 0 to black, because while most background screen
// palettes have a bright color 0 it should come out as black in the
// game.
palBuffer[0] = 0;
palBuffer[1] = 0;
palBuffer[2] = 0;
for (uint i = 4, j = 3; i < 4 * 256; i += 4, j += 3) {
palBuffer[j + 0] = palette[i + 0];
palBuffer[j + 1] = palette[i + 1];
palBuffer[j + 2] = palette[i + 2];
}
}
/**
* Returns a pointer to the start of the palette match table, given the pointer
* to the start of the screen file.
* It returns NULL when used with PSX version, as there are no palette match tables in
* the resource files.
*/
byte *Sword2Engine::fetchPaletteMatchTable(byte *screenFile) {
if (isPsx()) return nullptr;
MultiScreenHeader mscreenHeader;
mscreenHeader.read(screenFile + ResHeader::size());
return screenFile + ResHeader::size() + mscreenHeader.paletteTable;
}
/**
* Returns a pointer to the screen header, given the pointer to the start of
* the screen file.
*/
byte *Sword2Engine::fetchScreenHeader(byte *screenFile) {
if (isPsx()) { // In PSX version there's no MultiScreenHeader, so just skip resource header
return screenFile + ResHeader::size();
} else {
MultiScreenHeader mscreenHeader;
mscreenHeader.read(screenFile + ResHeader::size());
return screenFile + ResHeader::size() + mscreenHeader.screen;
}
}
/**
* Returns a pointer to the requested layer header, given the pointer to the
* start of the screen file. Drops out if the requested layer number exceeds
* the number of layers on this screen.
*/
byte *Sword2Engine::fetchLayerHeader(byte *screenFile, uint16 layerNo) {
#ifdef SWORD2_DEBUG
ScreenHeader screenHead;
screenHead.read(fetchScreenHeader(screenFile));
assert(layerNo < screenHead.noLayers);
#endif
if (isPsx()) {
return screenFile + ResHeader::size() + ScreenHeader::size() + 2 + 0x400 + layerNo * LayerHeader::size();
} else {
MultiScreenHeader mscreenHeader;
mscreenHeader.read(screenFile + ResHeader::size());
return screenFile + ResHeader::size() + mscreenHeader.layers + layerNo * LayerHeader::size();
}
}
/**
* Returns a pointer to the start of the shading mask, given the pointer to the
* start of the screen file.
* If we are non PSX, this will return NULL, as we don't have shading masks.
*/
byte *Sword2Engine::fetchShadingMask(byte *screenFile) {
if (isPsx()) return nullptr;
MultiScreenHeader mscreenHeader;
mscreenHeader.read(screenFile + ResHeader::size());
return screenFile + ResHeader::size() + mscreenHeader.maskOffset;
}
/**
* Returns a pointer to the anim header, given the pointer to the start of the
* anim file.
*/
byte *Sword2Engine::fetchAnimHeader(byte *animFile) {
return animFile + ResHeader::size();
}
/**
* Returns a pointer to the requested frame number's cdtEntry, given the
* pointer to the start of the anim file. Drops out if the requested frame
* number exceeds the number of frames in this anim.
*/
byte *Sword2Engine::fetchCdtEntry(byte *animFile, uint16 frameNo) {
#ifdef SWORD2_DEBUG
AnimHeader animHead;
animHead.read(fetchAnimHeader(animFile));
if (frameNo > animHead->noAnimFrames - 1)
error("fetchCdtEntry(animFile,%d) - anim only %d frames", frameNo, animHead->noAnimFrames);
#endif
return fetchAnimHeader(animFile) + AnimHeader::size() + frameNo * CdtEntry::size();
}
/**
* Returns a pointer to the requested frame number's header, given the pointer
* to the start of the anim file. Drops out if the requested frame number
* exceeds the number of frames in this anim
*/
byte *Sword2Engine::fetchFrameHeader(byte *animFile, uint16 frameNo) {
// required address = (address of the start of the anim header) + frameOffset
CdtEntry cdt;
cdt.read(fetchCdtEntry(animFile, frameNo));
return animFile + ResHeader::size() + cdt.frameOffset;
}
/**
* Returns a pointer to the requested parallax layer data.
*/
byte *Sword2Engine::fetchBackgroundParallaxLayer(byte *screenFile, int layer) {
if (isPsx()) {
byte *psxParallax = _screen->getPsxScrCache(0);
// Manage cache for background psx parallaxes
if (!_screen->getPsxScrCacheStatus(0)) { // This parallax layer is not present
return nullptr;
} else if (psxParallax != nullptr) { // Parallax layer present, and already in cache
return psxParallax;
} else { // Present, but not cached
uint32 locNo = _logic->getLocationNum();
// At game startup, we have a wrong location number stored
// in game vars (0, instead of 3), work around this.
locNo = (locNo == 0) ? 3 : locNo;
psxParallax = fetchPsxParallax(locNo, 0);
_screen->setPsxScrCache(psxParallax, 0);
return psxParallax;
}
} else {
MultiScreenHeader mscreenHeader;
mscreenHeader.read(screenFile + ResHeader::size());
assert(mscreenHeader.bg_parallax[layer]);
return screenFile + ResHeader::size() + mscreenHeader.bg_parallax[layer];
}
}
byte *Sword2Engine::fetchBackgroundLayer(byte *screenFile) {
if (isPsx()) {
byte *psxBackground = _screen->getPsxScrCache(1);
// Manage cache for psx backgrounds
if (psxBackground) { // Background is cached
return psxBackground;
} else { // Background not cached
uint32 locNo = _logic->getLocationNum();
// We have a wrong location number at start, fix that
locNo = (locNo == 0) ? 3 : locNo;
psxBackground = fetchPsxBackground(locNo);
_screen->setPsxScrCache(psxBackground, 1);
return psxBackground;
}
} else {
MultiScreenHeader mscreenHeader;
mscreenHeader.read(screenFile + ResHeader::size());
assert(mscreenHeader.screen);
return screenFile + ResHeader::size() + mscreenHeader.screen + ScreenHeader::size();
}
}
byte *Sword2Engine::fetchForegroundParallaxLayer(byte *screenFile, int layer) {
if (isPsx()) {
byte *psxParallax = _screen->getPsxScrCache(2);
// Manage cache for psx parallaxes
if (!_screen->getPsxScrCacheStatus(2)) { // This parallax layer is not present
return nullptr;
} else if (psxParallax) { // Parallax layer present and cached
return psxParallax;
} else { // Present, but still not cached
uint32 locNo = _logic->getLocationNum();
// We have a wrong location number at start, fix that
locNo = (locNo == 0) ? 3 : locNo;
psxParallax = fetchPsxParallax(locNo, 1);
_screen->setPsxScrCache(psxParallax, 2);
return psxParallax;
}
} else {
MultiScreenHeader mscreenHeader;
mscreenHeader.read(screenFile + ResHeader::size());
assert(mscreenHeader.fg_parallax[layer]);
return screenFile + ResHeader::size() + mscreenHeader.fg_parallax[layer];
}
}
byte *Sword2Engine::fetchTextLine(byte *file, uint32 text_line) {
TextHeader text_header;
static byte errorLine[128];
text_header.read(file + ResHeader::size());
if (text_line >= text_header.noOfLines) {
Common::sprintf_s(errorLine, "xxMissing line %d of %s (only 0..%d)", text_line, _resman->fetchName(file), text_header.noOfLines - 1);
// first 2 chars are NULL so that actor-number comes out as '0'
errorLine[0] = 0;
errorLine[1] = 0;
return errorLine;
}
// The "number of lines" field is followed by a lookup table
return file + READ_LE_UINT32(file + ResHeader::size() + 4 + 4 * text_line);
}
/**
* Returns a pointer to psx background data for passed location number
* At the beginning of the passed data there's an artificial header composed by
* uint16: background X resolution
* uint16: background Y resolution
* uint32: offset to subtract from offset table entries
*/
byte *Sword2Engine::fetchPsxBackground(uint32 location) {
Common::File file;
PSXScreensEntry header;
uint32 screenOffset, dataOffset;
uint32 totSize; // Total size of background, counting data, offset table and additional header
byte *buffer;
if (!file.open("screens.clu")) {
GUIErrorMessage("Broken Sword II: Cannot open screens.clu");
return nullptr;
}
file.seek(location * 4, SEEK_SET);
screenOffset = file.readUint32LE();
if (screenOffset == 0) { // We don't have screen data for this location number.
file.close();
return nullptr;
}
// Get to the beginning of PSXScreensEntry
file.seek(screenOffset + ResHeader::size(), SEEK_SET);
buffer = (byte *)malloc(PSXScreensEntry::size());
file.read(buffer, PSXScreensEntry::size());
// Prepare the header
header.read(buffer);
free(buffer);
file.seek(screenOffset + header.bgOffset + 4, SEEK_SET);
dataOffset = file.readUint32LE();
file.seek(screenOffset + header.bgOffset, SEEK_SET);
totSize = header.bgSize + (dataOffset - header.bgOffset) + 8;
buffer = (byte *)malloc(totSize);
// Write some informations before background data
WRITE_LE_UINT16(buffer, header.bgXres);
WRITE_LE_UINT16(buffer + 2, header.bgYres);
WRITE_LE_UINT32(buffer + 4, header.bgOffset);
file.read(buffer + 8, totSize - 8); // Do not write on the header
file.close();
return buffer;
}
/**
* Returns a pointer to selected psx parallax data for passed location number
* At the beginning of the passed data there's an artificial header composed by
* uint16: parallax X resolution
* uint16: parallax Y resolution
* uint16: width in 64x16 tiles of parallax
* uint16: height in 64x16 tiles of parallax
*/
byte *Sword2Engine::fetchPsxParallax(uint32 location, uint8 level) {
Common::File file;
PSXScreensEntry header;
uint32 screenOffset;
uint16 horTiles; // Number of horizontal tiles in the parallax grid
uint16 verTiles; // Number of vertical tiles in parallax grid
uint32 totSize; // Total size of parallax, counting data, grid, and additional header
byte *buffer;
uint16 plxXres;
uint16 plxYres;
uint32 plxOffset;
uint32 plxSize;
if (level > 1)
return nullptr;
if (!file.open("screens.clu")) {
GUIErrorMessage("Broken Sword II: Cannot open screens.clu");
return nullptr;
}
file.seek(location * 4, SEEK_SET);
screenOffset = file.readUint32LE();
if (screenOffset == 0) // There is no screen here
return nullptr;
// Get to the beginning of PSXScreensEntry
file.seek(screenOffset + ResHeader::size(), SEEK_SET);
buffer = (byte *)malloc(PSXScreensEntry::size());
file.read(buffer, PSXScreensEntry::size());
// Initialize the header
header.read(buffer);
free(buffer);
// We are fetching...
if (level == 0) { // a background parallax
plxXres = header.bgPlxXres;
plxYres = header.bgPlxYres;
plxOffset = header.bgPlxOffset;
plxSize = header.bgPlxSize;
} else { // a foreground parallax
plxXres = header.fgPlxXres;
plxYres = header.fgPlxYres;
plxOffset = header.fgPlxOffset;
plxSize = header.fgPlxSize;
}
if (plxXres == 0 || plxYres == 0 || plxSize == 0) // This screen has no parallax data.
return nullptr;
debug(2, "fetchPsxParallax() -> %s parallax, xRes: %u, yRes: %u", (level == 0) ? "Background" : "Foreground", plxXres, plxYres);
// Calculate the number of tiles which compose the parallax grid.
horTiles = (plxXres % 64) ? (plxXres / 64) + 1 : plxXres / 64;
verTiles = (plxYres % 16) ? (plxYres / 16) + 1 : plxYres / 16;
totSize = plxSize + horTiles * verTiles * 4 + 8;
file.seek(screenOffset + plxOffset, SEEK_SET);
buffer = (byte *)malloc(totSize);
// Insert parallax resolution information in the buffer,
// preceding parallax data.
WRITE_LE_UINT16(buffer, plxXres);
WRITE_LE_UINT16(buffer + 2, plxYres);
WRITE_LE_UINT16(buffer + 4, horTiles);
WRITE_LE_UINT16(buffer + 6, verTiles);
// Read parallax data from file and store it inside the buffer,
// skipping the generated header.
file.read(buffer + 8, totSize - 8);
file.close();
return buffer;
}
// Used for testing text & speech (see fnISpeak in speech.cpp)
bool Sword2Engine::checkTextLine(byte *file, uint32 text_line) {
TextHeader text_header;
text_header.read(file + ResHeader::size());
return text_line < text_header.noOfLines;
}
} // End of namespace Sword2

858
engines/sword2/render.cpp Normal file
View File

@@ -0,0 +1,858 @@
/* 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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/system.h"
#include "graphics/primitives.h"
#include "sword2/sword2.h"
#include "sword2/defs.h"
#include "sword2/screen.h"
namespace Sword2 {
#define RENDERAVERAGETOTAL 4
void Screen::updateRect(Common::Rect *r) {
_vm->_system->copyRectToScreen(_buffer + r->top * _screenWide + r->left,
_screenWide, r->left, r->top, r->right - r->left,
r->bottom - r->top);
}
void Screen::blitBlockSurface(BlockSurface *s, Common::Rect *r, Common::Rect *clipRect) {
if (!r->intersects(*clipRect))
return;
byte *src = s->data;
if (r->top < clipRect->top) {
src -= BLOCKWIDTH * (r->top - clipRect->top);
r->top = clipRect->top;
}
if (r->left < clipRect->left) {
src -= (r->left - clipRect->left);
r->left = clipRect->left;
}
if (r->bottom > clipRect->bottom)
r->bottom = clipRect->bottom;
if (r->right > clipRect->right)
r->right = clipRect->right;
byte *dst = _buffer + r->top * _screenWide + r->left;
int i;
if (s->transparent) {
for (i = 0; i < r->bottom - r->top; i++) {
for (int j = 0; j < r->right - r->left; j++) {
if (src[j])
dst[j] = src[j];
}
src += BLOCKWIDTH;
dst += _screenWide;
}
} else {
for (i = 0; i < r->bottom - r->top; i++) {
memcpy(dst, src, r->right - r->left);
src += BLOCKWIDTH;
dst += _screenWide;
}
}
}
// There are two different separate functions for scaling the image - one fast
// and one good. Or at least that's the theory. I'm sure there are better ways
// to scale an image than this. The latter is used at the highest graphics
// quality setting. Note that the "good" scaler takes extra parameters so that
// it can use the background image when calculating the average pixel value.
//
// This code isn't quite like the original DrawSprite(), but the result should
// be close enough, I hope.
void Screen::scaleImageFast(byte *dst, uint16 dstPitch, uint16 dstWidth, uint16 dstHeight, byte *src, uint16 srcPitch, uint16 srcWidth, uint16 srcHeight) {
int x, y;
for (x = 0; x < dstWidth; x++)
_xScale[x] = (x * srcWidth) / dstWidth;
for (y = 0; y < dstHeight; y++)
_yScale[y] = (y * srcHeight) / dstHeight;
for (y = 0; y < dstHeight; y++) {
for (x = 0; x < dstWidth; x++) {
dst[x] = src[_yScale[y] * srcPitch + _xScale[x]];
}
dst += dstPitch;
}
}
void Screen::scaleImageGood(byte *dst, uint16 dstPitch, uint16 dstWidth, uint16 dstHeight, byte *src, uint16 srcPitch, uint16 srcWidth, uint16 srcHeight, byte *backBuf, int16 bbXPos, int16 bbYPos) {
for (int y = 0; y < dstHeight; y++) {
for (int x = 0; x < dstWidth; x++) {
uint8 c1, c2, c3, c4;
uint32 xPos = (x * srcWidth) / dstWidth;
uint32 yPos = (y * srcHeight) / dstHeight;
uint32 xFrac = dstWidth - (x * srcWidth) % dstWidth;
uint32 yFrac = dstHeight - (y * srcHeight) % dstHeight;
byte *srcPtr = src + yPos * srcPitch + xPos;
bool transparent = true;
if (*srcPtr) {
c1 = *srcPtr;
transparent = false;
} else {
if (bbXPos + x >= 0 &&
bbXPos + x < RENDERWIDE &&
bbYPos + y >= MENUDEEP &&
bbYPos + y < MENUDEEP + RENDERDEEP) {
c1 = *(backBuf + _screenWide * (bbYPos + y) + bbXPos + x);
} else {
c1 = 0;
}
}
if (x < dstWidth - 1) {
if (*(srcPtr + 1)) {
c2 = *(srcPtr + 1);
transparent = false;
} else {
if (bbXPos + x + 1 >= 0 &&
bbXPos + x + 1 < RENDERWIDE &&
bbYPos + y >= MENUDEEP &&
bbYPos + y + 1 < MENUDEEP + RENDERDEEP) {
c2 = *(backBuf + _screenWide * (bbYPos + y) + bbXPos + x + 1);
} else {
c2 = c1;
}
}
} else {
c2 = c1;
}
if (y < dstHeight - 1) {
if (*(srcPtr + srcPitch)) {
c3 = *(srcPtr + srcPitch);
transparent = false;
} else {
if (bbXPos + x >= 0 &&
bbXPos + x < RENDERWIDE &&
bbYPos + y + 1 >= MENUDEEP &&
bbYPos + y + 1 < MENUDEEP + RENDERDEEP) {
c3 = *(backBuf + _screenWide * (bbYPos + y + 1) + bbXPos);
} else {
c3 = c1;
}
}
} else {
c3 = c1;
}
if (x < dstWidth - 1 && y < dstHeight - 1) {
if (*(srcPtr + srcPitch + 1)) {
c4 = *(srcPtr + srcPitch + 1);
transparent = false;
} else {
if (bbXPos + x + 1 >= 0 &&
bbXPos + x + 1 < RENDERWIDE &&
bbYPos + y + 1 >= MENUDEEP &&
bbYPos + y + 1 < MENUDEEP + RENDERDEEP) {
c4 = *(backBuf + _screenWide * (bbYPos + y + 1) + bbXPos + x + 1);
} else {
c4 = c3;
}
}
} else {
c4 = c3;
}
if (!transparent) {
uint32 r1 = _palette[c1 * 3 + 0];
uint32 g1 = _palette[c1 * 3 + 1];
uint32 b1 = _palette[c1 * 3 + 2];
uint32 r2 = _palette[c2 * 3 + 0];
uint32 g2 = _palette[c2 * 3 + 1];
uint32 b2 = _palette[c2 * 3 + 2];
uint32 r3 = _palette[c3 * 3 + 0];
uint32 g3 = _palette[c3 * 3 + 1];
uint32 b3 = _palette[c3 * 3 + 2];
uint32 r4 = _palette[c4 * 3 + 0];
uint32 g4 = _palette[c4 * 3 + 1];
uint32 b4 = _palette[c4 * 3 + 2];
uint32 r5 = (r1 * xFrac + r2 * (dstWidth - xFrac)) / dstWidth;
uint32 g5 = (g1 * xFrac + g2 * (dstWidth - xFrac)) / dstWidth;
uint32 b5 = (b1 * xFrac + b2 * (dstWidth - xFrac)) / dstWidth;
uint32 r6 = (r3 * xFrac + r4 * (dstWidth - xFrac)) / dstWidth;
uint32 g6 = (g3 * xFrac + g4 * (dstWidth - xFrac)) / dstWidth;
uint32 b6 = (b3 * xFrac + b4 * (dstWidth - xFrac)) / dstWidth;
uint32 r = (r5 * yFrac + r6 * (dstHeight - yFrac)) / dstHeight;
uint32 g = (g5 * yFrac + g6 * (dstHeight - yFrac)) / dstHeight;
uint32 b = (b5 * yFrac + b6 * (dstHeight - yFrac)) / dstHeight;
dst[y * dstWidth + x] = quickMatch(r, g, b);
} else
dst[y * dstWidth + x] = 0;
}
}
}
/**
* Plots a point relative to the top left corner of the screen. This is only
* used for debugging.
* @param x x-coordinate of the point
* @param y y-coordinate of the point
* @param color color of the point
*/
void Screen::plotPoint(int x, int y, uint8 color) {
byte *buf = _buffer + MENUDEEP * RENDERWIDE;
x -= _scrollX;
y -= _scrollY;
if (x >= 0 && x < RENDERWIDE && y >= 0 && y < RENDERDEEP) {
buf[y * RENDERWIDE + x] = color;
markAsDirty(x, y + MENUDEEP, x, y + MENUDEEP);
}
}
class ScreenPrimitives : public Graphics::Primitives {
void drawPoint(int x, int y, uint32 color, void *data) override {
Screen *screen = (Screen *)data;
screen->plotPoint(x, y, (uint8) color);
}
};
/**
* Draws a line from one point to another. This is only used for debugging.
* @param x0 x-coordinate of the start point
* @param y0 y-coordinate of the start point
* @param x1 x-coordinate of the end point
* @param y1 y-coordinate of the end point
* @param color color of the line
*/
void Screen::drawLine(int x0, int y0, int x1, int y1, uint8 color) {
ScreenPrimitives().drawLine(x0, y0, x1, y1, color, this);
}
/**
* This function tells the driver the size of the background screen for the
* current location.
* @param w width of the current location
* @param h height of the current location
*/
void Screen::setLocationMetrics(uint16 w, uint16 h) {
_locationWide = w;
_locationDeep = h;
setNeedFullRedraw();
}
/**
* Draws a parallax layer at the current position determined by the scroll. A
* parallax can be either foreground, background or the main screen.
*/
void Screen::renderParallax(byte *ptr, int16 l) {
int16 x, y;
uint16 xRes, yRes;
Common::Rect r;
if (!ptr)
return;
// Fetch resolution data from parallax
if (Sword2Engine::isPsx()) {
xRes = READ_LE_UINT16(ptr);
yRes = READ_LE_UINT16(ptr + 2) * 2;
} else {
Parallax p;
p.read(ptr);
xRes = p.w;
yRes = p.h;
}
if (_locationWide == _screenWide)
x = 0;
else
x = ((int32)((xRes - _screenWide) * _scrollX) / (int32)(_locationWide - _screenWide));
if (_locationDeep == _screenDeep - MENUDEEP * 2)
y = 0;
else
y = ((int32)((yRes - (_screenDeep - MENUDEEP * 2)) * _scrollY) / (int32)(_locationDeep - (_screenDeep - MENUDEEP * 2)));
Common::Rect clipRect;
// Leave enough space for the top and bottom menues
clipRect.left = 0;
clipRect.right = _screenWide;
clipRect.top = MENUDEEP;
clipRect.bottom = _screenDeep - MENUDEEP;
for (int j = 0; j < _yBlocks[l]; j++) {
for (int i = 0; i < _xBlocks[l]; i++) {
if (_blockSurfaces[l][i + j * _xBlocks[l]]) {
r.left = i * BLOCKWIDTH - x;
r.right = r.left + BLOCKWIDTH;
r.top = j * BLOCKHEIGHT - y + MENUDEEP;
r.bottom = r.top + BLOCKHEIGHT;
blitBlockSurface(_blockSurfaces[l][i + j * _xBlocks[l]], &r, &clipRect);
}
}
}
_parallaxScrollX = _scrollX - x;
_parallaxScrollY = _scrollY - y;
}
// Uncomment this when benchmarking the drawing routines.
#define LIMIT_FRAME_RATE
/**
* Initializes the timers before the render loop is entered.
*/
void Screen::initializeRenderCycle() {
_initialTime = _vm->_system->getMillis();
_totalTime = _initialTime + (1000 / _vm->getFramesPerSecond());
}
/**
* This function should be called when the game engine is ready to start the
* render cycle.
*/
void Screen::startRenderCycle() {
_scrollXOld = _scrollX;
_scrollYOld = _scrollY;
_startTime = _vm->_system->getMillis();
if (_startTime + _renderAverageTime >= _totalTime) {
_scrollX = _scrollXTarget;
_scrollY = _scrollYTarget;
_renderTooSlow = true;
} else {
_scrollX = (int16)(_scrollXOld + ((_scrollXTarget - _scrollXOld) * (_startTime - _initialTime + _renderAverageTime)) / (_totalTime - _initialTime));
_scrollY = (int16)(_scrollYOld + ((_scrollYTarget - _scrollYOld) * (_startTime - _initialTime + _renderAverageTime)) / (_totalTime - _initialTime));
_renderTooSlow = false;
}
if (_scrollXOld != _scrollX || _scrollYOld != _scrollY)
setNeedFullRedraw();
_framesPerGameCycle = 0;
}
/**
* This function should be called at the end of the render cycle.
* @return true if the render cycle is to be terminated,
* or false if it should continue
*/
bool Screen::endRenderCycle() {
static int32 renderTimeLog[4] = { 60, 60, 60, 60 };
static int32 renderCountIndex = 0;
int32 time;
time = _vm->_system->getMillis();
renderTimeLog[renderCountIndex] = time - _startTime;
_startTime = time;
_renderAverageTime = (renderTimeLog[0] + renderTimeLog[1] + renderTimeLog[2] + renderTimeLog[3]) >> 2;
_framesPerGameCycle++;
if (++renderCountIndex == RENDERAVERAGETOTAL)
renderCountIndex = 0;
if (_renderTooSlow) {
initializeRenderCycle();
return true;
}
if (_startTime + _renderAverageTime >= _totalTime) {
_totalTime += (1000 / _vm->getFramesPerSecond());
_initialTime = time;
return true;
}
#ifdef LIMIT_FRAME_RATE
if (_scrollXTarget == _scrollX && _scrollYTarget == _scrollY) {
// If we have already reached the scroll target sleep for the
// rest of the render cycle.
_vm->sleepUntil(_totalTime);
_initialTime = _vm->_system->getMillis();
_totalTime += (1000 / _vm->getFramesPerSecond());
return true;
}
#endif
// This is an attempt to ensure that we always reach the scroll target.
// Otherwise the game frequently tries to pump out new interpolation
// frames without ever getting anywhere.
if (ABS(_scrollX - _scrollXTarget) <= 1 && ABS(_scrollY - _scrollYTarget) <= 1) {
_scrollX = _scrollXTarget;
_scrollY = _scrollYTarget;
} else {
_scrollX = (int16)(_scrollXOld + ((_scrollXTarget - _scrollXOld) * (_startTime - _initialTime + _renderAverageTime)) / (_totalTime - _initialTime));
_scrollY = (int16)(_scrollYOld + ((_scrollYTarget - _scrollYOld) * (_startTime - _initialTime + _renderAverageTime)) / (_totalTime - _initialTime));
}
if (_scrollX != _scrollXOld || _scrollY != _scrollYOld)
setNeedFullRedraw();
#ifdef LIMIT_FRAME_RATE
// Give the other threads some breathing space. This apparently helps
// against bug #1386, though I was never able to reproduce it for
// myself.
_vm->_system->delayMillis(10);
#endif
return false;
}
/**
* Reset scrolling stuff. This function is called from initBackground()
*/
void Screen::resetRenderEngine() {
_parallaxScrollX = 0;
_parallaxScrollY = 0;
_scrollX = 0;
_scrollY = 0;
}
/**
* This function should be called five times with either the parallax layer
* or a NULL pointer in order of background parallax to foreground parallax.
*/
int32 Screen::initializeBackgroundLayer(byte *parallax) {
Parallax p;
uint16 i, j, k;
byte *data;
byte *dst;
debug(2, "initializeBackgroundLayer");
assert(_layer < MAXLAYERS);
if (!parallax) {
_layer++;
return RD_OK;
}
p.read(parallax);
_xBlocks[_layer] = (p.w + BLOCKWIDTH - 1) / BLOCKWIDTH;
_yBlocks[_layer] = (p.h + BLOCKHEIGHT - 1) / BLOCKHEIGHT;
_blockSurfaces[_layer] = (BlockSurface **)calloc(_xBlocks[_layer] * _yBlocks[_layer], sizeof(BlockSurface *));
if (!_blockSurfaces[_layer])
return RDERR_OUTOFMEMORY;
// Decode the parallax layer into a large chunk of memory
byte *memchunk = (byte *)calloc(_xBlocks[_layer] * _yBlocks[_layer], BLOCKWIDTH * BLOCKHEIGHT);
if (!memchunk)
return RDERR_OUTOFMEMORY;
for (i = 0; i < p.h; i++) {
uint32 p_offset = READ_LE_UINT32(parallax + Parallax::size() + 4 * i);
if (!p_offset)
continue;
byte *pLine = parallax + p_offset;
uint16 packets = READ_LE_UINT16(pLine);
uint16 offset = READ_LE_UINT16(pLine + 2);
data = pLine + 4;
dst = memchunk + i * p.w + offset;
if (!packets) {
memcpy(dst, data, p.w);
continue;
}
bool zeros = false;
for (j = 0; j < packets; j++) {
if (zeros) {
dst += *data;
offset += *data;
data++;
zeros = false;
} else if (!*data) {
data++;
zeros = true;
} else {
uint16 count = *data++;
memcpy(dst, data, count);
data += count;
dst += count;
offset += count;
zeros = true;
}
}
}
// The large memory chunk is now divided into a number of smaller
// surfaces. For most parallax layers, we'll end up using less memory
// this way, and it will be faster to draw since completely transparent
// surfaces are discarded.
for (i = 0; i < _xBlocks[_layer] * _yBlocks[_layer]; i++) {
bool block_has_data = false;
bool block_is_transparent = false;
int x = BLOCKWIDTH * (i % _xBlocks[_layer]);
int y = BLOCKHEIGHT * (i / _xBlocks[_layer]);
data = memchunk + p.w * y + x;
for (j = 0; j < BLOCKHEIGHT; j++) {
for (k = 0; k < BLOCKWIDTH; k++) {
if (x + k < p.w && y + j < p.h) {
if (data[j * p.w + k])
block_has_data = true;
else
block_is_transparent = true;
}
}
}
// Only assign a surface to the block if it contains data.
if (block_has_data) {
_blockSurfaces[_layer][i] = (BlockSurface *)malloc(sizeof(BlockSurface));
// Copy the data into the surfaces.
dst = _blockSurfaces[_layer][i]->data;
for (j = 0; j < BLOCKHEIGHT; j++) {
memcpy(dst, data, BLOCKWIDTH);
data += p.w;
dst += BLOCKWIDTH;
}
_blockSurfaces[_layer][i]->transparent = block_is_transparent;
} else
_blockSurfaces[_layer][i] = nullptr;
}
free(memchunk);
_layer++;
return RD_OK;
}
/**
* This converts PSX format background data into a format that
* can be understood by renderParallax functions.
* PSX Backgrounds are divided into tiles of 64x32 (with aspect
* ratio correction), while PC backgrounds are in tiles of 64x64.
*/
int32 Screen::initializePsxBackgroundLayer(byte *parallax) {
uint16 bgXres, bgYres;
uint16 trueXres, stripeNumber, totStripes;
uint32 baseAddress, stripePos;
uint16 i, j;
byte *dst;
debug(2, "initializePsxBackgroundLayer");
assert(_layer < MAXLAYERS);
if (!parallax) {
_layer++;
return RD_OK;
}
// Fetch data from buffer
bgXres = READ_LE_UINT16(parallax);
bgYres = READ_LE_UINT16(parallax + 2) * 2;
baseAddress = READ_LE_UINT32(parallax + 4);
parallax += 8;
// Calculate TRUE resolution of background, must be
// a multiple of 64
trueXres = (bgXres % 64) ? ((bgXres/64) + 1) * 64 : bgXres;
totStripes = trueXres / 64;
_xBlocks[_layer] = (bgXres + BLOCKWIDTH - 1) / BLOCKWIDTH;
_yBlocks[_layer] = (bgYres + BLOCKHEIGHT - 1) / BLOCKHEIGHT;
uint16 remLines = bgYres % 64;
byte *tileChunk = (byte *)malloc(BLOCKHEIGHT * BLOCKWIDTH);
if (!tileChunk)
return RDERR_OUTOFMEMORY;
_blockSurfaces[_layer] = (BlockSurface **)calloc(_xBlocks[_layer] * _yBlocks[_layer], sizeof(BlockSurface *));
if (!_blockSurfaces[_layer]) {
free(tileChunk);
return RDERR_OUTOFMEMORY;
}
// Group PSX background (64x32, when stretched vertically) tiles together,
// to make them compatible with pc version (composed by 64x64 tiles)
stripeNumber = 0;
stripePos = 0;
for (i = 0; i < _xBlocks[_layer] * _yBlocks[_layer]; i++) {
bool block_has_data = false;
bool block_is_transparent = false;
int posX = i / _yBlocks[_layer];
int posY = i % _yBlocks[_layer];
uint32 stripeOffset = READ_LE_UINT32(parallax + stripeNumber * 8 + 4) + stripePos - baseAddress;
memset(tileChunk, 1, BLOCKHEIGHT * BLOCKWIDTH);
if (!(remLines && posY == _yBlocks[_layer] - 1))
remLines = 32;
for (j = 0; j < remLines; j++) {
memcpy(tileChunk + j * BLOCKWIDTH * 2, parallax + stripeOffset + j * BLOCKWIDTH, BLOCKWIDTH);
memcpy(tileChunk + j * BLOCKWIDTH * 2 + BLOCKWIDTH, parallax + stripeOffset + j * BLOCKWIDTH, BLOCKWIDTH);
}
for (j = 0; j < BLOCKHEIGHT * BLOCKWIDTH; j++) {
if (tileChunk[j])
block_has_data = true;
else
block_is_transparent = true;
}
int tileIndex = totStripes * posY + posX;
// Only assign a surface to the block if it contains data.
if (block_has_data) {
_blockSurfaces[_layer][tileIndex] = (BlockSurface *)malloc(sizeof(BlockSurface));
// Copy the data into the surfaces.
dst = _blockSurfaces[_layer][tileIndex]->data;
memcpy(dst, tileChunk, BLOCKWIDTH * BLOCKHEIGHT);
_blockSurfaces[_layer][tileIndex]->transparent = block_is_transparent;
} else
_blockSurfaces[_layer][tileIndex] = nullptr;
if (posY == _yBlocks[_layer] - 1) {
stripeNumber++;
stripePos = 0;
} else {
stripePos += 0x800;
}
}
free(tileChunk);
_layer++;
return RD_OK;
}
/**
* This converts PSX format parallax data into a format that
* can be understood by renderParallax functions.
*/
int32 Screen::initializePsxParallaxLayer(byte *parallax) {
uint16 i, j, k;
byte *data;
byte *dst;
debug(2, "initializePsxParallaxLayer");
assert(_layer < MAXLAYERS);
if (!parallax) {
_layer++;
return RD_OK;
}
// uint16 plxXres = READ_LE_UINT16(parallax);
// uint16 plxYres = READ_LE_UINT16(parallax + 2);
uint16 xTiles = READ_LE_UINT16(parallax + 4);
uint16 yTiles = READ_LE_UINT16(parallax + 6);
// Beginning of parallax table composed by uint32,
// if word is 0, corresponding tile contains no data and must be skipped,
// if word is 0x400 tile contains data.
parallax += 8;
// Beginning if tiles data.
data = parallax + xTiles * yTiles * 4;
_xBlocks[_layer] = xTiles;
_yBlocks[_layer] = (yTiles / 2) + ((yTiles % 2) ? 1 : 0);
bool oddTiles = ((yTiles % 2) ? true : false);
_blockSurfaces[_layer] = (BlockSurface **)calloc(_xBlocks[_layer] * _yBlocks[_layer], sizeof(BlockSurface *));
if (!_blockSurfaces[_layer])
return RDERR_OUTOFMEMORY;
// We have to check two tiles for every block in PSX version, if one of those
// has data in it, the whole block has data. Also, tiles must be doublelined to
// get correct aspect ratio.
for (i = 0; i < _xBlocks[_layer] * _yBlocks[_layer]; i++) {
bool block_has_data = false;
bool block_is_transparent = false;
bool firstTilePresent, secondTilePresent;
int posX = i / _yBlocks[_layer];
int posY = i % _yBlocks[_layer];
if (oddTiles && posY == _yBlocks[_layer] - 1) {
firstTilePresent = READ_LE_UINT32(parallax) == 0x400;
secondTilePresent = false;
parallax += 4;
} else {
firstTilePresent = READ_LE_UINT32(parallax) == 0x400;
secondTilePresent = READ_LE_UINT32(parallax + 4) == 0x400;
parallax += 8;
}
// If one of the two grouped tiles has data, then the whole block has data
if (firstTilePresent || secondTilePresent) {
block_has_data = true;
// If one of the two grouped blocks is without data, then we also have transparency
if (!firstTilePresent || !secondTilePresent)
block_is_transparent = true;
}
// Now do a second check to see if we have a partially transparent block
if (block_has_data && !block_is_transparent) {
byte *block = data;
if (firstTilePresent) {
for (k = 0; k < 0x400; k++) {
if (*(block + k) == 0) {
block_is_transparent = true;
break;
}
}
block += 0x400; // On to next block...
}
// If we didn't find transparency in first block and we have
// a second tile, check it
if (secondTilePresent && !block_is_transparent) {
for (k = 0; k < 0x400; k++) {
if (*(block + k) == 0) {
block_is_transparent = true;
break;
}
}
}
}
int tileIndex = xTiles * posY + posX;
// Only assign a surface to the block if it contains data.
if (block_has_data) {
_blockSurfaces[_layer][tileIndex] = (BlockSurface *)malloc(sizeof(BlockSurface));
memset(_blockSurfaces[_layer][tileIndex], 0, BLOCKHEIGHT * BLOCKWIDTH);
// Copy the data into the surfaces.
dst = _blockSurfaces[_layer][tileIndex]->data;
if (firstTilePresent) { //There is data in the first tile
for (j = 0; j < 16; j++) {
memcpy(dst, data, BLOCKWIDTH);
dst += BLOCKWIDTH;
memcpy(dst, data, BLOCKWIDTH);
dst += BLOCKWIDTH;
data += BLOCKWIDTH;
}
} else {
dst += 0x800;
}
if (secondTilePresent) {
for (j = 0; j < 16; j++) {
memcpy(dst, data, BLOCKWIDTH);
dst += BLOCKWIDTH;
memcpy(dst, data, BLOCKWIDTH);
dst += BLOCKWIDTH;
data += BLOCKWIDTH;
}
}
_blockSurfaces[_layer][tileIndex]->transparent = block_is_transparent;
} else
_blockSurfaces[_layer][tileIndex] = nullptr;
}
_layer++;
return RD_OK;
}
/**
* Should be called once after leaving the room to free up memory.
*/
void Screen::closeBackgroundLayer() {
debug(2, "CloseBackgroundLayer");
if (Sword2Engine::isPsx())
flushPsxScrCache();
for (int i = 0; i < MAXLAYERS; i++) {
if (_blockSurfaces[i]) {
for (int j = 0; j < _xBlocks[i] * _yBlocks[i]; j++)
if (_blockSurfaces[i][j])
free(_blockSurfaces[i][j]);
free(_blockSurfaces[i]);
_blockSurfaces[i] = nullptr;
}
}
_layer = 0;
}
} // End of namespace Sword2

720
engines/sword2/resman.cpp Normal file
View File

@@ -0,0 +1,720 @@
/* 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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/file.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "sword2/sword2.h"
#include "sword2/defs.h"
#include "sword2/header.h"
#include "sword2/console.h"
#include "sword2/logic.h"
#include "sword2/memory.h"
#include "sword2/resman.h"
#include "sword2/router.h"
#include "sword2/screen.h"
#include "sword2/sound.h"
#define Debug_Printf _vm->_debugger->debugPrintf
namespace Sword2 {
// Welcome to the easy resource manager - written in simple code for easy
// maintenance
//
// The resource compiler will create two files
//
// resource.inf which is a list of ascii cluster file names
// resource.tab which is a table which tells us which cluster a resource
// is located in and the number within the cluster
enum {
BOTH = 0x0, // Cluster is on both CDs
CD1 = 0x1, // Cluster is on CD1 only
CD2 = 0x2, // Cluster is on CD2 only
LOCAL_CACHE = 0x4, // Cluster is cached on HDD
LOCAL_PERM = 0x8 // Cluster is on HDD.
};
struct CdInf {
uint8 clusterName[20]; // Null terminated cluster name.
uint8 cd; // Cd cluster is on and whether it is on the local drive or not.
};
ResourceManager::ResourceManager(Sword2Engine *vm) {
_vm = vm;
_totalClusters = 0;
_resList = nullptr;
_resConvTable = nullptr;
_cacheStart = nullptr;
_cacheEnd = nullptr;
_usedMem = 0;
}
ResourceManager::~ResourceManager() {
Resource *res = _cacheStart;
while (res) {
_vm->_memory->memFree(res->ptr);
res = res->next;
}
for (uint i = 0; i < _totalClusters; i++)
free(_resFiles[i].entryTab);
free(_resList);
free(_resConvTable);
}
bool ResourceManager::init() {
uint32 i, j;
// Until proven differently, assume we're on CD 1. This is so the start
// dialog will be able to play any music at all.
setCD(1);
// We read in the resource info which tells us the names of the
// resource cluster files ultimately, although there might be groups
// within the clusters at this point it makes no difference. We only
// wish to know what resource files there are and what is in each
Common::File file;
if (!file.open("resource.inf")) {
GUIErrorMessage("Broken Sword II: Cannot open resource.inf");
return false;
}
// The resource.inf file is a simple text file containing the names of
// all the resource files.
while (1) {
char *buf = _resFiles[_totalClusters].fileName;
uint len = sizeof(_resFiles[_totalClusters].fileName);
if (!file.readLine(buf, len))
break;
int pos = strlen(buf);
if (buf[pos - 1] == 0x0A)
buf[pos - 1] = 0;
_resFiles[_totalClusters].numEntries = -1;
_resFiles[_totalClusters].entryTab = nullptr;
if (++_totalClusters >= MAX_res_files) {
GUIErrorMessage("Broken Sword II: Too many entries in resource.inf");
return false;
}
}
file.close();
// Now load in the binary id to res conversion table
if (!file.open("resource.tab")) {
GUIErrorMessage("Broken Sword II: Cannot open resource.tab");
return false;
}
// Find how many resources
uint32 size = file.size();
_totalResFiles = size / 4;
// Table seems ok so malloc some space
_resConvTable = (uint16 *)malloc(size);
for (i = 0; i < size / 2; i++)
_resConvTable[i] = file.readUint16LE();
if (file.eos() || file.err()) {
file.close();
GUIErrorMessage("Broken Sword II: Cannot read resource.tab");
return false;
}
file.close();
// Check that we have cd.inf file, unless we are running PSX
// version, which has all files on one disc.
if (!file.open("cd.inf") && !Sword2Engine::isPsx()) {
GUIErrorMessage("Broken Sword II: Cannot open cd.inf");
return false;
}
CdInf *cdInf = new CdInf[_totalClusters];
for (i = 0; i < _totalClusters; i++) {
if (Sword2Engine::isPsx()) { // We are running PSX version, artificially fill CdInf structure
cdInf[i].cd = CD1;
} else { // We are running PC version, read cd.inf file
file.read(cdInf[i].clusterName, sizeof(cdInf[i].clusterName));
cdInf[i].cd = file.readByte();
if (file.eos() || file.err()) {
delete[] cdInf;
file.close();
GUIErrorMessage("Broken Sword II: Cannot read cd.inf");
return false;
}
}
// It has been reported that there are two different versions
// of the cd.inf file: One where all clusters on CD also have
// the LOCAL_CACHE bit set. This bit is no longer used. To
// avoid future problems, let's normalize the flag once and for
// all here.
if (cdInf[i].cd & LOCAL_PERM)
cdInf[i].cd = 0;
else if (cdInf[i].cd & CD1)
cdInf[i].cd = 1;
else if (cdInf[i].cd & CD2)
cdInf[i].cd = 2;
else
cdInf[i].cd = 0;
// Any file on "CD 0" may be needed at all times. Verify that
// it exists. Any other missing cluster will be requested with
// an "insert CD" message. Of course, the file may still vanish
// during game-play (oh, that wascally wabbit!) in which case
// the resource manager will print a fatal error.
if (cdInf[i].cd == 0 && !Common::File::exists((char *)cdInf[i].clusterName)) {
GUIErrorMessage("Broken Sword II: Cannot find " + Common::String((char *)cdInf[i].clusterName));
delete[] cdInf;
return false;
}
}
file.close();
// We check the presence of resource files in cd.inf
// This is ok in PC version, but in PSX version we don't
// have cd.inf so we'll have to skip this.
if (!Sword2Engine::isPsx()) {
for (i = 0; i < _totalClusters; i++) {
for (j = 0; j < _totalClusters; j++) {
if (scumm_stricmp((char *)cdInf[j].clusterName, _resFiles[i].fileName) == 0)
break;
}
if (j == _totalClusters) {
delete[] cdInf;
GUIErrorMessage(Common::String(_resFiles[i].fileName) + " is not in cd.inf");
return false;
}
// Use korean.clu instead of TEXT.CLU
if (_vm->_isKorTrs && !scumm_stricmp(_resFiles[i].fileName, "TEXT.CLU"))
Common::strcpy_s(_resFiles[i].fileName, 20, "korean.clu");
_resFiles[i].cd = cdInf[j].cd;
}
}
delete[] cdInf;
debug(1, "%d resources in %d cluster files", _totalResFiles, _totalClusters);
for (i = 0; i < _totalClusters; i++)
debug(2, "filename of cluster %d: -%s (%d)", i, _resFiles[i].fileName, _resFiles[i].cd);
_resList = (Resource *)malloc(_totalResFiles * sizeof(Resource));
for (i = 0; i < _totalResFiles; i++) {
_resList[i].ptr = nullptr;
_resList[i].size = 0;
_resList[i].refCount = 0;
_resList[i].prev = _resList[i].next = nullptr;
}
return true;
}
/**
* Returns the address of a resource. Loads if not in memory. Retains a count.
*/
byte *ResourceManager::openResource(uint32 res, bool dump) {
assert(res < _totalResFiles);
// FIXME: In PSX edition, not all top menu icons are present (TOP menu is not used).
// Though, at present state, the engine still ask for the resources.
if (Sword2Engine::isPsx()) { // We need to "rewire" missing icons
if (res == 342) res = 364; // Rewire RESTORE ICON to SAVE ICON
}
// Is the resource in memory already? If not, load it.
if (!_resList[res].ptr) {
// Fetch the correct file and read in the correct portion.
uint16 cluFileNum = _resConvTable[res * 2]; // points to the number of the ascii filename
assert(cluFileNum != 0xffff);
// Relative resource within the file
// First we have to find the file via the _resConvTable
uint16 actual_res = _resConvTable[(res * 2) + 1];
debug(5, "openResource %s res %d", _resFiles[cluFileNum].fileName, res);
// If we're loading a cluster that's only available from one
// of the CDs, remember which one so that we can play the
// correct speech and music.
if (Sword2Engine::isPsx()) // We have only one disk in PSX version
setCD(CD1);
else
setCD(_resFiles[cluFileNum].cd);
// Actually, as long as the file can be found we don't really
// care which CD it's on. But if we can't find it, keep asking
// for the CD until we do.
Common::File *file = openCluFile(cluFileNum);
if (_resFiles[cluFileNum].entryTab == nullptr) {
// we didn't read from this file before, get its index table
readCluIndex(cluFileNum, file);
}
assert(_resFiles[cluFileNum].entryTab);
uint32 pos = _resFiles[cluFileNum].entryTab[actual_res * 2 + 0];
uint32 len = _resFiles[cluFileNum].entryTab[actual_res * 2 + 1];
file->seek(pos, SEEK_SET);
debug(6, "res len %d", len);
if (res == ENGLISH_SPEECH_FONT_ID && _vm->_isKorTrs) {
// Load Korean Font
uint32 korFontSize = 0;
Common::File korFontFile;
korFontFile.open("bs2k.fnt");
if (korFontFile.isOpen()) {
korFontSize = korFontFile.size();
}
// Ok, we know the length so try and allocate the memory.
_resList[res].ptr = _vm->_memory->memAlloc(len + korFontSize, res);
_resList[res].size = len + korFontSize;
_resList[res].refCount = 0;
file->read(_resList[res].ptr, len);
if (korFontSize > 0) {
korFontFile.read(_resList[res].ptr + len, korFontSize);
}
len += korFontSize;
} else {
// Ok, we know the length so try and allocate the memory.
_resList[res].ptr = _vm->_memory->memAlloc(len, res);
_resList[res].size = len;
_resList[res].refCount = 0;
file->read(_resList[res].ptr, len);
}
debug(3, "Loaded resource '%s' (%d) from '%s' on CD %d (%d)", fetchName(_resList[res].ptr), res, _resFiles[cluFileNum].fileName, getCD(), _resFiles[cluFileNum].cd);
if (dump) {
char buf[256];
const char *tag;
switch (fetchType(_resList[res].ptr)) {
case ANIMATION_FILE:
tag = "anim";
break;
case SCREEN_FILE:
tag = "layer";
break;
case GAME_OBJECT:
tag = "object";
break;
case WALK_GRID_FILE:
tag = "walkgrid";
break;
case GLOBAL_VAR_FILE:
tag = "globals";
break;
case PARALLAX_FILE_null:
tag = "parallax"; // Not used!
break;
case RUN_LIST:
tag = "runlist";
break;
case TEXT_FILE:
tag = "text";
break;
case SCREEN_MANAGER:
tag = "screen";
break;
case MOUSE_FILE:
tag = "mouse";
break;
case WAV_FILE:
tag = "wav";
break;
case ICON_FILE:
tag = "icon";
break;
case PALETTE_FILE:
tag = "palette";
break;
default:
tag = "unknown";
break;
}
Common::sprintf_s(buf, "dumps/%s-%d.dmp", tag, res);
if (!Common::File::exists(buf)) {
Common::DumpFile out;
if (out.open(buf))
out.write(_resList[res].ptr, len);
}
}
// close the cluster
file->close();
delete file;
_usedMem += len;
checkMemUsage();
} else if (_resList[res].refCount == 0)
removeFromCacheList(_resList + res);
_resList[res].refCount++;
return _resList[res].ptr;
}
void ResourceManager::closeResource(uint32 res) {
assert(res < _totalResFiles);
// Don't try to close the resource if it has already been forcibly
// closed, e.g. by fnResetGlobals().
if (_resList[res].ptr == nullptr)
return;
assert(_resList[res].refCount > 0);
_resList[res].refCount--;
if (_resList[res].refCount == 0)
addToCacheList(_resList + res);
// It's tempting to free the resource immediately when refCount
// reaches zero, but that'd be a mistake. Closing a resource does not
// mean "I'm not going to use this resource any more". It means that
// "the next time I use this resource I'm going to ask for a new
// pointer to it".
//
// Since the original memory manager had to deal with memory
// fragmentation, keeping a resource open - and thus locked down to a
// specific memory address - was considered a bad thing.
}
void ResourceManager::removeFromCacheList(Resource *res) {
if (_cacheStart == res)
_cacheStart = res->next;
if (_cacheEnd == res)
_cacheEnd = res->prev;
if (res->prev)
res->prev->next = res->next;
if (res->next)
res->next->prev = res->prev;
res->prev = res->next = nullptr;
}
void ResourceManager::addToCacheList(Resource *res) {
res->prev = nullptr;
res->next = _cacheStart;
if (_cacheStart)
_cacheStart->prev = res;
_cacheStart = res;
if (!_cacheEnd)
_cacheEnd = res;
}
Common::File *ResourceManager::openCluFile(uint16 fileNum) {
Common::File *file = new Common::File;
while (!file->open(_resFiles[fileNum].fileName)) {
// HACK: We have to check for this, or it'll be impossible to
// quit while the game is asking for the user to insert a CD.
// But recovering from this situation gracefully is just too
// much trouble, so quit now.
if (_vm->shouldQuit())
g_system->quit();
// If the file is supposed to be on hard disk, or we're
// playing a demo, then we're in trouble if the file
// can't be found!
if ((_vm->_features & ADGF_DEMO) || _resFiles[fileNum].cd == 0)
error("Could not find '%s'", _resFiles[fileNum].fileName);
askForCD(_resFiles[fileNum].cd);
}
return file;
}
void ResourceManager::readCluIndex(uint16 fileNum, Common::File *file) {
// we didn't read from this file before, get its index table
assert(_resFiles[fileNum].entryTab == nullptr);
assert(file);
// 1st DWORD of a cluster is an offset to the look-up table
uint32 table_offset = file->readUint32LE();
debug(6, "table offset = %d", table_offset);
uint32 tableSize = file->size() - table_offset; // the table is stored at the end of the file
file->seek(table_offset);
assert((tableSize % 8) == 0);
_resFiles[fileNum].entryTab = (uint32 *)malloc(tableSize);
_resFiles[fileNum].numEntries = tableSize / 8;
assert(_resFiles[fileNum].entryTab);
file->read(_resFiles[fileNum].entryTab, tableSize);
if (file->eos() || file->err())
error("unable to read index table from file %s", _resFiles[fileNum].fileName);
#ifdef SCUMM_BIG_ENDIAN
for (int tabCnt = 0; tabCnt < _resFiles[fileNum].numEntries * 2; tabCnt++)
_resFiles[fileNum].entryTab[tabCnt] = FROM_LE_32(_resFiles[fileNum].entryTab[tabCnt]);
#endif
}
/**
* Returns true if resource is valid, otherwise false.
*/
bool ResourceManager::checkValid(uint32 res) {
// Resource number out of range
if (res >= _totalResFiles)
return false;
// Points to the number of the ascii filename
uint16 parent_res_file = _resConvTable[res * 2];
// Null & void resource
if (parent_res_file == 0xffff)
return false;
return true;
}
/**
* Fetch resource type
*/
uint8 ResourceManager::fetchType(byte *ptr) {
if (!Sword2Engine::isPsx()) {
return ptr[0];
} else { // in PSX version, some files got a "garbled" resource header, with type stored in ninth byte
if (ptr[0]) {
return ptr[0];
} else if (ptr[8]) {
return ptr[8];
} else { // In PSX version there is no resource header for audio files,
return WAV_FILE; // but hopefully all audio files got first 16 bytes zeroed,
} // Allowing us to check for this condition.
// Alas, this doesn't work with PSX DEMO audio files.
}
}
/**
* Returns the total file length of a resource - i.e. all headers are included
* too.
*/
uint32 ResourceManager::fetchLen(uint32 res) {
if (_resList[res].ptr)
return _resList[res].size;
// Does this ever happen?
warning("fetchLen: Resource %u is not loaded; reading length from file", res);
// Points to the number of the ascii filename
uint16 parent_res_file = _resConvTable[res * 2];
// relative resource within the file
uint16 actual_res = _resConvTable[(res * 2) + 1];
// first we have to find the file via the _resConvTable
// open the cluster file
if (_resFiles[parent_res_file].entryTab == nullptr) {
Common::File *file = openCluFile(parent_res_file);
readCluIndex(parent_res_file, file);
delete file;
}
return _resFiles[parent_res_file].entryTab[actual_res * 2 + 1];
}
void ResourceManager::checkMemUsage() {
while (_usedMem > MAX_MEM_CACHE) {
// we're using up more memory than we wanted to. free some old stuff.
// Newly loaded objects are added to the start of the list,
// we start freeing from the end, to free the oldest items first
if (_cacheEnd) {
Resource *tmp = _cacheEnd;
assert((tmp->refCount == 0) && (tmp->ptr) && (tmp->next == nullptr));
removeFromCacheList(tmp);
_vm->_memory->memFree(tmp->ptr);
tmp->ptr = nullptr;
_usedMem -= tmp->size;
} else {
warning("%d bytes of memory used, but cache list is empty", _usedMem);
return;
}
}
}
void ResourceManager::remove(int res) {
if (_resList[res].ptr) {
removeFromCacheList(_resList + res);
_vm->_memory->memFree(_resList[res].ptr);
_resList[res].ptr = nullptr;
_resList[res].refCount = 0;
_usedMem -= _resList[res].size;
}
}
/**
* Remove all res files from memory - ready for a total restart. This includes
* the player object and global variables resource.
*/
void ResourceManager::removeAll() {
// We need to clear the FX queue, because otherwise the sound system
// will still believe that the sound resources are in memory. We also
// need to kill the movie lead-in/out.
_vm->_sound->clearFxQueue(true);
for (uint i = 0; i < _totalResFiles; i++)
remove(i);
}
/**
* Remove all resources from memory.
*/
void ResourceManager::killAll(bool wantInfo) {
int nuked = 0;
// We need to clear the FX queue, because otherwise the sound system
// will still believe that the sound resources are in memory. We also
// need to kill the movie lead-in/out.
_vm->_sound->clearFxQueue(true);
for (uint i = 0; i < _totalResFiles; i++) {
// Don't nuke the global variables or the player object!
if (i == 1 || i == CUR_PLAYER_ID)
continue;
if (_resList[i].ptr) {
if (wantInfo)
Debug_Printf("Nuked %5d: %s\n", i, fetchName(_resList[i].ptr));
remove(i);
nuked++;
}
}
if (wantInfo)
Debug_Printf("Expelled %d resources\n", nuked);
}
/**
* Like killAll but only kills objects (except George & the variable table of
* course) - ie. forcing them to reload & restart their scripts, which
* simulates the effect of a save & restore, thus checking that each object's
* re-entrant logic works correctly, and doesn't cause a statuette to
* disappear forever, or some plaster-filled holes in sand to crash the game &
* get James in trouble again.
*/
void ResourceManager::killAllObjects(bool wantInfo) {
int nuked = 0;
for (uint i = 0; i < _totalResFiles; i++) {
// Don't nuke the global variables or the player object!
if (i == 1 || i == CUR_PLAYER_ID)
continue;
if (_resList[i].ptr) {
if (fetchType(_resList[i].ptr) == GAME_OBJECT) {
if (wantInfo)
Debug_Printf("Nuked %5d: %s\n", i, fetchName(_resList[i].ptr));
remove(i);
nuked++;
}
}
}
if (wantInfo)
Debug_Printf("Expelled %d resources\n", nuked);
}
void ResourceManager::askForCD(int cd) {
byte *textRes;
// Stop any music from playing - so the system no longer needs the
// current CD - otherwise when we take out the CD, Windows will
// complain!
_vm->_sound->stopMusic(true);
textRes = openResource(2283);
_vm->_screen->displayMsg(_vm->fetchTextLine(textRes, 5 + cd) + 2, 0);
closeResource(2283);
// The original code probably determined automagically when the correct
// CD had been inserted, but our backend doesn't support that, and
// anyway I don't know if all systems allow that sort of thing. So we
// wait for the user to press any key instead, or click the mouse.
//
// But just in case we ever try to identify the CDs by their labels,
// they should be:
//
// CD1: "RBSII1" (or "PCF76" for the PCF76 version, whatever that is)
// CD2: "RBSII2"
}
} // End of namespace Sword2

140
engines/sword2/resman.h Normal file
View File

@@ -0,0 +1,140 @@
/* 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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 SWORD2_RESMAN_H
#define SWORD2_RESMAN_H
namespace Common {
class File;
}
#define MAX_MEM_CACHE (8 * 1024 * 1024) // we keep up to 8 megs of resource data files in memory
#define MAX_res_files 20
namespace Sword2 {
class Sword2Engine;
struct Resource {
byte *ptr;
uint32 size;
uint32 refCount;
Resource *next, *prev;
};
struct ResourceFile {
char fileName[20];
int32 numEntries;
uint32 *entryTab;
uint8 cd;
};
class ResourceManager {
private:
Common::File *openCluFile(uint16 fileNum);
void readCluIndex(uint16 fileNum, Common::File *file);
void removeFromCacheList(Resource *res);
void addToCacheList(Resource *res);
void checkMemUsage();
Sword2Engine *_vm;
int _curCD;
uint32 _totalResFiles;
uint32 _totalClusters;
// Gode generated res-id to res number/rel number conversion table
uint16 *_resConvTable;
ResourceFile _resFiles[MAX_res_files];
Resource *_resList;
Resource *_cacheStart, *_cacheEnd;
uint32 _usedMem; // amount of used memory in bytes
public:
ResourceManager(Sword2Engine *vm); // read in the config file
~ResourceManager();
bool init();
uint32 getNumResFiles() { return _totalResFiles; }
uint32 getNumClusters() { return _totalClusters; }
ResourceFile *getResFiles() { return _resFiles; }
Resource *getResList() { return _resList; }
byte *openResource(uint32 res, bool dump = false);
void closeResource(uint32 res);
bool checkValid(uint32 res);
uint32 fetchLen(uint32 res);
uint8 fetchType(byte *ptr);
uint8 fetchType(uint32 res) {
byte *ptr = openResource(res);
uint8 type = fetchType(ptr);
closeResource(res);
return type;
}
byte *fetchName(uint32 res, byte *buf = nullptr) {
static byte tempbuf[NAME_LEN];
if (!buf)
buf = tempbuf;
byte *ptr = openResource(res);
memcpy(buf, ptr + 10, NAME_LEN);
closeResource(res);
return buf;
}
byte *fetchName(byte *ptr) {
return ptr + 10;
}
// Prompts the user for the specified CD.
void askForCD(int cd);
void setCD(int cd) {
if (cd)
_curCD = cd;
}
int getCD() {
return _curCD;
}
void remove(int res);
void removeAll();
// ----console commands
void killAll(bool wantInfo);
void killAllObjects(bool wantInfo);
};
} // End of namespace Sword2
#endif

2457
engines/sword2/router.cpp Normal file

File diff suppressed because it is too large Load Diff

251
engines/sword2/router.h Normal file
View File

@@ -0,0 +1,251 @@
/* 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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 SWORD2_ROUTER_H
#define SWORD2_ROUTER_H
// This used to be a variable, but it was never set. Actually, it wasn't even
// initialized!
//
// Define this to force the use of slidy router (so solid path not used when
// ending walk in ANY direction)
//
// #define FORCE_SLIDY
#include "sword2/object.h"
namespace Sword2 {
struct WalkData {
uint16 frame;
int16 x;
int16 y;
uint8 step;
uint8 dir;
};
struct BarData {
int16 x1;
int16 y1;
int16 x2;
int16 y2;
int16 xmin;
int16 ymin;
int16 xmax;
int16 ymax;
int16 dx; // x2 - x1
int16 dy; // y2 - y1
int32 co; // co = (y1*dx) - (x1*dy) from an equation for a line y*dx = x*dy + co
};
struct NodeData {
int16 x;
int16 y;
int16 level;
int16 prev;
int16 dist;
};
// because we only have 2 megas in the game!
#define TOTAL_ROUTE_SLOTS 2
#define MAX_FRAMES_PER_CYCLE 16
#define NO_DIRECTIONS 8
#define MAX_FRAMES_PER_CHAR (MAX_FRAMES_PER_CYCLE * NO_DIRECTIONS)
#define ROUTE_END_FLAG 255
#define MAX_WALKGRIDS 10
#define O_WALKANIM_SIZE 600 // max number of nodes in router output
#define O_GRID_SIZE 200 // max 200 lines & 200 points
#define O_ROUTE_SIZE 50 // max number of modules in a route
struct RouteData {
int32 x;
int32 y;
int32 dirS;
int32 dirD;
};
struct PathData {
int32 x;
int32 y;
int32 dir;
int32 num;
};
class Router {
private:
Sword2Engine *_vm;
int16 _standbyX; // see fnSetStandbyCoords()
int16 _standbyY;
int16 _standbyDir;
// stores pointers to mem blocks containing routes created & used by
// megas (NULL if slot not in use)
WalkData *_routeSlots[TOTAL_ROUTE_SLOTS];
BarData _bars[O_GRID_SIZE];
NodeData _node[O_GRID_SIZE];
int32 _walkGridList[MAX_WALKGRIDS];
int32 _nBars;
int32 _nNodes;
int32 _startX, _startY, _startDir;
int32 _targetX, _targetY, _targetDir;
int32 _scaleA, _scaleB;
RouteData _route[O_ROUTE_SIZE];
PathData _smoothPath[O_ROUTE_SIZE];
PathData _modularPath[O_ROUTE_SIZE];
int32 _routeLength;
int32 _framesPerStep;
int32 _framesPerChar;
ObjectWalkdata _walkData;
int8 _modX[NO_DIRECTIONS];
int8 _modY[NO_DIRECTIONS];
int32 _diagonalx;
int32 _diagonaly;
int32 _firstStandFrame;
int32 _firstStandingTurnLeftFrame;
int32 _firstStandingTurnRightFrame;
int32 _firstWalkingTurnLeftFrame; // left walking turn
int32 _firstWalkingTurnRightFrame; // right walking turn
uint32 _firstSlowInFrame[NO_DIRECTIONS];
int32 _firstSlowOutFrame;
// number of slow-out frames on for each leading-leg in each direction
// ie. total number of slow-out frames = (numberOfSlowOutFrames * 2 *
// NO_DIRECTIONS)
int32 _numberOfSlowOutFrames;
int32 _stepCount;
int32 _moduleX;
int32 _moduleY;
int32 _currentDir;
int32 _lastCount;
int32 _frame;
uint8 returnSlotNo(uint32 megaId);
int32 getRoute();
void extractRoute();
void loadWalkGrid();
void setUpWalkGrid(byte *ob_mega, int32 x, int32 y, int32 dir);
void loadWalkData(byte *ob_walkdata);
bool scan(int32 level);
int32 newCheck(int32 status, int32 x1, int32 y1, int32 x2, int32 y2);
bool lineCheck(int32 x1, int32 x2, int32 y1, int32 y2);
bool vertCheck(int32 x, int32 y1, int32 y2);
bool horizCheck(int32 x1, int32 y, int32 x2);
bool check(int32 x1, int32 y1, int32 x2, int32 y2);
int32 checkTarget(int32 x, int32 y);
int32 smoothestPath();
void slidyPath();
void smoothCheck(int32 &steps, int32 best, int32 p, int32 dirS, int32 dirD);
bool addSlowInFrames(WalkData *walkAnim);
void addSlowOutFrames(WalkData *walkAnim);
void slidyWalkAnimator(WalkData *walkAnim);
#ifndef FORCE_SLIDY
void solidPath();
int32 solidWalkAnimator(WalkData *walkAnim);
#endif
void plotCross(int16 x, int16 y, uint8 color);
public:
Router(Sword2Engine *vm) : _vm(vm), _diagonalx(0), _diagonaly(0) {
memset(_routeSlots, 0, sizeof(_routeSlots));
memset(_bars, 0, sizeof(_bars));
memset(_node, 0, sizeof(_node));
memset(_walkGridList, 0, sizeof(_walkGridList));
memset(_route, 0, sizeof(_route));
memset(_smoothPath, 0, sizeof(_smoothPath));
memset(_modularPath, 0, sizeof(_modularPath));
memset(_modX, 0, sizeof(_modX));
memset(_modY, 0, sizeof(_modY));
memset(_firstSlowInFrame, 0, sizeof(_firstSlowInFrame));
}
void setStandbyCoords(int16 x, int16 y, uint8 dir);
int whatTarget(int startX, int startY, int destX, int destY);
// Sprites
void setSpriteStatus(byte *ob_graph, uint32 type);
void setSpriteShading(byte *ob_graph, uint32 type);
// Animation
int doAnimate(byte *ob_logic, byte *ob_graph, int32 animRes, bool reverse);
int megaTableAnimate(byte *ob_logic, byte *ob_graph, byte *ob_mega, byte *animTable, bool reverse);
// Walking
int doWalk(byte *ob_logic, byte *ob_graph, byte *ob_mega, byte *ob_walkdata, int16 target_x, int16 target_y, uint8 target_dir);
int walkToAnim(byte *ob_logic, byte *ob_graph, byte *ob_mega, byte *ob_walkdata, uint32 animRes);
int walkToTalkToMega(byte *ob_logic, byte *ob_graph, byte *ob_mega, byte *ob_walkdata, uint32 megaId, uint32 separation);
// Turning
int doFace(byte *ob_logic, byte *ob_graph, byte *ob_mega, byte *ob_walkdata, uint8 target_dir);
int faceXY(byte *ob_logic, byte *ob_graph, byte *ob_mega, byte *ob_walkdata, int16 target_x, int16 target_y);
int faceMega(byte *ob_logic, byte *ob_graph, byte *ob_mega, byte *ob_walkdata, uint32 megaId);
// Standing
void standAt(byte *ob_graph, byte *ob_mega, int32 x, int32 y, int32 dir);
void standAfterAnim(byte *ob_graph, byte *ob_mega, uint32 animRes);
void standAtAnim(byte *ob_graph, byte *ob_mega, uint32 animRes);
int32 routeFinder(byte *ob_mega, byte *ob_walkdata, int32 x, int32 y, int32 dir);
void earlySlowOut(byte *ob_mega, byte *ob_walkdata);
void allocateRouteMem();
WalkData *getRouteMem();
void freeRouteMem();
void freeAllRouteMem();
void addWalkGrid(int32 gridResource);
void removeWalkGrid(int32 gridResource);
void clearWalkGridList();
void plotWalkGrid();
};
} // End of namespace Sword2
#endif

415
engines/sword2/saveload.cpp Normal file
View File

@@ -0,0 +1,415 @@
/* 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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/>.
*/
// ---------------------------------------------------------------------------
// SAVE_REST.CPP save, restore & restart functions
//
// James 05feb97
//
// "Jesus Saves", but could he Restore or Restart? He can now...
//
// ---------------------------------------------------------------------------
#include "common/memstream.h"
#include "common/savefile.h"
#include "common/textconsole.h"
#include "sword2/sword2.h"
#include "sword2/defs.h"
#include "sword2/header.h"
#include "sword2/logic.h"
#include "sword2/object.h"
#include "sword2/mouse.h"
#include "sword2/resman.h"
#include "sword2/saveload.h"
#include "sword2/screen.h"
#include "sword2/sound.h"
namespace Sword2 {
Common::String Sword2Engine::getSaveStateName(int slot) const {
return Common::String::format("%s.%.3d", _targetName.c_str(), slot);
}
/**
* Calculate size of required savegame buffer. A savegame consists of a header
* and the global variables.
*/
uint32 Sword2Engine::findBufferSize() {
return 212 + _resman->fetchLen(1);
}
/**
* Save the game.
*/
uint32 Sword2Engine::saveGame(uint16 slotNo, const byte *desc) {
char description[SAVE_DESCRIPTION_LEN];
uint32 bufferSize = findBufferSize();
byte *saveBuffer = (byte *)malloc(bufferSize);
ScreenInfo *screenInfo = _screen->getScreenInfo();
memset(description, 0, sizeof(description));
strncpy(description, (const char *)desc, SAVE_DESCRIPTION_LEN - 1);
Common::MemoryWriteStream writeS(saveBuffer, bufferSize);
byte *globalVars = _resman->openResource(1);
byte *objectHub = _resman->openResource(CUR_PLAYER_ID) + ResHeader::size();
// Script no. 7 - 'george_savedata_request' calls fnPassPlayerSaveData
_logic->runResScript(CUR_PLAYER_ID, 7);
writeS.writeUint32LE(0); // Checksum
writeS.write(description, SAVE_DESCRIPTION_LEN);
writeS.writeUint32LE(_resman->fetchLen(1));
writeS.writeUint32LE(screenInfo->background_layer_id);
writeS.writeUint32LE(_logic->getRunList());
writeS.writeUint32LE(screenInfo->feet_x);
writeS.writeUint32LE(screenInfo->feet_y);
writeS.writeUint32LE(_sound->getLoopingMusicId());
writeS.write(objectHub, ObjectHub::size());
writeS.write(_logic->_saveLogic, ObjectLogic::size());
writeS.write(_logic->_saveGraphic, ObjectGraphic::size());
writeS.write(_logic->_saveMega, ObjectMega::size());
writeS.write(globalVars, _resman->fetchLen(1));
WRITE_LE_UINT32(saveBuffer, calcChecksum(saveBuffer + 4, bufferSize - 4));
_resman->closeResource(CUR_PLAYER_ID);
_resman->closeResource(1);
uint32 errorCode = saveData(slotNo, saveBuffer, bufferSize);
free(saveBuffer);
if (errorCode != SR_OK) {
uint32 textId;
switch (errorCode) {
case SR_ERR_FILEOPEN:
textId = TEXT_SAVE_CANT_OPEN;
break;
default:
textId = TEXT_SAVE_FAILED;
break;
}
_screen->displayMsg(fetchTextLine(_resman->openResource(textId / SIZE), textId & 0xffff) + 2, 0);
}
return errorCode;
}
uint32 Sword2Engine::saveData(uint16 slotNo, byte *buffer, uint32 bufferSize) {
Common::String saveFileName = getSaveStateName(slotNo);
Common::OutSaveFile *out;
if (!(out = _saveFileMan->openForSaving(saveFileName))) {
return SR_ERR_FILEOPEN;
}
out->write(buffer, bufferSize);
out->finalize();
if (!out->err()) {
delete out;
return SR_OK;
}
delete out;
return SR_ERR_WRITEFAIL;
}
/**
* Restore the game.
*/
uint32 Sword2Engine::restoreGame(uint16 slotNo) {
uint32 bufferSize = findBufferSize();
byte *saveBufferMem = (byte *)malloc(bufferSize);
uint32 errorCode = restoreData(slotNo, saveBufferMem, bufferSize);
// If it was read in successfully, then restore the game from the
// buffer & free the buffer. Note that restoreFromBuffer() frees the
// buffer in order to clear it from memory before loading in the new
// screen and runlist, so we only need to free it in case of failure.
if (errorCode == SR_OK)
errorCode = restoreFromBuffer(saveBufferMem, bufferSize);
else
free(saveBufferMem);
if (errorCode != SR_OK) {
uint32 textId;
switch (errorCode) {
case SR_ERR_FILEOPEN:
textId = TEXT_RESTORE_CANT_OPEN;
break;
case SR_ERR_INCOMPATIBLE:
textId = TEXT_RESTORE_INCOMPATIBLE;
break;
default:
textId = TEXT_RESTORE_FAILED;
break;
}
_screen->displayMsg(fetchTextLine(_resman->openResource(textId / SIZE), textId & 0xffff) + 2, 0);
} else {
// Prime system with a game cycle
// Reset the graphic 'BuildUnit' list before a new logic list
// (see fnRegisterFrame)
_screen->resetRenderLists();
// Reset the mouse hot-spot list. See fnRegisterMouse()
// and fnRegisterFrame()
_mouse->resetMouseList();
if (_logic->processSession())
error("restore 1st cycle failed??");
}
// Force the game engine to pick a cursor. This appears to be needed
// when using the -x command-line option to restore a game.
_mouse->setMouseTouching(1);
return errorCode;
}
uint32 Sword2Engine::restoreData(uint16 slotNo, byte *buffer, uint32 bufferSize) {
Common::String saveFileName = getSaveStateName(slotNo);
Common::InSaveFile *in;
if (!(in = _saveFileMan->openForLoading(saveFileName))) {
// error: couldn't open file
return SR_ERR_FILEOPEN;
}
// Read savegame into the buffer
uint32 itemsRead = in->read(buffer, bufferSize);
delete in;
if (itemsRead != bufferSize) {
// We didn't get all of it. At the moment we have no way of
// knowing why, so assume that it's an incompatible savegame.
return SR_ERR_INCOMPATIBLE;
}
return SR_OK;
}
uint32 Sword2Engine::restoreFromBuffer(byte *buffer, uint32 size) {
Common::MemoryReadStream readS(buffer, size);
// Calc checksum & check that against the value stored in the header
if (readS.readUint32LE() != calcChecksum(buffer + 4, size - 4)) {
free(buffer);
return SR_ERR_INCOMPATIBLE;
}
readS.seek(SAVE_DESCRIPTION_LEN, SEEK_CUR);
// Check savegame against length of current global variables resource
// This would most probably be trapped by the checksum test anyway,
// but it doesn't do any harm to check this as well.
// Historical note: During development, earlier savegames would often
// be shorter than the current expected length.
if (readS.readUint32LE() != _resman->fetchLen(1)) {
free(buffer);
return SR_ERR_INCOMPATIBLE;
}
byte *globalVars = _resman->openResource(1);
byte *objectHub = _resman->openResource(CUR_PLAYER_ID) + ResHeader::size();
uint32 screenId = readS.readUint32LE();
uint32 runListId = readS.readUint32LE();
uint32 feetX = readS.readUint32LE();
uint32 feetY = readS.readUint32LE();
uint32 musicId = readS.readUint32LE();
// Trash all resources from memory except player object & global vars
_resman->killAll(false);
_logic->resetKillList();
readS.read(objectHub, ObjectHub::size());
readS.read(_logic->_saveLogic, ObjectLogic::size());
readS.read(_logic->_saveGraphic, ObjectGraphic::size());
readS.read(_logic->_saveMega, ObjectMega::size());
// Fill out the player object structures from the savegame structures.
// Also run the appropriate scripts to set up George's anim tables and
// walkdata, and Nico's anim tables.
// Script no. 8 - 'george_savedata_return' calls fnGetPlayerSaveData
_logic->runResScript(CUR_PLAYER_ID, 8);
// Script no. 14 - 'set_up_nico_anim_tables'
_logic->runResScript(CUR_PLAYER_ID, 14);
// Which megaset was the player at the time of saving?
ObjectMega obMega(_logic->_saveMega);
uint32 scriptNo = 0;
switch (obMega.getMegasetRes()) {
case 36: // GeoMega:
scriptNo = 9; // script no.9 - 'player_is_george'
break;
case 2003: // GeoMegaB:
scriptNo = 13; // script no.13 - 'player_is_georgeB'
break;
case 1366: // NicMegaA:
scriptNo = 11; // script no.11 - 'player_is_nicoA'
break;
case 1437: // NicMegaB:
scriptNo = 12; // script no.12 - 'player_is_nicoB'
break;
case 1575: // NicMegaC:
scriptNo = 10; // script no.10 - 'player_is_nicoC'
break;
default:
break;
}
_logic->runResScript(CUR_PLAYER_ID, scriptNo);
// Copy variables from savegame buffer to memory
readS.read(globalVars, _resman->fetchLen(1));
_resman->closeResource(CUR_PLAYER_ID);
_resman->closeResource(1);
free(buffer);
int32 pars[2];
pars[0] = screenId;
pars[1] = 1;
_logic->fnInitBackground(pars);
ScreenInfo *screenInfo = _screen->getScreenInfo();
// So palette not restored immediately after control panel - we want to
// fade up instead!
screenInfo->new_palette = 99;
// These need setting after the defaults get set in fnInitBackground.
// Remember that these can change through the game, so need saving &
// restoring too.
screenInfo->feet_x = feetX;
screenInfo->feet_y = feetY;
// Start the new run list
_logic->expressChangeSession(runListId);
// Force in the new scroll position, so unsightly scroll-catch-up does
// not occur when screen first draws after returning from restore panel
// Set the screen record of player position - ready for setScrolling()
screenInfo->player_feet_x = obMega.getFeetX();
screenInfo->player_feet_y = obMega.getFeetY();
// if this screen is wide, recompute the scroll offsets now
if (screenInfo->scroll_flag)
_screen->setScrolling();
// Any music required will be started after we've returned from
// restoreControl() - see systemMenuMouse() in mouse.cpp!
// Restart any looping music. Originally this was - and still is - done
// in systemMenuMouse(), but with ScummVM we have other ways of
// restoring savegames so it's easier to put it here as well.
if (musicId) {
pars[0] = musicId;
pars[1] = FX_LOOP;
_logic->fnPlayMusic(pars);
} else
_logic->fnStopMusic(nullptr);
return SR_OK;
}
/**
* Get the description of a savegame
*/
uint32 Sword2Engine::getSaveDescription(uint16 slotNo, byte *description) {
Common::String saveFileName = getSaveStateName(slotNo);
Common::InSaveFile *in;
if (!(in = _saveFileMan->openForLoading(saveFileName))) {
return SR_ERR_FILEOPEN;
}
in->readUint32LE();
in->read(description, SAVE_DESCRIPTION_LEN);
delete in;
return SR_OK;
}
bool Sword2Engine::saveExists() {
Common::String pattern = _targetName + ".???";
Common::StringArray filenames = _saveFileMan->listSavefiles(pattern);
return !filenames.empty();
}
bool Sword2Engine::saveExists(uint16 slotNo) {
Common::String saveFileName = getSaveStateName(slotNo);
Common::InSaveFile *in;
if (!(in = _saveFileMan->openForLoading(saveFileName))) {
return false;
}
delete in;
return true;
}
uint32 Sword2Engine::calcChecksum(byte *buffer, uint32 size) {
uint32 total = 0;
for (uint32 pos = 0; pos < size; pos++)
total += buffer[pos];
return total;
}
} // End of namespace Sword2

49
engines/sword2/saveload.h Normal file
View File

@@ -0,0 +1,49 @@
/* 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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 SWORD2_SAVELOAD_H
#define SWORD2_SAVELOAD_H
namespace Sword2 {
#define SAVE_DESCRIPTION_LEN 64
// Save & Restore error codes
enum {
SR_OK, // No worries
SR_ERR_FILEOPEN, // Can't open file - Couldn't create file for
// saving, or couldn't find file for loading.
SR_ERR_INCOMPATIBLE, // (Restore) Incompatible savegame data.
// Savegame file is obsolete. (Won't happen
// after development stops)
SR_ERR_READFAIL, // (Restore) Failed on reading savegame file -
// Something screwed up during the read
SR_ERR_WRITEFAIL // (Save) Failed on writing savegame file -
// Something screwed up during the write -
// could be hard-drive full..?
};
} // End of namespace Sword2
#endif

1328
engines/sword2/screen.cpp Normal file

File diff suppressed because it is too large Load Diff

466
engines/sword2/screen.h Normal file
View File

@@ -0,0 +1,466 @@
/* 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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 SWORD2_SCREEN_H
#define SWORD2_SCREEN_H
#include "common/rect.h"
#include "common/stream.h"
#define MAX_bgp0_sprites 6
#define MAX_bgp1_sprites 6
#define MAX_back_sprites 30
#define MAX_sort_sprites 30
#define MAX_fore_sprites 30
#define MAX_fgp0_sprites 6
#define MAX_fgp1_sprites 6
#define PALTABLESIZE (64 * 64 * 64)
#define BLOCKWIDTH 64
#define BLOCKHEIGHT 64
#define MAXLAYERS 5
#define MENUDEEP 40
#define RENDERWIDE 640
#define RENDERDEEP (480 - (MENUDEEP * 2))
// Maximum scaled size of a sprite
#define SCALE_MAXWIDTH 512
#define SCALE_MAXHEIGHT 512
// Dirty grid cell size
#define CELLWIDE 10
#define CELLDEEP 20
namespace Sword2 {
class Sword2Engine;
// Sprite defines
enum {
// This is the low byte part of the sprite type.
RDSPR_TRANS = 0x0001,
RDSPR_BLEND = 0x0004,
RDSPR_FLIP = 0x0008,
RDSPR_SHADOW = 0x0010,
RDSPR_DISPLAYALIGN = 0x0020,
RDSPR_NOCOMPRESSION = 0x0040,
RDSPR_EDGEBLEND = 0x0080, // Unused
// This is the high byte part of the sprite type, which defines what
// type of compression is used. Unless RDSPR_NOCOMPRESSION is set.
RDSPR_RLE16 = 0x0000,
RDSPR_RLE256 = 0x0100,
RDSPR_RLE256FAST = 0x0200
};
// Fading defines
enum {
RDFADE_NONE,
RDFADE_UP,
RDFADE_DOWN,
RDFADE_BLACK
};
// Palette defines
enum {
RDPAL_FADE,
RDPAL_INSTANT
};
// Blitting FX defines
enum {
RDBLTFX_SPRITEBLEND = 0x01,
RDBLTFX_SHADOWBLEND = 0x02,
RDBLTFX_EDGEBLEND = 0x04
};
// Structure filled out by each object to register its graphic printing
// requrements
struct BuildUnit {
int16 x;
int16 y;
uint16 scaled_width;
uint16 scaled_height;
int16 sort_y;
uint32 anim_resource;
uint16 anim_pc;
// Denotes a scaling sprite at print time - and holds the scaling value
// for the shrink routine
uint16 scale;
// Non-zero means this item is a layer - retrieve from background layer
// and send to special renderer
uint16 layer_number;
// True means we want this frame to be affected by the shading mask
bool shadingFlag;
};
struct ScreenInfo {
uint16 scroll_offset_x; // Position x
uint16 scroll_offset_y; // Position y
uint16 max_scroll_offset_x; // Calc'ed in fnInitBackground
uint16 max_scroll_offset_y;
int16 player_feet_x; // Feet coordinates to use - can't just
int16 player_feet_y; // fetch the player compact anymore
int16 feet_x; // Special offset-to-player position -
int16 feet_y; // tweek as desired - always set in
// screen manager object startup
uint16 screen_wide; // Size of background layer - hence
uint16 screen_deep; // size of back buffer itself (Paul
// actually malloc's it)
uint32 background_layer_id; // Id of the normal background layer
// from the header of the main
// background layer
uint16 number_of_layers;
uint8 new_palette; // Set to non zero to start the
// palette held within layer file
// fading up after a buildDisplay()
uint8 scroll_flag; // Scroll mode 0 off 1 on
bool mask_flag; // Using shading mask
};
// The SpriteInfo structure is used to tell the driver96 code what attributes
// are linked to a sprite for drawing. These include position, scaling and
// compression.
struct SpriteInfo {
int16 x; // coords for top-left of sprite
int16 y;
uint16 w; // dimensions of sprite (before scaling)
uint16 h;
uint16 scale; // scale at which to draw, given in 256ths ['0' or '256' MEANS DON'T SCALE]
uint16 scaledWidth; // new dimensions (we calc these for the mouse area, so may as well pass to you to save time)
uint16 scaledHeight; //
uint16 type; // mask containing 'RDSPR_' bits specifying compression type, flip, transparency, etc
uint16 blend; // holds the blending values.
byte *data; // pointer to the sprite data
byte *colorTable; // pointer to 16-byte color table, only applicable to 16-col compression type
bool isText; // It is a engine-generated sprite containing text
};
struct BlockSurface {
byte data[BLOCKWIDTH * BLOCKHEIGHT];
bool transparent;
};
struct Parallax {
uint16 w;
uint16 h;
// The dimensions are followed by an offset table, but we don't know in
// advance how big it is. See initializeBackgroundLayer().
static int size() {
return 4;
}
void read(const byte *addr);
void write(byte *addr);
};
class Screen {
private:
Sword2Engine *_vm;
// _thisScreen describes the current back buffer and its in-game scroll
// positions, etc.
ScreenInfo _thisScreen;
int32 _renderCaps;
int8 _renderLevel;
byte *_buffer;
byte *_lightMask;
// Game screen metrics
int16 _screenWide;
int16 _screenDeep;
bool _needFullRedraw;
// Scroll variables. _scrollX and _scrollY hold the current scroll
// position, and _scrollXTarget and _scrollYTarget are the target
// position for the end of the game cycle.
int16 _scrollX;
int16 _scrollY;
int16 _scrollXTarget;
int16 _scrollYTarget;
int16 _scrollXOld;
int16 _scrollYOld;
int16 _parallaxScrollX; // current x offset to link a sprite to the
// parallax layer
int16 _parallaxScrollY; // current y offset to link a sprite to the
// parallax layer
int16 _locationWide;
int16 _locationDeep;
// Dirty grid handling
byte *_dirtyGrid;
uint16 _gridWide;
uint16 _gridDeep;
byte _palette[256 * 3];
byte _paletteMatch[PALTABLESIZE];
uint8 _fadeStatus;
int32 _fadeStartTime;
int32 _fadeTotalTime;
// 'frames per second' counting stuff
uint32 _fps;
uint32 _cycleTime;
uint32 _frameCount;
int32 _initialTime;
int32 _startTime;
int32 _totalTime;
int32 _renderAverageTime;
int32 _framesPerGameCycle;
bool _renderTooSlow;
void startNewPalette();
void resetRenderEngine();
void startRenderCycle();
bool endRenderCycle();
// Holds the order of the sort list, i.e. the list stays static and we
// sort this array.
uint16 _sortOrder[MAX_sort_sprites];
BuildUnit _bgp0List[MAX_bgp0_sprites];
BuildUnit _bgp1List[MAX_bgp1_sprites];
BuildUnit _backList[MAX_back_sprites];
BuildUnit _sortList[MAX_sort_sprites];
BuildUnit _foreList[MAX_fore_sprites];
BuildUnit _fgp0List[MAX_fgp0_sprites];
BuildUnit _fgp1List[MAX_fgp1_sprites];
uint32 _curBgp0;
uint32 _curBgp1;
uint32 _curBack;
uint32 _curSort;
uint32 _curFore;
uint32 _curFgp0;
uint32 _curFgp1;
void drawBackPar0Frames();
void drawBackPar1Frames();
void drawBackFrames();
void drawSortFrames(byte *file);
void drawForeFrames();
void drawForePar0Frames();
void drawForePar1Frames();
void processLayer(byte *file, uint32 layer_number);
void processImage(BuildUnit *build_unit);
uint8 _scrollFraction;
// Last palette used - so that we can restore the correct one after a
// pause (which dims the screen) and it's not always the main screen
// palette that we want, eg. during the eclipse
// This flag gets set in startNewPalette() and setFullPalette()
uint32 _lastPaletteRes;
// Debugging stuff
uint32 _largestLayerArea;
uint32 _largestSpriteArea;
char _largestLayerInfo[128];
char _largestSpriteInfo[128];
void registerFrame(byte *ob_mouse, byte *ob_graph, byte *ob_mega, BuildUnit *build_unit);
void mirrorSprite(byte *dst, byte *src, int16 w, int16 h);
int32 decompressRLE256(byte *dst, byte *src, int32 decompSize);
void unwindRaw16(byte *dst, byte *src, uint16 blockSize, byte *colTable);
int32 decompressRLE16(byte *dst, byte *src, int32 decompSize, byte *colTable);
void renderParallax(byte *ptr, int16 layer);
void markAsDirty(int16 x0, int16 y0, int16 x1, int16 y1);
uint8 _xBlocks[MAXLAYERS];
uint8 _yBlocks[MAXLAYERS];
// This is used to cache PSX backgrounds and parallaxes
// data, as they are kept in a file unmanageable from
// resource manager. These get freed every time a user
// exits from a room.
byte *_psxScrCache[3];
bool _psxCacheEnabled[3];
// An array of sub-blocks, one for each of the parallax layers.
BlockSurface **_blockSurfaces[MAXLAYERS];
uint16 _xScale[SCALE_MAXWIDTH];
uint16 _yScale[SCALE_MAXHEIGHT];
void blitBlockSurface(BlockSurface *s, Common::Rect *r, Common::Rect *clipRect);
uint16 _layer;
bool _dimPalette;
uint32 _pauseTicks;
uint32 _pauseStartTick;
uint32 getTick();
public:
Screen(Sword2Engine *vm, int16 width, int16 height);
~Screen();
void pauseScreen(bool pause);
int8 getRenderLevel();
void setRenderLevel(int8 level);
byte *getScreen() { return _buffer; }
byte *getPalette() { return _palette; }
ScreenInfo *getScreenInfo() { return &_thisScreen; }
int16 getScreenWide() { return _screenWide; }
int16 getScreenDeep() { return _screenDeep; }
uint32 getCurBgp0() { return _curBgp0; }
uint32 getCurBgp1() { return _curBgp1; }
uint32 getCurBack() { return _curBack; }
uint32 getCurSort() { return _curSort; }
uint32 getCurFore() { return _curFore; }
uint32 getCurFgp0() { return _curFgp0; }
uint32 getCurFgp1() { return _curFgp1; }
uint32 getFps() { return _fps; }
uint32 getLargestLayerArea() { return _largestLayerArea; }
uint32 getLargestSpriteArea() { return _largestSpriteArea; }
char *getLargestLayerInfo() { return _largestLayerInfo; }
char *getLargestSpriteInfo() { return _largestSpriteInfo; }
void setNeedFullRedraw();
void clearScene();
void resetRenderLists();
void setLocationMetrics(uint16 w, uint16 h);
int32 initializeBackgroundLayer(byte *parallax);
int32 initializePsxParallaxLayer(byte *parallax); // These are used to initialize psx backgrounds and
int32 initializePsxBackgroundLayer(byte *parallax); // parallaxes, which are different from pc counterparts.
void closeBackgroundLayer();
void initializeRenderCycle();
void initBackground(int32 res, int32 new_palette);
void initPsxBackground(int32 res, int32 new_palette);
void registerFrame(byte *ob_mouse, byte *ob_graph, byte *ob_mega);
void setScrollFraction(uint8 f) { _scrollFraction = f; }
void setScrollTarget(int16 x, int16 y);
void setScrolling();
void setFullPalette(int32 palRes);
void setPalette(int16 startEntry, int16 noEntries, byte *palette, uint8 setNow);
void setSystemPalette(const byte *colors, uint start, uint num);
uint8 quickMatch(uint8 r, uint8 g, uint8 b);
int32 fadeUp(float time = 0.75f);
int32 fadeDown(float time = 0.75f);
uint8 getFadeStatus();
void dimPalette(bool dim);
void waitForFade();
void fadeServer();
void updateDisplay(bool redrawScene = true);
void displayMsg(byte *text, int time);
int32 createSurface(SpriteInfo *s, byte **surface);
void drawSurface(SpriteInfo *s, byte *surface, Common::Rect *clipRect = nullptr);
void deleteSurface(byte *surface);
int32 drawSprite(SpriteInfo *s);
void scaleImageFast(byte *dst, uint16 dstPitch, uint16 dstWidth,
uint16 dstHeight, byte *src, uint16 srcPitch, uint16 srcWidth,
uint16 srcHeight);
void scaleImageGood(byte *dst, uint16 dstPitch, uint16 dstWidth,
uint16 dstHeight, byte *src, uint16 srcPitch, uint16 srcWidth,
uint16 srcHeight, byte *backBuf, int16 bbXPos, int16 bbYPos);
void updateRect(Common::Rect *r);
int32 openLightMask(SpriteInfo *s);
int32 closeLightMask();
void buildDisplay();
void plotPoint(int x, int y, uint8 color);
void drawLine(int x0, int y0, int x1, int y1, uint8 color);
void rollCredits();
void splashScreen();
// Some sprites are compressed in HIF format
static uint32 decompressHIF(byte *src, byte *dst, uint32 *skipData = nullptr);
// This is used to resize psx sprites back to original resolution
static void resizePsxSprite(byte *dst, byte *src, uint16 destW, uint16 destH);
// Some sprites are divided into 254 pixel wide stripes, this recomposes them
// and generates a "normal" sprite.
static void recomposePsxSprite(SpriteInfo *s);
static void recomposeCompPsxSprite(SpriteInfo *s);
// These functions manage the PSX screen cache
void setPsxScrCache(byte *psxScrCache, uint8 level);
byte *getPsxScrCache(uint8 level);
bool getPsxScrCacheStatus(uint8 level);
void flushPsxScrCache();
};
} // End of namespace Sword2
#endif

156
engines/sword2/scroll.cpp Normal file
View File

@@ -0,0 +1,156 @@
/* 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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 "sword2/sword2.h"
#include "sword2/defs.h"
#include "sword2/header.h"
#include "sword2/logic.h"
#include "sword2/screen.h"
namespace Sword2 {
// Max no of pixel allowed to scroll per cycle
#define MAX_SCROLL_DISTANCE 8
/**
* Sets the scroll target position for the end of the game cycle. The driver
* will then automatically scroll as many times as it can to reach this
* position in the allotted time.
*/
void Screen::setScrollTarget(int16 sx, int16 sy) {
_scrollXTarget = sx;
_scrollYTarget = sy;
}
/**
* If the room is larger than the physical screen, this function is called
* every game cycle to update the scroll offsets.
*/
void Screen::setScrolling() {
// Normally we aim to get George's feet at (320,250) from top left
// of screen window
// feet_x = 128 + 320
// feet_y = 128 + 250
// Set scroll offsets according to the player's coords
// If the scroll offsets are being forced in script, ensure that they
// are neither too far to the right nor too far down.
uint32 scrollX = _vm->_logic->readVar(SCROLL_X);
uint32 scrollY = _vm->_logic->readVar(SCROLL_Y);
if (scrollX || scrollY) {
_thisScreen.scroll_offset_x = MIN((uint16)scrollX, _thisScreen.max_scroll_offset_x);
_thisScreen.scroll_offset_y = MIN((uint16)scrollY, _thisScreen.max_scroll_offset_y);
return;
}
// George's offset from the center - the desired position for him
int16 offset_x = _thisScreen.player_feet_x - _thisScreen.feet_x;
int16 offset_y = _thisScreen.player_feet_y - _thisScreen.feet_y;
// Prevent scrolling too far left/right/up/down
if (offset_x < 0)
offset_x = 0;
else if (offset_x > _thisScreen.max_scroll_offset_x)
offset_x = _thisScreen.max_scroll_offset_x;
if (offset_y < 0)
offset_y = 0;
else if (offset_y > _thisScreen.max_scroll_offset_y)
offset_y = _thisScreen.max_scroll_offset_y;
// First time on this screen - need absolute scroll immediately!
if (_thisScreen.scroll_flag == 2) {
debug(5, "init scroll");
_thisScreen.scroll_offset_x = offset_x;
_thisScreen.scroll_offset_y = offset_y;
_thisScreen.scroll_flag = 1;
return;
}
// Catch up with required scroll offsets - speed depending on distance
// to catch up (dx and dy) and _scrollFraction used, but limit to
// certain number of pixels per cycle (MAX_SCROLL_DISTANCE)
int16 dx = _thisScreen.scroll_offset_x - offset_x;
int16 dy = _thisScreen.scroll_offset_y - offset_y;
uint16 scroll_distance_x; // how much we want to scroll
uint16 scroll_distance_y;
if (dx < 0) {
// Current scroll_offset_x is less than the required value
// NB. I'm adding 1 to the result of dx / SCROLL_FRACTION,
// because it would otherwise not scroll at all when
// dx < SCROLL_FRACTION
// => inc by (fraction of the differnce) NB. dx is -ve, so we
// subtract dx / SCROLL_FRACTION
scroll_distance_x = 1 - dx / _scrollFraction;
if (scroll_distance_x > MAX_SCROLL_DISTANCE)
scroll_distance_x = MAX_SCROLL_DISTANCE;
_thisScreen.scroll_offset_x += scroll_distance_x;
} else if (dx > 0) {
// Current scroll_offset_x is greater than
// the required value
// => dec by (fraction of the differnce)
scroll_distance_x = 1 + dx / _scrollFraction;
if (scroll_distance_x > MAX_SCROLL_DISTANCE)
scroll_distance_x = MAX_SCROLL_DISTANCE;
_thisScreen.scroll_offset_x -= scroll_distance_x;
}
if (dy < 0) {
scroll_distance_y = 1 - dy / _scrollFraction;
if (scroll_distance_y > MAX_SCROLL_DISTANCE)
scroll_distance_y = MAX_SCROLL_DISTANCE;
_thisScreen.scroll_offset_y += scroll_distance_y;
} else if (dy > 0) {
scroll_distance_y = 1 + dy / _scrollFraction;
if (scroll_distance_y > MAX_SCROLL_DISTANCE)
scroll_distance_y = MAX_SCROLL_DISTANCE;
_thisScreen.scroll_offset_y -= scroll_distance_y;
}
}
} // End of namespace Sword2

436
engines/sword2/sound.cpp Normal file
View File

@@ -0,0 +1,436 @@
/* 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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/>.
*/
// ---------------------------------------------------------------------------
// BROKEN SWORD 2
//
// SOUND.CPP Contains the sound engine, fx & music functions
// Some very 'sound' code in here ;)
//
// (16Dec96 JEL)
//
// ---------------------------------------------------------------------------
#include "common/file.h"
#include "common/memstream.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "sword2/sword2.h"
#include "sword2/defs.h"
#include "sword2/header.h"
#include "sword2/console.h"
#include "sword2/logic.h"
#include "sword2/resman.h"
#include "sword2/sound.h"
#include "audio/decoders/wave.h"
#include "audio/decoders/xa.h"
#define Debug_Printf _vm->_debugger->debugPrintf
namespace Sword2 {
Sound::Sound(Sword2Engine *vm) {
int i;
_vm = vm;
for (i = 0; i < FXQ_LENGTH; i++)
_fxQueue[i].resource = 0;
for (i = 0; i < MAXMUS; i++) {
_music[i] = NULL;
_musicFile[i].idxTab = NULL;
_musicFile[i].idxLen = 0;
_musicFile[i].fileSize = 0;
_musicFile[i].fileType = 0;
_musicFile[i].inUse = false;
_speechFile[i].idxTab = NULL;
_speechFile[i].idxLen = 0;
_speechFile[i].fileSize = 0;
_speechFile[i].fileType = 0;
_speechFile[i].inUse = false;
}
_speechPaused = false;
_musicPaused = false;
_fxPaused = false;
_speechMuted = false;
_musicMuted = false;
_fxMuted = false;
_reverseStereo = false;
_loopingMusicId = 0;
_mixBuffer = NULL;
_mixBufferLen = 0;
_vm->_mixer->playStream(Audio::Mixer::kMusicSoundType, &_mixerSoundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
}
Sound::~Sound() {
_vm->_mixer->stopHandle(_mixerSoundHandle);
clearFxQueue(true);
stopMusic(true);
stopSpeech();
free(_mixBuffer);
for (int i = 0; i < MAXMUS; i++) {
if (_musicFile[i].file.isOpen())
_musicFile[i].file.close();
if (_speechFile[i].file.isOpen())
_speechFile[i].file.close();
free(_musicFile[i].idxTab);
free(_speechFile[i].idxTab);
}
}
void Sound::setReverseStereo(bool reverse) {
if (reverse != _reverseStereo) {
_reverseStereo = reverse;
for (int i = 0; i < FXQ_LENGTH; i++) {
if (!_fxQueue[i].resource)
continue;
_fxQueue[i].pan = -_fxQueue[i].pan;
_vm->_mixer->setChannelBalance(_fxQueue[i].handle, _fxQueue[i].pan);
}
}
}
/**
* Stop all sounds, close their resources and clear the FX queue. This is used
* when going from one room to another, among other things.
*/
void Sound::clearFxQueue(bool killMovieSounds) {
for (int i = 0; i < FXQ_LENGTH; i++) {
if (_fxQueue[i].resource) {
stopFx(i);
}
}
// We aren't just going to change rooms or anything like that, we are
// killing off resources (e.g. when restoring or restarting). We need
// to also kill any movie lead-in/out sounds.
if (killMovieSounds) {
_vm->_mixer->stopHandle(_leadInHandle);
_vm->_mixer->stopHandle(_leadOutHandle);
}
}
/**
* Process the FX queue. This function is called once every game cycle.
*/
void Sound::processFxQueue() {
for (int i = 0; i < FXQ_LENGTH; i++) {
if (!_fxQueue[i].resource)
continue;
switch (_fxQueue[i].type) {
case FX_RANDOM:
// 1 in 'delay' chance of this fx occurring
if (_vm->_rnd.getRandomNumber(_fxQueue[i].delay) == 0)
playFx(&_fxQueue[i]);
break;
case FX_SPOT:
if (_fxQueue[i].delay)
_fxQueue[i].delay--;
else {
playFx(&_fxQueue[i]);
_fxQueue[i].type = FX_SPOT2;
}
break;
case FX_LOOP:
playFx(&_fxQueue[i]);
_fxQueue[i].type = FX_LOOPING;
break;
case FX_SPOT2:
// Once the FX has finished remove it from the queue.
if (!_vm->_mixer->isSoundHandleActive(_fxQueue[i].handle)) {
_vm->_resman->closeResource(_fxQueue[i].resource);
_fxQueue[i].resource = 0;
}
break;
case FX_LOOPING:
// Once the looped FX has started we can ignore it,
// but we can't close it since the WAV data is in use.
break;
default:
break;
}
}
}
/**
* This function is used by the cutscene player to play the movie lead-in/out.
* @param res The sound resource to play
* @param type Either kLeadInSound or kLeadOutSound
*/
void Sound::playMovieSound(int32 res, int type) {
Audio::SoundHandle *handle;
if (type == kLeadInSound)
handle = &_leadInHandle;
else
handle = &_leadOutHandle;
if (_vm->_mixer->isSoundHandleActive(*handle)) {
_vm->_mixer->stopHandle(*handle);
}
byte *data = _vm->_resman->openResource(res);
uint32 len = _vm->_resman->fetchLen(res);
assert(_vm->_resman->fetchType(data) == WAV_FILE);
// We want to close the resource right away, so to be safe we make a
// private copy of the sound;
byte *soundData = (byte *)malloc(len);
if (soundData) {
memcpy(soundData, data, len);
Common::MemoryReadStream *stream = new Common::MemoryReadStream(soundData, len, DisposeAfterUse::YES);
// In PSX version we have nothing to skip here, as data starts
// right away.
if (!Sword2Engine::isPsx()) {
stream->seek(ResHeader::size());
}
Audio::RewindableAudioStream *input = 0;
if (Sword2Engine::isPsx()) {
input = Audio::makeXAStream(stream, 11025);
} else {
input = Audio::makeWAVStream(stream, DisposeAfterUse::YES);
}
_vm->_mixer->playStream(
Audio::Mixer::kMusicSoundType, handle, input,
-1, Audio::Mixer::kMaxChannelVolume, 0,
DisposeAfterUse::YES, false, isReverseStereo());
} else {
warning("Sound::playMovieSound: Could not allocate %d bytes\n", len);
}
_vm->_resman->closeResource(res);
}
void Sound::stopMovieSounds() {
if (_vm->_mixer->isSoundHandleActive(_leadInHandle)) {
_vm->_mixer->stopHandle(_leadInHandle);
}
if (_vm->_mixer->isSoundHandleActive(_leadOutHandle)) {
_vm->_mixer->stopHandle(_leadOutHandle);
}
}
/**
* Queue a sound effect for playing later.
* @param res the sound resource number
* @param type the type of sound effect
* @param delay when to play the sound effect
* @param volume the sound effect volume (0 through 16)
* @param pan the sound effect panning (-16 through 16)
*/
void Sound::queueFx(int32 res, int32 type, int32 delay, int32 volume, int32 pan) {
if (_vm->_wantSfxDebug) {
const char *typeStr;
switch (type) {
case FX_SPOT:
typeStr = "SPOT";
break;
case FX_LOOP:
typeStr = "LOOPED";
break;
case FX_RANDOM:
typeStr = "RANDOM";
break;
default:
typeStr = "INVALID";
break;
}
debug(0, "SFX (sample=\"%s\", vol=%d, pan=%d, delay=%d, type=%s)", _vm->_resman->fetchName(res), volume, pan, delay, typeStr);
}
for (int i = 0; i < FXQ_LENGTH; i++) {
if (!_fxQueue[i].resource) {
byte *data = _vm->_resman->openResource(res);
// Check that we really have a WAV file here, alas this
// check is useless with psx demo game, because psx audio files
// are headerless and there is no way to check the type
if (!(Sword2Engine::isPsx() && (_vm->_features & ADGF_DEMO)))
assert(_vm->_resman->fetchType(data) == WAV_FILE);
uint32 len = _vm->_resman->fetchLen(res);
// Skip the header if using PC version
if (!Sword2Engine::isPsx())
len -= ResHeader::size();
if (type == FX_RANDOM) {
// For spot effects and loops the delay is the
// number of frames to wait. For random
// effects, however, it's the average number of
// seconds between playing the sound, so we
// have to multiply by the frame rate.
delay *= FRAMES_PER_SECOND;
}
volume = (volume * Audio::Mixer::kMaxChannelVolume) / 16;
pan = (pan * 127) / 16;
if (isReverseStereo())
pan = -pan;
_fxQueue[i].resource = res;
if (Sword2Engine::isPsx())
_fxQueue[i].data = data;
else
_fxQueue[i].data = data + ResHeader::size();
_fxQueue[i].len = len;
_fxQueue[i].delay = delay;
_fxQueue[i].volume = volume;
_fxQueue[i].pan = pan;
_fxQueue[i].type = type;
// Keep track of the index in the loop so that
// fnStopFx() can be used later to kill this sound.
// Mainly for FX_LOOP and FX_RANDOM.
_vm->_logic->writeVar(RESULT, i);
return;
}
}
warning("No free slot in FX queue");
}
int32 Sound::playFx(FxQueueEntry *fx) {
return playFx(&fx->handle, fx->data, fx->len, fx->volume, fx->pan, (fx->type == FX_LOOP), Audio::Mixer::kSFXSoundType);
}
int32 Sound::playFx(Audio::SoundHandle *handle, byte *data, uint32 len, uint8 vol, int8 pan, bool loop, Audio::Mixer::SoundType soundType) {
if (_fxMuted)
return RD_OK;
if (_vm->_mixer->isSoundHandleActive(*handle))
return RDERR_FXALREADYOPEN;
Common::MemoryReadStream *stream = new Common::MemoryReadStream(data, len);
Audio::RewindableAudioStream *input = 0;
if (Sword2Engine::isPsx())
input = Audio::makeXAStream(stream, 11025);
else
input = Audio::makeWAVStream(stream, DisposeAfterUse::YES);
assert(input);
_vm->_mixer->playStream(soundType, handle,
Audio::makeLoopingAudioStream(input, loop ? 0 : 1),
-1, vol, pan, DisposeAfterUse::YES, false, isReverseStereo());
return RD_OK;
}
/**
* This function closes a sound effect which has been previously opened for
* playing. Sound effects must be closed when they are finished with, otherwise
* you will run out of sound effect buffers.
* @param i the index of the sound to close
*/
int32 Sound::stopFx(int32 i) {
if (!_fxQueue[i].resource)
return RDERR_FXNOTOPEN;
_vm->_mixer->stopHandle(_fxQueue[i].handle);
_vm->_resman->closeResource(_fxQueue[i].resource);
_fxQueue[i].resource = 0;
return RD_OK;
}
void Sound::printFxQueue() {
int freeSlots = 0;
for (int i = 0; i < FXQ_LENGTH; i++) {
if (_fxQueue[i].resource) {
const char *type;
switch (_fxQueue[i].type) {
case FX_SPOT:
type = "SPOT";
break;
case FX_LOOP:
type = "LOOP";
break;
case FX_RANDOM:
type = "RANDOM";
break;
case FX_SPOT2:
type = "SPOT2";
break;
case FX_LOOPING:
type = "LOOPING";
break;
default:
type = "UNKNOWN";
break;
}
Debug_Printf("%d: res: %d ('%s') %s (%d) delay: %d vol: %d pan: %d\n",
i, _fxQueue[i].resource,
_vm->_resman->fetchName(_fxQueue[i].resource),
type, _fxQueue[i].type, _fxQueue[i].delay,
_fxQueue[i].volume, _fxQueue[i].pan);
} else {
freeSlots++;
}
}
Debug_Printf("Free slots: %d\n", freeSlots);
}
} // End of namespace Sword2

284
engines/sword2/sound.h Normal file
View File

@@ -0,0 +1,284 @@
/* 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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/>.
*/
/*****************************************************************************
* SOUND.H Sound engine
*
* SOUND.CPP Contains the sound engine, fx & music functions
* Some very 'sound' code in here ;)
*
* (16Dec96 JEL)
*
****************************************************************************/
#ifndef SWORD2_SOUND_H
#define SWORD2_SOUND_H
#include "common/file.h"
#include "common/mutex.h"
#include "audio/audiostream.h"
#include "audio/mixer.h"
// Max number of sound fx
#define MAXMUS 2
// Max number of fx in queue at once
#define FXQ_LENGTH 32
#define BUFFER_SIZE 4096
namespace Sword2 {
enum {
kCLUMode = 1,
kMP3Mode,
kVorbisMode,
kFLACMode
};
enum {
kLeadInSound,
kLeadOutSound
};
enum {
// These three types correspond to types set by the scripts
FX_SPOT = 0,
FX_LOOP = 1,
FX_RANDOM = 2,
// These are used for FX queue bookkeeping
FX_SPOT2 = 3,
FX_LOOPING = 4
};
// Sound defines
enum {
RDSE_SAMPLEFINISHED = 0,
RDSE_SAMPLEPLAYING = 1,
RDSE_FXTOCLEAR = 0, // Unused
RDSE_FXCACHED = 1, // Unused
RDSE_FXSPOT = 0,
RDSE_FXLOOP = 1,
RDSE_FXLEADIN = 2,
RDSE_FXLEADOUT = 3,
RDSE_QUIET = 1,
RDSE_SPEAKING = 0
};
class CLUInputStream : public Audio::AudioStream {
private:
Common::File *_file;
bool _firstTime;
uint32 _file_pos;
uint32 _end_pos;
int16 _outbuf[BUFFER_SIZE];
byte _inbuf[BUFFER_SIZE];
const int16 *_bufferEnd;
const int16 *_pos;
uint16 _prev;
void refill();
inline bool eosIntern() const {
return !_file->isOpen() || _pos >= _bufferEnd;
}
public:
CLUInputStream(Common::File *file, int size);
~CLUInputStream() override;
int readBuffer(int16 *buffer, const int numSamples) override;
bool endOfData() const override { return eosIntern(); }
bool isStereo() const override { return false; }
int getRate() const override { return 22050; }
};
struct SoundFileHandle {
Common::File file;
uint32 *idxTab;
uint32 idxLen;
int32 fileSize;
uint32 fileType;
volatile bool inUse;
};
class MusicInputStream : public Audio::AudioStream {
private:
int _cd;
SoundFileHandle *_fh;
uint32 _musicId;
Audio::AudioStream *_decoder;
int16 _buffer[BUFFER_SIZE];
const int16 *_bufferEnd;
const int16 *_pos;
bool _remove;
uint32 _numSamples;
uint32 _samplesLeft;
bool _looping;
int32 _fading;
int32 _fadeSamples;
void refill();
inline bool eosIntern() const {
if (_looping)
return false;
return _remove || _pos >= _bufferEnd;
}
public:
MusicInputStream(int cd, SoundFileHandle *fh, uint32 musicId, bool looping);
~MusicInputStream() override;
int readBuffer(int16 *buffer, const int numSamples) override;
bool endOfData() const override { return eosIntern(); }
bool isStereo() const override { return _decoder->isStereo(); }
int getRate() const override { return _decoder->getRate(); }
int getCD() { return _cd; }
void fadeUp();
void fadeDown();
bool isReady() { return _decoder != nullptr; }
int32 isFading() { return _fading; }
bool readyToRemove();
int32 getTimeRemaining();
};
class Sound : public Audio::AudioStream {
private:
Sword2Engine *_vm;
Common::Mutex _mutex;
Audio::SoundHandle _mixerSoundHandle;
Audio::SoundHandle _leadInHandle;
Audio::SoundHandle _leadOutHandle;
struct FxQueueEntry {
Audio::SoundHandle handle; // sound handle
uint32 resource; // resource id of sample
byte *data; // pointer to WAV data
uint32 len; // WAV data length
uint16 delay; // cycles to wait before playing (or 'random chance' if FX_RANDOM)
uint8 volume; // sound volume
int8 pan; // sound panning
uint8 type; // FX_SPOT, FX_RANDOM, FX_LOOP
};
FxQueueEntry _fxQueue[FXQ_LENGTH];
void triggerFx(uint8 i);
bool _reverseStereo;
bool _speechMuted;
bool _fxMuted;
bool _musicMuted;
bool _speechPaused;
bool _fxPaused;
bool _musicPaused;
int32 _loopingMusicId;
Audio::SoundHandle _soundHandleSpeech;
MusicInputStream *_music[MAXMUS];
SoundFileHandle _musicFile[MAXMUS];
SoundFileHandle _speechFile[MAXMUS];
int16 *_mixBuffer;
int _mixBufferLen;
public:
Sound(Sword2Engine *vm);
~Sound() override;
// AudioStream API
int readBuffer(int16 *buffer, const int numSamples) override;
bool isStereo() const override { return false; }
bool endOfData() const override;
int getRate() const override { return Sword2Engine::isPsx() ? 11025 : 22050; }
// End of AudioStream API
void clearFxQueue(bool killMovieSounds);
void processFxQueue();
void setReverseStereo(bool reverse);
bool isReverseStereo() const { return _reverseStereo; }
void muteSpeech(bool mute);
bool isSpeechMute() const { return _speechMuted; }
void muteFx(bool mute);
bool isFxMute() const { return _fxMuted; }
void muteMusic(bool mute) { _musicMuted = mute; }
bool isMusicMute() const { return _musicMuted; }
void setLoopingMusicId(int32 id) { _loopingMusicId = id; }
int32 getLoopingMusicId() const { return _loopingMusicId; }
void pauseSpeech();
void unpauseSpeech();
void pauseFx();
void unpauseFx();
void pauseMusic();
void unpauseMusic();
void playMovieSound(int32 res, int type);
void stopMovieSounds();
void queueFx(int32 res, int32 type, int32 delay, int32 volume, int32 pan);
int32 playFx(FxQueueEntry *fx);
int32 playFx(Audio::SoundHandle *handle, byte *data, uint32 len, uint8 vol, int8 pan, bool loop, Audio::Mixer::SoundType soundType);
int32 stopFx(int32 i);
int32 setFxIdVolumePan(int32 id, int vol, int pan = 255);
int32 getSpeechStatus();
int32 amISpeaking();
int32 playCompSpeech(uint32 speechId, uint8 vol, int8 pan);
int32 stopSpeech();
int32 streamCompMusic(uint32 musicId, bool loop);
void stopMusic(bool immediately);
int32 musicTimeRemaining();
void printFxQueue();
};
} // End of namespace Sword2
#endif

235
engines/sword2/speech.cpp Normal file
View File

@@ -0,0 +1,235 @@
/* 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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/file.h"
#include "common/textconsole.h"
#include "sword2/sword2.h"
#include "sword2/defs.h"
#include "sword2/header.h"
#include "sword2/console.h"
#include "sword2/logic.h"
#include "sword2/maketext.h"
#include "sword2/object.h"
#include "sword2/resman.h"
#include "sword2/screen.h"
namespace Sword2 {
// To request the status of a target, we run its 4th script, get-speech-state.
// This will cause RESULT to be set to either 1 (target is waiting) or 0
// (target is busy).
// Distance kept above talking sprite
#define GAP_ABOVE_HEAD 20
enum {
S_OB_GRAPHIC = 0,
S_OB_SPEECH = 1,
S_OB_LOGIC = 2,
S_OB_MEGA = 3,
S_TEXT = 4,
S_WAV = 5,
S_ANIM = 6,
S_DIR_TABLE = 7,
S_ANIM_MODE = 8
};
/**
* Sets _textX and _textY for position of text sprite. Note that _textX is
* also used to calculate speech pan.
*/
void Logic::locateTalker(int32 *params) {
// params: 0 pointer to ob_graphic
// 1 pointer to ob_speech
// 2 pointer to ob_logic
// 3 pointer to ob_mega
// 4 encoded text number
// 5 wav res id
// 6 anim res id
// 7 pointer to anim table
// 8 animation mode 0 lip synced,
// 1 just straight animation
if (!_animId) {
// There is no animation. Assume it's voice-over text, and put
// it at the bottom of the screen.
_textX = 320;
_textY = 400;
return;
}
byte *file = _vm->_resman->openResource(_animId);
// '0' means 1st frame
CdtEntry cdt_entry;
FrameHeader frame_head;
cdt_entry.read(_vm->fetchCdtEntry(file, 0));
frame_head.read(_vm->fetchFrameHeader(file, 0));
// Note: This part of the code is quite similar to registerFrame().
if (cdt_entry.frameType & FRAME_OFFSET) {
// The frame has offsets, i.e. it's a scalable mega frame
ObjectMega obMega(decodePtr(params[S_OB_MEGA]));
uint16 scale = obMega.calcScale();
// Calc suitable center point above the head, based on scaled
// height
// just use 'feet_x' as center
_textX = obMega.getFeetX();
// Add scaled y-offset to feet_y coord to get top of sprite
_textY = obMega.getFeetY() + (cdt_entry.y * scale) / 256;
} else {
// It's a non-scaling anim - calc suitable center point above
// the head, based on scaled width
// x-coord + half of width
_textX = cdt_entry.x + frame_head.width / 2;
_textY = cdt_entry.y;
}
_vm->_resman->closeResource(_animId);
// Leave space above their head
_textY -= GAP_ABOVE_HEAD;
// Adjust the text coords for RDSPR_DISPLAYALIGN
ScreenInfo *screenInfo = _vm->_screen->getScreenInfo();
_textX -= screenInfo->scroll_offset_x;
_textY -= screenInfo->scroll_offset_y;
}
/**
* This function is called the first time to build the text, if we need one. If
* If necessary it also brings in the wav and sets up the animation.
*
* If there is an animation it can be repeating lip-sync or run-once.
*
* If there is no wav, then the text comes up instead. There can be any
* combination of text/wav playing.
*/
void Logic::formText(int32 *params) {
// params 0 pointer to ob_graphic
// 1 pointer to ob_speech
// 2 pointer to ob_logic
// 3 pointer to ob_mega
// 4 encoded text number
// 5 wav res id
// 6 anim res id
// 7 pointer to anim table
// 8 animation mode 0 lip synced,
// 1 just straight animation
// There should always be a text line, as all text is derived from it.
// If there is none, that's bad...
if (!params[S_TEXT]) {
warning("No text line for speech wav %d", params[S_WAV]);
return;
}
ObjectSpeech obSpeech(decodePtr(params[S_OB_SPEECH]));
// Establish the max width allowed for this text sprite.
uint32 textWidth = obSpeech.getWidth();
if (!textWidth)
textWidth = 400;
// Pull out the text line, and make the sprite and text block
uint32 text_res = params[S_TEXT] / SIZE;
uint32 local_text = params[S_TEXT] & 0xffff;
byte *text = _vm->fetchTextLine(_vm->_resman->openResource(text_res), local_text);
// 'text + 2' to skip the first 2 bytes which form the line reference
// number
_speechTextBlocNo = _vm->_fontRenderer->buildNewBloc(
text + 2, _textX, _textY,
textWidth, obSpeech.getPen(),
RDSPR_TRANS | RDSPR_DISPLAYALIGN,
_vm->_speechFontId, POSITION_AT_CENTER_OF_BASE);
_vm->_resman->closeResource(text_res);
// Set speech duration, in case not using a wav.
_speechTime = strlen((char *)text) + 30;
}
/**
* There are some hard-coded cases where speech is used to illustrate a sound
* effect. In this case there is no sound associated with the speech itself.
*/
bool Logic::wantSpeechForLine(uint32 wavId) {
switch (wavId) {
case 1328: // AttendantSpeech
// SFX(Phone71);
// FX <Telephone rings>
case 2059: // PabloSpeech
// SFX (2059);
// FX <Sound of sporadic gunfire from below>
case 4082: // DuaneSpeech
// SFX (4082);
// FX <Pffffffffffft! Frp. (Unimpressive, flatulent noise.)>
case 4214: // cat_52
// SFX (4214);
// 4214FXMeow!
case 4568: // trapdoor_13
// SFX (4568);
// 4568fx<door slamming>
case 4913: // LobineauSpeech
// SFX (tone2);
// FX <Lobineau hangs up>
case 5120: // bush_66
// SFX (5120);
// 5120FX<loud buzzing>
case 528: // PresidentaSpeech
// SFX (528);
// FX <Nearby Crash of Collapsing Masonry>
case 920: // Zombie Island forest maze (bird)
case 923: // Zombie Island forest maze (monkey)
case 926: // Zombie Island forest maze (zombie)
// Don't want speech for these lines!
return false;
default:
// Ok for all other lines
return true;
}
}
} // End of namespace Sword2

885
engines/sword2/sprite.cpp Normal file
View File

@@ -0,0 +1,885 @@
/* 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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 "sword2/sword2.h"
#include "sword2/defs.h"
#include "sword2/screen.h"
namespace Sword2 {
/**
* This function takes a sprite and creates a mirror image of it.
* @param dst destination buffer
* @param src source buffer
* @param w width of the sprite
* @param h height of the sprite
*/
void Screen::mirrorSprite(byte *dst, byte *src, int16 w, int16 h) {
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
*dst++ = *(src + w - x - 1);
}
src += w;
}
}
/**
* This function takes a compressed frame of a sprite with up to 256 colors
* and decompresses it.
* @param dst destination buffer
* @param src source buffer
* @param decompSize the expected size of the decompressed sprite
*/
int32 Screen::decompressRLE256(byte *dst, byte *src, int32 decompSize) {
// PARAMETERS:
// source points to the start of the sprite data for input
// decompSize gives size of decompressed data in bytes
// dest points to start of destination buffer for decompressed
// data
byte headerByte; // block header byte
byte *endDest = dst + decompSize; // pointer to byte after end of decomp buffer
int32 rv;
while (1) {
// FLAT block
// read FLAT block header & increment 'scan' to first pixel
// of block
headerByte = *src++;
// if this isn't a zero-length block
if (headerByte) {
if (dst + headerByte > endDest) {
rv = 1;
break;
}
// set the next 'headerByte' pixels to the next color
// at 'source'
memset(dst, *src, headerByte);
// increment destination pointer to just after this
// block
dst += headerByte;
// increment source pointer to just after this color
src++;
// if we've decompressed all of the data
if (dst == endDest) {
rv = 0; // return "OK"
break;
}
}
// RAW block
// read RAW block header & increment 'scan' to first pixel of
// block
headerByte = *src++;
// if this isn't a zero-length block
if (headerByte) {
if (dst + headerByte > endDest) {
rv = 1;
break;
}
// copy the next 'headerByte' pixels from source to
// destination
memcpy(dst, src, headerByte);
// increment destination pointer to just after this
// block
dst += headerByte;
// increment source pointer to just after this block
src += headerByte;
// if we've decompressed all of the data
if (dst == endDest) {
rv = 0; // return "OK"
break;
}
}
}
return rv;
}
/**
* Unwinds a run of 16-color data into 256-color palette data.
*/
void Screen::unwindRaw16(byte *dst, byte *src, uint16 blockSize, byte *colTable) {
// for each pair of pixels
while (blockSize > 1) {
if (Sword2Engine::isPsx()) {
// 1st color = number in table at position given by upper
// nibble of source byte
*dst++ = colTable[(*src) & 0x0f];
// 2nd color = number in table at position given by lower
// nibble of source byte
*dst++ = colTable[(*src) >> 4];
} else {
*dst++ = colTable[(*src) >> 4];
*dst++ = colTable[(*src) & 0x0f];
}
// point to next source byte
src++;
// decrement count of how many pixels left to read
blockSize -= 2;
}
// if there's a final odd pixel
if (blockSize) {
// color = number in table at position given by upper nibble
// of source byte
*dst++ = colTable[(*src) >> 4];
}
}
/**
* This function takes a compressed frame of a sprite (with up to 16 colors)
* and decompresses it.
* @param dst destination buffer
* @param src source buffer
* @param decompSize the expected size of the uncompressed sprite
* @param colTable mapping from the 16 encoded colors to the current palette
*/
int32 Screen::decompressRLE16(byte *dst, byte *src, int32 decompSize, byte *colTable) {
byte headerByte; // block header byte
byte *endDest = dst + decompSize; // pointer to byte after end of decomp buffer
int32 rv;
while (1) {
// FLAT block
// read FLAT block header & increment 'scan' to first pixel
// of block
headerByte = *src++;
// if this isn't a zero-length block
if (headerByte) {
if (dst + headerByte > endDest) {
rv = 1;
break;
}
// set the next 'headerByte' pixels to the next
// color at 'source'
memset(dst, *src, headerByte);
// increment destination pointer to just after this
// block
dst += headerByte;
// increment source pointer to just after this color
src++;
// if we've decompressed all of the data
if (dst == endDest) {
rv = 0; // return "OK"
break;
}
}
// RAW block
// read RAW block header & increment 'scan' to first pixel of
// block
headerByte = *src++;
// if this isn't a zero-length block
if (headerByte) {
if (dst + headerByte > endDest) {
rv = 1;
break;
}
// copy the next 'headerByte' pixels from source to
// destination (NB. 2 pixels per byte)
unwindRaw16(dst, src, headerByte, colTable);
// increment destination pointer to just after this
// block
dst += headerByte;
// increment source pointer to just after this block
// (NB. headerByte gives pixels, so /2 for bytes)
src += (headerByte + 1) / 2;
// if we've decompressed all of the data
if (dst >= endDest) {
rv = 0; // return "OK"
break;
}
}
}
return rv;
}
/**
* This function takes a compressed HIF image and decompresses it.
* Used for PSX version sprites.
* @param dst destination buffer
* @param src source buffer
* @param skipData if pointer != NULL, value of pointed var
* is set to number of bytes composing the compressed data.
*/
uint32 Screen::decompressHIF(byte *src, byte *dst, uint32 *skipData) {
uint32 decompSize = 0;
uint32 readByte = 0;
for (;;) { // Main loop
byte control_byte = *src++;
readByte++;
uint32 byte_count = 0;
while (byte_count < 8) {
if (control_byte & 0x80) {
uint16 info_word = READ_BE_UINT16(src); // Read the info word
src += 2;
readByte += 2;
if (info_word == 0xFFFF) { // Got 0xFFFF code, finished.
if (skipData != nullptr) *(skipData) = readByte;
return decompSize;
}
int32 repeat_count = (info_word >> 12) + 2; // How many time data needs to be refetched
while (repeat_count >= 0) {
uint16 refetchData = (info_word & 0xFFF) + 1;
if (refetchData > decompSize) return 0; // We have a problem here...
uint8 *old_data_src = dst - refetchData;
*dst++ = *old_data_src;
decompSize++;
repeat_count--;
}
} else {
*dst++ = *src++;
readByte++;
decompSize++;
}
byte_count++;
control_byte <<= 1; // Shifting left the control code one bit
}
}
}
// Double line image to keep aspect ratio.
// Used in PSX version.
void Screen::resizePsxSprite(byte *dst, byte *src, uint16 destW, uint16 destH) {
for (int i = 0; i < destH / 2; i++) {
memcpy(dst + i * destW * 2, src + i * destW, destW);
memcpy(dst + i * destW * 2 + destW, src + i * destW, destW);
}
}
// Sprites wider than 254px in PSX version are divided
// into slices, this recomposes the image.
void Screen::recomposePsxSprite(SpriteInfo *s) {
if (!s)
return;
uint16 noStripes = (s->w / 254) + ((s->w % 254) ? 1 : 0);
uint16 lastStripeSize = (s->w % 254) ? s->w % 254 : 254;
byte *buffer = (byte *)malloc(s->w * s->h / 2);
memset(buffer, 0, s->w * s->h / 2);
for (int idx = 0; idx < noStripes; idx++) {
uint16 stripeSize = (idx == noStripes - 1) ? lastStripeSize : 254;
for (int line = 0; line < s->h / 2; line++) {
memcpy(buffer + idx * 254 + line * s->w, s->data, stripeSize);
s->data += stripeSize;
}
}
s->data = buffer;
}
// Recomposes sprites wider than 254 pixels but also
// compressed with HIF.
// Used in PSX version.
void Screen::recomposeCompPsxSprite(SpriteInfo *s) {
if (!s)
return;
uint16 noStripes = (s->w / 254) + ((s->w % 254) ? 1 : 0);
uint16 lastStripeSize = (s->w % 254) ? s->w % 254 : 254;
byte *buffer = (byte *)malloc(s->w * s->h / 2);
byte *stripeBuffer = (byte *)malloc(254 * s->h);
memset(buffer, 0, s->w * s->h / 2);
uint32 skipData = 0;
uint32 compBytes = 0;
for (int idx = 0; idx < noStripes; idx++) {
uint16 stripeSize = (idx == noStripes - 1) ? lastStripeSize : 254;
decompressHIF((s->data) + skipData, stripeBuffer, &compBytes);
skipData += compBytes;
for (int line = 0; line < s->h / 2; line++) {
memcpy(buffer + idx * 254 + line * s->w, stripeBuffer + line * stripeSize, stripeSize);
}
}
free(stripeBuffer);
s->data = buffer;
}
/**
* Creates a sprite surface. Sprite surfaces are used by the in-game dialogs
* and for displaying cutscene subtitles, which makes them much easier to draw
* than standard sprites.
* @param s information about how to decode the sprite
* @param sprite the buffer that will be created to store the surface
* @return RD_OK, or an error code
*/
int32 Screen::createSurface(SpriteInfo *s, byte **sprite) {
*sprite = (byte *)malloc(s->w * s->h);
if (!*sprite)
return RDERR_OUTOFMEMORY;
// Surfaces are either uncompressed or RLE256-compressed. No need to
// test for anything else.
if (s->type & RDSPR_NOCOMPRESSION) {
memcpy(*sprite, s->data, s->w * s->h);
} else if (decompressRLE256(*sprite, s->data, s->w * s->h)) {
free(*sprite);
return RDERR_DECOMPRESSION;
}
return RD_OK;
}
/**
* Draws the sprite surface created earlier.
* @param s information about how to place the sprite
* @param surface pointer to the surface created earlier
* @param clipRect the clipping rectangle
*/
void Screen::drawSurface(SpriteInfo *s, byte *surface, Common::Rect *clipRect) {
Common::Rect rd, rs;
uint16 x, y;
byte *src, *dst;
rs.left = 0;
rs.right = s->w;
rs.top = 0;
rs.bottom = s->h;
rd.left = s->x;
rd.right = rd.left + rs.right;
rd.top = s->y;
rd.bottom = rd.top + rs.bottom;
Common::Rect defClipRect(0, 0, _screenWide, _screenDeep);
if (!clipRect) {
clipRect = &defClipRect;
}
if (clipRect->left > rd.left) {
rs.left += (clipRect->left - rd.left);
rd.left = clipRect->left;
}
if (clipRect->top > rd.top) {
rs.top += (clipRect->top - rd.top);
rd.top = clipRect->top;
}
if (clipRect->right < rd.right) {
rd.right = clipRect->right;
}
if (clipRect->bottom < rd.bottom) {
rd.bottom = clipRect->bottom;
}
if (rd.width() <= 0 || rd.height() <= 0)
return;
src = surface + rs.top * s->w + rs.left;
dst = _buffer + _screenWide * rd.top + rd.left;
// Surfaces are always transparent.
for (y = 0; y < rd.height(); y++) {
for (x = 0; x < rd.width(); x++) {
if (src[x])
dst[x] = src[x];
}
src += s->w;
dst += _screenWide;
}
updateRect(&rd);
}
/**
* Destroys a surface.
*/
void Screen::deleteSurface(byte *surface) {
free(surface);
}
/**
* Draws a sprite onto the screen. The type of the sprite can be a combination
* of the following flags, some of which are mutually exclusive:
* RDSPR_DISPLAYALIGN The sprite is drawn relative to the top left corner
* of the screen
* RDSPR_FLIP The sprite is mirrored
* RDSPR_TRANS The sprite has a transparent color zero
* RDSPR_BLEND The sprite is translucent
* RDSPR_SHADOW The sprite is affected by the light mask. (Scaled
* sprites always are.)
* RDSPR_NOCOMPRESSION The sprite data is not compressed
* RDSPR_RLE16 The sprite data is a 16-color compressed sprite
* RDSPR_RLE256 The sprite data is a 256-color compressed sprite
* @param s all the information needed to draw the sprite
* @warning Sprites will only be drawn onto the background, not over menubar
* areas.
*/
// FIXME: I'm sure this could be optimized. There's plenty of data copying and
// mallocing here.
int32 Screen::drawSprite(SpriteInfo *s) {
byte *src, *dst;
byte *sprite, *newSprite;
uint16 scale;
int16 i, j;
uint16 srcPitch;
bool freeSprite = false;
Common::Rect rd, rs;
// -----------------------------------------------------------------
// Decompression and mirroring
// -----------------------------------------------------------------
if (s->type & RDSPR_NOCOMPRESSION) {
if (Sword2Engine::isPsx()) { // PSX Uncompressed sprites
if (s->w > 254 && !s->isText) { // We need to recompose these frames
recomposePsxSprite(s);
}
// If the height is not an even value, fix it.
// Apparently it's a problem in the data of a few sprites
// of the PSX version. This should fix an evident problem
// in the foyer at the beginning of the game, where a line
// of pixels is missing near the stairs. But it should also
// fix a more subtle one in the glease gallery and in quaramonte
// police office.
if (s->h % 2)
s->h++;
freeSprite = true;
byte *tempBuf = (byte *)malloc(s->w * s->h * 2);
memset(tempBuf, 0, s->w * s->h * 2);
resizePsxSprite(tempBuf, s->data, s->w, s->h);
if (s->w > 254 && !s->isText) {
free(s->data);
}
sprite = tempBuf;
} else { // PC Uncompressed sprites
sprite = s->data;
}
} else {
freeSprite = true;
if ((s->type & 0xff00) == RDSPR_RLE16) {
if (Sword2Engine::isPsx()) { // PSX HIF16 sprites
uint32 decompData;
byte *tempBuf = (byte *)malloc(s->w * s->h);
memset(tempBuf, 0, s->w * s->h);
decompData = decompressHIF(s->data, tempBuf);
// Check that we correctly decompressed data
if (!decompData) {
free(tempBuf);
return RDERR_DECOMPRESSION;
}
s->w = (decompData / (s->h / 2)) * 2;
byte *tempBuf2 = (byte *)malloc(s->w * s->h * 10);
memset(tempBuf2, 0, s->w * s->h * 2);
unwindRaw16(tempBuf2, tempBuf, (s->w * (s->h / 2)), s->colorTable);
sprite = (byte *)malloc(s->w * s->h);
if (!sprite) {
free(tempBuf2);
free(tempBuf);
return RDERR_OUTOFMEMORY;
}
resizePsxSprite(sprite, tempBuf2, s->w, s->h);
free(tempBuf2);
free(tempBuf);
} else { // PC RLE16 sprites
sprite = (byte *)malloc(s->w * s->h);
if (!sprite)
return RDERR_OUTOFMEMORY;
if (decompressRLE16(sprite, s->data, s->w * s->h, s->colorTable)) {
free(sprite);
return RDERR_DECOMPRESSION;
}
}
} else {
if (Sword2Engine::isPsx()) { // PSX HIF256 sprites
if (s->w > 255) {
sprite = (byte *)malloc(s->w * s->h);
recomposeCompPsxSprite(s);
resizePsxSprite(sprite, s->data, s->w, s->h);
free(s->data);
} else {
byte *tempBuf = (byte *)malloc(s->w * s->h);
uint32 decompData = decompressHIF(s->data, tempBuf);
// Check that we correctly decompressed data
if (!decompData) {
free(tempBuf);
return RDERR_DECOMPRESSION;
}
s->w = (decompData / (s->h / 2));
sprite = (byte *)malloc(s->w * s->h);
if (!sprite) {
free(tempBuf);
return RDERR_OUTOFMEMORY;
}
resizePsxSprite(sprite, tempBuf, s->w, s->h);
free(tempBuf);
}
} else { // PC RLE256 sprites
sprite = (byte *)malloc(s->w * s->h);
if (!sprite)
return RDERR_OUTOFMEMORY;
if (decompressRLE256(sprite, s->data, s->w * s->h)) {
free(sprite);
return RDERR_DECOMPRESSION;
}
}
}
}
if (s->type & RDSPR_FLIP) {
newSprite = (byte *)malloc(s->w * s->h);
if (newSprite == nullptr) {
if (freeSprite)
free(sprite);
return RDERR_OUTOFMEMORY;
}
mirrorSprite(newSprite, sprite, s->w, s->h);
if (freeSprite)
free(sprite);
sprite = newSprite;
freeSprite = true;
}
// -----------------------------------------------------------------
// Positioning and clipping.
// -----------------------------------------------------------------
int16 spriteX = s->x;
int16 spriteY = s->y;
if (!(s->type & RDSPR_DISPLAYALIGN)) {
spriteX += _parallaxScrollX;
spriteY += _parallaxScrollY;
}
spriteY += MENUDEEP;
// A scale factor 0 or 256 means don't scale. Why do they use two
// different values to mean the same thing? Normalize it here for
// convenience.
scale = (s->scale == 0) ? 256 : s->scale;
rs.top = 0;
rs.left = 0;
if (scale != 256) {
rs.right = s->scaledWidth;
rs.bottom = s->scaledHeight;
srcPitch = s->scaledWidth;
} else {
rs.right = s->w;
rs.bottom = s->h;
srcPitch = s->w;
}
rd.top = spriteY;
rd.left = spriteX;
if (!(s->type & RDSPR_DISPLAYALIGN)) {
rd.top -= _scrollY;
rd.left -= _scrollX;
}
rd.right = rd.left + rs.right;
rd.bottom = rd.top + rs.bottom;
// Check if the sprite would end up completely outside the screen.
if (rd.left > RENDERWIDE || rd.top > RENDERDEEP + MENUDEEP || rd.right < 0 || rd.bottom < MENUDEEP) {
if (freeSprite)
free(sprite);
return RD_OK;
}
if (rd.top < MENUDEEP) {
rs.top = MENUDEEP - rd.top;
rd.top = MENUDEEP;
}
if (rd.bottom > RENDERDEEP + MENUDEEP) {
rd.bottom = RENDERDEEP + MENUDEEP;
rs.bottom = rs.top + (rd.bottom - rd.top);
}
if (rd.left < 0) {
rs.left = -rd.left;
rd.left = 0;
}
if (rd.right > RENDERWIDE) {
rd.right = RENDERWIDE;
rs.right = rs.left + (rd.right - rd.left);
}
// -----------------------------------------------------------------
// Scaling
// -----------------------------------------------------------------
if (scale != 256) {
if (s->scaledWidth > SCALE_MAXWIDTH || s->scaledHeight > SCALE_MAXHEIGHT) {
if (freeSprite)
free(sprite);
return RDERR_NOTIMPLEMENTED;
}
newSprite = (byte *)malloc(s->scaledWidth * s->scaledHeight);
if (newSprite == nullptr) {
if (freeSprite)
free(sprite);
return RDERR_OUTOFMEMORY;
}
// We cannot use good scaling for PSX version, as we are missing
// some required data.
if (_renderCaps & RDBLTFX_EDGEBLEND && !Sword2Engine::isPsx())
scaleImageGood(newSprite, s->scaledWidth, s->scaledWidth, s->scaledHeight, sprite, s->w, s->w, s->h, _buffer, rd.left, rd.top);
else
scaleImageFast(newSprite, s->scaledWidth, s->scaledWidth, s->scaledHeight, sprite, s->w, s->w, s->h);
if (freeSprite)
free(sprite);
sprite = newSprite;
freeSprite = true;
}
// -----------------------------------------------------------------
// Light masking
// -----------------------------------------------------------------
// The light mask is an optional layer that covers the entire room
// and which is used to simulate light and shadows. Scaled sprites
// (actors, presumably) are always affected.
// Light masking makes use of palette match table, so it's unavailable
// in PSX version.
if ((_renderCaps & RDBLTFX_SHADOWBLEND) && _lightMask && (scale != 256 || ((s->type & RDSPR_SHADOW) && !Sword2Engine::isPsx()) )) {
byte *lightMap;
// Make sure that we never apply the shadow to the original
// resource data. This could only ever happen in the
// RDSPR_NOCOMPRESSION case.
if (!freeSprite) {
newSprite = (byte *)malloc(s->w * s->h);
memcpy(newSprite, sprite, s->w * s->h);
sprite = newSprite;
freeSprite = true;
}
src = sprite + rs.top * srcPitch + rs.left;
lightMap = _lightMask + (rd.top + _scrollY - MENUDEEP) * _locationWide + rd.left + _scrollX;
for (i = 0; i < rs.height(); i++) {
for (j = 0; j < rs.width(); j++) {
if (src[j] && lightMap[j]) {
uint8 r = ((32 - lightMap[j]) * _palette[src[j] * 3 + 0]) >> 5;
uint8 g = ((32 - lightMap[j]) * _palette[src[j] * 3 + 1]) >> 5;
uint8 b = ((32 - lightMap[j]) * _palette[src[j] * 3 + 2]) >> 5;
src[j] = quickMatch(r, g, b);
}
}
src += srcPitch;
lightMap += _locationWide;
}
}
// -----------------------------------------------------------------
// Drawing
// -----------------------------------------------------------------
src = sprite + rs.top * srcPitch + rs.left;
dst = _buffer + _screenWide * rd.top + rd.left;
if (s->type & RDSPR_BLEND) {
// The original code had two different blending cases. One for
// s->blend & 0x01 and one for s->blend & 0x02. However, the
// only values that actually appear in the cluster files are
// 0, 513 and 1025 so the s->blend & 0x02 case was never used.
// Which is just as well since that code made no sense to me.
// TODO: In PSX version, blending is done through hardware transparency.
// The only correct way to simulate this would be using 16-bit mode.
// As this is not yet available for this engine, fake transparency is used
// as placeholder.
if (!(_renderCaps & RDBLTFX_SPRITEBLEND) || Sword2Engine::isPsx()) {
for (i = 0; i < rs.height(); i++) {
for (j = 0; j < rs.width(); j++) {
if (src[j] && ((i & 1) == (j & 1)))
dst[j] = src[j];
}
src += srcPitch;
dst += _screenWide;
}
} else {
uint8 n = s->blend >> 8;
for (i = 0; i < rs.height(); i++) {
for (j = 0; j < rs.width(); j++) {
if (src[j]) {
uint8 r1 = _palette[src[j] * 3 + 0];
uint8 g1 = _palette[src[j] * 3 + 1];
uint8 b1 = _palette[src[j] * 3 + 2];
uint8 r2 = _palette[dst[j] * 3 + 0];
uint8 g2 = _palette[dst[j] * 3 + 1];
uint8 b2 = _palette[dst[j] * 3 + 2];
uint8 r = (r1 * n + r2 * (8 - n)) >> 3;
uint8 g = (g1 * n + g2 * (8 - n)) >> 3;
uint8 b = (b1 * n + b2 * (8 - n)) >> 3;
dst[j] = quickMatch(r, g, b);
}
}
src += srcPitch;
dst += _screenWide;
}
}
} else {
if (s->type & RDSPR_TRANS) {
for (i = 0; i < rs.height(); i++) {
for (j = 0; j < rs.width(); j++) {
if (src[j])
dst[j] = src[j];
}
src += srcPitch;
dst += _screenWide;
}
} else {
for (i = 0; i < rs.height(); i++) {
memcpy(dst, src, rs.width());
src += srcPitch;
dst += _screenWide;
}
}
}
if (freeSprite)
free(sprite);
markAsDirty(rd.left, rd.top, rd.right - 1, rd.bottom - 1);
return RD_OK;
}
/**
* Opens the light masking sprite for a room.
*/
int32 Screen::openLightMask(SpriteInfo *s) {
// FIXME: The light mask is only needed on higher graphics detail
// settings, so to save memory we could simply ignore it on lower
// settings. But then we need to figure out how to ensure that it
// is properly loaded if the user changes the settings in mid-game.
if (_lightMask)
return RDERR_NOTCLOSED;
_lightMask = (byte *)malloc(s->w * s->h);
if (!_lightMask)
return RDERR_OUTOFMEMORY;
if (s->data == nullptr) // Check, as there's no mask in psx version
return RDERR_NOTOPEN;
if (decompressRLE256(_lightMask, s->data, s->w * s->h))
return RDERR_DECOMPRESSION;
return RD_OK;
}
/**
* Closes the light masking sprite for a room.
*/
int32 Screen::closeLightMask() {
if (!_lightMask)
return RDERR_NOTOPEN;
free(_lightMask);
_lightMask = nullptr;
return RD_OK;
}
} // End of namespace Sword2

177
engines/sword2/startup.cpp Normal file
View File

@@ -0,0 +1,177 @@
/* 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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/file.h"
#include "common/textconsole.h"
#include "sword2/sword2.h"
#include "sword2/defs.h"
#include "sword2/header.h"
#include "sword2/logic.h"
#include "sword2/maketext.h"
#include "sword2/memory.h"
#include "sword2/resman.h"
#include "sword2/router.h"
#include "sword2/sound.h"
namespace Sword2 {
bool Sword2Engine::initStartMenu() {
// Print out a list of all the start points available.
// There should be a linc produced file called startup.txt.
// This file should contain ascii numbers of all the resource game
// objects that are screen managers.
// We query each in turn and setup an array of start structures.
// If the file doesn't exist then we say so and return a 0.
Common::File fp;
// ok, load in the master screen manager file
_totalStartups = 0;
_totalScreenManagers = 0;
if (!fp.open("startup.inf")) {
warning("Cannot open startup.inf - the debugger won't have a start menu");
return false;
}
// The startup.inf file which contains a list of all the files. Now
// extract the filenames
int start_ids[MAX_starts];
int lineno = 0;
while (!fp.eos() && !fp.err()) {
Common::String line = fp.readLine();
// Skip empty lines or, more likely, the end of the stream.
if (line.size() == 0)
continue;
char *errptr;
int id;
lineno++;
id = strtol(line.c_str(), &errptr, 10);
if (*errptr) {
warning("startup.inf:%d: Invalid string '%s'", lineno, line.c_str());
continue;
}
if (!_resman->checkValid(id)) {
warning("startup.inf:%d: Invalid resource %d", lineno, id);
continue;
}
if (_resman->fetchType(id) != SCREEN_MANAGER) {
warning("startup.inf:%d: '%s' (%d) is not a screen manager", lineno, _resman->fetchName(id), id);
continue;
}
start_ids[_totalScreenManagers] = id;
if (++_totalScreenManagers >= MAX_starts) {
warning("Too many entries in startup.inf");
break;
}
}
// An I/O error before EOS? That's bad, but this is not a vital file.
if (fp.err() && !fp.eos())
warning("I/O error while reading startup.inf");
fp.close();
// Using this method the Gode generated resource.inf must have #0d0a
// on the last entry
debug(1, "%d screen manager objects", _totalScreenManagers);
// Open each object and make a query call. The object must fill in a
// startup structure. It may fill in several if it wishes - for
// instance a startup could be set for later in the game where
// specific vars are set
for (uint i = 0; i < _totalScreenManagers; i++) {
_startRes = start_ids[i];
debug(2, "Querying screen manager %d", _startRes);
// Open each one and run through the interpreter. Script 0 is
// the query request script. We have already made reasonably
// sure the resource is ok.
_logic->runResScript(_startRes, 0);
}
return 1;
}
void Sword2Engine::registerStartPoint(int32 key, char *name) {
assert(_totalStartups < MAX_starts);
_startList[_totalStartups].start_res_id = _startRes;
_startList[_totalStartups].key = key;
strncpy(_startList[_totalStartups].description, name, MAX_description);
_startList[_totalStartups].description[MAX_description - 1] = 0;
_totalStartups++;
}
void Sword2Engine::runStart(int start) {
// Restarting - stop sfx, music & speech!
_sound->clearFxQueue(true);
_logic->fnStopMusic(nullptr);
_sound->unpauseSpeech();
_sound->stopSpeech();
// Remove all resources from memory, including player object and global
// variables
_resman->removeAll();
// Reopen global variables resource and player object
setupPersistentResources();
// Free all the route memory blocks from previous game
_logic->_router->freeAllRouteMem();
// If there was speech text, kill the text block
if (_logic->_speechTextBlocNo) {
_fontRenderer->killTextBloc(_logic->_speechTextBlocNo);
_logic->_speechTextBlocNo = 0;
}
_logic->runResObjScript(_startList[start].start_res_id, CUR_PLAYER_ID, _startList[start].key & 0xffff);
// Make sure there's a mouse, in case restarting while mouse not
// available
_logic->fnAddHuman(nullptr);
}
} // End of namespace Sword2

642
engines/sword2/sword2.cpp Normal file
View File

@@ -0,0 +1,642 @@
/* 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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/textconsole.h"
#include "common/translation.h"
#include "common/error.h"
#include "engines/util.h"
#include "sword2/sword2.h"
#include "sword2/defs.h"
#include "sword2/detection.h"
#include "sword2/header.h"
#include "sword2/console.h"
#include "sword2/controls.h"
#include "sword2/logic.h"
#include "sword2/maketext.h"
#include "sword2/memory.h"
#include "sword2/mouse.h"
#include "sword2/resman.h"
#include "sword2/router.h"
#include "sword2/screen.h"
#include "sword2/sound.h"
namespace Sword2 {
Common::Platform Sword2Engine::_platform;
Sword2Engine::Sword2Engine(OSystem *syst, const ADGameDescription *gameDesc) : Engine(syst), _rnd("sword2") {
// Add default file directories
const Common::FSNode gameDataDir(ConfMan.getPath("path"));
SearchMan.addSubDirectoryMatching(gameDataDir, "clusters");
SearchMan.addSubDirectoryMatching(gameDataDir, "sword2");
SearchMan.addSubDirectoryMatching(gameDataDir, "video");
SearchMan.addSubDirectoryMatching(gameDataDir, "smacks");
SearchMan.addSubDirectoryMatching(gameDataDir, "extras"); // GOG.com
SearchMan.addSubDirectoryMatching(gameDataDir, "streams"); // PSX video
_features = gameDesc->flags;
Sword2Engine::_platform = gameDesc->platform;
_bootParam = ConfMan.getInt("boot_param");
_saveSlot = ConfMan.getInt("save_slot");
_memory = nullptr;
_resman = nullptr;
_sound = nullptr;
_screen = nullptr;
_mouse = nullptr;
_logic = nullptr;
_fontRenderer = nullptr;
_isRTL = Common::parseLanguage(ConfMan.get("language")) == Common::HE_ISR;
_debugger = nullptr;
_keyboardEvent.pending = false;
_mouseEvent.pending = false;
_wantSfxDebug = false;
_gameCycle = 0;
_gameSpeed = 1;
_gmmLoadSlot = -1; // Used to manage GMM Loading
_isKorTrs = gameDesc->language == Common::KO_KOR;
}
Sword2Engine::~Sword2Engine() {
// Unpause the game now, or it will be done automatically when the
// game engine is half-deleted, causing it to crash.
if (isPaused())
_gamePauseToken.clear();
//_debugger is deleted by Engine
delete _sound;
delete _fontRenderer;
delete _screen;
delete _mouse;
delete _logic;
delete _resman;
delete _memory;
}
void Sword2Engine::registerDefaultSettings() {
ConfMan.registerDefault("gfx_details", 2);
ConfMan.registerDefault("reverse_stereo", false);
}
void Sword2Engine::syncSoundSettings() {
Engine::syncSoundSettings();
bool mute = ConfMan.getBool("mute");
setSubtitles(ConfMan.getBool("subtitles"));
// Our own settings dialog can mute the music, speech and sound effects
// individually. ScummVM's settings dialog has one master mute setting.
if (ConfMan.hasKey("mute")) {
ConfMan.setBool("music_mute", ConfMan.getBool("mute"));
ConfMan.setBool("speech_mute", ConfMan.getBool("mute"));
ConfMan.setBool("sfx_mute", ConfMan.getBool("mute"));
if (!mute) // it is false
// So remove it in order to let individual volumes work
ConfMan.removeKey("mute", ConfMan.getActiveDomainName());
}
_sound->muteMusic(ConfMan.getBool("music_mute"));
_sound->muteSpeech(ConfMan.getBool("speech_mute"));
_sound->muteFx(ConfMan.getBool("sfx_mute"));
_sound->setReverseStereo(ConfMan.getBool("reverse_stereo"));
}
void Sword2Engine::readSettings() {
syncSoundSettings();
_mouse->setObjectLabels(ConfMan.getBool("object_labels"));
_screen->setRenderLevel(ConfMan.getInt("gfx_details"));
}
void Sword2Engine::writeSettings() {
ConfMan.setInt("music_volume", _mixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType));
ConfMan.setInt("speech_volume", _mixer->getVolumeForSoundType(Audio::Mixer::kSpeechSoundType));
ConfMan.setInt("sfx_volume", _mixer->getVolumeForSoundType(Audio::Mixer::kSFXSoundType));
ConfMan.setBool("music_mute", _sound->isMusicMute());
ConfMan.setBool("speech_mute", _sound->isSpeechMute());
ConfMan.setBool("sfx_mute", _sound->isFxMute());
ConfMan.setInt("gfx_details", _screen->getRenderLevel());
ConfMan.setBool("subtitles", getSubtitles());
ConfMan.setBool("object_labels", _mouse->getObjectLabels());
ConfMan.setInt("reverse_stereo", _sound->isReverseStereo());
// If even one sound type is unmuted, we can't say that all sound is
// muted.
if (!_sound->isMusicMute() || !_sound->isSpeechMute() || !_sound->isFxMute()) {
ConfMan.setBool("mute", false);
}
ConfMan.flushToDisk();
}
int Sword2Engine::getFramesPerSecond() {
return _gameSpeed * FRAMES_PER_SECOND;
}
/**
* The global script variables and player object should be kept open throughout
* the game, so that they are never expelled by the resource manager.
*/
void Sword2Engine::setupPersistentResources() {
_logic->_scriptVars = _resman->openResource(1) + ResHeader::size();
_resman->openResource(CUR_PLAYER_ID);
}
Common::Error Sword2Engine::run() {
// Get some falling RAM and put it in your pocket, never let it slip
// away
_debugger = nullptr;
_sound = nullptr;
_fontRenderer = nullptr;
_screen = nullptr;
_mouse = nullptr;
_logic = nullptr;
_resman = nullptr;
_memory = nullptr;
initGraphics(640, 480);
_screen = new Screen(this, 640, 480);
// Create the debugger as early as possible (but not before the
// screen object!) so that errors can be displayed in it. In
// particular, we want errors about missing files to be clearly
// visible to the user.
_debugger = new Debugger(this);
setDebugger(_debugger);
_memory = new MemoryManager();
_resman = new ResourceManager(this);
if (!_resman->init())
return Common::kUnknownError;
_logic = new Logic(this);
_fontRenderer = new FontRenderer(this);
_sound = new Sound(this);
_mouse = new Mouse(this);
_fontRenderer->loadTranslations();
registerDefaultSettings();
readSettings();
initStartMenu();
// During normal gameplay, we care neither about mouse button releases
// nor the scroll wheel.
setInputEventFilter(RD_LEFTBUTTONUP | RD_RIGHTBUTTONUP | RD_WHEELUP | RD_WHEELDOWN);
setupPersistentResources();
initializeFontResourceFlags();
if (_features & ADGF_DEMO)
_logic->writeVar(DEMO, 1);
else
_logic->writeVar(DEMO, 0);
if (_saveSlot != -1) {
if (saveExists(_saveSlot))
restoreGame(_saveSlot);
else {
RestoreDialog dialog(this);
if (!dialog.runModal())
startGame();
}
} else if (!_bootParam && saveExists() && !isPsx()) { // Initial load/restart panel disabled in PSX
int32 pars[2] = { 221, FX_LOOP }; // version because of missing panel resources
bool result;
_mouse->setMouse(NORMAL_MOUSE_ID);
_logic->fnPlayMusic(pars);
StartDialog dialog(this);
result = (dialog.runModal() != 0);
// If the game is started from the beginning, the cutscene
// player will kill the music for us. Otherwise, the restore
// will either have killed the music, or done a crossfade.
if (shouldQuit())
return Common::kNoError;
if (result)
startGame();
} else
startGame();
_screen->initializeRenderCycle();
while (1) {
// Handle GMM Loading
if (_gmmLoadSlot != -1) {
// Hide mouse cursor and fade screen
_mouse->hideMouse();
_screen->fadeDown();
// Clean up and load game
_logic->_router->freeAllRouteMem();
// TODO: manage error handling
restoreGame(_gmmLoadSlot);
// Reset load slot
_gmmLoadSlot = -1;
// Show mouse
_mouse->addHuman();
}
KeyboardEvent *ke = keyboardEvent();
if (ke) {
if (ke->kbd.hasFlags(0) || ke->kbd.hasFlags(Common::KBD_SHIFT)) {
switch (ke->kbd.keycode) {
case Common::KEYCODE_p:
if (isPaused()) {
_screen->dimPalette(false);
_gamePauseToken.clear();
} else {
_gamePauseToken = pauseEngine();
_screen->dimPalette(true);
}
break;
#if 0
// Disabled because of strange rumors about the
// credits running spontaneously every few
// minutes.
case Common::KEYCODE_c:
if (!_logic->readVar(DEMO) && !_mouse->isChoosing()) {
ScreenInfo *screenInfo = _screen->getScreenInfo();
_logic->fnPlayCredits(nullptr);
screenInfo->new_palette = 99;
}
break;
#endif
default:
break;
}
}
}
// skip GameCycle if we're paused
if (!isPaused()) {
_gameCycle++;
gameCycle();
}
// We can't use this as termination condition for the loop,
// because we want the break to happen before updating the
// screen again.
if (shouldQuit())
break;
// creates the debug text blocks
_debugger->buildDebugText();
_screen->buildDisplay();
}
return Common::kNoError;
}
void Sword2Engine::restartGame() {
ScreenInfo *screenInfo = _screen->getScreenInfo();
uint32 temp_demo_flag;
_mouse->closeMenuImmediately();
// Restart the game. To do this, we must...
// Stop music instantly!
_sound->stopMusic(true);
// In case we were dead - well we're not anymore!
_logic->writeVar(DEAD, 0);
// Restart the game. Clear all memory and reset the globals
temp_demo_flag = _logic->readVar(DEMO);
// Remove all resources from memory, including player object and
// global variables
_resman->removeAll();
// Reopen global variables resource and player object
setupPersistentResources();
_logic->writeVar(DEMO, temp_demo_flag);
// Free all the route memory blocks from previous game
_logic->_router->freeAllRouteMem();
// Call the same function that first started us up
startGame();
// Prime system with a game cycle
// Reset the graphic 'BuildUnit' list before a new logic list
// (see fnRegisterFrame)
_screen->resetRenderLists();
// Reset the mouse hot-spot list (see fnRegisterMouse and
// fnRegisterFrame)
_mouse->resetMouseList();
_mouse->closeMenuImmediately();
// FOR THE DEMO - FORCE THE SCROLLING TO BE RESET!
// - this is taken from fnInitBackground
// switch on scrolling (2 means first time on screen)
screenInfo->scroll_flag = 2;
if (_logic->processSession())
error("restart 1st cycle failed??");
// So palette not restored immediately after control panel - we want
// to fade up instead!
screenInfo->new_palette = 99;
}
bool Sword2Engine::checkForMouseEvents() {
return _mouseEvent.pending;
}
MouseEvent *Sword2Engine::mouseEvent() {
if (!_mouseEvent.pending)
return nullptr;
_mouseEvent.pending = false;
return &_mouseEvent;
}
KeyboardEvent *Sword2Engine::keyboardEvent() {
if (!_keyboardEvent.pending)
return nullptr;
_keyboardEvent.pending = false;
return &_keyboardEvent;
}
uint32 Sword2Engine::setInputEventFilter(uint32 filter) {
uint32 oldFilter = _inputEventFilter;
_inputEventFilter = filter;
return oldFilter;
}
/**
* OSystem Event Handler. Full of cross platform goodness and 99% fat free!
*/
void Sword2Engine::parseInputEvents() {
Common::Event event;
while (_eventMan->pollEvent(event)) {
switch (event.type) {
case Common::EVENT_KEYDOWN:
if (event.kbd.hasFlags(Common::KBD_CTRL)) {
if (event.kbd.keycode == Common::KEYCODE_f) {
if (_gameSpeed == 1)
_gameSpeed = 2;
else
_gameSpeed = 1;
}
}
if (!(_inputEventFilter & RD_KEYDOWN)) {
_keyboardEvent.pending = true;
_keyboardEvent.kbd = event.kbd;
}
break;
case Common::EVENT_LBUTTONDOWN:
if (!(_inputEventFilter & RD_LEFTBUTTONDOWN)) {
_mouseEvent.pending = true;
_mouseEvent.buttons = RD_LEFTBUTTONDOWN;
}
break;
case Common::EVENT_RBUTTONDOWN:
if (!(_inputEventFilter & RD_RIGHTBUTTONDOWN)) {
_mouseEvent.pending = true;
_mouseEvent.buttons = RD_RIGHTBUTTONDOWN;
}
break;
case Common::EVENT_LBUTTONUP:
if (!(_inputEventFilter & RD_LEFTBUTTONUP)) {
_mouseEvent.pending = true;
_mouseEvent.buttons = RD_LEFTBUTTONUP;
}
break;
case Common::EVENT_RBUTTONUP:
if (!(_inputEventFilter & RD_RIGHTBUTTONUP)) {
_mouseEvent.pending = true;
_mouseEvent.buttons = RD_RIGHTBUTTONUP;
}
break;
case Common::EVENT_WHEELUP:
if (!(_inputEventFilter & RD_WHEELUP)) {
_mouseEvent.pending = true;
_mouseEvent.buttons = RD_WHEELUP;
}
break;
case Common::EVENT_WHEELDOWN:
if (!(_inputEventFilter & RD_WHEELDOWN)) {
_mouseEvent.pending = true;
_mouseEvent.buttons = RD_WHEELDOWN;
}
break;
default:
break;
}
}
}
void Sword2Engine::gameCycle() {
// Do one game cycle, that is run the logic session until a full loop
// has been performed.
if (_logic->getRunList()) {
do {
// Reset the 'BuildUnit' and mouse hot-spot lists
// before each new logic list. The service scripts
// will fill thrm through fnRegisterFrame() and
// fnRegisterMouse().
_screen->resetRenderLists();
_mouse->resetMouseList();
// Keep going as long as new lists keep getting put in
// - i.e. screen changes.
} while (_logic->processSession());
} else {
// Start the console and print the start options perhaps?
_debugger->attach("AWAITING START COMMAND: (Enter 's 1' then 'q' to start from beginning)");
}
// If this screen is wide, recompute the scroll offsets every cycle
ScreenInfo *screenInfo = _screen->getScreenInfo();
if (screenInfo->scroll_flag)
_screen->setScrolling();
_mouse->mouseEngine();
_sound->processFxQueue();
}
void Sword2Engine::startGame() {
// Normally not needed, but we could be coming here from a cancelled
// load game dialog. And since the first cutscene doesn't cover the
// entire screen, that would leave garbage on the rest of it.
_screen->clearScene();
_screen->updateDisplay();
// Boot the game straight into a start script. It's always George's
// script #1, but with different ScreenManager objects depending on
// if it's the demo or the full game, or if we're using a boot param.
int screen_manager_id = 0;
debug(5, "startGame() STARTING:");
if (!_bootParam) {
if (_logic->readVar(DEMO))
screen_manager_id = 19; // DOCKS SECTION START
else
screen_manager_id = 949; // INTRO & PARIS START
} else {
// FIXME this could be validated against startup.inf for valid
// numbers to stop people shooting themselves in the foot
if (_bootParam != 0)
screen_manager_id = _bootParam;
}
_logic->runResObjScript(screen_manager_id, CUR_PLAYER_ID, 1);
}
// FIXME: Move this to some better place?
void Sword2Engine::sleepUntil(uint32 time) {
while (getMillis() < time) {
// Make sure menu animations and fades don't suffer, but don't
// redraw the entire scene.
_mouse->processMenu();
_screen->updateDisplay(false);
_system->delayMillis(10);
}
}
void Sword2Engine::pauseEngineIntern(bool pause) {
Engine::pauseEngineIntern(pause);
if (pause) {
_screen->pauseScreen(true);
} else {
_screen->pauseScreen(false);
}
}
uint32 Sword2Engine::getMillis() {
return _system->getMillis();
}
Common::Error Sword2Engine::saveGameState(int slot, const Common::String &desc, bool isAutosave) {
uint32 saveVal = saveGame(slot, (const byte *)desc.c_str());
if (saveVal == SR_OK)
return Common::kNoError;
else if (saveVal == SR_ERR_WRITEFAIL || saveVal == SR_ERR_FILEOPEN)
return Common::kWritingFailed;
else
return Common::kUnknownError;
}
bool Sword2Engine::canSaveGameStateCurrently(Common::U32String *msg) {
bool canSave = true;
// No save if dead
if (_logic->readVar(DEAD))
canSave = false;
// No save if mouse not shown
else if (_mouse->getMouseStatus())
canSave = false;
// No save if inside a menu
else if (_mouse->getMouseMode() == MOUSE_system_menu)
canSave = false;
// No save if fading
else if (_screen->getFadeStatus())
canSave = false;
return canSave;
}
Common::Error Sword2Engine::loadGameState(int slot) {
// Prepare the game to load through GMM
_gmmLoadSlot = slot;
// TODO: error handling.
return Common::kNoError;
}
bool Sword2Engine::canLoadGameStateCurrently(Common::U32String *msg) {
bool canLoad = true;
// No load if mouse is disabled
if (_mouse->getMouseStatus())
canLoad = false;
// No load if mouse is in system menu
else if (_mouse->getMouseMode() == MOUSE_system_menu)
canLoad = false;
// No load if we are fading
else if (_screen->getFadeStatus())
canLoad = false;
// But if we are dead, ignore previous conditions
if (_logic->readVar(DEAD))
canLoad = true;
return canLoad;
}
} // End of namespace Sword2

259
engines/sword2/sword2.h Normal file
View File

@@ -0,0 +1,259 @@
/* 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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 SWORD2_SWORD2_H
#define SWORD2_SWORD2_H
#define FRAMES_PER_SECOND 12
// Enable this to make it possible to clear the mouse cursor luggage by
// right-clicking. The original didn't do this, but it feels natural to me.
// However, I'm afraid that it'll interfer badly with parts of the game, so
// for now I'll keep it disabled.
#define RIGHT_CLICK_CLEARS_LUGGAGE 0
#include "engines/engine.h"
#include "common/events.h"
#include "common/util.h"
#include "common/random.h"
#include "sword2/detection.h"
#define MAX_starts 100
#define MAX_description 100
class OSystem;
/**
* This is the namespace of the Sword2 engine.
*
* Status of this engine: ???
*
* Games using this engine:
* - Broken Sword II: The Smoking Mirror
*/
namespace Sword2 {
class MemoryManager;
class ResourceManager;
class Sound;
class Screen;
class Mouse;
class Logic;
class FontRenderer;
class Gui;
class Debugger;
enum {
RD_LEFTBUTTONDOWN = 0x01,
RD_LEFTBUTTONUP = 0x02,
RD_RIGHTBUTTONDOWN = 0x04,
RD_RIGHTBUTTONUP = 0x08,
RD_WHEELUP = 0x10,
RD_WHEELDOWN = 0x20,
RD_KEYDOWN = 0x40
};
struct MouseEvent {
bool pending;
uint16 buttons;
};
struct KeyboardEvent {
bool pending;
Common::KeyState kbd;
};
struct StartUp {
char description[MAX_description];
// id of screen manager object
uint32 start_res_id;
// Tell the manager which startup you want (if there are more than 1)
// (i.e more than 1 entrance to a screen and/or separate game boots)
uint32 key;
};
class Sword2Engine : public Engine {
private:
uint32 _inputEventFilter;
// The event "buffers"
MouseEvent _mouseEvent;
KeyboardEvent _keyboardEvent;
uint32 _bootParam;
int32 _saveSlot;
void getPlayerStructures();
void putPlayerStructures();
uint32 saveData(uint16 slotNo, byte *buffer, uint32 bufferSize);
uint32 restoreData(uint16 slotNo, byte *buffer, uint32 bufferSize);
uint32 calcChecksum(byte *buffer, uint32 size);
uint32 _totalStartups;
uint32 _totalScreenManagers;
uint32 _startRes;
bool _useSubtitles;
int _gameSpeed;
// Used to trigger GMM Loading
int _gmmLoadSlot;
StartUp _startList[MAX_starts];
// We need these to fetch data from SCREENS.CLU, which is
// a resource file with custom format keeping background and
// parallax data (which is removed from multiscreen files).
byte *fetchPsxBackground(uint32 location);
byte *fetchPsxParallax(uint32 location, uint8 level); // level: 0 -> bg, 1 -> fg
// Original game platform (PC/PSX)
static Common::Platform _platform;
PauseToken _gamePauseToken;
protected:
// Engine APIs
Common::Error run() override;
bool hasFeature(EngineFeature f) const override;
void syncSoundSettings() override;
void pauseEngineIntern(bool pause) override;
public:
Sword2Engine(OSystem *syst, const ADGameDescription *gameDesc);
~Sword2Engine() override;
int getFramesPerSecond();
void registerDefaultSettings();
void readSettings();
void writeSettings();
void setupPersistentResources();
bool getSubtitles() { return _useSubtitles; }
void setSubtitles(bool b) { _useSubtitles = b; }
// GMM Loading/Saving
Common::Error saveGameState(int slot, const Common::String &desc, bool isAutosave = false) override;
bool canSaveGameStateCurrently(Common::U32String *msg = nullptr) override;
Common::Error loadGameState(int slot) override;
bool canLoadGameStateCurrently(Common::U32String *msg = nullptr) override;
uint32 _features;
MemoryManager *_memory;
ResourceManager *_resman;
Sound *_sound;
Screen *_screen;
Mouse *_mouse;
Logic *_logic;
FontRenderer *_fontRenderer;
bool _isRTL;
Debugger *_debugger;
Common::RandomSource _rnd;
uint32 _speechFontId;
uint32 _controlsFontId;
uint32 _redFontId;
uint32 setInputEventFilter(uint32 filter);
void parseInputEvents();
bool checkForMouseEvents();
MouseEvent *mouseEvent();
KeyboardEvent *keyboardEvent();
bool _wantSfxDebug;
int32 _gameCycle;
bool _isKorTrs;
#if RIGHT_CLICK_CLEARS_LUGGAGE
bool heldIsInInventory();
#endif
void fetchPalette(byte *screenFile, byte *palBuffer);
byte *fetchScreenHeader(byte *screenFile);
byte *fetchLayerHeader(byte *screenFile, uint16 layerNo);
byte *fetchShadingMask(byte *screenFile);
byte *fetchAnimHeader(byte *animFile);
byte *fetchCdtEntry(byte *animFile, uint16 frameNo);
byte *fetchFrameHeader(byte *animFile, uint16 frameNo);
byte *fetchBackgroundParallaxLayer(byte *screenFile, int layer);
byte *fetchBackgroundLayer(byte *screenFile);
byte *fetchForegroundParallaxLayer(byte *screenFile, int layer);
byte *fetchTextLine(byte *file, uint32 text_line);
bool checkTextLine(byte *file, uint32 text_line);
byte *fetchPaletteMatchTable(byte *screenFile);
uint32 saveGame(uint16 slotNo, const byte *description);
uint32 restoreGame(uint16 slotNo);
uint32 getSaveDescription(uint16 slotNo, byte *description);
bool saveExists();
bool saveExists(uint16 slotNo);
uint32 restoreFromBuffer(byte *buffer, uint32 size);
Common::String getSaveStateName(int slot) const override;
uint32 findBufferSize();
void startGame();
void gameCycle();
void restartGame();
void sleepUntil(uint32 time);
void initializeFontResourceFlags();
void initializeFontResourceFlags(uint8 language);
bool initStartMenu();
void registerStartPoint(int32 key, char *name);
uint32 getNumStarts() { return _totalStartups; }
uint32 getNumScreenManagers() { return _totalScreenManagers; }
StartUp *getStartList() { return _startList; }
void runStart(int start);
// Convenience alias for OSystem::getMillis().
// This is a bit hackish, of course :-).
uint32 getMillis();
// Used to check whether we are running PSX version
static bool isPsx() { return _platform == Common::kPlatformPSX; }
};
} // End of namespace Sword2
#endif

81
engines/sword2/sync.cpp Normal file
View File

@@ -0,0 +1,81 @@
/* 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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/textconsole.h"
#include "sword2/sword2.h"
#include "sword2/defs.h"
#include "sword2/header.h"
#include "sword2/logic.h"
namespace Sword2 {
/**
* Clear any syncs registered for this id. Call this just after the id has been
* processed. Theoretically there could be more than one sync waiting for us,
* so clear the lot.
*/
void Logic::clearSyncs(uint32 id) {
for (int i = 0; i < ARRAYSIZE(_syncList); i++) {
if (_syncList[i].id == id) {
debug(5, "removing sync %d for %d", i, id);
_syncList[i].id = 0;
}
}
}
void Logic::sendSync(uint32 id, uint32 sync) {
for (int i = 0; i < ARRAYSIZE(_syncList); i++) {
if (_syncList[i].id == 0) {
debug(5, "%d sends sync %d to %d", readVar(ID), sync, id);
_syncList[i].id = id;
_syncList[i].sync = sync;
return;
}
}
// The original code didn't even check for this condition, so maybe
// it should be a fatal error?
warning("No free sync slot");
}
/**
* Check for a sync waiting for this character. Called from fnAnim() to see if
* animation is to be finished. Returns an index into _syncList[], or -1.
*/
int Logic::getSync() {
uint32 id = readVar(ID);
for (int i = 0; i < ARRAYSIZE(_syncList); i++) {
if (_syncList[i].id == id)
return i;
}
return -1;
}
} // End of namespace Sword2

459
engines/sword2/walker.cpp Normal file
View File

@@ -0,0 +1,459 @@
/* 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.
*
* Additional copyright for this file:
* Copyright (C) 1994-1998 Revolution Software Ltd.
*
* 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/>.
*/
// WALKER.CPP by James (14nov96)
// Functions for moving megas about the place & also for keeping tabs on them
#include "sword2/sword2.h"
#include "sword2/defs.h"
#include "sword2/header.h"
#include "sword2/interpreter.h"
#include "sword2/logic.h"
#include "sword2/resman.h"
#include "sword2/router.h"
#include "sword2/screen.h"
namespace Sword2 {
void Router::setStandbyCoords(int16 x, int16 y, uint8 dir) {
assert(dir <= 7);
_standbyX = x;
_standbyY = y;
_standbyDir = dir;
}
/**
* Work out direction from start to dest.
*/
// Used in whatTarget(); not valid for all megas
#define diagonalx 36
#define diagonaly 8
int Router::whatTarget(int startX, int startY, int destX, int destY) {
int deltaX = destX - startX;
int deltaY = destY - startY;
// 7 0 1
// 6 2
// 5 4 3
// Flat route
if (ABS(deltaY) * diagonalx < ABS(deltaX) * diagonaly / 2)
return (deltaX > 0) ? 2 : 6;
// Vertical route
if (ABS(deltaY) * diagonalx / 2 > ABS(deltaX) * diagonaly)
return (deltaY > 0) ? 4 : 0;
// Diagonal route
if (deltaX > 0)
return (deltaY > 0) ? 3 : 1;
return (deltaY > 0) ? 5 : 7;
}
/**
* Walk meta to (x,y,dir). Set RESULT to 0 if it succeeded. Otherwise, set
* RESULT to 1. Return true if the mega has finished walking.
*/
int Router::doWalk(byte *ob_logic, byte *ob_graph, byte *ob_mega, byte *ob_walkdata, int16 target_x, int16 target_y, uint8 target_dir) {
ObjectLogic obLogic(ob_logic);
ObjectGraphic obGraph(ob_graph);
ObjectMega obMega(ob_mega);
// If this is the start of the walk, calculate the route.
if (obLogic.getLooping() == 0) {
// If we're already there, don't even bother allocating
// memory and calling the router, just quit back & continue
// the script! This avoids an embarrassing mega stand frame
// appearing for one cycle when we're already in position for
// an anim eg. repeatedly clicking on same object to repeat
// an anim - no mega frame will appear in between runs of the
// anim.
if (obMega.getFeetX() == target_x && obMega.getFeetY() == target_y && obMega.getCurDir() == target_dir) {
_vm->_logic->writeVar(RESULT, 0);
return IR_CONT;
}
assert(target_dir <= 8);
obMega.setWalkPc(0);
// Set up mem for _walkData in route_slots[] & set mega's
// 'route_slot_id' accordingly
allocateRouteMem();
int32 route = routeFinder(ob_mega, ob_walkdata, target_x, target_y, target_dir);
// 0 = can't make route to target
// 1 = created route
// 2 = zero route but may need to turn
if (route != 1 && route != 2) {
freeRouteMem();
_vm->_logic->writeVar(RESULT, 1);
return IR_CONT;
}
// Walk is about to start
obMega.setIsWalking(1);
obLogic.setLooping(1);
obGraph.setAnimResource(obMega.getMegasetRes());
} else if (_vm->_logic->readVar(EXIT_FADING) && _vm->_screen->getFadeStatus() == RDFADE_BLACK) {
// Double clicked an exit, and the screen has faded down to
// black. Ok, that's it. Back to script and change screen.
// We have to clear te EXIT_CLICK_ID variable in case there's a
// walk instruction on the new screen, or it'd be cut short.
freeRouteMem();
obLogic.setLooping(0);
obMega.setIsWalking(0);
_vm->_logic->writeVar(EXIT_CLICK_ID, 0);
_vm->_logic->writeVar(RESULT, 0);
return IR_CONT;
}
// Get pointer to walkanim & current frame position
WalkData *walkAnim = getRouteMem();
int32 walk_pc = obMega.getWalkPc();
// If stopping the walk early, overwrite the next step with a
// slow-out, then finish
if (_vm->_logic->checkEventWaiting() && walkAnim[walk_pc].step == 0 && walkAnim[walk_pc + 1].step == 1) {
// At the beginning of a step
earlySlowOut(ob_mega, ob_walkdata);
}
// Get new frame of walk
obGraph.setAnimPc(walkAnim[walk_pc].frame);
obMega.setCurDir(walkAnim[walk_pc].dir);
obMega.setFeetX(walkAnim[walk_pc].x);
obMega.setFeetY(walkAnim[walk_pc].y);
// Is the NEXT frame is the end-marker (512) of the walk sequence?
if (walkAnim[walk_pc + 1].frame != 512) {
// No, it wasn't. Increment the walk-anim frame number and
// come back next cycle.
obMega.setWalkPc(obMega.getWalkPc() + 1);
return IR_REPEAT;
}
// We have reached the end-marker, which means we can return to the
// script just as the final (stand) frame of the walk is set.
freeRouteMem();
obLogic.setLooping(0);
obMega.setIsWalking(0);
// If George's walk has been interrupted to run a new action script for
// instance or Nico's walk has been interrupted by player clicking on
// her to talk
// There used to be code here for checking if two megas were colliding,
// but it had been commented out, and it was only run if a function
// that always returned zero returned non-zero.
if (_vm->_logic->checkEventWaiting()) {
_vm->_logic->startEvent();
_vm->_logic->writeVar(RESULT, 1);
return IR_TERMINATE;
}
_vm->_logic->writeVar(RESULT, 0);
// CONTINUE the script so that RESULT can be checked! Also, if an anim
// command follows the fnWalk command, the 1st frame of the anim (which
// is always a stand frame itself) can replace the final stand frame of
// the walk, to hide the slight difference between the shrinking on the
// mega frames and the pre-shrunk anim start-frame.
return IR_CONT;
}
/**
* Walk mega to start position of anim
*/
int Router::walkToAnim(byte *ob_logic, byte *ob_graph, byte *ob_mega, byte *ob_walkdata, uint32 animRes) {
int16 target_x = 0;
int16 target_y = 0;
uint8 target_dir = 0;
// Walkdata is needed for earlySlowOut if player clicks elsewhere
// during the walk.
// If this is the start of the walk, read anim file to get start coords
ObjectLogic obLogic(ob_logic);
if (obLogic.getLooping() == 0) {
byte *anim_file = _vm->_resman->openResource(animRes);
AnimHeader anim_head;
anim_head.read(_vm->fetchAnimHeader(anim_file));
target_x = anim_head.feetStartX;
target_y = anim_head.feetStartY;
target_dir = anim_head.feetStartDir;
_vm->_resman->closeResource(animRes);
// If start coords not yet set in anim header, use the standby
// coords (which should be set beforehand in the script).
if (target_x == 0 && target_y == 0) {
target_x = _standbyX;
target_y = _standbyY;
target_dir = _standbyDir;
}
assert(target_dir <= 7);
}
return doWalk(ob_logic, ob_graph, ob_mega, ob_walkdata, target_x, target_y, target_dir);
}
/**
* Route to the left or right hand side of target id, if possible.
*/
int Router::walkToTalkToMega(byte *ob_logic, byte *ob_graph, byte *ob_mega, byte *ob_walkdata, uint32 megaId, uint32 separation) {
ObjectMega obMega(ob_mega);
int16 target_x = 0;
int16 target_y = 0;
uint8 target_dir = 0;
// If this is the start of the walk, calculate the route.
ObjectLogic obLogic(ob_logic);
if (obLogic.getLooping() == 0) {
assert(_vm->_resman->fetchType(megaId) == GAME_OBJECT);
// Call the base script. This is the graphic/mouse service
// call, and will set _engineMega to the ObjectMega of mega we
// want to route to.
_vm->_logic->runResScript(megaId, 3);
ObjectMega targetMega(_vm->_logic->getEngineMega());
// Stand exactly beside the mega, ie. at same y-coord
target_y = targetMega.getFeetY();
int scale = obMega.calcScale();
int mega_separation = (separation * scale) / 256;
debug(4, "Target is at (%d, %d), separation %d", targetMega.getFeetX(), targetMega.getFeetY(), mega_separation);
if (targetMega.getFeetX() < obMega.getFeetX()) {
// Target is left of us, so aim to stand to their
// right. Face down_left
target_x = targetMega.getFeetX() + mega_separation;
target_dir = 5;
} else {
// Ok, must be right of us so aim to stand to their
// left. Face down_right.
target_x = targetMega.getFeetX() - mega_separation;
target_dir = 3;
}
}
return doWalk(ob_logic, ob_graph, ob_mega, ob_walkdata, target_x, target_y, target_dir);
}
/**
* Turn mega to the specified direction. Just needs to call doWalk() with
* current feet coords, so router can produce anim of turn frames.
*/
int Router::doFace(byte *ob_logic, byte *ob_graph, byte *ob_mega, byte *ob_walkdata, uint8 target_dir) {
int16 target_x = 0;
int16 target_y = 0;
// If this is the start of the turn, get the mega's current feet
// coords + the required direction
ObjectLogic obLogic(ob_logic);
if (obLogic.getLooping() == 0) {
assert(target_dir <= 7);
ObjectMega obMega(ob_mega);
target_x = obMega.getFeetX();
target_y = obMega.getFeetY();
}
return doWalk(ob_logic, ob_graph, ob_mega, ob_walkdata, target_x, target_y, target_dir);
}
/**
* Turn mega to face point (x,y) on the floor
*/
int Router::faceXY(byte *ob_logic, byte *ob_graph, byte *ob_mega, byte *ob_walkdata, int16 target_x, int16 target_y) {
uint8 target_dir = 0;
// If this is the start of the turn, get the mega's current feet
// coords + the required direction
ObjectLogic obLogic(ob_logic);
if (obLogic.getLooping() == 0) {
ObjectMega obMega(ob_mega);
target_dir = whatTarget(obMega.getFeetX(), obMega.getFeetY(), target_x, target_y);
}
return doFace(ob_logic, ob_graph, ob_mega, ob_walkdata, target_dir);
}
/**
* Turn mega to face another mega.
*/
int Router::faceMega(byte *ob_logic, byte *ob_graph, byte *ob_mega, byte *ob_walkdata, uint32 megaId) {
uint8 target_dir = 0;
// If this is the start of the walk, decide where to walk to.
ObjectLogic obLogic(ob_logic);
if (obLogic.getLooping() == 0) {
assert(_vm->_resman->fetchType(megaId) == GAME_OBJECT);
// Call the base script. This is the graphic/mouse service
// call, and will set _engineMega to the ObjectMega of mega we
// want to turn to face.
_vm->_logic->runResScript(megaId, 3);
ObjectMega obMega(ob_mega);
ObjectMega targetMega(_vm->_logic->getEngineMega());
target_dir = whatTarget(obMega.getFeetX(), obMega.getFeetY(), targetMega.getFeetX(), targetMega.getFeetY());
}
return doFace(ob_logic, ob_graph, ob_mega, ob_walkdata, target_dir);
}
/**
* Stand mega at (x,y,dir)
* Sets up the graphic object, but also needs to set the new 'current_dir' in
* the mega object, so the router knows in future
*/
void Router::standAt(byte *ob_graph, byte *ob_mega, int32 x, int32 y, int32 dir) {
assert(dir >= 0 && dir <= 7);
ObjectGraphic obGraph(ob_graph);
ObjectMega obMega(ob_mega);
// Set up the stand frame & set the mega's new direction
obMega.setFeetX(x);
obMega.setFeetY(y);
obMega.setCurDir(dir);
// Mega-set animation file
obGraph.setAnimResource(obMega.getMegasetRes());
// Dir + first stand frame (always frame 96)
obGraph.setAnimPc(dir + 96);
}
/**
* Stand mega at end position of anim
*/
void Router::standAfterAnim(byte *ob_graph, byte *ob_mega, uint32 animRes) {
byte *anim_file = _vm->_resman->openResource(animRes);
AnimHeader anim_head;
anim_head.read(_vm->fetchAnimHeader(anim_file));
int32 x = anim_head.feetEndX;
int32 y = anim_head.feetEndY;
int32 dir = anim_head.feetEndDir;
_vm->_resman->closeResource(animRes);
// If start coords not available either use the standby coords (which
// should be set beforehand in the script)
if (x == 0 && y == 0) {
x = _standbyX;
y = _standbyY;
dir = _standbyDir;
}
standAt(ob_graph, ob_mega, x, y, dir);
}
void Router::standAtAnim(byte *ob_graph, byte *ob_mega, uint32 animRes) {
byte *anim_file = _vm->_resman->openResource(animRes);
AnimHeader anim_head;
anim_head.read(_vm->fetchAnimHeader(anim_file));
int32 x = anim_head.feetStartX;
int32 y = anim_head.feetStartY;
int32 dir = anim_head.feetStartDir;
_vm->_resman->closeResource(animRes);
// If start coords not available use the standby coords (which should
// be set beforehand in the script)
if (x == 0 && y == 0) {
x = _standbyX;
y = _standbyY;
dir = _standbyDir;
}
standAt(ob_graph, ob_mega, x, y, dir);
}
} // End of namespace Sword2