/* 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 "cge2/snail.h" #include "cge2/fileio.h" #include "cge2/hero.h" #include "cge2/text.h" #include "cge2/sound.h" #include "cge2/events.h" namespace CGE2 { const char *CommandHandler::_commandText[] = { "NOP", "USE", "PAUSE", "INF", "CAVE", "SETX", "SETY", "SETZ", "ADD", "FLASH", "CYCLE", "CLEAR", "MOUSE", "MAP", "MIDI", ".DUMMY.", "WAIT", "HIDE", "ROOM", "SAY", "SOUND", "KILL", "RSEQ", "SEQ", "SEND", "SWAP", "KEEP", "GIVE", "GETPOS", "GOTO", "PORT", "NEXT", "NNEXT", "MTNEXT", "FTNEXT", "RNNEXT", "RMTNEXT", "RFTNEXT", "RMNEAR", "RMMTAKE", "RMFTAKE", "SETREF", "WALKTO", "REACH", "COVER", "UNCOVER", "EXEC", "GHOST", nullptr }; CommandHandler::CommandHandler(CGE2Engine *vm, bool turbo) : _turbo(turbo), _textDelay(false), _timerExpiry(0), _talkEnable(true), _head(0), _tail(0), _commandList((Command *)malloc(sizeof(Command)* 256)), _vm(vm) { } CommandHandler::~CommandHandler() { free(_commandList); } void CommandHandler::runCommand() { if (!_turbo && _vm->_soundStat._wait) { if (*(_vm->_soundStat._wait)) return; ++_vm->_soundStat._ref[0]; if (_vm->_fx->exist(_vm->_soundStat._ref[1], _vm->_soundStat._ref[0])) { int16 oldRepeat = _vm->_sound->getRepeat(); _vm->_sound->setRepeat(1); _vm->_sound->play(Audio::Mixer::kSpeechSoundType, _vm->_fx->load(_vm->_soundStat._ref[1], _vm->_soundStat._ref[0]), _vm->_sound->_smpinf._span); _vm->_sound->setRepeat(oldRepeat); return; } _vm->_soundStat._wait = nullptr; } uint8 tmpHead = _head; while (_tail != tmpHead) { Command tailCmd = _commandList[_tail]; if (!_turbo) { // only for the slower one if (_vm->_waitRef) break; if (_timerExpiry) { // Delay in progress if (_timerExpiry > g_system->getMillis()) // Delay not yet ended break; // Delay is finished _timerExpiry = 0; } else if (_textDelay) { if (_vm->_talk) { _vm->snKill((Sprite *)_vm->_talk); _vm->_talk = nullptr; } _textDelay = false; } if (_vm->_talk && tailCmd._commandType != kCmdPause) break; } ++_tail; _vm->_taken = false; Sprite *spr = nullptr; if (tailCmd._commandType > kCmdSpr) spr = (tailCmd._ref < 0) ? ((Sprite *)tailCmd._spritePtr) : _vm->locate(tailCmd._ref); Common::String sprStr; if (spr && *spr->_file && (tailCmd._commandType != kCmdGhost)) // In case of kCmdGhost _spritePtr stores a pointer to a Bitmap, not to a Sprite... sprStr = Common::String(spr->_file); else sprStr = "None"; if (sprStr.empty()) sprStr = "None"; debugC(1, kCGE2DebugOpcode, "Command: %s; Ref: %d; Val: %d; Sprite: %s;", getComStr(tailCmd._commandType), tailCmd._ref, tailCmd._val, sprStr.c_str()); switch (tailCmd._commandType) { case kCmdUse: break; case kCmdPause: _timerExpiry = g_system->getMillis() + tailCmd._val * kCommandFrameDelay; if (_vm->_talk) _textDelay = true; break; case kCmdWait: if (spr && spr->active() && (spr->_scene == _vm->_now || spr->_scene == 0)) { _vm->_waitSeq = tailCmd._val; _vm->_waitRef = spr->_ref; } break; case kCmdHide: _vm->snHide(spr, tailCmd._val); break; case kCmdSay: _vm->snSay(spr, tailCmd._val); break; case kCmdInf: if (_talkEnable) _vm->inf(((tailCmd._val) >= 0) ? _vm->_text->getText(tailCmd._val) : (const char *)tailCmd._spritePtr); break; case kCmdCave: _vm->switchScene(tailCmd._val); break; case kCmdMidi: _vm->snMidi(tailCmd._val); break; case kCmdKill: _vm->snKill(spr); break; case kCmdSeq: _vm->snSeq(spr, tailCmd._val); break; case kCmdRSeq: _vm->snRSeq(spr, tailCmd._val); break; case kCmdSend: _vm->snSend(spr, tailCmd._val); break; case kCmdSwap: _vm->snSwap(spr, tailCmd._val); break; case kCmdCover: _vm->snCover(spr, tailCmd._val); break; case kCmdUncover: _vm->snUncover(spr, (tailCmd._val >= 0) ? _vm->locate(tailCmd._val) : ((Sprite *)tailCmd._spritePtr)); break; case kCmdKeep: _vm->snKeep(spr, tailCmd._val); break; case kCmdGive: _vm->snGive(spr, tailCmd._val); break; case kCmdSetX: _vm->_point[tailCmd._val]->_x = tailCmd._ref; break; case kCmdSetY: _vm->_point[tailCmd._val]->_y = tailCmd._ref; break; case kCmdSetZ: _vm->_point[tailCmd._val]->_z = tailCmd._ref; break; case kCmdAdd: *(_vm->_point[tailCmd._ref]) = *(_vm->_point[tailCmd._ref]) + *(_vm->_point[tailCmd._val]); break; case kCmdGetPos: if (spr) *(_vm->_point[tailCmd._val]) = spr->_pos3D; break; case kCmdGoto: _vm->snGoto(spr, tailCmd._val); break; case kCmdPort: _vm->snPort(spr, tailCmd._val); break; case kCmdNext: break; case kCmdMouse: _vm->snMouse(tailCmd._val != 0); break; case kCmdNNext: _vm->snNNext(spr, kNear, tailCmd._val); break; case kCmdMTNext: _vm->snNNext(spr, kMTake, tailCmd._val); break; case kCmdFTNext: _vm->snNNext(spr, kFTake, tailCmd._val); break; case kCmdRNNext: _vm->snRNNext(spr, tailCmd._val); break; case kCmdRMTNext: _vm->snRMTNext(spr, tailCmd._val); break; case kCmdRFTNext: _vm->snRFTNext(spr, tailCmd._val); break; case kCmdRMNear: _vm->snRmNear(spr); break; case kCmdRMMTake: _vm->snRmMTake(spr); break; case kCmdRMFTake: _vm->snRmFTake(spr); break; case kCmdSetRef: _vm->snSetRef(spr, tailCmd._val); break; case kCmdFlash: _vm->snFlash(tailCmd._val != 0); break; case kCmdCycle: _vm->snCycle(tailCmd._val); break; case kCmdWalk: _vm->snWalk(spr, tailCmd._val); break; case kCmdReach: _vm->snReach(spr, tailCmd._val); break; case kCmdSound: _vm->snSound(spr, tailCmd._val); _vm->_sound->setRepeat(1); break; case kCmdMap: _vm->_heroTab[tailCmd._ref & 1]->_ptr->_ignoreMap = tailCmd._val == 0; break; case kCmdRoom: _vm->snRoom(spr, tailCmd._val); break; case kCmdExec: switch (tailCmd._cbType) { case kQGame: _vm->qGame(); break; case kXScene: _vm->xScene(); break; default: error("Unknown Callback Type in SNEXEC"); break; } break; case kCmdGhost: _vm->snGhost((Bitmap *)tailCmd._spritePtr); break; case kCmdNop: // Do nothing. break; default: warning("Unhandled command"); break; } if (_vm->_taken && spr) _vm->_spare->dispose(spr); if (!_turbo) break; } } void CGE2Engine::snKill(Sprite *spr) { if (spr) { if (spr->_flags._kept) releasePocket(spr); Sprite *nx = spr->_next; hide1(spr); _vga->_showQ->remove(spr); _eventManager->clearEvent(spr); if (spr->_flags._kill) { _spare->take(spr->_ref); delete spr; } else { spr->setScene(-1); _spare->dispose(spr); } if (nx && nx->_flags._slav) snKill(nx); } } void CGE2Engine::snHide(Sprite *spr, int val) { if (spr) { spr->_flags._hide = (val >= 0) ? (val != 0) : (!spr->_flags._hide); if (spr->_flags._shad) spr->_prev->_flags._hide = spr->_flags._hide; } } void CGE2Engine::snMidi(int val) { if (val < 0) _midiPlayer->killMidi(); else if (_music) _midiPlayer->loadMidi(val); } void CGE2Engine::snSeq(Sprite *spr, int val) { if (spr) { if (isHero(spr) && (val == 0)) ((Hero*)spr)->park(); else spr->step(val); } } void CGE2Engine::snRSeq(Sprite *spr, int val) { if (spr) snSeq(spr, spr->_seqPtr + val); } void CGE2Engine::snSend(Sprite *spr, int val) { if (!spr) return; // Sending", spr->_file // from scene", spr->_scene // to scene", val bool was1 = (_vga->_showQ->locate(spr->_ref) != nullptr); bool val1 = (val == 0 || val == _now); spr->_scene = val; releasePocket(spr); if (val1 != was1) { if (was1) { // deactivating hide1(spr); spr->_flags._slav = false; if ((spr == _heroTab[_sex]->_ptr) && (_heroTab[!_sex]->_ptr->_scene == _now)) switchHero(!_sex); _spare->dispose(spr); } else { // activating if (byte(spr->_ref) == 0) _bitmapPalette = _vga->_sysPal; _vga->_showQ->insert(spr); if (isHero(spr)) { V2D p = *_heroTab[spr->_ref & 1]->_posTab[val]; spr->gotoxyz(V3D(p.x, 0, p.y)); ((Hero*)spr)->setCurrent(); } _taken = false; _bitmapPalette = nullptr; } } } void CGE2Engine::snSwap(Sprite *spr, int val) { bool tak = _taken; Sprite *xspr = locate(val); if (spr && xspr) { bool was1 = (_vga->_showQ->locate(spr->_ref) != nullptr); bool xwas1 = (_vga->_showQ->locate(val) != nullptr); int tmp = spr->_scene; spr->setScene(xspr->_scene); xspr->setScene(tmp); SWAP(spr->_pos2D, xspr->_pos2D); SWAP(spr->_pos3D, xspr->_pos3D); if (spr->_flags._kept) swapInPocket(spr, xspr); if (xwas1 != was1) { if (was1) { hide1(spr); _spare->dispose(spr); } else expandSprite(spr); if (xwas1) { hide1(xspr); _spare->dispose(xspr); } else { expandSprite(xspr); _taken = false; } } } if (_taken) _spare->dispose(xspr); _taken = tak; } void CGE2Engine::snCover(Sprite *spr, int val) { bool tak = _taken; Sprite *xspr = locate(val); if (spr && xspr) { spr->_flags._hide = true; xspr->setScene(spr->_scene); xspr->gotoxyz(spr->_pos3D); expandSprite(xspr); if ((xspr->_flags._shad = spr->_flags._shad) == true) { _vga->_showQ->insert(_vga->_showQ->remove(spr->_prev), xspr); spr->_flags._shad = false; } feedSnail(xspr, kNear, _heroTab[_sex]->_ptr); _taken = false; } if (_taken) _spare->dispose(xspr); _taken = tak; } void CGE2Engine::snUncover(Sprite *spr, Sprite *spr2) { if (spr && spr2) { spr->_flags._hide = false; spr->setScene(spr2->_scene); if ((spr->_flags._shad = spr2->_flags._shad) == true) { _vga->_showQ->insert(_vga->_showQ->remove(spr2->_prev), spr); spr2->_flags._shad = false; } spr->gotoxyz(spr2->_pos3D); snSend(spr2, -1); if (spr->_time == 0) ++spr->_time; } } void CGE2Engine::snKeep(Sprite *spr, int stp) { int sex = _sex; if (stp > 127) { _sex = stp & 1; // for another hero stp = -1; } HeroTab *ht = _heroTab[_sex]; selectPocket(-1); int pp = ht->_pocPtr; if (spr && !spr->_flags._kept && ht->_pocket[pp] == nullptr) { V3D pos(14, -10, -1); int16 oldRepeat = _sound->getRepeat(); _sound->setRepeat(1); snSound(ht->_ptr, 3); _sound->setRepeat(oldRepeat); if (_taken) { _vga->_showQ->insert(spr); _taken = false; } ht->_pocket[pp] = spr; spr->setScene(0); spr->_flags._kept = true; if (!_sex) pos._x += kScrWidth - 58; if (pp & 1) pos._x += 29; if (pp >> 1) pos._y -= 20; pos._y -= (spr->_siz.y / 2); spr->gotoxyz(pos); if (stp >= 0) spr->step(stp); } _sex = sex; selectPocket(-1); } void CGE2Engine::snGive(Sprite *spr, int val) { if (spr) { int p = findActivePocket(spr->_ref); if (p >= 0) { releasePocket(spr); spr->setScene(_now); if (val >= 0) spr->step(val); } } selectPocket(-1); } void CGE2Engine::snGoto(Sprite *spr, int val) { if (spr) { V3D eye = *_eye; if (spr->_scene > 0) setEye(*_eyeTab[spr->_scene]); spr->gotoxyz(*_point[val]); setEye(eye); } } void CGE2Engine::snPort(Sprite *spr, int port) { if (spr) spr->_flags._port = (port < 0) ? !spr->_flags._port : (port != 0); } void CGE2Engine::snMouse(bool on) { if (on) _mouse->on(); else _mouse->off(); } void CGE2Engine::snNNext(Sprite *spr, Action act, int val) { if (spr) { if (val > 255) val = spr->labVal(act, val >> 8); spr->_actionCtrl[act]._ptr = val; } } void CGE2Engine::snRNNext(Sprite *spr, int val) { if (spr) spr->_actionCtrl[kNear]._ptr += val; } void CGE2Engine::snRMTNext(Sprite *spr, int val) { if (spr) spr->_actionCtrl[kMTake]._ptr += val; } void CGE2Engine::snRFTNext(Sprite * spr, int val) { if (spr) spr->_actionCtrl[kFTake]._ptr += val; } void CGE2Engine::snRmNear(Sprite *spr) { if (spr) spr->_actionCtrl[kNear]._cnt = 0; } void CGE2Engine::snRmMTake(Sprite *spr) { if (spr) spr->_actionCtrl[kMTake]._cnt = 0; } void CGE2Engine::snRmFTake(Sprite *spr) { if (spr) spr->_actionCtrl[kFTake]._cnt = 0; } void CGE2Engine::snSetRef(Sprite *spr, int val) { if (spr) spr->_ref = val; } void CGE2Engine::snFlash(bool on) { if (on) { Dac *pal = (Dac *)malloc(sizeof(Dac) * kPalCount); if (pal) { memcpy(pal, _vga->_sysPal, kPalSize); for (int i = 0; i < kPalCount; i++) { int c; c = pal[i]._r << 1; pal[i]._r = (c < 64) ? c : 63; c = pal[i]._g << 1; pal[i]._g = (c < 64) ? c : 63; c = pal[i]._b << 1; pal[i]._b = (c < 64) ? c : 63; } _vga->setColors(pal, 64); } free(pal); } else _vga->setColors(_vga->_sysPal, 64); _dark = false; } void CGE2Engine::snCycle(int cnt) { _vga->_rot._len = cnt; } void CGE2Engine::snWalk(Sprite *spr, int val) { if (isHero(spr)) { if (val < kMaxPoint) ((Hero *)spr)->walkTo(*_point[val]); else { Sprite *s = _vga->_showQ->locate(val); if (s) ((Hero *)spr)->walkTo(s); } ((Hero *)spr)->_time = 1; } } void CGE2Engine::snReach(Sprite *spr, int val) { if (isHero(spr)) ((Hero *)spr)->reach(val); } void CGE2Engine::snSound(Sprite *spr, int wav, Audio::Mixer::SoundType soundType) { if (wav == -1) _sound->stop(); else { if (_sound->_smpinf._counter && wav < 20) return; if (_soundStat._wait && ((wav & 255) > 80)) return; _soundStat._ref[1] = wav; _soundStat._ref[0] = !_fx->exist(_soundStat._ref[1]); _sound->play(soundType, _fx->load(_soundStat._ref[1], _soundStat._ref[0]), (spr) ? (spr->_pos2D.x / (kScrWidth / 16)) : 8); } } void CGE2Engine::snRoom(Sprite *spr, bool on) { if (!isHero(spr)) return; int sex = spr->_ref & 1; Sprite **p = _heroTab[sex]->_pocket; if (on) { if (freePockets(sex) == 0 && p[kPocketMax] == nullptr) { SWAP(p[kPocketMax], p[kPocketMax - 1]); snHide(p[kPocketMax], 1); } } else if (p[kPocketMax]) { for (int i = 0; i < kPocketMax; i++) { if (p[i] == nullptr) { snHide(p[kPocketMax], 0); SWAP(p[kPocketMax], p[i]); break; } } } } void CGE2Engine::snGhost(Bitmap *bmp) { V2D p(this, bmp->_map & 0xFFFF, bmp->_map >> 16); bmp->hide(p); bmp->release(); delete[] bmp->_b; bmp->_b = nullptr; delete bmp; bmp = nullptr; } void CGE2Engine::snSay(Sprite *spr, int val) { if (spr && spr->active() && _commandHandler->_talkEnable) { //-- mouth animation if (isHero(spr) && spr->seqTest(-1)) ((Hero *)spr)->say(); if (_sayCap) _text->say(_text->getText(val), spr); if (_sayVox) { int i = val; if (i < 256) i -= 100; int16 oldRepeat = _sound->getRepeat(); _sound->setRepeat(1); snSound(spr, i, Audio::Mixer::kSpeechSoundType); _sound->setRepeat(oldRepeat); _soundStat._wait = &_sound->_smpinf._counter; } } } void CGE2Engine::hide1(Sprite *spr) { _commandHandlerTurbo->addCommand(kCmdGhost, -1, 0, spr->ghost()); } void CGE2Engine::swapInPocket(Sprite *spr, Sprite *xspr) { for (int i = 0; i < 2; i++) { for (int j = 0; j < kPocketMax; j++) { Sprite *&poc = _heroTab[i]->_pocket[j]; if (poc == spr) { spr->_flags._kept = false; poc = xspr; xspr->_flags._kept = true; xspr->_flags._port = false; return; } } } } Sprite *CGE2Engine::expandSprite(Sprite *spr) { if (spr) _vga->_showQ->insert(spr); return spr; } void CGE2Engine::qGame() { // Write out the user's progress saveGame(0, Common::String("Automatic Savegame")); busy(false); _vga->sunset(); _endGame = true; } void CGE2Engine::xScene() { sceneDown(); sceneUp(_req); } void CommandHandler::addCommand(CommandType com, int ref, int val, void *ptr) { if (ref == -2) ref = 142 - _vm->_sex; Command *headCmd = &_commandList[_head++]; headCmd->_commandType = com; headCmd->_ref = ref; headCmd->_val = val; headCmd->_spritePtr = ptr; headCmd->_cbType = kNullCB; if (headCmd->_commandType == kCmdClear) { clear(); } } void CommandHandler::addCallback(CommandType com, int ref, int val, CallbackType cbType) { Command *headCmd = &_commandList[_head++]; headCmd->_commandType = com; headCmd->_ref = ref; headCmd->_val = val; headCmd->_spritePtr = nullptr; headCmd->_cbType = cbType; if (headCmd->_commandType == kCmdClear) { _tail = _head; _vm->killText(); _timerExpiry = 0; } } void CommandHandler::insertCommand(CommandType com, int ref, int val, void *ptr) { if (ref == -2) ref = 142 - _vm->_sex; --_tail; Command *tailCmd = &_commandList[_tail]; tailCmd->_commandType = com; tailCmd->_ref = ref; tailCmd->_val = val; tailCmd->_spritePtr = ptr; tailCmd->_cbType = kNullCB; if (com == kCmdClear) { _tail = _head; _vm->killText(); _timerExpiry = 0; } } bool CommandHandler::idle() { return (!_vm->_waitRef && _head == _tail); } void CommandHandler::clear() { _tail = _head; _vm->killText(); _timerExpiry = 0; } int CommandHandler::getComId(const char *com) { int i = _vm->takeEnum(_commandText, com); return (i < 0) ? i : i + kCmdCom0 + 1; } const char *CommandHandler::getComStr(CommandType cmdType) { return _commandText[cmdType - kCmdNop]; } void CGE2Engine::feedSnail(Sprite *spr, Action snq, Hero *hero) { if (!spr || !spr->active()) return; int cnt = spr->_actionCtrl[snq]._cnt; if (cnt) { byte ptr = spr->_actionCtrl[snq]._ptr; CommandHandler::Command *comtab = spr->snList(snq); CommandHandler::Command *c = &comtab[ptr]; CommandHandler::Command *q = &comtab[cnt]; if (hero != nullptr) { int pocFre = freePockets(hero->_ref & 1); int pocReq = 0; CommandHandler::Command *p = c; for (; p < q && p->_commandType != kCmdNext; p++) { // scan commands // drop from pocket? if ((p->_commandType == kCmdSend && p->_val != _now) || p->_commandType == kCmdGive) { int ref = p->_ref; if (ref < 0) ref = spr->_ref; if (findActivePocket(ref) >= 0) --pocReq; } // make/dispose additional room? if (p->_commandType == kCmdRoom) { if (p->_val == 0) ++pocReq; else --pocReq; } // put into pocket? if (p->_commandType == kCmdKeep) ++pocReq; // overloaded? if (pocReq > pocFre) { pocFul(); return; } } } while (c < q) { if ((c->_val == -1) && (c->_commandType == kCmdWalk || c->_commandType == kCmdReach)) c->_val = spr->_ref; if (c->_commandType == kCmdNext) { Sprite *s; switch (c->_ref) { case -2: s = hero; break; case -1: s = spr; break; default: s = _vga->_showQ->locate(c->_ref); break; } if (s && s->_actionCtrl[snq]._cnt) { int v; switch (c->_val) { case -1: v = int(c - comtab + 1); break; case -2: v = int(c - comtab); break; case -3: v = -1; break; default: v = c->_val; if ((v > 255) && s) v = s->labVal(snq, v >> 8); break; } if (v >= 0) s->_actionCtrl[snq]._ptr = v; } if (s == spr) break; } _commandHandler->addCommand(c->_commandType, c->_ref, c->_val, spr); ++c; } } } } // End of namespace CGE2.