scummvm/engines/cge2/cge2_main.cpp
Tomasz Długosz eaab877d66 JANITORIAL: fix the name of original author of cge and cge2
The first name is Janusz, not Janus.
The correct name was used in AUTHORS and credits.
In case of doubts, see his personal webpage: https://www.jbw.pl/ - name is in the page footer
2020-04-18 20:59:57 +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 Janusz 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