aboutsummaryrefslogtreecommitdiff
path: root/engines/cge2/snail.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/cge2/snail.cpp')
-rw-r--r--engines/cge2/snail.cpp865
1 files changed, 865 insertions, 0 deletions
diff --git a/engines/cge2/snail.cpp b/engines/cge2/snail.cpp
new file mode 100644
index 0000000000..7580ef4425
--- /dev/null
+++ b/engines/cge2/snail.cpp
@@ -0,0 +1,865 @@
+/* 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++) {
+ register 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.