scummvm/engines/cge2/cge2_main.cpp
2014-10-28 16:06:07 +02:00

960 lines
21 KiB
C++

/* 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 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
/*
* This code is based on original Sfinx source code
* Copyright (c) 1994-1997 Janus B. Wisniewski and L.K. Avalon
*/
#include "sound.h"
#include "cge2/cge2_main.h"
#include "cge2/cge2.h"
#include "cge2/vga13h.h"
#include "cge2/text.h"
#include "cge2/snail.h"
#include "cge2/hero.h"
#include "cge2/spare.h"
#include "cge2/map.h"
namespace CGE2 {
System::System(CGE2Engine *vm) : Sprite(vm), _vm(vm) {
_blinkCounter = 0;
_blinkSprite = nullptr;
tick();
}
void System::touch(uint16 mask, V2D pos, Common::KeyCode keyCode) {
if (mask & kEventKeyb) {
if (keyCode == Common::KEYCODE_ESCAPE) {
// The original was calling keyClick()
// The sound is uselessly annoying and noisy, so it has been removed
_vm->killText();
if (_vm->_gamePhase == kPhaseIntro) {
_vm->_commandHandler->addCommand(kCmdClear, -1, 0, nullptr);
return;
}
}
} else {
if (_vm->_gamePhase != kPhaseInGame)
return;
_vm->_infoLine->setText(nullptr);
if (mask & kMouseLeftUp) {
if (pos.y >= 0) { // world
if (!_vm->_talk && pos.y < _vm->_mouseTop)
_vm->_heroTab[_vm->_sex]->_ptr->walkTo(pos);
} else { // panel
if (_vm->_commandHandler->idle()) {
int sex = pos.x < kPocketsWidth;
if (sex || pos.x >= kScrWidth - kPocketsWidth) {
_vm->switchHero(sex);
if (_vm->_sex == sex) {
int dx = kPocketsWidth >> 1,
dy = 1 - (kPanHeight >> 1);
Sprite *s;
if (!sex)
pos.x -= kScrWidth - kPocketsWidth;
dx -= pos.x;
dy -= pos.y;
if (dx * dx + dy * dy > 10 * 10) {
int n = 0;
if (1 - pos.y >= (kPanHeight >> 1))
n += 2;
if (pos.x >= (kPocketsWidth >> 1))
++n;
s = _vm->_heroTab[_vm->_sex]->_pocket[n];
if (_vm->_sys->_blinkSprite)
_vm->_sys->_blinkSprite->_flags._hide = false;
if (_vm->_sys->_blinkSprite == s)
_vm->_sys->_blinkSprite = nullptr;
else
_vm->_sys->_blinkSprite = s;
}
}
}
}
}
}
}
}
void System::tick() {
_time = kSysTimeRate;
if (_blinkCounter)
--_blinkCounter;
else {
if (_blinkSprite)
_blinkSprite->_flags._hide ^= 1;
_blinkCounter = kBlinkRate;
}
}
int CGE2Engine::number(char *str) {
char *s = token(str);
if (s == nullptr)
error("Wrong input for CGE2Engine::number()");
int r = atoi(s);
char *pp = strchr(s, ':');
if (pp)
r = (r << 8) + atoi(pp + 1);
return r;
}
char *CGE2Engine::token(char *s) {
return strtok(s, " =\t,;/()");
}
char *CGE2Engine::tail(char *s) {
if (s && (*s == '='))
s++;
return s;
}
int CGE2Engine::takeEnum(const char **tab, const char *text) {
if (text) {
for (const char **e = tab; *e; e++) {
if (scumm_stricmp(text, *e) == 0)
return e - tab;
}
}
return -1;
}
ID CGE2Engine::ident(const char *s) {
return ID(takeEnum(EncryptedStream::kIdTab, s));
}
bool CGE2Engine::testBool(char *s) {
return number(s) != 0;
}
void CGE2Engine::badLab(const char *fn) {
error("Misplaced label in %s!", fn);
}
Sprite *CGE2Engine::loadSprite(const char *fname, int ref, int scene, V3D &pos) {
int shpcnt = 0;
int seqcnt = 0;
int cnt[kActions];
for (int i = 0; i < kActions; i++)
cnt[i] = 0;
ID section = kIdPhase;
bool frnt = true;
bool east = false;
bool port = false;
bool tran = false;
Hero *h;
ID id;
char tmpStr[kLineMax + 1];
mergeExt(tmpStr, fname, kSprExt);
if (_resman->exist(tmpStr)) { // sprite description file exist
EncryptedStream sprf(this, tmpStr);
if (sprf.err())
error("Bad SPR [%s]", tmpStr);
int label = kNoByte;
Common::String line;
for (line = sprf.readLine(); !sprf.eos(); line = sprf.readLine()){
if (line.empty())
continue;
Common::strlcpy(tmpStr, line.c_str(), sizeof(tmpStr));
char *p = token(tmpStr);
if (*p == '@') {
if (label != kNoByte)
badLab(fname);
label = atoi(p + 1);
continue;
}
id = ident(p);
switch (id) {
case kIdName: // will be taken in Expand routine
if (label != kNoByte)
badLab(fname);
break;
case kIdType:
if (label != kNoByte)
badLab(fname);
break;
case kIdNear:
case kIdMTake:
case kIdFTake:
case kIdPhase:
case kIdSeq:
if (label != kNoByte)
badLab(fname);
section = id;
break;
case kIdFront:
if (label != kNoByte)
badLab(fname);
p = token(nullptr);
frnt = testBool(p);
break;
case kIdEast:
if (label != kNoByte)
badLab(fname);
p = token(nullptr);
east = testBool(p);
break;
case kIdPortable:
if (label != kNoByte)
badLab(fname);
p = token(nullptr);
port = testBool(p);
break;
case kIdTransparent:
if (label != kNoByte)
badLab(fname);
p = token(nullptr);
tran = testBool(p);
break;
default:
if (id >= kIdNear)
break;
switch (section) {
case kIdNear:
case kIdMTake:
case kIdFTake:
if (_commandHandler->getComId(p) >= 0)
++cnt[section];
else
error("Bad line %d [%s]", sprf.getLineCount(), tmpStr);
break;
case kIdPhase:
if (label != kNoByte)
badLab(fname);
++shpcnt;
break;
case kIdSeq:
if (label != kNoByte)
badLab(fname);
++seqcnt;
break;
default:
break;
}
break;
}
label = kNoByte;
}
if (!shpcnt)
error("No shapes - %s", fname);
} else // No sprite description: mono-shaped sprite with only .BMP file.
++shpcnt;
// Make sprite of chosen type:
Sprite *sprite = nullptr;
char c = *fname | 0x20;
if (c >= 'a' && c <= 'z' && fname[1] == '0' && fname[2] == '\0') {
h = new Hero(this);
h->gotoxyz(pos);
sprite = h;
} else {
sprite = new Sprite(this);
sprite->gotoxyz(pos);
}
if (sprite) {
sprite->_ref = ref;
sprite->_scene = scene;
sprite->_flags._frnt = frnt;
sprite->_flags._east = east;
sprite->_flags._port = port;
sprite->_flags._tran = tran;
sprite->_flags._kill = true;
// Extract the filename, without the extension
Common::strlcpy(sprite->_file, fname, sizeof(sprite->_file));
char *p = strchr(sprite->_file, '.');
if (p)
*p = '\0';
sprite->_shpCnt = shpcnt;
sprite->_seqCnt = seqcnt;
for (int i = 0; i < kActions; i++)
sprite->_actionCtrl[i]._cnt = cnt[i];
}
return sprite;
}
void CGE2Engine::loadScript(const char *fname, bool onlyToolbar) {
EncryptedStream scrf(this, fname);
if (scrf.err())
return;
bool ok = true;
int lcnt = 0;
char tmpStr[kLineMax + 1];
Common::String line;
for (line = scrf.readLine(); !scrf.eos(); line = scrf.readLine()) {
if (line.empty())
continue;
lcnt++;
Common::strlcpy(tmpStr, line.c_str(), sizeof(tmpStr));
ok = false; // not OK if break
V3D P;
// sprite ident number
int SpI = number(tmpStr);
if (onlyToolbar && SpI >= 141)
return;
// sprite file name
char *SpN;
if ((SpN = token(nullptr)) == nullptr)
break;
// sprite scene
int SpA = number(nullptr);
// sprite column
P._x = number(nullptr);
// sprite row
P._y = number(nullptr);
// sprite Z pos
P._z = number(nullptr);
// sprite life
bool BkG = number(nullptr) == 0;
ok = true; // no break: OK
Sprite *sprite = loadSprite(SpN, SpI, SpA, P);
if (sprite) {
if (BkG)
sprite->_flags._back = true;
int n = _spare->count();
if (_spare->locate(sprite->_ref) == nullptr)
_spare->dispose(sprite);
else
delete sprite;
if (_spare->count() == n)
error("Duplicate reference! %s", SpN);
}
}
if (!ok)
error("Bad INI line %d [%s]", scrf.getLineCount(), fname);
}
void CGE2Engine::movie(const char *ext) {
assert(ext);
if (_quitFlag)
return;
char fn[12];
snprintf(fn, 12, "CGE%s", ext);
if (_resman->exist(fn)) {
int now = _now;
_now = atoi(ext + 2);
loadScript(fn);
sceneUp(_now);
_keyboard->setClient(_sys);
while (!_commandHandler->idle() && !_quitFlag)
mainLoop();
_keyboard->setClient(nullptr);
_commandHandler->addCommand(kCmdClear, -1, 0, nullptr);
_commandHandlerTurbo->addCommand(kCmdClear, -1, 0, nullptr);
_spare->clear();
_vga->_showQ->clear();
_now = now;
}
}
void CGE2Engine::sceneUp(int cav) {
_now = cav;
int bakRef = _now << 8;
if (_music)
_midiPlayer->loadMidi(bakRef);
showBak(bakRef);
*_eye = *(_eyeTab[_now]);
_mouseTop = V2D(this, V3D(0, 1, kScrDepth)).y;
_map->load(_now);
_spare->takeScene(_now);
openPocket();
for (int i = 0; i < 2; i++) {
Hero *h = _heroTab[i]->_ptr;
if (h && h->_scene == _now) {
V2D p = *_heroTab[i]->_posTab[_now];
h->gotoxyz(V3D(p.x, 0, p.y));
h->clrHide();
_vga->_showQ->insert(h);
h->park();
h->setCurrent();
h->setContact();
}
}
_sound->stop();
_fx->clear();
selectPocket(-1);
_infoLine->setText(nullptr);
busy(false);
if (!_dark)
_vga->sunset();
_vga->show();
_vga->copyPage(1, 0);
_vga->show();
_vga->sunrise(_vga->_sysPal);
_dark = false;
if (_gamePhase == kPhaseInGame)
_mouse->on();
feedSnail(_vga->_showQ->locate(bakRef + 255), kNear, _heroTab[_sex]->_ptr);
}
void CGE2Engine::sceneDown() {
busy(true);
_soundStat._wait = nullptr; // unlock snail
Sprite *spr = _vga->_showQ->locate((_now << 8) | 254);
if (spr)
feedSnail(spr, kNear, _heroTab[_sex]->_ptr);
while (!(_commandHandler->idle() && _commandHandlerTurbo->idle())) {
_commandHandlerTurbo->runCommand();
_commandHandler->runCommand();
}
closePocket();
for (int i = 0; i < 2; i++)
_spare->update(_vga->_showQ->remove(_heroTab[i]->_ptr));
_spare->dispose();
}
void CGE2Engine::switchScene(int scene) {
if (scene == _now)
return;
_req = scene;
storeHeroPos();
*(_eyeTab[_now]) = *_eye;
if (scene < 0)
_commandHandler->addCallback(kCmdExec, -1, 0, kQGame); // quit game
else {
if (_heroTab[_sex]->_ptr->_scene == _now) {
_heroTab[_sex]->_ptr->setScene(scene);
if (_heroTab[!_sex]->_ptr->_scene == _now)
_heroTab[!_sex]->_ptr->setScene(scene);
}
_mouse->off();
if (_heroTab[_sex]->_ptr)
_heroTab[_sex]->_ptr->park();
killText();
_commandHandler->addCallback(kCmdExec, -1, 0, kXScene); // switch scene
}
}
void CGE2Engine::storeHeroPos() {
for (int i = 0; i < 2; i++) {
Hero *h = _heroTab[i]->_ptr;
if (h->_scene == _now) {
delete _heroTab[i]->_posTab[_now];
V2D *temp = new V2D(this, h->_pos3D._x.trunc(), h->_pos3D._z.trunc());
_heroTab[i]->_posTab[_now] = temp;
}
}
}
void CGE2Engine::showBak(int ref) {
Sprite *spr = _spare->locate(ref);
if (spr != nullptr) {
_bitmapPalette = _vga->_sysPal;
spr->expand();
_bitmapPalette = nullptr;
spr->show(2);
_vga->copyPage(1, 2);
}
}
void CGE2Engine::mainLoop() {
if (_gamePhase == kPhaseInGame)
checkSounds();
_vga->show();
_commandHandlerTurbo->runCommand();
_commandHandler->runCommand();
// Handle a delay between game frames
handleFrame();
// Handle any pending events
_eventManager->poll();
// Check shouldQuit()
_quitFlag = shouldQuit();
}
void CGE2Engine::checkSounds() {
checkMute();
_sound->checkSoundHandles();
checkVolumeSwitches();
_midiPlayer->syncVolume();
syncSoundSettings();
}
void CGE2Engine::handleFrame() {
// Game frame delay
uint32 millis = g_system->getMillis();
while (!_quitFlag && (millis < (_lastFrame + kGameFrameDelay))) {
// Handle any pending events
_eventManager->poll();
if (millis >= (_lastTick + kGameTickDelay)) {
// Dispatch the tick to any active objects
tick();
_lastTick = millis;
}
// Slight delay
g_system->delayMillis(5);
millis = g_system->getMillis();
}
_lastFrame = millis;
if (millis >= (_lastTick + kGameTickDelay)) {
// Dispatch the tick to any active objects
tick();
_lastTick = millis;
}
}
Sprite *CGE2Engine::locate(int ref) {
_taken = false;
Sprite *spr = _vga->_showQ->locate(ref);
if (!spr) {
spr = _spare->locate(ref);
if (spr)
_taken = true;
}
return spr;
}
bool CGE2Engine::isHero(Sprite *spr) {
return spr && spr->_ref / 10 == 14;
}
void CGE2Engine::tick() {
// system pseudo-sprite
if (_sys && _sys->_time && (--_sys->_time == 0))
_sys->tick();
for (Sprite *spr = _vga->_showQ->first(); spr; spr = spr->_next) {
if (spr->_time && (--spr->_time == 0))
spr->tick();
if (_waitRef && (_waitRef == spr->_ref) && spr->seqTest(_waitSeq))
_waitRef = 0;
}
_mouse->tick();
}
void CGE2Engine::busy(bool on) {
if (on) {
_spriteNotify = _midiNotify = &CGE2::CGE2Engine::busyStep;
busyStep();
} else {
if (_busyPtr)
_busyPtr->step(0);
_spriteNotify = _midiNotify = nullptr;
}
}
void CGE2Engine::busyStep() {
if (_busyPtr) {
_busyPtr->step((_busyPtr->_seqPtr) ? -1 : 1);
_busyPtr->show(0);
}
}
void CGE2Engine::runGame() {
if (_quitFlag)
return;
loadUser();
sceneUp(_now);
initToolbar();
// main loop
while (!_endGame && !_quitFlag)
mainLoop();
// If leaving the game (close window, return to launcher, etc.
// - except finishing the game), explicitly save it's state:
if (!_endGame && canSaveGameStateCurrently())
qGame();
_keyboard->setClient(nullptr);
_commandHandler->addCommand(kCmdClear, -1, 0, nullptr);
_commandHandlerTurbo->addCommand(kCmdClear, -1, 0, nullptr);
_mouse->off();
}
void CGE2Engine::loadUser() {
loadPos();
if (_startGameSlot != -1)
loadGame(_startGameSlot);
else {
loadScript("CGE.INI");
loadHeroes();
}
}
void CGE2Engine::loadHeroes() { // Original name: loadGame()
// load sprites & pocket
Sprite *s;
Hero *h = nullptr;
// initialize Andzia/Anna
s = _spare->take(142);
if (s) {
h = new Hero(this);
*(Sprite*)h = *s;
delete s;
h->expand();
_spare->update(h);
}
_heroTab[0]->_ptr = h;
s = _spare->locate(152);
_vga->_showQ->insert(s);
_heroTab[0]->_face = s;
// initialize Wacek/Vincent
s = _spare->take(141);
if (s) {
h = new Hero(this);
*(Sprite*)h = *s;
delete s;
h->expand();
_spare->update(h);
}
_heroTab[1]->_ptr = h;
s = _spare->locate(151);
_vga->_showQ->insert(s);
_heroTab[1]->_face = s;
//--- start!
switchHero(_sex);
}
void CGE2Engine::loadPos() {
if (_resman->exist("CGE.HXY")) {
for (int cav = 0; cav < kSceneMax; cav++)
_heroTab[1]->_posTab[cav] = new V2D(this, 180, 10);
EncryptedStream file(this, "CGE.HXY");
for (int cav = 0; cav < kSceneMax; cav++) {
_heroTab[0]->_posTab[cav] = new V2D(this);
_heroTab[0]->_posTab[cav]->x = file.readSint16LE();
_heroTab[0]->_posTab[cav]->y = file.readSint16LE();
}
for (int cav = 0; cav < 41; cav++) { // (564 - 400) / 4 = 41
_heroTab[1]->_posTab[cav]->x = file.readSint16LE();
_heroTab[1]->_posTab[cav]->y = file.readSint16LE();
}
} else
error("Missing file: CGE.HXY");
}
void CGE2Engine::loadTab() {
setEye(_text->getText(240));
for (int i = 0; i < kSceneMax; i++)
*(_eyeTab[i]) = *_eye;
if (_resman->exist(kTabName)) {
EncryptedStream f(this, kTabName);
for (int i = 0; i < kSceneMax; i++) {
uint32 v = f.readUint32LE();
_eyeTab[i]->_x = FXP(v >> 8, static_cast<int>((int8)(v & 0xff)));
v = f.readUint32LE();
_eyeTab[i]->_y = FXP(v >> 8, static_cast<int>((int8)(v & 0xff)));
v = f.readUint32LE();
_eyeTab[i]->_z = FXP(v >> 8, static_cast<int>((int8)(v & 0xff)));
}
}
}
void CGE2Engine::cge2_main() {
loadTab();
if (_startGameSlot != -1) {
// Starting up a savegame from the launcher
runGame();
return;
}
if (showTitle("WELCOME")) {
movie(kIntroExt);
if (_text->getText(255) != nullptr) {
runGame();
_gamePhase = kPhaseOver;
}
_vga->sunset();
} else
_vga->sunset();
}
char *CGE2Engine::mergeExt(char *buf, const char *name, const char *ext) {
strcpy(buf, name);
char *dot = strrchr(buf, '.');
if (!dot)
strcat(buf, ext);
return buf;
}
void CGE2Engine::setEye(const V3D &e) {
*_eye = e;
}
void CGE2Engine::setEye(const V2D& e2, int z) {
_eye->_x = e2.x;
_eye->_y = e2.y;
_eye->_z = z;
}
void CGE2Engine::setEye(const char *s) {
char *tempStr = new char[strlen(s) + 1];
strcpy(tempStr, s);
_eye->_x = atoi(token(tempStr));
_eye->_y = atoi(token(nullptr));
_eye->_z = atoi(token(nullptr));
delete[] tempStr;
}
int CGE2Engine::newRandom(int range) {
if (!range)
return 0;
return _randomSource.getRandomNumber(range - 1);
}
bool CGE2Engine::showTitle(const char *name) {
if (_quitFlag)
return false;
_bitmapPalette = _vga->_sysPal;
BitmapPtr LB = new Bitmap[1];
LB[0] = Bitmap(this, name);
_bitmapPalette = nullptr;
Sprite D(this, LB, 1);
D._flags._kill = true;
D.gotoxyz(kScrWidth >> 1, -(kPanHeight >> 1));
_vga->sunset();
D.show(2);
_vga->copyPage(1, 2);
_vga->copyPage(0, 1);
_vga->sunrise(_vga->_sysPal);
_vga->update();
g_system->delayMillis(2500);
return true;
}
void CGE2Engine::killText() {
if (!_talk)
return;
_commandHandlerTurbo->addCommand(kCmdKill, -1, 0, _talk);
_talk = nullptr;
}
void CGE2Engine::switchHero(int sex) {
if (sex != _sex) {
int scene = _heroTab[sex]->_ptr->_scene;
if (_sys->_blinkSprite) {
_sys->_blinkSprite->_flags._hide = false;
_sys->_blinkSprite = nullptr;
}
if (scene >= 0) {
_commandHandler->addCommand(kCmdSeq, -1, 2, _heroTab[_sex]->_face);
_sex ^= 1;
switchScene(scene);
}
}
Sprite *face = _heroTab[_sex]->_face;
if (face->_seqPtr == 0)
_commandHandler->addCommand(kCmdSeq, -1, 1, face);
}
void Sprite::touch(uint16 mask, V2D pos, Common::KeyCode keyCode) {
if ((mask & kEventAttn) != 0)
return;
if (_vm->_gamePhase == kPhaseInGame)
_vm->_infoLine->setText(name());
if (_ref < 0)
return; // cannot access system sprites
if (_ref / 10 == 12) {
_vm->optionTouch(_ref % 10, mask);
return;
}
if ((mask & kMouseLeftUp) && _vm->_commandHandler->idle()) {
if (_vm->isHero(this) && !_vm->_sys->_blinkSprite) {
_vm->switchHero((this == _vm->_heroTab[1]->_ptr) ? 1 : 0);
} else if (_flags._kept) { // sprite in pocket
for (int sex = 0; sex < 2; ++sex) {
for (int p = 0; p < kPocketMax; ++p) {
if (_vm->_heroTab[sex]->_pocket[p] == this) {
_vm->switchHero(sex);
if (_vm->_sex == sex) {
if (_vm->_sys->_blinkSprite)
_vm->_sys->_blinkSprite->_flags._hide = false;
if (_vm->_sys->_blinkSprite == this)
_vm->_sys->_blinkSprite = nullptr;
else
_vm->_sys->_blinkSprite = this;
}
}
}
}
} else { // sprite NOT in pocket
Hero *h = _vm->_heroTab[_vm->_sex]->_ptr;
if (!_vm->_talk) {
// the "+3" is a hack used to work around a script issue in scene 5
if ((_ref & 0xFF) < 200 && h->distance(this) > (h->_maxDist << 1) + 3)
h->walkTo(this);
else if (_vm->_sys->_blinkSprite) {
if (works(_vm->_sys->_blinkSprite)) {
_vm->feedSnail(_vm->_sys->_blinkSprite, (_vm->_sex) ? kMTake : kFTake, _vm->_heroTab[_vm->_sex]->_ptr);
_vm->_sys->_blinkSprite->_flags._hide = false;
_vm->_sys->_blinkSprite = nullptr;
} else
_vm->offUse();
_vm->selectPocket(-1);
// else, no pocket sprite selected
} else if (_flags._port) { // portable
if (_vm->findActivePocket(-1) < 0)
_vm->pocFul();
else {
_vm->_commandHandler->addCommand(kCmdReach, -2, _ref, nullptr);
_vm->_commandHandler->addCommand(kCmdKeep, -1, -1, this);
_flags._port = false;
}
} else { // non-portable
Action a = h->action();
if (_actionCtrl[a]._cnt) {
CommandHandler::Command *cmdList = snList(a);
if (cmdList[_actionCtrl[a]._ptr]._commandType == kCmdNext)
_vm->offUse();
else
_vm->feedSnail(this, a, h);
} else
_vm->offUse();
}
}
}
}
}
void CGE2Engine::keyClick() {
_commandHandlerTurbo->addCommand(kCmdSound, -1, 5, nullptr);
}
void CGE2Engine::offUse() {
int seq = 0;
int offUseCount = atoi(_text->getText(kOffUseCount));
// This fixes the issue of empty speech bubbles in the original.
// Now we only let this cycle pass if it randoms a valid value for getText().
int txt = 0;
do {
txt = kOffUseText + _sex * offUseCount + newRandom(offUseCount);
} while (_text->getText(txt) == nullptr);
Hero *h = _heroTab[_sex]->_ptr;
h->park();
_commandHandler->addCommand(kCmdWait, -1, -1, h);
_commandHandler->addCommand(kCmdSeq, -1, seq, h);
if (!_sayVox)
_commandHandler->addCommand(kCmdSound, -1, 6 + _sex, h);
_commandHandler->addCommand(kCmdWait, -1, -1, h);
_commandHandler->addCommand(kCmdSay, -1, txt, h);
}
Sprite *CGE2Engine::spriteAt(V2D pos) {
Sprite *spr;
for (spr = _vga->_showQ->last(); spr; spr = spr->_prev) {
if (!spr->_flags._hide && !spr->_flags._tran && (spr->getShp()->solidAt(pos - spr->_pos2D)))
break;
}
return spr;
}
} // End of namespace CGE2